Mercurial > code > home > repos > light9
diff web/AutoDependencies.ts @ 2376:4556eebe5d73
topdir reorgs; let pdm have its src/ dir; separate vite area from light9/
author | drewp@bigasterisk.com |
---|---|
date | Sun, 12 May 2024 19:02:10 -0700 |
parents | light9/web/AutoDependencies.ts@9ca3d356b950 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/AutoDependencies.ts Sun May 12 19:02:10 2024 -0700 @@ -0,0 +1,137 @@ +import debug from "debug"; +import { NamedNode, Quad_Graph, Quad_Object, Quad_Predicate, Quad_Subject, Term, Util } from "n3"; +import { filter } from "underscore"; +import { Patch, QuadPattern } from "./patch"; +import { SubEvent } from "sub-events"; +import { SyncedGraph } from "./SyncedGraph"; + +const log = debug("autodep"); + +// use patch as an optional optimization, but you can't count on it +export type HandlerFunc = (p?: Patch) => void; + +class Handler { + patterns: QuadPattern[]; + innerHandlers: Handler[]; + // a function and the quad patterns it cared about + constructor(public func: HandlerFunc | null, public label: string) { + this.patterns = []; // s,p,o,g quads that should trigger the next run + this.innerHandlers = []; // Handlers requested while this one was running + } +} + +export class AutoDependencies { + handlers: Handler; + handlerStack: Handler[]; + graphError: SubEvent<string> = new SubEvent(); + constructor(private graph: SyncedGraph) { + // tree of all known Handlers (at least those with non-empty + // patterns). Top node is not a handler. + this.handlers = new Handler(null, "root"); + this.handlerStack = [this.handlers]; // currently running + log("window.ad"); + (window as any).ad = this; + } + + runHandler(func: HandlerFunc, label: string) { + // what if we have this func already? duplicate is safe? + if (label == null) { + throw new Error("missing label"); + } + + const h = new Handler(func, label); + const tailChildren = this.handlerStack[this.handlerStack.length - 1].innerHandlers; + const matchingLabel = filter(tailChildren, (c: Handler) => c.label === label).length; + // ohno, something depends on some handlers getting run twice :( + if (matchingLabel < 2) { + tailChildren.push(h); + } + //console.time("handler #{label}") + // todo: this may fire 1-2 times before the + // graph is initially loaded, which is a waste. Try deferring it if we + // haven't gotten the graph yet. + this._rerunHandler(h, /*patch=*/ undefined); + log(`new handler ${label} ran first time and requested ${h.patterns.length} pats`); + } + + _rerunHandler(handler: Handler, patch?: Patch) { + handler.patterns = []; + this.handlerStack.push(handler); + try { + if (handler.func === null) { + throw new Error("tried to rerun root"); + } + handler.func(patch); + } catch (e) { + this.graphError.emit(String(e)); + } finally { + // assuming here it didn't get to do all its queries, we could + // add a *,*,*,* handler to call for sure the next time? + // log('done. got: ', handler.patterns) + this.handlerStack.pop(); + } + } + + // handler might have no watches, in which case we could forget about it + logHandlerTree() { + log("handler tree:"); + const shorten = (x: Term | null) => { + if (x === null) { + return "null"; + } + if (!Util.isNamedNode(x)) { + return x.value; + } + return this.graph.shorten(x as NamedNode); + }; + + var prn = (h: Handler, indent: string) => { + log(`${indent} 🤝 handler "${h.label}" ${h.patterns.length} pats`); + for (let pat of h.patterns) { + log(`${indent} ⣝ s=${shorten(pat.subject)} p=${shorten(pat.predicate)} o=${shorten(pat.object)}`); + } + Array.from(h.innerHandlers).map((c: any) => prn(c, indent + " ")); + }; + prn(this.handlers, ""); + } + + _handlerIsAffected(child: Handler, patch: Patch): boolean { + // it should be correct but slow to always return true here + for (let pat of child.patterns) { + if (patch.matches(pat)) { + return true; + } + } + return false; + } + + graphChanged(patch: Patch) { + // SyncedGraph is telling us this patch just got applied to the graph. + + var rerunInners = (cur: Handler) => { + const toRun = cur.innerHandlers.slice(); + for (let child of Array.from(toRun)) { + const match = this._handlerIsAffected(child, patch); + + if (match) { + log("match", child.label, match); + child.innerHandlers = []; // let all children get called again + this._rerunHandler(child, patch); + } else { + rerunInners(child); + } + } + }; + rerunInners(this.handlers); + } + + askedFor(s: Quad_Subject | null, p: Quad_Predicate | null, o: Quad_Object | null, g: Quad_Graph | null) { + // SyncedGraph is telling us someone did a query that depended on + // quads in the given pattern. + // console.log(` asked for s/${s?.id} p/${p?.id} o/${o?.id}`) + const current = this.handlerStack[this.handlerStack.length - 1]; + if (current != null && current !== this.handlers) { + current.patterns.push({ subject: s, predicate: p, object: o, graph: g } as QuadPattern); + } + } +}