Mercurial > code > home > repos > light9
annotate light9/live/Effect.ts @ 2235:f9edd9819b7d
move live/ out of web; it's just a normal (web-only) tool now
author | drewp@bigasterisk.com |
---|---|
date | Wed, 24 May 2023 14:37:11 -0700 |
parents | light9/web/live/Effect.ts@611c3e97de2f |
children | 91ae65157e5f |
rev | line source |
---|---|
2087 | 1 import debug from "debug"; |
2 import { Literal, NamedNode, Quad_Object, Quad_Predicate, Quad_Subject, Term } from "n3"; | |
3 import { some } from "underscore"; | |
2235
f9edd9819b7d
move live/ out of web; it's just a normal (web-only) tool now
drewp@bigasterisk.com
parents:
2160
diff
changeset
|
4 import { Patch, patchContainsPreds, patchUpdate } from "../web/patch"; |
f9edd9819b7d
move live/ out of web; it's just a normal (web-only) tool now
drewp@bigasterisk.com
parents:
2160
diff
changeset
|
5 import { SyncedGraph } from "../web/SyncedGraph"; |
f9edd9819b7d
move live/ out of web; it's just a normal (web-only) tool now
drewp@bigasterisk.com
parents:
2160
diff
changeset
|
6 import { shortShow } from "../web/show_specific"; |
2087 | 7 |
8 type Color = string; | |
9 export type ControlValue = number | Color | NamedNode; | |
10 | |
11 const log = debug("effect"); | |
12 | |
13 function isUri(x: Term | number | string): x is NamedNode { | |
14 return typeof x == "object" && x.termType == "NamedNode"; | |
15 } | |
16 | |
17 function valuePred(graph: SyncedGraph, attr: NamedNode): NamedNode { | |
18 const U = graph.U(); | |
19 const scaledAttributeTypes = [U(":color"), U(":brightness"), U(":uv")]; | |
20 if (some(scaledAttributeTypes, (x: NamedNode) => attr.equals(x))) { | |
21 return U(":scaledValue"); | |
22 } else { | |
23 return U(":value"); | |
24 } | |
25 } | |
26 | |
27 // effect settings data; r/w sync with the graph | |
28 export class Effect { | |
29 private settings: Array<{ device: NamedNode; deviceAttr: NamedNode; setting: NamedNode; value: ControlValue }> = []; | |
2102
9c2e1b5c16e9
speed: don't redo uri string replace all the time
drewp@bigasterisk.com
parents:
2091
diff
changeset
|
30 private ctxForEffect: NamedNode |
2087 | 31 constructor( |
32 public graph: SyncedGraph, | |
33 public uri: NamedNode, | |
34 // called if the graph changes our values and not when the caller uses edit() | |
35 private onValuesChanged: (values: void) => void | |
36 ) { | |
2160 | 37 this.ctxForEffect = this.graph.Uri(this.uri.value.replace("light9.bigasterisk.com/effect", `light9.bigasterisk.com/show/${shortShow}/effect`)); |
2087 | 38 graph.runHandler(this.rebuildSettingsFromGraph.bind(this), `effect sync ${uri.value}`); |
39 } | |
40 | |
41 addNewEffectToGraph() { | |
42 const U = this.graph.U(); | |
2102
9c2e1b5c16e9
speed: don't redo uri string replace all the time
drewp@bigasterisk.com
parents:
2091
diff
changeset
|
43 const quad = (s: Quad_Subject, p: Quad_Predicate, o: Quad_Object) => this.graph.Quad(s, p, o, this.ctxForEffect); |
2087 | 44 |
45 const addQuads = [ | |
46 quad(this.uri, U("rdf:type"), U(":Effect")), | |
47 quad(this.uri, U("rdfs:label"), this.graph.Literal(this.uri.value.replace(/.*\//, ""))), | |
48 quad(this.uri, U(":publishAttr"), U(":strength")), | |
49 ]; | |
50 const patch = { adds: addQuads, dels: [] } as Patch; | |
51 log("init new effect", patch); | |
52 this.settings = []; | |
53 this.graph.applyAndSendPatch(patch); | |
54 } | |
55 | |
2089
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
56 rebuildSettingsFromGraph(patch?: Patch) { |
2087 | 57 const U = this.graph.U(); |
2089
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
58 if (patch && !patchContainsPreds(patch, [U(":setting"), U(":device"), U(":deviceAttr")])) { |
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
59 // that's an approx list of preds , but it just means we'll miss some pathological settings edits |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
60 // return; |
2089
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
61 } |
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
62 |
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
63 // log("syncFromGraph", this.uri); |
2087 | 64 |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
65 // 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 |
2087 | 66 const newSettings = []; |
67 | |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
68 const seenDevAttrPairs: Set<string> = new Set(); |
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
69 |
2087 | 70 for (let setting of Array.from(this.graph.objects(this.uri, U(":setting")))) { |
2089
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
71 // log(` setting ${setting.value}`); |
2087 | 72 if (!isUri(setting)) throw new Error(); |
73 let value: ControlValue; | |
74 const device = this.graph.uriValue(setting, U(":device")); | |
75 const deviceAttr = this.graph.uriValue(setting, U(":deviceAttr")); | |
76 | |
77 const pred = valuePred(this.graph, deviceAttr); | |
78 try { | |
79 value = this.graph.uriValue(setting, pred); | |
80 if (!(value as NamedNode).id.match(/^http/)) { | |
81 throw new Error("not uri"); | |
82 } | |
83 } catch (error) { | |
84 try { | |
85 value = this.graph.floatValue(setting, pred); | |
86 } catch (error1) { | |
87 value = this.graph.stringValue(setting, pred); // this may find multi values and throw | |
88 } | |
89 } | |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
90 // log(`change: graph contains ${deviceAttr.value} ${value}`); |
2087 | 91 |
92 newSettings.push({ device, deviceAttr, setting, value }); | |
93 } | |
94 this.settings = newSettings; | |
95 log(`rebuild to ${this.settings.length}`); | |
96 this.onValuesChanged(); | |
97 } | |
2089
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
98 |
2087 | 99 currentValue(device: NamedNode, deviceAttr: NamedNode): ControlValue | null { |
100 for (let s of this.settings) { | |
101 if (device.equals(s.device) && deviceAttr.equals(s.deviceAttr)) { | |
102 return s.value; | |
103 } | |
104 } | |
105 return null; | |
106 } | |
2089
bbd6816d9e9e
big optimization on effect editing. 50% time still in rebuildSettingsFromGraph though, and the rest is in setLabel
drewp@bigasterisk.com
parents:
2087
diff
changeset
|
107 |
2087 | 108 // change this object now, but return the patch to be applied to the graph so it can be coalesced. |
109 edit(device: NamedNode, deviceAttr: NamedNode, newValue: ControlValue | null): Patch { | |
110 log(`edit: value=${newValue}`); | |
111 let existingSetting: NamedNode | null = null; | |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
112 let result = { adds: [], dels: [] }; |
2087 | 113 for (let s of this.settings) { |
114 if (device.equals(s.device) && deviceAttr.equals(s.deviceAttr)) { | |
115 if (existingSetting !== null) { | |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
116 // 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. |
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
117 log(`${this.uri.value} had two settings for ${device.value} - ${deviceAttr.value} - deleting ${s.setting}`); |
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
118 patchUpdate(result, this._removeEffectSetting(s.setting)); |
2087 | 119 } |
120 existingSetting = s.setting; | |
121 } | |
122 } | |
123 | |
124 if (newValue !== null && this.shouldBeStored(deviceAttr, newValue)) { | |
125 if (existingSetting === null) { | |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
126 patchUpdate(result, this._addEffectSetting(device, deviceAttr, newValue)); |
2087 | 127 } else { |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
128 patchUpdate(result, this._patchExistingEffectSetting(existingSetting, deviceAttr, newValue)); |
2087 | 129 } |
130 } else { | |
131 if (existingSetting !== null) { | |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
132 patchUpdate(result, this._removeEffectSetting(existingSetting)); |
2087 | 133 } |
134 } | |
2091
9324fc8285ad
Effect repairs duplicate :settings edges when it finds them
drewp@bigasterisk.com
parents:
2089
diff
changeset
|
135 return result; |
2087 | 136 } |
137 | |
138 shouldBeStored(deviceAttr: NamedNode, value: ControlValue | null): boolean { | |
139 // this is a bug for zoom=0, since collector will default it to | |
140 // stick at the last setting if we don't explicitly send the | |
141 // 0. rx/ry similar though not the exact same deal because of | |
142 // their remap. | |
143 return value != null && value !== 0 && value !== "#000000"; | |
144 } | |
145 | |
146 _addEffectSetting(device: NamedNode, deviceAttr: NamedNode, value: ControlValue): Patch { | |
147 log(" _addEffectSetting", deviceAttr.value, value); | |
148 const U = (x: string) => this.graph.Uri(x); | |
2102
9c2e1b5c16e9
speed: don't redo uri string replace all the time
drewp@bigasterisk.com
parents:
2091
diff
changeset
|
149 const quad = (s: Quad_Subject, p: Quad_Predicate, o: Quad_Object) => this.graph.Quad(s, p, o, this.ctxForEffect); |
2087 | 150 if (!this.uri) throw new Error("effect unset"); |
151 const setting = this.graph.nextNumberedResource(this.uri.value + "_set"); | |
152 | |
153 const addQuads = [ | |
154 quad(this.uri, U(":setting"), setting), | |
155 quad(setting, U(":device"), device), | |
156 quad(setting, U(":deviceAttr"), deviceAttr), | |
157 quad(setting, valuePred(this.graph, deviceAttr), this._nodeForValue(value)), | |
158 ]; | |
159 const patch = { adds: addQuads, dels: [] } as Patch; | |
160 log(" save", patch); | |
161 this.settings.push({ device, deviceAttr, setting, value }); | |
162 return patch; | |
163 } | |
164 | |
165 _patchExistingEffectSetting(effectSetting: NamedNode, deviceAttr: NamedNode, value: ControlValue): Patch { | |
166 log(" patch existing", effectSetting.value); | |
167 return this.graph.getObjectPatch( | |
168 effectSetting, // | |
169 valuePred(this.graph, deviceAttr), | |
170 this._nodeForValue(value), | |
2102
9c2e1b5c16e9
speed: don't redo uri string replace all the time
drewp@bigasterisk.com
parents:
2091
diff
changeset
|
171 this.ctxForEffect |
2087 | 172 ); |
173 } | |
174 | |
175 _removeEffectSetting(effectSetting: NamedNode): Patch { | |
176 const U = (x: string) => this.graph.Uri(x); | |
177 log(" _removeEffectSetting", effectSetting.value); | |
2102
9c2e1b5c16e9
speed: don't redo uri string replace all the time
drewp@bigasterisk.com
parents:
2091
diff
changeset
|
178 const toDel = [this.graph.Quad(this.uri, U(":setting"), effectSetting, this.ctxForEffect)]; |
2087 | 179 for (let q of this.graph.subjectStatements(effectSetting)) { |
180 toDel.push(q); | |
181 } | |
182 return { dels: toDel, adds: [] }; | |
183 } | |
184 | |
185 _nodeForValue(value: ControlValue): NamedNode | Literal { | |
186 if (value === null) { | |
187 throw new Error("no value"); | |
188 } | |
189 if (isUri(value)) { | |
190 return value; | |
191 } | |
192 return this.graph.prettyLiteral(value); | |
193 } | |
194 } |