comparison 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
comparison
equal deleted inserted replaced
2375:623836db99af 2376:4556eebe5d73
1 import debug from "debug";
2 import { NamedNode, Quad_Graph, Quad_Object, Quad_Predicate, Quad_Subject, Term, Util } from "n3";
3 import { filter } from "underscore";
4 import { Patch, QuadPattern } from "./patch";
5 import { SubEvent } from "sub-events";
6 import { SyncedGraph } from "./SyncedGraph";
7
8 const log = debug("autodep");
9
10 // use patch as an optional optimization, but you can't count on it
11 export type HandlerFunc = (p?: Patch) => void;
12
13 class Handler {
14 patterns: QuadPattern[];
15 innerHandlers: Handler[];
16 // a function and the quad patterns it cared about
17 constructor(public func: HandlerFunc | null, public label: string) {
18 this.patterns = []; // s,p,o,g quads that should trigger the next run
19 this.innerHandlers = []; // Handlers requested while this one was running
20 }
21 }
22
23 export class AutoDependencies {
24 handlers: Handler;
25 handlerStack: Handler[];
26 graphError: SubEvent<string> = new SubEvent();
27 constructor(private graph: SyncedGraph) {
28 // tree of all known Handlers (at least those with non-empty
29 // patterns). Top node is not a handler.
30 this.handlers = new Handler(null, "root");
31 this.handlerStack = [this.handlers]; // currently running
32 log("window.ad");
33 (window as any).ad = this;
34 }
35
36 runHandler(func: HandlerFunc, label: string) {
37 // what if we have this func already? duplicate is safe?
38 if (label == null) {
39 throw new Error("missing label");
40 }
41
42 const h = new Handler(func, label);
43 const tailChildren = this.handlerStack[this.handlerStack.length - 1].innerHandlers;
44 const matchingLabel = filter(tailChildren, (c: Handler) => c.label === label).length;
45 // ohno, something depends on some handlers getting run twice :(
46 if (matchingLabel < 2) {
47 tailChildren.push(h);
48 }
49 //console.time("handler #{label}")
50 // todo: this may fire 1-2 times before the
51 // graph is initially loaded, which is a waste. Try deferring it if we
52 // haven't gotten the graph yet.
53 this._rerunHandler(h, /*patch=*/ undefined);
54 log(`new handler ${label} ran first time and requested ${h.patterns.length} pats`);
55 }
56
57 _rerunHandler(handler: Handler, patch?: Patch) {
58 handler.patterns = [];
59 this.handlerStack.push(handler);
60 try {
61 if (handler.func === null) {
62 throw new Error("tried to rerun root");
63 }
64 handler.func(patch);
65 } catch (e) {
66 this.graphError.emit(String(e));
67 } finally {
68 // assuming here it didn't get to do all its queries, we could
69 // add a *,*,*,* handler to call for sure the next time?
70 // log('done. got: ', handler.patterns)
71 this.handlerStack.pop();
72 }
73 }
74
75 // handler might have no watches, in which case we could forget about it
76 logHandlerTree() {
77 log("handler tree:");
78 const shorten = (x: Term | null) => {
79 if (x === null) {
80 return "null";
81 }
82 if (!Util.isNamedNode(x)) {
83 return x.value;
84 }
85 return this.graph.shorten(x as NamedNode);
86 };
87
88 var prn = (h: Handler, indent: string) => {
89 log(`${indent} 🤝 handler "${h.label}" ${h.patterns.length} pats`);
90 for (let pat of h.patterns) {
91 log(`${indent} ⣝ s=${shorten(pat.subject)} p=${shorten(pat.predicate)} o=${shorten(pat.object)}`);
92 }
93 Array.from(h.innerHandlers).map((c: any) => prn(c, indent + " "));
94 };
95 prn(this.handlers, "");
96 }
97
98 _handlerIsAffected(child: Handler, patch: Patch): boolean {
99 // it should be correct but slow to always return true here
100 for (let pat of child.patterns) {
101 if (patch.matches(pat)) {
102 return true;
103 }
104 }
105 return false;
106 }
107
108 graphChanged(patch: Patch) {
109 // SyncedGraph is telling us this patch just got applied to the graph.
110
111 var rerunInners = (cur: Handler) => {
112 const toRun = cur.innerHandlers.slice();
113 for (let child of Array.from(toRun)) {
114 const match = this._handlerIsAffected(child, patch);
115
116 if (match) {
117 log("match", child.label, match);
118 child.innerHandlers = []; // let all children get called again
119 this._rerunHandler(child, patch);
120 } else {
121 rerunInners(child);
122 }
123 }
124 };
125 rerunInners(this.handlers);
126 }
127
128 askedFor(s: Quad_Subject | null, p: Quad_Predicate | null, o: Quad_Object | null, g: Quad_Graph | null) {
129 // SyncedGraph is telling us someone did a query that depended on
130 // quads in the given pattern.
131 // console.log(` asked for s/${s?.id} p/${p?.id} o/${o?.id}`)
132 const current = this.handlerStack[this.handlerStack.length - 1];
133 if (current != null && current !== this.handlers) {
134 current.patterns.push({ subject: s, predicate: p, object: o, graph: g } as QuadPattern);
135 }
136 }
137 }