# HG changeset patch # User drewp@bigasterisk.com # Date 2023-05-31 06:58:47 # Node ID a6b611f32ccec02bb8a8500c90e6ec0757e94828 # Parent 4c9c2ab23831d2bcd76fb6b0beb3e4f1cee4afcc rewrite Effect.ts to operate on new effect graphs diff --git a/light9/live/Effect.ts b/light9/live/Effect.ts --- a/light9/live/Effect.ts +++ b/light9/live/Effect.ts @@ -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 @@ function valuePred(graph: SyncedGraph, a } } +// 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 @@ export function newEffect(graph: SyncedG 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 @@ export function newEffect(graph: SyncedG // 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 = 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 @@ export class Effect { 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 @@ export class Effect { 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 @@ export class Effect { 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 @@ export class Effect { 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) {