import debug from "debug"; import { Quad_Graph, Quad_Object, Quad_Predicate, Quad_Subject } from "n3"; import { filter } from "underscore"; import { allPatchSubjs, Patch } from "./patch"; const log = debug("autodep"); interface QuadPattern { subject: Quad_Subject | null; predicate: Quad_Predicate | null; object: Quad_Object | null; graph: Quad_Graph | null; } // 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[]; constructor() { // 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 } 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, undefined); } //console.timeEnd("handler #{label}") //@_logHandlerTree() _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) { log("error running handler: ", 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:"); var prn = function (h: Handler, depth: number) { let indent = ""; for (let i = 0; i < depth; i++) { indent += " "; } log(`${indent} \"${h.label}\" ${h.patterns.length} pats`); Array.from(h.innerHandlers).map((c: any) => prn(c, depth + 1)); }; prn(this.handlers, 0); } _handlerIsAffected(child: Handler, patchSubjs: Set) { if (patchSubjs === null) { return true; } if (!child.patterns.length) { return false; } for (let stmt of Array.from(child.patterns)) { if (stmt.subject === null) { // wildcard on subject return true; } if (patchSubjs.has(stmt.subject.value)) { return true; } } return false; } graphChanged(patch: Patch) { // SyncedGraph is telling us this patch just got applied to the graph. const subjs = allPatchSubjs(patch); var rerunInners = (cur: Handler) => { const toRun = cur.innerHandlers.slice(); for (let child of Array.from(toRun)) { //match = @_handlerIsAffected(child, subjs) //continue if not match //log('match', child.label, match) //child.innerHandlers = [] # let all children get called again this._rerunHandler(child, patch); 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. 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); } } }