Files
@ 6697a68800d2
Branch filter:
Location: light9/web/AutoDependencies.ts
6697a68800d2
4.6 KiB
video/MP2T
junk merge just to avoid two heads
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 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);
}
}
}
|