feat(canvas): schedule deep copy planning cooperatively
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import type { Document, Registry, Target } from '../canvas-core/types.ts';
|
||||
import { planDuplicateSteps, type DuplicateOptions, type DuplicatePlan } from '../canvas-core/duplicate.ts';
|
||||
import { clone, fault, freeze } from '../canvas-core/values.ts';
|
||||
|
||||
export type DuplicateScheduling = {
|
||||
readonly signal?: AbortSignal;
|
||||
readonly frameBudgetMs?: number;
|
||||
readonly maxStepsPerSlice?: number;
|
||||
readonly now?: () => number;
|
||||
readonly yieldTask?: () => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cooperative planning from a captured immutable StateRoot document, not a live editor getter.
|
||||
* No document publication or network effects. The caller must persist source/mapping and
|
||||
* revalidate external identities before applying a plan; this is not the import executor.
|
||||
*/
|
||||
export async function planDuplicateAsync(document: Document, registry: Registry, selection: readonly Target[], options: DuplicateOptions, scheduling: DuplicateScheduling = {}): Promise<DuplicatePlan> {
|
||||
const budget = scheduling.frameBudgetMs ?? 6, limit = scheduling.maxStepsPerSlice ?? 512;
|
||||
if (!Number.isFinite(budget) || budget <= 0 || !Number.isSafeInteger(limit) || limit < 1) throw fault('invalid_copy_budget');
|
||||
const signal = scheduling.signal, now = scheduling.now ?? (() => performance.now()), yieldTask = scheduling.yieldTask ?? (() => new Promise<void>(resolve => setTimeout(resolve, 0)));
|
||||
const check = () => { if (signal?.aborted) throw fault('copy_aborted'); };
|
||||
check();
|
||||
// Capture caller-owned small inputs before the first await. Document/registry are immutable core snapshots.
|
||||
const targets = freeze(clone(selection)), settings: DuplicateOptions = Object.freeze({ createId: options.createId, ...(options.worldOffset ? {worldOffset: freeze(clone(options.worldOffset))} : {}), ...(options.mapping ? {mapping: freeze(clone(options.mapping))} : {}) });
|
||||
const steps = planDuplicateSteps(document, registry, targets, settings);
|
||||
try {
|
||||
while (true) {
|
||||
check();
|
||||
const start = now(); let count = 0;
|
||||
do {
|
||||
check();
|
||||
const step = steps.next();
|
||||
check();
|
||||
if (step.done) return step.value;
|
||||
count++;
|
||||
} while (count < limit && now() - start < budget);
|
||||
await yieldTask();
|
||||
}
|
||||
} finally {
|
||||
// Release snapshot references after cancellation or host scheduling failures.
|
||||
steps.return(undefined as never);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user