Changeset - 7d26fa1ed4e7
[Not reviewed]
default
0 4 0
drewp@bigasterisk.com - 20 months ago 2023-05-29 18:44:22
drewp@bigasterisk.com
renames and comments (mostly)
4 files changed with 37 insertions and 34 deletions:
0 comments (0 inline, 0 general)
light9/live/Effect.ts
Show inline comments
 
@@ -136,25 +136,25 @@ export class Effect {
 
    }
 
    return result;
 
  }
 

	
 
  shouldBeStored(deviceAttr: NamedNode, value: ControlValue | null): boolean {
 
    // this is a bug for zoom=0, since collector will default it to
 
    // stick at the last setting if we don't explicitly send the
 
    // 0. rx/ry similar though not the exact same deal because of
 
    // their remap.
 
    return value != null && value !== 0 && value !== "#000000";
 
  }
 

	
 
  _addEffectSetting(device: NamedNode, deviceAttr: NamedNode, value: ControlValue): Patch {
 
  private addEffectSetting(device: NamedNode, deviceAttr: NamedNode, value: ControlValue): Patch {
 
    log("  _addEffectSetting", deviceAttr.value, value);
 
    const U = (x: string) => this.graph.Uri(x);
 
    const quad = (s: Quad_Subject, p: Quad_Predicate, o: Quad_Object) => this.graph.Quad(s, p, o, this.ctxForEffect);
 
    if (!this.uri) throw new Error("effect unset");
 
    const setting = this.graph.nextNumberedResource(this.uri.value + "_set");
 

	
 
    const patch = new Patch(
 
      [],
 
      [
 
        quad(this.uri, U(":setting"), setting),
 
        quad(setting, U(":device"), device),
 
        quad(setting, U(":deviceAttr"), deviceAttr),
 
@@ -162,37 +162,37 @@ export class Effect {
 
      ]
 
    );
 
    log("  save", patch);
 
    this.settings.push({ device, deviceAttr, setting, value });
 
    return patch;
 
  }
 

	
 
  private patchExistingEffectSetting(effectSetting: NamedNode, deviceAttr: NamedNode, value: ControlValue): Patch {
 
    log("  patch existing", effectSetting.value);
 
    return this.graph.getObjectPatch(
 
      effectSetting, //
 
      valuePred(this.graph, deviceAttr),
 
      this._nodeForValue(value),
 
      this.nodeForValue(value),
 
      this.ctxForEffect
 
    );
 
  }
 

	
 
  _removeEffectSetting(effectSetting: NamedNode): Patch {
 
  private removeEffectSetting(effectSetting: NamedNode): Patch {
 
    const U = (x: string) => this.graph.Uri(x);
 
    log("  _removeEffectSetting", effectSetting.value);
 
    const toDel = [this.graph.Quad(this.uri, U(":setting"), effectSetting, this.ctxForEffect)];
 
    for (let q of this.graph.subjectStatements(effectSetting)) {
 
      toDel.push(q);
 
    }
 
    return new Patch(toDel, []);
 
  }
 

	
 
  _nodeForValue(value: ControlValue): NamedNode | Literal {
 
  private nodeForValue(value: ControlValue): NamedNode | Literal {
 
    if (value === null) {
 
      throw new Error("no value");
 
    }
 
    if (isUri(value)) {
 
      return value;
 
    }
 
    return this.graph.prettyLiteral(value);
 
  }
 
}
light9/live/Light9DeviceSettings.ts
Show inline comments
 
@@ -64,50 +64,50 @@ export class Light9DeviceSettings extend
 
    `;
 
  }
 

	
 
  devices: Array<NamedNode> = [];
 
  @property() currentEffect: Effect | null = null;
 
  okToWriteUrl: boolean = false;
 

	
 
  constructor() {
 
    super();
 

	
 
    getTopGraph().then((g) => {
 
      this.graph = g;
 
      this.graph.runHandler(this.findDevices.bind(this), "findDevices");
 
      this.graph.runHandler(this.compile.bind(this), "findDevices");
 
      this.setEffectFromUrl();
 
    });
 
  }
 

	
 
  onEffectChoice2(ev: CustomEvent) {
 
    const uri = ev.detail.newValue as NamedNode;
 
    if (uri === null) {
 
      this.currentEffect = null;
 
    } else {
 
      this.currentEffect = new Effect(this.graph, uri);
 
    }
 
  }
 

	
 
  updated(changedProperties: PropertyValues<this>) {
 
    log("ctls udpated", changedProperties);
 
    if (changedProperties.has("currentEffect")) {
 
      log(`effectChoice to ${this.currentEffect?.uri?.value}`);
 
      this.writeToUrl(this.currentEffect?.uri);
 
    }
 
    // this.graphToControls?.debugDump();
 
  }
 

	
 
  // Note that this doesn't fetch setting values, so it only should get rerun
 
  // upon (rarer) changes to the devices etc. todo: make that be true
 
  findDevices(patch?: Patch) {
 
  private compile(patch?: Patch) {
 
    const U = this.graph.U();
 
    // if (patch && !patchContainsPreds(patch, [U("rdf:type")])) {
 
    //   return;
 
    // }
 

	
 
    this.devices = [];
 
    let classes = this.graph.subjects(U("rdf:type"), U(":DeviceClass"));
 
    log(`found ${classes.length} device classes`);
 
    uniq(sortBy(classes, "value"), true).forEach((dc) => {
 
      sortBy(this.graph.subjects(U("rdf:type"), dc), "value").forEach((dev) => {
 
        this.devices.push(dev as NamedNode);
 
      });
light9/web/AutoDependencies.ts
Show inline comments
 
@@ -65,25 +65,25 @@ export class AutoDependencies {
 
      }
 
      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() {
 
  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);
 
  }
 

	
light9/web/SyncedGraph.ts
Show inline comments
 
import debug from "debug";
 
import * as N3 from "n3";
 
import { Quad, Quad_Object, Quad_Predicate, Quad_Subject } from "n3";
 
import { sortBy, unique } from "underscore";
 
import { AutoDependencies, HandlerFunc } from "./AutoDependencies";
 
import { Patch, patchToDeleteEntireGraph } from "./patch";
 
import { RdfDbClient } from "./rdfdbclient";
 

	
 
const log = debug("graph");
 

	
 
const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
 

	
 
export class SyncedGraph {
 
  private _autoDeps: AutoDependencies;
 
  private _client: RdfDbClient;
 
  private autoDeps: AutoDependencies;
 
  private client: RdfDbClient;
 
  private graph: N3.Store;
 
  cachedFloatValues: any;
 
  cachedUriValues: any;
 
  private cachedFloatValues: Map<string, number> = new Map();
 
  private cachedUriValues: Map<string, N3.NamedNode> = new Map();
 
  private prefixFuncs: (prefix: string) => N3.PrefixedToIri;
 
  serial: any;
 
  _nextNumber: any;
 
  private serial: any;
 
  private nextNumber: any;
 
  // Main graph object for a browser to use. Consider using RdfdbSyncedGraph element to create & own
 
  // one of these. Syncs both ways with rdfdb. Meant to hide the choice of RDF lib, so we can change it
 
  // later.
 
  //
 
  // Note that _applyPatch is the only method to write to the graph, so
 
  // it can fire subscriptions.
 

	
 
  constructor(
 
    // url is the /syncedGraph path of an rdfdb server.
 
    public url: any,
 
    // The /syncedGraph path of an rdfdb server.
 
    patchSenderUrl: string,
 
    // prefixes can be used in Uri(curie) calls. This mapping may grow during loadTrig calls.
 
    public prefixes: Map<string, string>,
 
    private setStatus: any,
 
    // called if we clear the graph
 
    private clearCb: any
 
    private setStatus: (status: string) => void
 
  ) {
 
    this.prefixFuncs = this.rebuildPrefixFuncs(prefixes);
 
    this.graph = new N3.Store();
 
    this._autoDeps = new AutoDependencies();
 
    this.autoDeps = new AutoDependencies(this);
 
    this.clearGraph();
 

	
 
    this._client = new RdfDbClient(this.url, this._clearGraphOnNewConnection.bind(this), this._applyPatch.bind(this), this.setStatus);
 
    this.client = new RdfDbClient(patchSenderUrl, this._clearGraphOnNewConnection.bind(this), this._applyPatch.bind(this), this.setStatus);
 
  }
 

	
 
  clearGraph() {
 
    // must not try send a patch to the server!
 
    // just deletes the statements; watchers are unaffected.
 
    this.cachedFloatValues = new Map(); // s + '|' + p -> number
 
    this.cachedUriValues = new Map(); // s + '|' + p -> Uri
 

	
 
    this._applyPatch(patchToDeleteEntireGraph(this.graph));
 
    // if we had a Store already, this lets N3.Store free all its indices/etc
 
    this.graph = new N3.Store();
 
    this.rebuildPrefixFuncs(this.prefixes);
 
  }
 

	
 
  _clearGraphOnNewConnection() {
 
    // must not send a patch to the server!
 
    // must not try send a patch to the server
 

	
 
    log("clearGraphOnNewConnection");
 
    this.clearGraph();
 
    log("clearGraphOnNewConnection done");
 
    if (this.clearCb != null) {
 
      return this.clearCb();
 
    }
 
  }
 

	
 
  private rebuildPrefixFuncs(prefixes: Map<string, string>) {
 
    const p = Object.create(null);
 
    prefixes.forEach((v: string, k: string) => (p[k] = v));
 

	
 
@@ -131,32 +134,32 @@ export class SyncedGraph {
 
        }
 
      }
 
    });
 
  }
 

	
 
  quads(): any {
 
    // for debugging
 
    return Array.from(this.graph.getQuads(null, null, null, null)).map((q: Quad) => [q.subject, q.predicate, q.object, q.graph]);
 
  }
 

	
 
  applyAndSendPatch(patch: Patch) {
 
    console.time("applyAndSendPatch");
 
    if (!this._client) {
 
    if (!this.client) {
 
      log("not connected-- dropping patch");
 
      return;
 
    }
 

	
 
    this._applyPatch(patch);
 
    if (this._client) {
 
      this._client.sendPatch(patch);
 
    if (this.client) {
 
      this.client.sendPatch(patch);
 
    }
 
    console.timeEnd("applyAndSendPatch");
 
  }
 

	
 
  _applyPatch(patch: Patch) {
 
    // In most cases you want applyAndSendPatch.
 
    //
 
    // This is the only method that writes to this.graph!
 
    log("patch from server [1]");
 
    this.cachedFloatValues.clear();
 
    this.cachedUriValues.clear();
 
    patch.applyToGraph(this.graph);
 
@@ -184,29 +187,29 @@ export class SyncedGraph {
 
    // matches what you queried, we runHandler your func again (and
 
    // forget your queries from the first time).
 

	
 
    // helps with memleak? not sure yet. The point was if two matching
 
    // labels get puushed on, we should run only one. So maybe
 
    // appending a serial number is backwards.
 
    if (!this.serial) {
 
      this.serial = 1;
 
    }
 
    this.serial += 1;
 
    //label = label + @serial
 

	
 
    this._autoDeps.runHandler(func, label);
 
    this.autoDeps.runHandler(func, label);
 
  }
 

	
 
  _singleValue(s: Quad_Subject, p: Quad_Predicate) {
 
    this._autoDeps.askedFor(s, p, null, null);
 
    this.autoDeps.askedFor(s, p, null, null);
 
    const quads = this.graph.getQuads(s, p, null, null);
 
    const objs = new Set(Array.from(quads).map((q: Quad) => q.object));
 

	
 
    switch (objs.size) {
 
      case 0:
 
        throw new Error("no value for " + s.value + " " + p.value);
 
      case 1:
 
        var obj = objs.values().next().value;
 
        return obj;
 
      default:
 
        throw new Error("too many different values: " + JSON.stringify(quads));
 
    }
 
@@ -251,112 +254,112 @@ export class SyncedGraph {
 
      ret = this.stringValue(uri, this.Uri("rdfs:label"));
 
    } catch (error) {
 
      const words = uri.value.split("/");
 
      ret = words[words.length - 1];
 
    }
 
    if (!ret) {
 
      ret = uri.value;
 
    }
 
    return ret;
 
  }
 

	
 
  objects(s: any, p: any): Quad_Object[] {
 
    this._autoDeps.askedFor(s, p, null, null);
 
    this.autoDeps.askedFor(s, p, null, null);
 
    const quads = this.graph.getQuads(s, p, null, null);
 
    return Array.from(quads).map((q: { object: any }) => q.object);
 
  }
 

	
 
  subjects(p: any, o: any): Quad_Subject[] {
 
    this._autoDeps.askedFor(null, p, o, null);
 
    this.autoDeps.askedFor(null, p, o, null);
 
    const quads = this.graph.getQuads(null, p, o, null);
 
    return Array.from(quads).map((q: { subject: any }) => q.subject);
 
  }
 

	
 
  subjectStatements(s: Quad_Subject): Quad[] {
 
    this._autoDeps.askedFor(s, null, null, null);
 
    this.autoDeps.askedFor(s, null, null, null);
 
    const quads = this.graph.getQuads(s, null, null, null);
 
    return quads;
 
  }
 

	
 
  items(list: any) {
 
    const out = [];
 
    let current = list;
 
    while (true) {
 
      if (current.value === RDF + "nil") {
 
        break;
 
      }
 

	
 
      this._autoDeps.askedFor(current, null, null, null); // a little loose
 
      this.autoDeps.askedFor(current, null, null, null); // a little loose
 

	
 
      const firsts = this.graph.getQuads(current, RDF + "first", null, null);
 
      const rests = this.graph.getQuads(current, RDF + "rest", null, null);
 
      if (firsts.length !== 1) {
 
        throw new Error(`list node ${current} has ${firsts.length} rdf:first edges`);
 
      }
 
      out.push(firsts[0].object);
 

	
 
      if (rests.length !== 1) {
 
        throw new Error(`list node ${current} has ${rests.length} rdf:rest edges`);
 
      }
 
      current = rests[0].object;
 
    }
 

	
 
    return out;
 
  }
 

	
 
  contains(s: any, p: any, o: any): boolean {
 
    this._autoDeps.askedFor(s, p, o, null);
 
    this.autoDeps.askedFor(s, p, o, null);
 
    // Sure this is a nice warning to remind me to rewrite, but the graph.size call itself was taking 80% of the time in here
 
    // log("contains calling getQuads when graph has ", this.graph.size);
 
    return this.graph.getQuads(s, p, o, null).length > 0;
 
  }
 

	
 
  nextNumberedResources(base: { id: any }, howMany: number) {
 
    // base is NamedNode or string
 
    // Note this is unsafe before we're synced with the graph. It'll
 
    // always return 'name0'.
 
    if (base.id) {
 
      base = base.id;
 
    }
 
    const results = [];
 

	
 
    // @contains is really slow.
 
    if (this._nextNumber == null) {
 
      this._nextNumber = new Map();
 
    if (this.nextNumber == null) {
 
      this.nextNumber = new Map();
 
    }
 
    let start = this._nextNumber.get(base);
 
    let start = this.nextNumber.get(base);
 
    if (start === undefined) {
 
      start = 0;
 
    }
 

	
 
    for (let serial = start, asc = start <= 1000; asc ? serial <= 1000 : serial >= 1000; asc ? serial++ : serial--) {
 
      const uri = this.Uri(`${base}${serial}`);
 
      if (!this.contains(uri, null, null)) {
 
        results.push(uri);
 
        log("nextNumberedResources", `picked ${uri}`);
 
        this._nextNumber.set(base, serial + 1);
 
        this.nextNumber.set(base, serial + 1);
 
        if (results.length >= howMany) {
 
          return results;
 
        }
 
      }
 
    }
 
    throw new Error(`can't make sequential uri with base ${base}`);
 
  }
 

	
 
  nextNumberedResource(base: any) {
 
    return this.nextNumberedResources(base, 1)[0];
 
  }
 

	
 
  contextsWithPattern(s: any, p: any, o: any) {
 
    this._autoDeps.askedFor(s, p, o, null);
 
    this.autoDeps.askedFor(s, p, o, null);
 
    const ctxs = [];
 
    for (let q of Array.from(this.graph.getQuads(s, p, o, null))) {
 
      ctxs.push(q.graph);
 
    }
 
    return unique(ctxs);
 
  }
 

	
 
  sortKey(uri: N3.NamedNode) {
 
    const parts = uri.value.split(/([0-9]+)/);
 
    const expanded = parts.map(function (p: string) {
 
      const f = parseInt(p);
 
      if (isNaN(f)) {
0 comments (0 inline, 0 general)