Skip to content

Commit

Permalink
refactor: make Dataview querying async
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-lednev committed Nov 23, 2024
1 parent e960b2b commit 86b6b0e
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 32 deletions.
6 changes: 3 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
createDailyNote,
getDateFromPath,
} from "obsidian-daily-notes-interface";
import { type STask } from "obsidian-dataview";
import { getAPI, type STask } from "obsidian-dataview";
import { mount } from "svelte";
import { fromStore, get, writable, type Writable } from "svelte/store";
import { isInstanceOf, isNotVoid } from "typed-assert";
Expand Down Expand Up @@ -86,7 +86,7 @@ export default class DayPlanner extends Plugin {
this.app.workspace,
this.vaultFacade,
);
this.dataviewFacade = new DataviewFacade(this.app);
this.dataviewFacade = new DataviewFacade(() => getAPI(this.app));
this.sTaskEditor = new STaskEditor(
this.workspaceFacade,
this.vaultFacade,
Expand Down Expand Up @@ -545,7 +545,7 @@ export default class DayPlanner extends Plugin {
sTaskEditor: this.sTaskEditor,
workspaceFacade: this.workspaceFacade,
initWeeklyView: this.initWeeklyLeaf,
refreshTasks: this.dataviewFacade.getAllTasksFrom,
refreshTasks: this.dataviewFacade.legacy_getAllTasksFrom,
dataviewLoaded,
renderMarkdown: createRenderMarkdown(this.app),
toggleCheckboxInFile: this.vaultFacade.toggleCheckboxInFile,
Expand Down
35 changes: 29 additions & 6 deletions src/service/dataview-facade.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
import { App } from "obsidian";
import { getAPI, STask } from "obsidian-dataview";
import { getAPI, STask, type DataviewApi } from "obsidian-dataview";
import {
type Scheduler,
createBackgroundBatchScheduler,
} from "../util/scheduler";

export class DataviewFacade {
constructor(private readonly app: App) {}
private readonly scheduler: Scheduler<STask[]> =
createBackgroundBatchScheduler({
timeRemainingLowerLimit: 5,
});

getAllTasksFrom = (source: string) => {
return getAPI(this.app)?.pages(source).file.tasks.array() || [];
constructor(private readonly getDataview: () => DataviewApi | undefined) {}

getAllTasksFrom = async (source: string) => {
return new Promise<STask[]>((resolve) => {
const paths: string[] = this.getDataview()?.pagePaths(source).array();

const pageQueries: Array<() => STask[]> = paths.map(
(path) => () => this.getDataview()?.page(path)?.file.tasks.array(),
);

this.scheduler.enqueueTasks(pageQueries, (results) => {
resolve(results.flat());
});
});
};

legacy_getAllTasksFrom = (source: string) => {
return this.getDataview()?.pages(source).file.tasks.array() || [];
};

getAllListsFrom = (source: string) => {
return getAPI(this.app)?.pages(source).file.lists.array() || [];
return this.getDataview()?.pages(source).file.lists.array() || [];
};

getTaskAtLine({ path, line }: { path: string; line: number }) {
Expand All @@ -19,6 +42,6 @@ export class DataviewFacade {
}

private getTasksFromPath = (path: string): STask[] => {
return getAPI(this.app)?.page(path)?.file.tasks || [];
return this.getDataview()?.page(path)?.file.tasks || [];
};
}
3 changes: 2 additions & 1 deletion src/ui/hooks/use-search.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ export function useSearch(props: {
return [];
}

// todo: use async
return (
dataviewFacade
.getAllTasksFrom(currentSource)
.legacy_getAllTasksFrom(currentSource)
.filter((task) => {
return task.text.toLowerCase().includes(currentQuery);
})
Expand Down
21 changes: 13 additions & 8 deletions src/ui/hooks/use-tasks-from-extra-sources.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { derived, type Readable } from "svelte/store";

import { DataviewFacade } from "../../service/dataview-facade";
import type { STask } from "obsidian-dataview";

interface UseTasksFromExtraSourcesProps {
dataviewSource: Readable<string>;
Expand All @@ -13,13 +14,17 @@ export function useTasksFromExtraSources({
refreshSignal,
dataviewFacade,
}: UseTasksFromExtraSourcesProps) {
return derived([dataviewSource, refreshSignal], ([$dataviewSource]) => {
const noAdditionalSource = $dataviewSource.trim().length === 0;
return derived(
[dataviewSource, refreshSignal],
([$dataviewSource], set: (tasks: STask[]) => void) => {
dataviewFacade
.getAllTasksFrom($dataviewSource)
.then(set, (reason) => {
console.error("Failed to fetch tasks from dataview source: ", reason);

if (noAdditionalSource) {
return [];
}

return dataviewFacade.getAllTasksFrom($dataviewSource);
});
set([]);
});
},
[],
);
}
2 changes: 1 addition & 1 deletion src/util/create-hooks.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function useTasks(props: {
$currentTime,
task.startTime.clone().startOf("minute"),
),
truncated: "bottom",
truncated: "bottom" as const,
})),
);

Expand Down
21 changes: 14 additions & 7 deletions src/util/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@ const cancelJob =
clearTimeout(id);
});

export type Scheduler<T> = ReturnType<typeof createBackgroundBatchScheduler<T>>;

/**
* A scheduler accepts a list of tasks (a batch) and reports back when all of them are done.
* If a new batch of tasks is added, the scheduler will discard the previous batch and run the new one.
*/
export function createBackgroundBatchScheduler<T>(
onFinish: (results: T[]) => void,
) {
const timeRemainingLowerLimit = 10;
export function createBackgroundBatchScheduler<T>(props: {
timeRemainingLowerLimit: number;
}) {
const { timeRemainingLowerLimit } = props;

let results: T[] = [];
let tasks: Array<() => T> = [];
let currentTaskHandle: number | null = null;
let currentOnFinish: (results: T[]) => void;

function runTaskQueue(deadline: IdleDeadline) {
while (
Expand All @@ -58,13 +61,17 @@ export function createBackgroundBatchScheduler<T>(
if (tasks.length > 0) {
currentTaskHandle = enqueueJob(runTaskQueue);
} else {
onFinish(results);
currentOnFinish(results);
currentTaskHandle = null;
}
}

function enqueueTasks(newTasks: Array<() => T>) {
performance.mark("batch-start");
function enqueueTasks(
newTasks: Array<() => T>,
onFinish: (results: T[]) => void,
) {
currentOnFinish = onFinish;

if (currentTaskHandle) {
cancelJob(currentTaskHandle);
currentTaskHandle = null;
Expand Down
15 changes: 9 additions & 6 deletions src/util/use-remote-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import type { WithIcalConfig } from "../types";

import { canHappenAfter, icalEventToTasks } from "./ical";
import { getEarliestMoment } from "./moment";
import { createBackgroundBatchScheduler } from "./scheduler";
import {
createBackgroundBatchScheduler,
} from "./scheduler";

function isVEvent(event: ical.CalendarComponent): event is ical.VEvent {
return event.type === "VEVENT";
Expand Down Expand Up @@ -93,12 +95,13 @@ export function useRemoteTasks(props: {
const tasksFromEvents = readable<Array<ReturnType<typeof icalEventToTasks>>>(
[],
(set) => {
const scheduler =
createBackgroundBatchScheduler<ReturnType<typeof icalEventToTasks>>(
set,
);
const scheduler = createBackgroundBatchScheduler<
ReturnType<typeof icalEventToTasks>
>({ timeRemainingLowerLimit: 10 });

return schedulerQueue.subscribe(scheduler.enqueueTasks);
return schedulerQueue.subscribe((next) => {
scheduler.enqueueTasks(next, set);
});
},
);

Expand Down

0 comments on commit 86b6b0e

Please sign in to comment.