changeset 2302:a6b611f32cce

rewrite Effect.ts to operate on new effect graphs
author drewp@bigasterisk.com
date Tue, 30 May 2023 23:58:47 -0700
parents 4c9c2ab23831
children 10dd81f20f4b
files light9/live/Effect.ts
diffstat 1 files changed, 120 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/light9/live/Effect.ts	Tue May 30 23:58:07 2023 -0700
+++ b/light9/live/Effect.ts	Tue May 30 23:58:47 2023 -0700
@@ -1,5 +1,5 @@
 import debug from "debug";
-import { Literal, NamedNode, Quad_Object, Quad_Predicate, Quad_Subject, Term } from "n3";
+import { Literal, NamedNode, Quad, Quad_Object, Quad_Predicate, Quad_Subject, Term } from "n3";
 import { some } from "underscore";
 import { Patch } from "../web/patch";
 import { SyncedGraph } from "../web/SyncedGraph";
@@ -28,6 +28,7 @@
   }
 }
 
+// also see resourcedisplay's version of this
 function effContext(graph: SyncedGraph, uri: NamedNode): NamedNode {
   return graph.Uri(uri.value.replace("light9.bigasterisk.com/effect", `light9.bigasterisk.com/show/${shortShow}/effect`));
 }
@@ -46,6 +47,7 @@
     quad(uri, U("rdf:type"), U(":Effect")),
     quad(uri, U("rdfs:label"), graph.Literal(uri.value.replace(/.*\//, ""))),
     quad(uri, U(":publishAttr"), U(":strength")),
+    quad(uri, U(":effectFunction"), U(":effectFunction/scale")),
   ];
   const patch = new Patch([], addQuads);
   log("init new effect", patch);
@@ -56,61 +58,96 @@
 
 // effect settings data; r/w sync with the graph
 export class Effect {
-  private settings: Array<{ device: NamedNode; deviceAttr: NamedNode; setting: NamedNode; value: ControlValue }> = [];
+  // :effect1 a Effect; :setting ?eset . ?eset :effectAttr :deviceSettings; :value ?dset . ?dset :device ..
+  private eset?: NamedNode;
+  private dsettings: Array<{ dset: NamedNode; device: NamedNode; deviceAttr: NamedNode; value: ControlValue }> = [];
+
   private ctxForEffect: NamedNode;
   settingsChanged: SubEvent<void> = new SubEvent();
-  constructor(
-    public graph: SyncedGraph,
-    public uri: NamedNode // called if the graph changes our values and not when the caller uses edit()
-  ) {
+
+  constructor(public graph: SyncedGraph, public uri: NamedNode) {
     this.ctxForEffect = effContext(this.graph, this.uri);
     graph.runHandler(this.rebuildSettingsFromGraph.bind(this), `effect sync ${uri.value}`);
   }
 
-  rebuildSettingsFromGraph(patch?: Patch) {
+  private getExistingEset(): NamedNode | null {
+    const U = this.graph.U();
+    for (let eset of this.graph.objects(this.uri, U(":setting"))) {
+      if (this.graph.uriValue(eset as Quad_Subject, U(":effectAttr")).equals(U(":deviceSettings"))) {
+        return eset as NamedNode;
+      }
+    }
+    return null;
+  }
+  private getExistingEsetValueNode(): NamedNode | null {
     const U = this.graph.U();
-    if (patch && !patch.containsAnyPreds([U(":setting"), U(":device"), U(":deviceAttr")])) {
-      // that's an approx list of preds , but it just means we'll miss some pathological settings edits
-      //   return;
+    const eset = this.getExistingEset();
+    if (eset === null) return null;
+    try {
+      return this.graph.uriValue(eset, U(":value"));
+    } catch (e) {
+      return null;
     }
+  }
+  private patchForANewEset(): { p: Patch; eset: NamedNode } {
+    const U = this.graph.U();
+    const eset = this.graph.nextNumberedResource(U(":e_set"));
+    return {
+      eset: eset,
+      p: new Patch(
+        [],
+        [
+          //
+          new Quad(this.uri, U(":setting"), eset, this.ctxForEffect),
+          new Quad(eset, U(":effectAttr"), U(":deviceSettings"), this.ctxForEffect),
+        ]
+      ),
+    };
+  }
 
-    // log("syncFromGraph", this.uri);
+  private rebuildSettingsFromGraph(patch?: Patch) {
+    const U = this.graph.U();
+
+    log("syncFromGraph", this.uri);
 
     // this repeats work- it gathers all settings when really some values changed (and we might even know about them). maybe push the value-fetching into a secnod phase of the run, and have the 1st phase drop out early
     const newSettings = [];
 
-    for (let setting of Array.from(this.graph.objects(this.uri, U(":setting")))) {
-      // log(`  setting ${setting.value}`);
-      if (!isUri(setting)) throw new Error();
-      let value: ControlValue;
-      const device = this.graph.uriValue(setting, U(":device"));
-      const deviceAttr = this.graph.uriValue(setting, U(":deviceAttr"));
+    const deviceSettingsNode = this.getExistingEsetValueNode();
+    if (deviceSettingsNode !== null) {
+      for (let dset of Array.from(this.graph.objects(deviceSettingsNode, U(":setting"))) as NamedNode[]) {
+        //   // log(`  setting ${setting.value}`);
+        //   if (!isUri(dset)) throw new Error();
+        let value: ControlValue;
+        const device = this.graph.uriValue(dset, U(":device"));
+        const deviceAttr = this.graph.uriValue(dset, U(":deviceAttr"));
 
-      const pred = valuePred(this.graph, deviceAttr);
-      try {
-        value = this.graph.uriValue(setting, pred);
-        if (!(value as NamedNode).id.match(/^http/)) {
-          throw new Error("not uri");
+        const pred = valuePred(this.graph, deviceAttr);
+        try {
+          value = this.graph.uriValue(dset, pred);
+          if (!(value as NamedNode).id.match(/^http/)) {
+            throw new Error("not uri");
+          }
+        } catch (error) {
+          try {
+            value = this.graph.floatValue(dset, pred);
+          } catch (error1) {
+            value = this.graph.stringValue(dset, pred); // this may find multi values and throw
+          }
         }
-      } catch (error) {
-        try {
-          value = this.graph.floatValue(setting, pred);
-        } catch (error1) {
-          value = this.graph.stringValue(setting, pred); // this may find multi values and throw
-        }
+        //   log(`change: graph contains ${deviceAttr.value} ${value}`);
+
+        newSettings.push({ dset, device, deviceAttr, value });
       }
-      //   log(`change: graph contains ${deviceAttr.value} ${value}`);
-
-      newSettings.push({ device, deviceAttr, setting, value });
     }
-    this.settings = newSettings;
-    log(`settings is rebuilt to length ${this.settings.length}`);
+    this.dsettings = newSettings;
+    log(`settings is rebuilt to length ${this.dsettings.length}`);
     this.settingsChanged.emit(); // maybe one emitter per dev+attr?
     // this.onValuesChanged();
   }
 
   currentValue(device: NamedNode, deviceAttr: NamedNode): ControlValue | null {
-    for (let s of this.settings) {
+    for (let s of this.dsettings) {
       if (device.equals(s.device) && deviceAttr.equals(s.deviceAttr)) {
         return s.value;
       }
@@ -123,14 +160,15 @@
     log(`edit: value=${newValue}`);
     let existingSetting: NamedNode | null = null;
     let result = new Patch([], []);
-    for (let s of this.settings) {
+
+    for (let s of this.dsettings) {
       if (device.equals(s.device) && deviceAttr.equals(s.deviceAttr)) {
         if (existingSetting !== null) {
           // this is corrupt. There was only supposed to be one setting per (dev,attr) pair. But we can fix it because we're going to update existingSetting to the user's requested value.
-          log(`${this.uri.value} had two settings for ${device.value} - ${deviceAttr.value} - deleting ${s.setting}`);
-          result = result.update(this.removeEffectSetting(s.setting));
+          log(`${this.uri.value} had two settings for ${device.value} - ${deviceAttr.value} - deleting ${s.dset}`);
+          result = result.update(this.removeEffectSetting(s.dset));
         }
-        existingSetting = s.setting;
+        existingSetting = s.dset;
       }
     }
 
@@ -138,7 +176,7 @@
       if (existingSetting === null) {
         result = result.update(this.addEffectSetting(device, deviceAttr, newValue));
       } else {
-        result = result.update(this.patchExistingEffectSetting(existingSetting, deviceAttr, newValue));
+        result = result.update(this.patchExistingDevSetting(existingSetting, deviceAttr, newValue));
       }
     } else {
       if (existingSetting !== null) {
@@ -160,27 +198,46 @@
     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");
+
+    let patch = new Patch([], []);
+
+    let eset = this.getExistingEset();
+    if (eset === null) {
+      const ret = this.patchForANewEset();
+      patch = patch.update(ret.p);
+      eset = ret.eset;
+    }
 
-    const patch = new Patch(
-      [],
-      [
-        quad(this.uri, U(":setting"), setting),
-        quad(setting, U(":device"), device),
-        quad(setting, U(":deviceAttr"), deviceAttr),
-        quad(setting, valuePred(this.graph, deviceAttr), this.nodeForValue(value)),
-      ]
+    let dsValue;
+    try {
+      dsValue = this.graph.uriValue(eset, U(":value"));
+    } catch (e) {
+      dsValue = this.graph.nextNumberedResource(U(":ds_val"));
+      patch = patch.update(new Patch([], [quad(eset, U(":value"), dsValue)]));
+    }
+
+    const dset = this.graph.nextNumberedResource(this.uri.value + "_set");
+
+    patch = patch.update(
+      new Patch(
+        [],
+        [
+          quad(dsValue, U(":setting"), dset),
+          quad(dset, U(":device"), device),
+          quad(dset, U(":deviceAttr"), deviceAttr),
+          quad(dset, valuePred(this.graph, deviceAttr), this.nodeForValue(value)),
+        ]
+      )
     );
     log("  save", patch);
-    this.settings.push({ device, deviceAttr, setting, value });
+    this.dsettings.push({ dset, device, deviceAttr, value });
     return patch;
   }
 
-  private patchExistingEffectSetting(effectSetting: NamedNode, deviceAttr: NamedNode, value: ControlValue): Patch {
-    log("  patch existing", effectSetting.value);
+  private patchExistingDevSetting(devSetting: NamedNode, deviceAttr: NamedNode, value: ControlValue): Patch {
+    log("  patch existing", devSetting.value);
     return this.graph.getObjectPatch(
-      effectSetting, //
+      devSetting, //
       valuePred(this.graph, deviceAttr),
       this.nodeForValue(value),
       this.ctxForEffect
@@ -190,18 +247,23 @@
   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)];
+
+    const eset = this.getExistingEset();
+    if (eset === null) throw "unexpected";
+    const dsValue = this.graph.uriValue(eset, U(":value"));
+    if (dsValue === null) throw "unexpected";
+    const toDel = [this.graph.Quad(dsValue, U(":setting"), effectSetting, this.ctxForEffect)];
     for (let q of this.graph.subjectStatements(effectSetting)) {
       toDel.push(q);
     }
     return new Patch(toDel, []);
   }
 
-clearAllSettings() {
-  for (let s of this.settings) {
-    this.graph.applyAndSendPatch(this.removeEffectSetting(s.setting))
+  clearAllSettings() {
+    for (let s of this.dsettings) {
+      this.graph.applyAndSendPatch(this.removeEffectSetting(s.dset));
+    }
   }
-}
 
   private nodeForValue(value: ControlValue): NamedNode | Literal {
     if (value === null) {