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