Mercurial > code > home > repos > light9
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 } |