Files @ 90792e984249
Branch filter:

Location: light9/web/AutoDependencies.ts - annotation

drewp@bigasterisk.com
isolate import warnings to one file
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
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);
    }
  }
}