You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
3.7 KiB
124 lines
3.7 KiB
import updateShape from "src/ng-mock/wasm-land/updateShape";
|
|
import type { Connection, Diff, Scope, Shape } from "../types";
|
|
import requestShape from "src/ng-mock/wasm-land/requestShape";
|
|
import { applyDiff } from "./applyDiff";
|
|
import { deepSignal, watch, batch } from "alien-deepsignals";
|
|
import type { DeepPatch, DeepSignalObject } from "alien-deepsignals";
|
|
import type { CompactShapeType } from "node_modules/@ldo/ldo/dist/types/ShapeType";
|
|
import type { LdoCompactBase } from "@ldo/ldo";
|
|
|
|
interface PoolEntry<T extends LdoCompactBase> {
|
|
key: string;
|
|
shape: CompactShapeType<T>;
|
|
scopeKey: string;
|
|
signalObject: DeepSignalObject<T | {}>;
|
|
refCount: number;
|
|
stopListening: (() => void) | null;
|
|
registerCleanup?: (fn: () => void) => void;
|
|
connectionId?: string;
|
|
ready: Promise<string>; // resolves to connectionId
|
|
resolveReady: (id: string) => void;
|
|
suspendDeepWatcher: boolean;
|
|
}
|
|
|
|
const pool = new Map<string, PoolEntry<any>>();
|
|
|
|
function canonicalScope(scope: Scope | undefined): string {
|
|
if (scope == null) return "";
|
|
return Array.isArray(scope) ? scope.slice().sort().join(",") : String(scope);
|
|
}
|
|
|
|
export function deepPatchesToDiff(patches: DeepPatch[]): Diff {
|
|
return patches.map((patch) => {
|
|
const path = "/" + patch.path.join("/");
|
|
return { ...patch, path };
|
|
}) as Diff;
|
|
}
|
|
|
|
export function createSignalObjectForShape<T extends LdoCompactBase>(
|
|
shape: CompactShapeType<T>,
|
|
scope?: Scope,
|
|
poolSignal = true
|
|
) {
|
|
const scopeKey = canonicalScope(scope);
|
|
const key = `${shape}::${scopeKey}`;
|
|
|
|
if (poolSignal) {
|
|
const existing = pool.get(key);
|
|
if (existing) {
|
|
existing.refCount++;
|
|
return buildReturn(existing);
|
|
}
|
|
}
|
|
|
|
const signalObject = deepSignal<T | {}>({});
|
|
let resolveReady!: (id: string) => void;
|
|
const ready = new Promise<string>((res) => (resolveReady = res));
|
|
const entry: PoolEntry<T> = {
|
|
key,
|
|
shape,
|
|
scopeKey,
|
|
signalObject,
|
|
refCount: 1,
|
|
stopListening: null,
|
|
registerCleanup: undefined,
|
|
connectionId: undefined,
|
|
ready,
|
|
resolveReady,
|
|
suspendDeepWatcher: false,
|
|
};
|
|
if (poolSignal) pool.set(key, entry);
|
|
|
|
const onUpdateFromDb = (diff: Diff, connectionId: Connection["id"]) => {
|
|
console.debug("[shape][diff] applying", connectionId, diff);
|
|
entry.suspendDeepWatcher = true;
|
|
batch(() => applyDiff(signalObject, diff));
|
|
queueMicrotask(() => {
|
|
entry.suspendDeepWatcher = false;
|
|
});
|
|
};
|
|
|
|
requestShape(shape, scope, onUpdateFromDb).then(
|
|
({ connectionId, shapeObject }) => {
|
|
entry.connectionId = connectionId;
|
|
entry.suspendDeepWatcher = true;
|
|
batch(() => {
|
|
for (const k of Object.keys(shapeObject)) {
|
|
(signalObject as any)[k] = (shapeObject as any)[k];
|
|
}
|
|
});
|
|
const watcher = watch(signalObject, ({ patches }) => {
|
|
if (entry.suspendDeepWatcher || !patches.length) return;
|
|
const diff = deepPatchesToDiff(patches);
|
|
updateShape(connectionId as any, diff as any);
|
|
});
|
|
entry.stopListening = watcher.stopListening;
|
|
entry.registerCleanup = watcher.registerCleanup;
|
|
queueMicrotask(() => {
|
|
entry.suspendDeepWatcher = false;
|
|
});
|
|
entry.resolveReady(connectionId);
|
|
}
|
|
);
|
|
|
|
function buildReturn(entry: PoolEntry<T>) {
|
|
const release = () => {
|
|
if (entry.refCount > 0) entry.refCount--;
|
|
if (entry.refCount === 0) {
|
|
entry.stopListening?.();
|
|
if (poolSignal) pool.delete(entry.key);
|
|
}
|
|
};
|
|
return {
|
|
signalObject: entry.signalObject,
|
|
stop: release,
|
|
ready: entry.ready, // Promise<string>
|
|
get connectionId() {
|
|
return entry.connectionId;
|
|
},
|
|
registerCleanup: entry.registerCleanup,
|
|
};
|
|
}
|
|
|
|
return buildReturn(entry);
|
|
}
|
|
|