@opendaw/lib-box 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/address.d.ts +47 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +133 -0
- package/dist/array.d.ts +19 -0
- package/dist/array.d.ts.map +1 -0
- package/dist/array.js +32 -0
- package/dist/box.d.ts +51 -0
- package/dist/box.d.ts.map +1 -0
- package/dist/box.js +122 -0
- package/dist/dispatchers.d.ts +16 -0
- package/dist/dispatchers.d.ts.map +1 -0
- package/dist/dispatchers.js +127 -0
- package/dist/editing.d.ts +21 -0
- package/dist/editing.d.ts.map +1 -0
- package/dist/editing.js +131 -0
- package/dist/field.d.ts +42 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +80 -0
- package/dist/graph-edges.d.ts +16 -0
- package/dist/graph-edges.d.ts.map +1 -0
- package/dist/graph-edges.js +109 -0
- package/dist/graph.d.ts +55 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +262 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/object.d.ts +17 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +19 -0
- package/dist/pointer-hub.d.ts +26 -0
- package/dist/pointer-hub.d.ts.map +1 -0
- package/dist/pointer-hub.js +110 -0
- package/dist/pointer.d.ts +32 -0
- package/dist/pointer.d.ts.map +1 -0
- package/dist/pointer.js +82 -0
- package/dist/primitive.d.ts +110 -0
- package/dist/primitive.d.ts.map +1 -0
- package/dist/primitive.js +152 -0
- package/dist/serializer.d.ts +7 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +29 -0
- package/dist/sync-source.d.ts +11 -0
- package/dist/sync-source.d.ts.map +1 -0
- package/dist/sync-source.js +72 -0
- package/dist/sync-target.d.ts +5 -0
- package/dist/sync-target.d.ts.map +1 -0
- package/dist/sync-target.js +40 -0
- package/dist/sync.d.ts +24 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +1 -0
- package/dist/updates.d.ts +70 -0
- package/dist/updates.d.ts.map +1 -0
- package/dist/updates.js +178 -0
- package/dist/vertex.d.ts +41 -0
- package/dist/vertex.d.ts.map +1 -0
- package/dist/vertex.js +1 -0
- package/package.json +33 -0
package/dist/graph.js
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
import { assert, ByteArrayInput, ByteArrayOutput, Checksum, isDefined, Listeners, Option, panic, UUID } from "@opendaw/lib-std";
|
2
|
+
import { Dispatchers } from "./dispatchers";
|
3
|
+
import { DeleteUpdate, NewUpdate, PointerUpdate, PrimitiveUpdate } from "./updates";
|
4
|
+
import { GraphEdges } from "./graph-edges";
|
5
|
+
export class BoxGraph {
|
6
|
+
#boxFactory;
|
7
|
+
#boxes;
|
8
|
+
#deferredPointerUpdates;
|
9
|
+
#updateListeners;
|
10
|
+
#immediateUpdateListeners;
|
11
|
+
#transactionListeners;
|
12
|
+
#dispatchers;
|
13
|
+
#edges;
|
14
|
+
#finalizeTransactionObservers;
|
15
|
+
#inTransaction = false;
|
16
|
+
#constructingBox = false;
|
17
|
+
constructor(boxFactory = Option.None) {
|
18
|
+
this.#boxFactory = boxFactory;
|
19
|
+
this.#boxes = UUID.newSet(box => box.address.uuid);
|
20
|
+
this.#deferredPointerUpdates = [];
|
21
|
+
this.#dispatchers = Dispatchers.create();
|
22
|
+
this.#updateListeners = new Listeners();
|
23
|
+
this.#immediateUpdateListeners = new Listeners();
|
24
|
+
this.#transactionListeners = new Listeners();
|
25
|
+
this.#edges = new GraphEdges();
|
26
|
+
this.#finalizeTransactionObservers = [];
|
27
|
+
}
|
28
|
+
beginTransaction() {
|
29
|
+
assert(!this.#inTransaction, "Transaction already in progress");
|
30
|
+
this.#inTransaction = true;
|
31
|
+
this.#transactionListeners.proxy.onBeginTransaction();
|
32
|
+
}
|
33
|
+
endTransaction() {
|
34
|
+
assert(this.#inTransaction, "No transaction in progress");
|
35
|
+
this.#inTransaction = false;
|
36
|
+
this.resolvePointers();
|
37
|
+
// it is possible that new observers will be added while executing
|
38
|
+
while (this.#finalizeTransactionObservers.length > 0) {
|
39
|
+
this.#finalizeTransactionObservers.splice(0).forEach(observer => observer());
|
40
|
+
if (this.#finalizeTransactionObservers.length > 0) {
|
41
|
+
console.debug(`${this.#finalizeTransactionObservers.length} new observers while notifying`);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
this.#transactionListeners.proxy.onEndTransaction();
|
45
|
+
}
|
46
|
+
inTransaction() { return this.#inTransaction; }
|
47
|
+
constructingBox() { return this.#constructingBox; }
|
48
|
+
resolvePointers() {
|
49
|
+
if (this.#deferredPointerUpdates.length === 0) {
|
50
|
+
return;
|
51
|
+
}
|
52
|
+
this.#deferredPointerUpdates.forEach(({ pointerField, update }) => this.#processPointerValueUpdate(pointerField, update));
|
53
|
+
this.#deferredPointerUpdates.length = 0;
|
54
|
+
}
|
55
|
+
createBox(name, uuid, constructor) {
|
56
|
+
this.#boxFactory.unwrap("No box-factory installed")(name, this, uuid, constructor);
|
57
|
+
}
|
58
|
+
stageBox(box, constructor) {
|
59
|
+
this.#assertTransaction();
|
60
|
+
assert(!this.#constructingBox, "Cannot construct box while other box is constructing");
|
61
|
+
if (isDefined(constructor)) {
|
62
|
+
this.#constructingBox = true;
|
63
|
+
constructor(box);
|
64
|
+
this.#constructingBox = false;
|
65
|
+
}
|
66
|
+
const added = this.#boxes.add(box);
|
67
|
+
assert(added, `${box} already staged`);
|
68
|
+
const update = new NewUpdate(box.address.uuid, box.name, box.toArrayBuffer());
|
69
|
+
this.#updateListeners.proxy.onUpdate(update);
|
70
|
+
this.#immediateUpdateListeners.proxy.onUpdate(update);
|
71
|
+
return box;
|
72
|
+
}
|
73
|
+
subscribeTransaction(listener) {
|
74
|
+
return this.#transactionListeners.subscribe(listener);
|
75
|
+
}
|
76
|
+
subscribeToAllUpdates(listener) {
|
77
|
+
return this.#updateListeners.subscribe(listener);
|
78
|
+
}
|
79
|
+
subscribeToAllUpdatesImmediate(listener) {
|
80
|
+
return this.#immediateUpdateListeners.subscribe(listener);
|
81
|
+
}
|
82
|
+
subscribeVertexUpdates(propagation, address, procedure) {
|
83
|
+
return this.#dispatchers.subscribe(propagation, address, procedure);
|
84
|
+
}
|
85
|
+
subscribeEndTransaction(observer) { this.#finalizeTransactionObservers.push(observer); }
|
86
|
+
unstageBox(box) {
|
87
|
+
this.#assertTransaction();
|
88
|
+
const deleted = this.#boxes.removeByKey(box.address.uuid);
|
89
|
+
assert(deleted === box, `${box} could not be found to unstage`);
|
90
|
+
this.#edges.unwatchVerticesOf(box);
|
91
|
+
const update = new DeleteUpdate(box.address.uuid, box.name, box.toArrayBuffer());
|
92
|
+
this.#updateListeners.proxy.onUpdate(update);
|
93
|
+
this.#immediateUpdateListeners.proxy.onUpdate(update);
|
94
|
+
}
|
95
|
+
findBox(uuid) {
|
96
|
+
return this.#boxes.opt(uuid);
|
97
|
+
}
|
98
|
+
findVertex(address) {
|
99
|
+
return this.#boxes.opt(address.uuid).flatMap(box => box.searchVertex(address.fieldKeys));
|
100
|
+
}
|
101
|
+
boxes() { return this.#boxes.values(); }
|
102
|
+
edges() { return this.#edges; }
|
103
|
+
checksum() {
|
104
|
+
const checksum = new Checksum();
|
105
|
+
this.boxes().forEach(box => box.write(checksum));
|
106
|
+
return checksum.result();
|
107
|
+
}
|
108
|
+
onPrimitiveValueUpdate(field, oldValue, newValue) {
|
109
|
+
this.#assertTransaction();
|
110
|
+
if (field.isAttached() && !this.#constructingBox) {
|
111
|
+
const update = new PrimitiveUpdate(field.address, field.serialization(), oldValue, newValue);
|
112
|
+
this.#dispatchers.dispatch(update);
|
113
|
+
this.#updateListeners.proxy.onUpdate(update);
|
114
|
+
this.#immediateUpdateListeners.proxy.onUpdate(update);
|
115
|
+
}
|
116
|
+
}
|
117
|
+
onPointerAddressUpdated(pointerField, oldValue, newValue) {
|
118
|
+
this.#assertTransaction();
|
119
|
+
const update = new PointerUpdate(pointerField.address, oldValue, newValue);
|
120
|
+
if (this.#constructingBox) {
|
121
|
+
this.#deferredPointerUpdates.push({ pointerField, update });
|
122
|
+
}
|
123
|
+
else {
|
124
|
+
this.#processPointerValueUpdate(pointerField, update);
|
125
|
+
this.#immediateUpdateListeners.proxy.onUpdate(update);
|
126
|
+
}
|
127
|
+
}
|
128
|
+
#processPointerValueUpdate(pointerField, update) {
|
129
|
+
const oldVertex = pointerField.targetVertex.unwrapOrNull();
|
130
|
+
pointerField.resolve();
|
131
|
+
const newVertex = pointerField.targetVertex.unwrapOrNull();
|
132
|
+
if (oldVertex !== newVertex) {
|
133
|
+
oldVertex?.pointerHub.onRemoved(pointerField);
|
134
|
+
newVertex?.pointerHub.onAdded(pointerField);
|
135
|
+
if (oldVertex !== null) {
|
136
|
+
this.#edges.disconnect(pointerField);
|
137
|
+
}
|
138
|
+
if (newVertex !== null) {
|
139
|
+
this.#edges.connect(pointerField, newVertex);
|
140
|
+
}
|
141
|
+
}
|
142
|
+
this.#dispatchers.dispatch(update);
|
143
|
+
this.#updateListeners.proxy.onUpdate(update);
|
144
|
+
}
|
145
|
+
dependenciesOf(box) {
|
146
|
+
const boxes = new Set();
|
147
|
+
const pointers = new Set();
|
148
|
+
const trace = (box) => {
|
149
|
+
if (boxes.has(box)) {
|
150
|
+
return;
|
151
|
+
}
|
152
|
+
boxes.add(box);
|
153
|
+
box.outgoingEdges()
|
154
|
+
.filter(([pointer]) => !pointers.has(pointer))
|
155
|
+
.forEach(([source, target]) => {
|
156
|
+
pointers.add(source);
|
157
|
+
if (target.pointerRules.mandatory &&
|
158
|
+
target.pointerHub.incoming().every(pointer => pointers.has(pointer))) {
|
159
|
+
return trace(target.box);
|
160
|
+
}
|
161
|
+
});
|
162
|
+
box.incomingEdges()
|
163
|
+
.forEach(pointer => {
|
164
|
+
pointers.add(pointer);
|
165
|
+
if (pointer.mandatory) {
|
166
|
+
trace(pointer.box);
|
167
|
+
}
|
168
|
+
});
|
169
|
+
};
|
170
|
+
trace(box);
|
171
|
+
boxes.delete(box);
|
172
|
+
return { boxes: boxes, pointers: Array.from(pointers).reverse() };
|
173
|
+
}
|
174
|
+
verifyPointers() {
|
175
|
+
console.debug("validate requirements");
|
176
|
+
this.#edges.validateRequirements();
|
177
|
+
this.#edges.verifyPointers();
|
178
|
+
console.debug("verify pointers");
|
179
|
+
let count = 0 | 0;
|
180
|
+
const verify = (vertex) => {
|
181
|
+
for (const field of vertex.fields()) {
|
182
|
+
field.accept({
|
183
|
+
visitPointerField: (pointer) => {
|
184
|
+
if (pointer.targetAddress.nonEmpty()) {
|
185
|
+
const isResolved = pointer.targetVertex.nonEmpty();
|
186
|
+
const inGraph = this.findVertex(pointer.targetAddress.unwrap()).nonEmpty();
|
187
|
+
assert(isResolved, `pointer ${pointer.address} is broken`);
|
188
|
+
assert(inGraph, `Cannot find target for pointer ${pointer.address}`);
|
189
|
+
count++;
|
190
|
+
}
|
191
|
+
},
|
192
|
+
visitObjectField: (object) => verify(object)
|
193
|
+
});
|
194
|
+
}
|
195
|
+
};
|
196
|
+
this.#boxes.forEach((box) => verify(box));
|
197
|
+
console.debug("verification complete.");
|
198
|
+
return { count };
|
199
|
+
}
|
200
|
+
debugBoxes() {
|
201
|
+
console.table(this.#boxes.values().reduce((dict, box) => {
|
202
|
+
dict[UUID.toString(box.address.uuid)] = {
|
203
|
+
class: box.name,
|
204
|
+
"incoming links": box.incomingEdges().length,
|
205
|
+
"outgoing links": box.outgoingEdges().length,
|
206
|
+
"est. memory (bytes)": box.estimateMemory()
|
207
|
+
};
|
208
|
+
return dict;
|
209
|
+
}, {}));
|
210
|
+
}
|
211
|
+
debugDependencies() {
|
212
|
+
console.debug("Dependencies:");
|
213
|
+
this.boxes().forEach(box => {
|
214
|
+
console.debug(`\t${box}`);
|
215
|
+
for (const dependency of this.dependenciesOf(box).boxes) {
|
216
|
+
console.debug(`\t\t${dependency}`);
|
217
|
+
}
|
218
|
+
});
|
219
|
+
}
|
220
|
+
addressToDebugPath(address) {
|
221
|
+
return address.flatMap(address => address.isBox()
|
222
|
+
? this.findBox(address.uuid).map(box => box.name)
|
223
|
+
: this.findBox(address.uuid)
|
224
|
+
.flatMap(box => box.searchVertex(address.fieldKeys)
|
225
|
+
.map(vertex => vertex.isField() ? vertex.debugPath : panic("Unknown address"))));
|
226
|
+
}
|
227
|
+
toArrayBuffer() {
|
228
|
+
const output = ByteArrayOutput.create();
|
229
|
+
const boxes = this.#boxes.values();
|
230
|
+
output.writeInt(boxes.length);
|
231
|
+
boxes.forEach(box => {
|
232
|
+
const buffer = box.serialize();
|
233
|
+
output.writeInt(buffer.byteLength);
|
234
|
+
output.writeBytes(new Int8Array(buffer));
|
235
|
+
});
|
236
|
+
return output.toArrayBuffer();
|
237
|
+
}
|
238
|
+
fromArrayBuffer(arrayBuffer) {
|
239
|
+
assert(this.#boxes.isEmpty(), "Cannot call fromArrayBuffer if boxes is not empty");
|
240
|
+
const input = new ByteArrayInput(arrayBuffer);
|
241
|
+
const numBoxes = input.readInt();
|
242
|
+
this.beginTransaction();
|
243
|
+
const boxes = [];
|
244
|
+
for (let i = 0; i < numBoxes; i++) {
|
245
|
+
const length = input.readInt();
|
246
|
+
const int8Array = new Int8Array(length);
|
247
|
+
input.readBytes(int8Array);
|
248
|
+
const boxStream = new ByteArrayInput(int8Array.buffer);
|
249
|
+
const creationIndex = boxStream.readInt();
|
250
|
+
const name = boxStream.readString();
|
251
|
+
const uuid = UUID.fromDataInput(boxStream);
|
252
|
+
boxes.push({ creationIndex, name, uuid, boxStream });
|
253
|
+
}
|
254
|
+
boxes
|
255
|
+
.sort((a, b) => a.creationIndex - b.creationIndex)
|
256
|
+
.forEach(({ name, uuid, boxStream }) => this.createBox(name, uuid, box => box.read(boxStream)));
|
257
|
+
this.endTransaction();
|
258
|
+
}
|
259
|
+
#assertTransaction() {
|
260
|
+
assert(this.#inTransaction, () => "Modification only prohibited in transaction mode.");
|
261
|
+
}
|
262
|
+
}
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
export * from "./address";
|
2
|
+
export * from "./array";
|
3
|
+
export * from "./box";
|
4
|
+
export * from "./dispatchers";
|
5
|
+
export * from "./editing";
|
6
|
+
export * from "./field";
|
7
|
+
export * from "./graph";
|
8
|
+
export * from "./graph-edges";
|
9
|
+
export * from "./object";
|
10
|
+
export * from "./pointer";
|
11
|
+
export * from "./pointer-hub";
|
12
|
+
export * from "./primitive";
|
13
|
+
export * from "./sync";
|
14
|
+
export * from "./sync-source";
|
15
|
+
export * from "./sync-target";
|
16
|
+
export * from "./updates";
|
17
|
+
export * from "./vertex";
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,OAAO,CAAA;AACrB,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA"}
|
package/dist/index.js
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
const key = Symbol.for("@openDAW/lib-box");
|
2
|
+
if (globalThis[key]) {
|
3
|
+
console.debug(`%c${key.description}%c is already available in ${globalThis.constructor.name}.`, "color: hsl(10, 83%, 60%)", "color: inherit");
|
4
|
+
}
|
5
|
+
else {
|
6
|
+
globalThis[key] = true;
|
7
|
+
console.debug(`%c${key.description}%c is now available in ${globalThis.constructor.name}.`, "color: hsl(200, 83%, 60%)", "color: inherit");
|
8
|
+
}
|
9
|
+
export * from "./address";
|
10
|
+
export * from "./array";
|
11
|
+
export * from "./box";
|
12
|
+
export * from "./dispatchers";
|
13
|
+
export * from "./editing";
|
14
|
+
export * from "./field";
|
15
|
+
export * from "./graph";
|
16
|
+
export * from "./graph-edges";
|
17
|
+
export * from "./object";
|
18
|
+
export * from "./pointer";
|
19
|
+
export * from "./pointer-hub";
|
20
|
+
export * from "./primitive";
|
21
|
+
export * from "./sync";
|
22
|
+
export * from "./sync-source";
|
23
|
+
export * from "./sync-target";
|
24
|
+
export * from "./updates";
|
25
|
+
export * from "./vertex";
|
package/dist/object.d.ts
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
import { Field, FieldConstruct, Fields } from "./field";
|
2
|
+
import { UnreferenceableType } from "./pointer";
|
3
|
+
import { DataInput, DataOutput, Nullish, Option } from "@opendaw/lib-std";
|
4
|
+
import { VertexVisitor } from "./vertex";
|
5
|
+
export declare abstract class ObjectField<FIELDS extends Fields> extends Field<UnreferenceableType, FIELDS> {
|
6
|
+
#private;
|
7
|
+
protected constructor(construct: FieldConstruct<UnreferenceableType>);
|
8
|
+
protected abstract initializeFields(): FIELDS;
|
9
|
+
accept<RETURN>(visitor: VertexVisitor<RETURN>): Nullish<RETURN>;
|
10
|
+
fields(): Iterable<Field>;
|
11
|
+
getField<K extends keyof FIELDS>(key: K): FIELDS[K];
|
12
|
+
optField<K extends keyof FIELDS>(key: K): Option<FIELDS[K]>;
|
13
|
+
read(input: DataInput): void;
|
14
|
+
write(output: DataOutput): void;
|
15
|
+
toJSON(): FIELDS;
|
16
|
+
}
|
17
|
+
//# sourceMappingURL=object.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAC,MAAM,SAAS,CAAA;AACrD,OAAO,EAAC,mBAAmB,EAAC,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAY,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAc,MAAM,kBAAkB,CAAA;AAE/F,OAAO,EAAC,aAAa,EAAC,MAAM,UAAU,CAAA;AAEtC,8BAAsB,WAAW,CAAC,MAAM,SAAS,MAAM,CAAE,SAAQ,KAAK,CAAC,mBAAmB,EAAE,MAAM,CAAC;;IAG/F,SAAS,aAAa,SAAS,EAAE,cAAc,CAAC,mBAAmB,CAAC;IAMpE,SAAS,CAAC,QAAQ,CAAC,gBAAgB,IAAI,MAAM;IAE7C,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAI/D,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC;IACzB,QAAQ,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAE3D,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAC5B,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAE/B,MAAM;CACT"}
|
package/dist/object.js
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
import { Field } from "./field";
|
2
|
+
import { asDefined, Option, safeExecute } from "@opendaw/lib-std";
|
3
|
+
import { Serializer } from "./serializer";
|
4
|
+
export class ObjectField extends Field {
|
5
|
+
#fields;
|
6
|
+
constructor(construct) {
|
7
|
+
super(construct);
|
8
|
+
this.#fields = this.initializeFields();
|
9
|
+
}
|
10
|
+
accept(visitor) {
|
11
|
+
return safeExecute(visitor.visitObjectField, this);
|
12
|
+
}
|
13
|
+
fields() { return Object.values(this.#fields); }
|
14
|
+
getField(key) { return asDefined(this.#fields[key]); }
|
15
|
+
optField(key) { return Option.wrap(this.#fields[key]); }
|
16
|
+
read(input) { Serializer.readFields(input, this.#fields); }
|
17
|
+
write(output) { Serializer.writeFields(output, this.#fields); }
|
18
|
+
toJSON() { return this.#fields; }
|
19
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { PointerField, PointerTypes } from "./pointer";
|
2
|
+
import { Vertex } from "./vertex";
|
3
|
+
import { int, Option, Subscription } from "@opendaw/lib-std";
|
4
|
+
export interface PointerListener {
|
5
|
+
onAdd(pointer: PointerField): void;
|
6
|
+
onRemove(pointer: PointerField): void;
|
7
|
+
}
|
8
|
+
export declare class PointerHub implements Iterable<PointerField> {
|
9
|
+
#private;
|
10
|
+
static validate(pointer: PointerField, target: Vertex): Option<string>;
|
11
|
+
constructor(vertex: Vertex);
|
12
|
+
subscribeImmediate(listener: PointerListener, ...filter: ReadonlyArray<PointerTypes>): Subscription;
|
13
|
+
subscribeTransactual(listener: PointerListener, ...filter: ReadonlyArray<PointerTypes>): Subscription;
|
14
|
+
catchupAndSubscribeTransactual(listener: PointerListener, ...filter: ReadonlyArray<PointerTypes>): Subscription;
|
15
|
+
filter<P extends PointerTypes>(...types: ReadonlyArray<P>): Array<PointerField<P>>;
|
16
|
+
size(): int;
|
17
|
+
isEmpty(): boolean;
|
18
|
+
nonEmpty(): boolean;
|
19
|
+
contains(pointer: PointerField): boolean;
|
20
|
+
incoming(): ReadonlyArray<PointerField>;
|
21
|
+
onAdded(pointerField: PointerField): void;
|
22
|
+
onRemoved(pointerField: PointerField): void;
|
23
|
+
[Symbol.iterator](): Iterator<PointerField>;
|
24
|
+
toString(): string;
|
25
|
+
}
|
26
|
+
//# sourceMappingURL=pointer-hub.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"pointer-hub.d.ts","sourceRoot":"","sources":["../src/pointer-hub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,YAAY,EAAC,MAAM,WAAW,CAAA;AACpD,OAAO,EAAC,MAAM,EAAC,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAO,GAAG,EAAwB,MAAM,EAAoB,YAAY,EAAC,MAAM,kBAAkB,CAAA;AAGxG,MAAM,WAAW,eAAe;IAC5B,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAClC,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CACxC;AAED,qBAAa,UAAW,YAAW,QAAQ,CAAC,YAAY,CAAC;;IACrD,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;gBAkB1D,MAAM,EAAE,MAAM;IAO1B,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY;IAInG,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY;IAIrG,8BAA8B,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY;IAoB/G,MAAM,CAAC,CAAC,SAAS,YAAY,EAAE,GAAG,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAKlF,IAAI,IAAI,GAAG;IACX,OAAO,IAAI,OAAO;IAClB,QAAQ,IAAI,OAAO;IACnB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IACxC,QAAQ,IAAI,aAAa,CAAC,YAAY,CAAC;IAEvC,OAAO,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI;IAUzC,SAAS,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI;IAQ3C,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,YAAY,CAAC;IAE3C,QAAQ,IAAI,MAAM;CAuCrB"}
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import { Iterables, Listeners, Option, panic } from "@opendaw/lib-std";
|
2
|
+
import { Address } from "./address";
|
3
|
+
export class PointerHub {
|
4
|
+
static validate(pointer, target) {
|
5
|
+
if (pointer.address.equals(target.address)) {
|
6
|
+
return Option.wrap(`PointerField cannot point to itself: ${pointer}`);
|
7
|
+
}
|
8
|
+
if (!target.pointerRules.accepts.some((type) => type === pointer.pointerType)) {
|
9
|
+
console.warn(target.pointerRules);
|
10
|
+
return Option.wrap(`${pointer.toString()} does not satisfy any of the allowed types (${(target.pointerRules)}).`);
|
11
|
+
}
|
12
|
+
return Option.None;
|
13
|
+
}
|
14
|
+
#vertex;
|
15
|
+
#immediateListeners;
|
16
|
+
#transactualListeners;
|
17
|
+
#inTransaction = Option.None;
|
18
|
+
constructor(vertex) {
|
19
|
+
this.#vertex = vertex;
|
20
|
+
this.#immediateListeners = new Listeners();
|
21
|
+
this.#transactualListeners = new Listeners();
|
22
|
+
}
|
23
|
+
subscribeImmediate(listener, ...filter) {
|
24
|
+
return this.#addFilteredListener(this.#immediateListeners, listener, filter);
|
25
|
+
}
|
26
|
+
subscribeTransactual(listener, ...filter) {
|
27
|
+
return this.#addFilteredListener(this.#transactualListeners, listener, filter);
|
28
|
+
}
|
29
|
+
catchupAndSubscribeTransactual(listener, ...filter) {
|
30
|
+
const added = Address.newSet(pointer => pointer.address);
|
31
|
+
added.addMany(this.filter(...filter));
|
32
|
+
added.forEach(pointer => listener.onAdd(pointer));
|
33
|
+
// This takes track of the listeners notification state.
|
34
|
+
// It is possible that the pointer has been added, but it has not been notified yet.
|
35
|
+
// That would cause the listener.onAdd method to be involked twice.
|
36
|
+
return this.subscribeTransactual({
|
37
|
+
onAdd: (pointer) => {
|
38
|
+
if (added.add(pointer)) {
|
39
|
+
listener.onAdd(pointer);
|
40
|
+
}
|
41
|
+
},
|
42
|
+
onRemove: (pointer) => {
|
43
|
+
added.removeByKey(pointer.address);
|
44
|
+
listener.onRemove(pointer);
|
45
|
+
}
|
46
|
+
}, ...filter);
|
47
|
+
}
|
48
|
+
filter(...types) {
|
49
|
+
return (types.length === 0 ? this.incoming() : Iterables.filter(this, (pointerField) => types.some((type) => type === pointerField.pointerType)));
|
50
|
+
}
|
51
|
+
size() { return this.incoming().length; }
|
52
|
+
isEmpty() { return this.size() === 0; }
|
53
|
+
nonEmpty() { return this.size() > 0; }
|
54
|
+
contains(pointer) { return this.incoming().some(incoming => pointer.address.equals(incoming.address)); }
|
55
|
+
incoming() { return this.#vertex.graph.edges().incomingEdgesOf(this.#vertex); }
|
56
|
+
onAdded(pointerField) {
|
57
|
+
const issue = PointerHub.validate(pointerField, this.#vertex);
|
58
|
+
if (issue.nonEmpty()) {
|
59
|
+
return panic(issue.unwrap());
|
60
|
+
}
|
61
|
+
if (this.#inTransaction.isEmpty()) {
|
62
|
+
this.#vertex.graph.subscribeEndTransaction(this.#onEndTransaction);
|
63
|
+
this.#inTransaction = Option.wrap(new Set(this));
|
64
|
+
}
|
65
|
+
this.#immediateListeners.proxy.onAdd(pointerField);
|
66
|
+
}
|
67
|
+
onRemoved(pointerField) {
|
68
|
+
if (this.#inTransaction.isEmpty()) {
|
69
|
+
this.#vertex.graph.subscribeEndTransaction(this.#onEndTransaction);
|
70
|
+
this.#inTransaction = Option.wrap(new Set(this));
|
71
|
+
}
|
72
|
+
this.#immediateListeners.proxy.onRemove(pointerField);
|
73
|
+
}
|
74
|
+
[Symbol.iterator]() { return this.incoming().values(); }
|
75
|
+
toString() {
|
76
|
+
return `{Pointers ${this.#vertex.address}, pointers: ${Array.from(this)
|
77
|
+
.map((pointerField) => pointerField.toString())}}`;
|
78
|
+
}
|
79
|
+
#addFilteredListener(listeners, listener, filter) {
|
80
|
+
return listeners.subscribe({
|
81
|
+
onAdd: (pointer) => {
|
82
|
+
if (filter.length === 0 || filter.some((type) => type === pointer.pointerType)) {
|
83
|
+
listener.onAdd(pointer);
|
84
|
+
}
|
85
|
+
},
|
86
|
+
onRemove: (pointer) => {
|
87
|
+
if (filter.length === 0 || filter.some((type) => type === pointer.pointerType)) {
|
88
|
+
listener.onRemove(pointer);
|
89
|
+
}
|
90
|
+
}
|
91
|
+
});
|
92
|
+
}
|
93
|
+
#onEndTransaction = () => {
|
94
|
+
if (this.#vertex.isAttached()) {
|
95
|
+
const beforeState = this.#inTransaction.unwrap("Callback without transaction");
|
96
|
+
const afterState = new Set(this);
|
97
|
+
beforeState.forEach(pointer => {
|
98
|
+
if (!afterState.has(pointer)) {
|
99
|
+
this.#transactualListeners.proxy.onRemove(pointer);
|
100
|
+
}
|
101
|
+
});
|
102
|
+
afterState.forEach(pointer => {
|
103
|
+
if (!beforeState.has(pointer)) {
|
104
|
+
this.#transactualListeners.proxy.onAdd(pointer);
|
105
|
+
}
|
106
|
+
});
|
107
|
+
}
|
108
|
+
this.#inTransaction = Option.None;
|
109
|
+
};
|
110
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { DataInput, DataOutput, Nullish, Observer, Option, Subscription } from "@opendaw/lib-std";
|
2
|
+
import { Vertex, VertexVisitor } from "./vertex";
|
3
|
+
import { Address } from "./address";
|
4
|
+
import { PointerHub } from "./pointer-hub";
|
5
|
+
import { Field, FieldConstruct } from "./field";
|
6
|
+
declare const _Unreferenceable: unique symbol;
|
7
|
+
export type UnreferenceableType = typeof _Unreferenceable;
|
8
|
+
export type PointerTypes = number | string | UnreferenceableType;
|
9
|
+
export declare class PointerField<P extends PointerTypes = PointerTypes> extends Field<UnreferenceableType, never> {
|
10
|
+
#private;
|
11
|
+
static create<P extends PointerTypes>(construct: FieldConstruct<UnreferenceableType>, pointerType: P, mandatory: boolean): PointerField<P>;
|
12
|
+
private constructor();
|
13
|
+
get pointerHub(): PointerHub;
|
14
|
+
get pointerType(): P;
|
15
|
+
get mandatory(): boolean;
|
16
|
+
accept<RETURN>(visitor: VertexVisitor<RETURN>): Nullish<RETURN>;
|
17
|
+
subscribe(observer: Observer<this>): Subscription;
|
18
|
+
catchupAndSubscribe(observer: Observer<this>): Subscription;
|
19
|
+
refer<TARGET extends PointerTypes>(vertex: Vertex<P & TARGET extends never ? never : TARGET>): void;
|
20
|
+
defer(): void;
|
21
|
+
get targetVertex(): Option<Vertex>;
|
22
|
+
set targetVertex(option: Option<Vertex>);
|
23
|
+
get targetAddress(): Option<Address>;
|
24
|
+
set targetAddress(newValue: Option<Address>);
|
25
|
+
isEmpty(): boolean;
|
26
|
+
nonEmpty(): boolean;
|
27
|
+
resolve(): void;
|
28
|
+
read(input: DataInput): void;
|
29
|
+
write(output: DataOutput): void;
|
30
|
+
}
|
31
|
+
export {};
|
32
|
+
//# sourceMappingURL=pointer.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"pointer.d.ts","sourceRoot":"","sources":["../src/pointer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAsB,YAAY,EAAC,MAAM,kBAAkB,CAAA;AACnH,OAAO,EAAC,MAAM,EAAE,aAAa,EAAC,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAA;AACxC,OAAO,EAAC,KAAK,EAAE,cAAc,EAAC,MAAM,SAAS,CAAA;AAG7C,QAAA,MAAM,gBAAgB,eAA4B,CAAA;AAElD,MAAM,MAAM,mBAAmB,GAAG,OAAO,gBAAgB,CAAA;AAEzD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,mBAAmB,CAAA;AAEhE,qBAAa,YAAY,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,CAAE,SAAQ,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC;;IACtG,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,YAAY,EAAE,SAAS,EAAE,cAAc,CAAC,mBAAmB,CAAC,EAC9C,WAAW,EAAE,CAAC,EACd,SAAS,EAAE,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC;IAU1E,OAAO;IASP,IAAI,UAAU,IAAI,UAAU,CAA+C;IAE3E,IAAI,WAAW,IAAI,CAAC,CAA2B;IAC/C,IAAI,SAAS,IAAI,OAAO,CAAyB;IAEjD,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAI/D,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY;IAIjD,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY;IAM3D,KAAK,CAAC,MAAM,SAAS,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,SAAS,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG,IAAI;IAInG,KAAK,IAAI,IAAI;IAEb,IAAI,YAAY,IAAI,MAAM,CAAC,MAAM,CAAC,CAA4B;IAC9D,IAAI,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAQtC;IAED,IAAI,aAAa,IAAI,MAAM,CAAC,OAAO,CAAC,CAA6B;IACjE,IAAI,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,EAM1C;IAED,OAAO,IAAI,OAAO;IAClB,QAAQ,IAAI,OAAO;IAEnB,OAAO,IAAI,IAAI;IASf,IAAI,CAAC,KAAK,EAAE,SAAS;IAIrB,KAAK,CAAC,MAAM,EAAE,UAAU;CAS3B"}
|
package/dist/pointer.js
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
import { Option, panic, safeExecute } from "@opendaw/lib-std";
|
2
|
+
import { Address } from "./address";
|
3
|
+
import { PointerHub } from "./pointer-hub";
|
4
|
+
import { Field } from "./field";
|
5
|
+
import { Propagation } from "./dispatchers";
|
6
|
+
const _Unreferenceable = Symbol("Unreferenceable");
|
7
|
+
export class PointerField extends Field {
|
8
|
+
static create(construct, pointerType, mandatory) {
|
9
|
+
return new PointerField(construct, pointerType, mandatory);
|
10
|
+
}
|
11
|
+
#pointerType;
|
12
|
+
#mandatory;
|
13
|
+
#targetVertex = Option.None;
|
14
|
+
#targetAddress = Option.None;
|
15
|
+
constructor(field, pointerType, mandatory) {
|
16
|
+
super(field);
|
17
|
+
this.#pointerType = pointerType;
|
18
|
+
this.#mandatory = mandatory;
|
19
|
+
if (mandatory) {
|
20
|
+
this.graph.edges().watchVertex(this);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
get pointerHub() { return panic(`${this} cannot be pointed to`); }
|
24
|
+
get pointerType() { return this.#pointerType; }
|
25
|
+
get mandatory() { return this.#mandatory; }
|
26
|
+
accept(visitor) {
|
27
|
+
return safeExecute(visitor.visitPointerField, this);
|
28
|
+
}
|
29
|
+
subscribe(observer) {
|
30
|
+
return this.graph.subscribeVertexUpdates(Propagation.This, this.address, () => observer(this));
|
31
|
+
}
|
32
|
+
catchupAndSubscribe(observer) {
|
33
|
+
observer(this);
|
34
|
+
return this.graph.subscribeVertexUpdates(Propagation.This, this.address, () => this.graph.subscribeEndTransaction(() => observer(this)));
|
35
|
+
}
|
36
|
+
refer(vertex) {
|
37
|
+
this.targetVertex = Option.wrap(vertex);
|
38
|
+
}
|
39
|
+
defer() { this.targetVertex = Option.None; }
|
40
|
+
get targetVertex() { return this.#targetVertex; }
|
41
|
+
set targetVertex(option) {
|
42
|
+
if (option.nonEmpty()) {
|
43
|
+
const issue = PointerHub.validate(this, option.unwrap());
|
44
|
+
if (issue.nonEmpty()) {
|
45
|
+
panic(issue.unwrap());
|
46
|
+
}
|
47
|
+
}
|
48
|
+
this.targetAddress = option.map(vertex => vertex.address);
|
49
|
+
}
|
50
|
+
get targetAddress() { return this.#targetAddress; }
|
51
|
+
set targetAddress(newValue) {
|
52
|
+
const oldValue = this.#targetAddress;
|
53
|
+
if ((oldValue.isEmpty() && newValue.isEmpty())
|
54
|
+
|| (newValue.nonEmpty() && oldValue.unwrapOrNull()?.equals(newValue.unwrap())) === true) {
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
this.#targetAddress = newValue;
|
58
|
+
this.graph.onPointerAddressUpdated(this, oldValue, newValue);
|
59
|
+
}
|
60
|
+
isEmpty() { return this.targetAddress.isEmpty(); }
|
61
|
+
nonEmpty() { return this.targetAddress.nonEmpty(); }
|
62
|
+
resolve() {
|
63
|
+
const targetAddress = this.targetAddress;
|
64
|
+
const target = targetAddress.flatMap((address) => this.graph.findVertex(address));
|
65
|
+
if (targetAddress.nonEmpty() && target.isEmpty()) {
|
66
|
+
return panic(`${targetAddress.unwrapOrNull()?.toString()} could not be resolved`);
|
67
|
+
}
|
68
|
+
this.#targetVertex = target;
|
69
|
+
}
|
70
|
+
read(input) {
|
71
|
+
this.targetAddress = input.readBoolean() ? Option.wrap(Address.read(input)) : Option.None;
|
72
|
+
}
|
73
|
+
write(output) {
|
74
|
+
this.#targetAddress.match({
|
75
|
+
none: () => output.writeBoolean(false),
|
76
|
+
some: address => {
|
77
|
+
output.writeBoolean(true);
|
78
|
+
address.write(output);
|
79
|
+
}
|
80
|
+
});
|
81
|
+
}
|
82
|
+
}
|