Changeset - a6b611f32cce
[Not reviewed]
default
0 1 0
drewp@bigasterisk.com - 20 months ago 2023-05-31 06:58:47
drewp@bigasterisk.com
rewrite Effect.ts to operate on new effect graphs
1 file changed with 104 insertions and 42 deletions:
0 comments (0 inline, 0 general)
light9/live/Effect.ts
Show inline comments
 
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";
 
import { shortShow } from "../web/show_specific";
 
import { SubEvent } from "sub-events";
 

	
 
@@ -25,12 +25,13 @@ function valuePred(graph: SyncedGraph, a
 
    return U(":value");
 
  } else {
 
    return U(":value");
 
  }
 
}
 

	
 
// 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`));
 
}
 

	
 
export function newEffect(graph: SyncedGraph): NamedNode {
 
  // wrong- this should be our editor's scratch effect, promoted to a
 
@@ -43,105 +44,142 @@ export function newEffect(graph: SyncedG
 
  const quad = (s: Quad_Subject, p: Quad_Predicate, o: Quad_Object) => graph.Quad(s, p, o, ctx);
 

	
 
  const addQuads = [
 
    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);
 
  graph.applyAndSendPatch(patch);
 

	
 
  return effect.uri;
 
}
 

	
 
// 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();
 
    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(setting, U(":device"));
 
      const deviceAttr = this.graph.uriValue(setting, U(":deviceAttr"));
 
        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);
 
          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(setting, pred);
 
            value = this.graph.floatValue(dset, pred);
 
        } catch (error1) {
 
          value = this.graph.stringValue(setting, pred); // this may find multi values and throw
 
            value = this.graph.stringValue(dset, pred); // this may find multi values and throw
 
        }
 
      }
 
      //   log(`change: graph contains ${deviceAttr.value} ${value}`);
 

	
 
      newSettings.push({ device, deviceAttr, setting, value });
 
        newSettings.push({ dset, device, deviceAttr, 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;
 
      }
 
    }
 
    return null;
 
  }
 

	
 
  // change this object now, but return the patch to be applied to the graph so it can be coalesced.
 
  edit(device: NamedNode, deviceAttr: NamedNode, newValue: ControlValue | null): Patch {
 
    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;
 
      }
 
    }
 

	
 
    if (newValue !== null && this.shouldBeStored(deviceAttr, newValue)) {
 
      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) {
 
        result = result.update(this.removeEffectSetting(existingSetting));
 
      }
 
    }
 
@@ -157,52 +195,76 @@ export class Effect {
 
  }
 

	
 
  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");
 

	
 
    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(
 
    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(this.uri, U(":setting"), setting),
 
        quad(setting, U(":device"), device),
 
        quad(setting, U(":deviceAttr"), deviceAttr),
 
        quad(setting, valuePred(this.graph, deviceAttr), this.nodeForValue(value)),
 
          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
 
    );
 
  }
 

	
 
  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))
 
    for (let s of this.dsettings) {
 
      this.graph.applyAndSendPatch(this.removeEffectSetting(s.dset));
 
  }
 
}
 

	
 
  private nodeForValue(value: ControlValue): NamedNode | Literal {
 
    if (value === null) {
 
      throw new Error("no value");
0 comments (0 inline, 0 general)