@colyseus/schema 4.0.19 → 5.0.0
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 +2 -0
- package/build/Metadata.d.ts +55 -2
- package/build/Reflection.d.ts +24 -30
- package/build/Schema.d.ts +70 -9
- package/build/annotations.d.ts +56 -13
- package/build/codegen/cli.cjs +84 -44
- package/build/codegen/cli.cjs.map +1 -1
- package/build/decoder/DecodeOperation.d.ts +48 -5
- package/build/decoder/Decoder.d.ts +2 -2
- package/build/decoder/strategy/Callbacks.d.ts +1 -1
- package/build/encoder/ChangeRecorder.d.ts +107 -0
- package/build/encoder/ChangeTree.d.ts +218 -69
- package/build/encoder/EncodeDescriptor.d.ts +63 -0
- package/build/encoder/EncodeOperation.d.ts +25 -2
- package/build/encoder/Encoder.d.ts +59 -3
- package/build/encoder/MapJournal.d.ts +62 -0
- package/build/encoder/RefIdAllocator.d.ts +35 -0
- package/build/encoder/Root.d.ts +94 -13
- package/build/encoder/StateView.d.ts +116 -8
- package/build/encoder/changeTree/inheritedFlags.d.ts +34 -0
- package/build/encoder/changeTree/liveIteration.d.ts +3 -0
- package/build/encoder/changeTree/parentChain.d.ts +24 -0
- package/build/encoder/changeTree/treeAttachment.d.ts +13 -0
- package/build/encoder/streaming.d.ts +73 -0
- package/build/encoder/subscriptions.d.ts +25 -0
- package/build/index.cjs +5202 -1552
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +7 -3
- package/build/index.js +5202 -1552
- package/build/index.mjs +5193 -1552
- package/build/index.mjs.map +1 -1
- package/build/input/InputDecoder.d.ts +32 -0
- package/build/input/InputEncoder.d.ts +117 -0
- package/build/input/index.cjs +7429 -0
- package/build/input/index.cjs.map +1 -0
- package/build/input/index.d.ts +3 -0
- package/build/input/index.mjs +7426 -0
- package/build/input/index.mjs.map +1 -0
- package/build/types/HelperTypes.d.ts +22 -8
- package/build/types/TypeContext.d.ts +9 -0
- package/build/types/builder.d.ts +162 -0
- package/build/types/custom/ArraySchema.d.ts +25 -4
- package/build/types/custom/CollectionSchema.d.ts +30 -2
- package/build/types/custom/MapSchema.d.ts +52 -3
- package/build/types/custom/SetSchema.d.ts +32 -2
- package/build/types/custom/StreamSchema.d.ts +114 -0
- package/build/types/symbols.d.ts +48 -5
- package/package.json +8 -2
- package/src/Metadata.ts +258 -31
- package/src/Reflection.ts +15 -13
- package/src/Schema.ts +176 -134
- package/src/annotations.ts +308 -236
- package/src/bench_bloat.ts +173 -0
- package/src/bench_decode.ts +221 -0
- package/src/bench_decode_mem.ts +165 -0
- package/src/bench_encode.ts +108 -0
- package/src/bench_init.ts +150 -0
- package/src/bench_static.ts +109 -0
- package/src/bench_stream.ts +295 -0
- package/src/bench_view_cmp.ts +142 -0
- package/src/codegen/parser.ts +83 -61
- package/src/decoder/DecodeOperation.ts +168 -63
- package/src/decoder/Decoder.ts +20 -10
- package/src/decoder/ReferenceTracker.ts +4 -0
- package/src/decoder/strategy/Callbacks.ts +30 -26
- package/src/decoder/strategy/getDecoderStateCallbacks.ts +16 -13
- package/src/encoder/ChangeRecorder.ts +276 -0
- package/src/encoder/ChangeTree.ts +674 -519
- package/src/encoder/EncodeDescriptor.ts +213 -0
- package/src/encoder/EncodeOperation.ts +107 -65
- package/src/encoder/Encoder.ts +630 -119
- package/src/encoder/MapJournal.ts +124 -0
- package/src/encoder/RefIdAllocator.ts +68 -0
- package/src/encoder/Root.ts +247 -120
- package/src/encoder/StateView.ts +592 -121
- package/src/encoder/changeTree/inheritedFlags.ts +217 -0
- package/src/encoder/changeTree/liveIteration.ts +74 -0
- package/src/encoder/changeTree/parentChain.ts +131 -0
- package/src/encoder/changeTree/treeAttachment.ts +171 -0
- package/src/encoder/streaming.ts +232 -0
- package/src/encoder/subscriptions.ts +71 -0
- package/src/index.ts +15 -3
- package/src/input/InputDecoder.ts +57 -0
- package/src/input/InputEncoder.ts +303 -0
- package/src/input/index.ts +3 -0
- package/src/types/HelperTypes.ts +21 -9
- package/src/types/TypeContext.ts +14 -2
- package/src/types/builder.ts +285 -0
- package/src/types/custom/ArraySchema.ts +210 -197
- package/src/types/custom/CollectionSchema.ts +115 -35
- package/src/types/custom/MapSchema.ts +162 -58
- package/src/types/custom/SetSchema.ts +128 -39
- package/src/types/custom/StreamSchema.ts +310 -0
- package/src/types/symbols.ts +54 -6
- package/src/utils.ts +4 -6
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { $changes, $childType, $decoder, $deleteByIndex, $onEncodeEnd, $encoder, $filter, $getByIndex, $onDecodeEnd, $refId } from "../symbols.js";
|
|
1
|
+
import { $changes, $childType, $decoder, $deleteByIndex, $onEncodeEnd, $encoder, $filter, $getByIndex, $onDecodeEnd, $proxyTarget, $refId } from "../symbols.js";
|
|
2
2
|
import type { Schema } from "../../Schema.js";
|
|
3
|
-
import { type IRef, ChangeTree,
|
|
3
|
+
import { type IRef, ChangeTree, installUntrackedChangeTree } from "../../encoder/ChangeTree.js";
|
|
4
4
|
import { OPERATION } from "../../encoding/spec.js";
|
|
5
5
|
import { registerType } from "../registry.js";
|
|
6
6
|
import { Collection } from "../HelperTypes.js";
|
|
7
7
|
|
|
8
8
|
import { encodeArray } from "../../encoder/EncodeOperation.js";
|
|
9
|
-
import { decodeArray } from "../../decoder/DecodeOperation.js";
|
|
9
|
+
import { CollectionKind, decodeArray } from "../../decoder/DecodeOperation.js";
|
|
10
10
|
import type { StateView } from "../../encoder/StateView.js";
|
|
11
11
|
import { assertInstanceType } from "../../encoding/assert.js";
|
|
12
12
|
|
|
@@ -18,20 +18,113 @@ const DEFAULT_SORT = (a: any, b: any) => {
|
|
|
18
18
|
else return 0
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Module-level Proxy handler shared by every `ArraySchema` instance. Hoisted
|
|
23
|
+
* out of the ctor so per-instance Proxy setup stops allocating ~6 arrow
|
|
24
|
+
* closures (the `__name` wrappers around those closures dominated one slice
|
|
25
|
+
* of the decoder profile). The handlers reference the target via the trap's
|
|
26
|
+
* `obj` arg — they don't need a captured `this`. Both `new ArraySchema()`
|
|
27
|
+
* and `ArraySchema.initializeForDecoder()` plug into it.
|
|
28
|
+
*/
|
|
29
|
+
const ARRAY_PROXY_HANDLER: ProxyHandler<any> = {
|
|
30
|
+
get: (obj, prop) => {
|
|
31
|
+
if (
|
|
32
|
+
typeof (prop) !== "symbol" &&
|
|
33
|
+
// FIXME: d8 accuses this as low performance
|
|
34
|
+
!isNaN(prop as any) // https://stackoverflow.com/a/175787/892698
|
|
35
|
+
) {
|
|
36
|
+
return obj.items[prop as unknown as number];
|
|
37
|
+
}
|
|
38
|
+
return Reflect.get(obj, prop);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
set: (obj, key, setValue) => {
|
|
42
|
+
if (typeof (key) !== "symbol" && !isNaN(key as any)) {
|
|
43
|
+
if (setValue === undefined || setValue === null) {
|
|
44
|
+
obj.$deleteAt(key as unknown as number);
|
|
45
|
+
|
|
46
|
+
} else {
|
|
47
|
+
if (setValue[$changes]) {
|
|
48
|
+
assertInstanceType(setValue, obj[$childType] as typeof Schema, obj, key);
|
|
49
|
+
|
|
50
|
+
const previousValue = obj.items[key as unknown as number];
|
|
51
|
+
|
|
52
|
+
if (!obj.isMovingItems) {
|
|
53
|
+
obj.$changeAt(Number(key), setValue);
|
|
54
|
+
|
|
55
|
+
} else {
|
|
56
|
+
if (previousValue !== undefined) {
|
|
57
|
+
if (setValue[$changes].isNew) {
|
|
58
|
+
obj[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
|
|
59
|
+
|
|
60
|
+
} else {
|
|
61
|
+
if ((obj[$changes].getChange(Number(key)) & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
62
|
+
obj[$changes].indexedOperation(Number(key), OPERATION.DELETE_AND_MOVE);
|
|
63
|
+
|
|
64
|
+
} else {
|
|
65
|
+
obj[$changes].indexedOperation(Number(key), OPERATION.MOVE);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} else if (setValue[$changes].isNew) {
|
|
70
|
+
obj[$changes].indexedOperation(Number(key), OPERATION.ADD);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setValue[$changes].setParent(obj, obj[$changes].root, key);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (previousValue !== undefined) {
|
|
77
|
+
// remove root reference from previous value
|
|
78
|
+
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
} else {
|
|
82
|
+
obj.$changeAt(Number(key), setValue);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
obj.items[key as unknown as number] = setValue;
|
|
86
|
+
obj.tmpItems[key as unknown as number] = setValue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return Reflect.set(obj, key, setValue);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
deleteProperty: (obj, prop) => {
|
|
95
|
+
if (typeof (prop) === "number") {
|
|
96
|
+
obj.$deleteAt(prop);
|
|
97
|
+
} else {
|
|
98
|
+
delete obj[prop as unknown as number];
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
has: (obj, key) => {
|
|
104
|
+
if (typeof (key) !== "symbol" && !isNaN(Number(key))) {
|
|
105
|
+
return Reflect.has(obj.items, key);
|
|
106
|
+
}
|
|
107
|
+
return Reflect.has(obj, key);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
21
111
|
export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IRef {
|
|
22
112
|
[n: number]: V;
|
|
23
113
|
[$changes]: ChangeTree;
|
|
24
114
|
[$refId]?: number;
|
|
115
|
+
[$proxyTarget]: this;
|
|
25
116
|
|
|
26
117
|
protected [$childType]: string | typeof Schema;
|
|
27
118
|
|
|
28
119
|
protected items: V[] = [];
|
|
29
120
|
protected tmpItems: V[] = [];
|
|
30
|
-
protected deletedIndexes:
|
|
121
|
+
protected deletedIndexes: boolean[] = [];
|
|
31
122
|
protected isMovingItems = false;
|
|
32
123
|
|
|
33
124
|
static [$encoder] = encodeArray;
|
|
34
125
|
static [$decoder] = decodeArray;
|
|
126
|
+
/** Integer tag read by `decodeKeyValueOperation` — see `CollectionKind`. */
|
|
127
|
+
static readonly COLLECTION_KIND = CollectionKind.Array;
|
|
35
128
|
|
|
36
129
|
/**
|
|
37
130
|
* Determine if a property must be filtered.
|
|
@@ -65,99 +158,12 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
65
158
|
}
|
|
66
159
|
|
|
67
160
|
constructor (...items: V[]) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
configurable: true,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const proxy = new Proxy(this, {
|
|
76
|
-
get: (obj, prop) => {
|
|
77
|
-
if (
|
|
78
|
-
typeof (prop) !== "symbol" &&
|
|
79
|
-
// FIXME: d8 accuses this as low performance
|
|
80
|
-
!isNaN(prop as any) // https://stackoverflow.com/a/175787/892698
|
|
81
|
-
) {
|
|
82
|
-
return this.items[prop as unknown as number];
|
|
83
|
-
|
|
84
|
-
} else {
|
|
85
|
-
return Reflect.get(obj, prop);
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
set: (obj, key, setValue) => {
|
|
90
|
-
if (typeof (key) !== "symbol" && !isNaN(key as any)) {
|
|
91
|
-
if (setValue === undefined || setValue === null) {
|
|
92
|
-
obj.$deleteAt(key as unknown as number);
|
|
93
|
-
|
|
94
|
-
} else {
|
|
95
|
-
if (setValue[$changes]) {
|
|
96
|
-
assertInstanceType(setValue, obj[$childType] as typeof Schema, obj, key);
|
|
97
|
-
|
|
98
|
-
const previousValue = obj.items[key as unknown as number];
|
|
99
|
-
|
|
100
|
-
if (!obj.isMovingItems) {
|
|
101
|
-
obj.$changeAt(Number(key), setValue);
|
|
102
|
-
|
|
103
|
-
} else {
|
|
104
|
-
if (previousValue !== undefined) {
|
|
105
|
-
if (setValue[$changes].isNew) {
|
|
106
|
-
obj[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
|
|
107
|
-
|
|
108
|
-
} else {
|
|
109
|
-
if ((obj[$changes].getChange(Number(key)) & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
110
|
-
obj[$changes].indexedOperation(Number(key), OPERATION.DELETE_AND_MOVE);
|
|
111
|
-
|
|
112
|
-
} else {
|
|
113
|
-
obj[$changes].indexedOperation(Number(key), OPERATION.MOVE);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
} else if (setValue[$changes].isNew) {
|
|
118
|
-
obj[$changes].indexedOperation(Number(key), OPERATION.ADD);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
setValue[$changes].setParent(this, obj[$changes].root, key);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (previousValue !== undefined) {
|
|
125
|
-
// remove root reference from previous value
|
|
126
|
-
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
} else {
|
|
130
|
-
obj.$changeAt(Number(key), setValue);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
obj.items[key as unknown as number] = setValue;
|
|
134
|
-
obj.tmpItems[key as unknown as number] = setValue;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return true;
|
|
138
|
-
} else {
|
|
139
|
-
return Reflect.set(obj, key, setValue);
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
deleteProperty: (obj, prop) => {
|
|
144
|
-
if (typeof (prop) === "number") {
|
|
145
|
-
obj.$deleteAt(prop);
|
|
146
|
-
|
|
147
|
-
} else {
|
|
148
|
-
delete obj[prop as unknown as number];
|
|
149
|
-
}
|
|
161
|
+
this[$childType] = undefined as any;
|
|
162
|
+
// Self-reference so methods called via the Proxy can recover the
|
|
163
|
+
// underlying instance and access fields directly. See $proxyTarget.
|
|
164
|
+
this[$proxyTarget] = this;
|
|
150
165
|
|
|
151
|
-
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
has: (obj, key) => {
|
|
155
|
-
if (typeof (key) !== "symbol" && !isNaN(Number(key))) {
|
|
156
|
-
return Reflect.has(this.items, key);
|
|
157
|
-
}
|
|
158
|
-
return Reflect.has(obj, key)
|
|
159
|
-
}
|
|
160
|
-
});
|
|
166
|
+
const proxy = new Proxy(this, ARRAY_PROXY_HANDLER);
|
|
161
167
|
|
|
162
168
|
Object.defineProperty(this, $changes, {
|
|
163
169
|
value: new ChangeTree(proxy),
|
|
@@ -172,6 +178,30 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
172
178
|
return proxy;
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Decoder-side factory. Skips the `ChangeTree` allocation and
|
|
183
|
+
* replicates the class-field initializers by hand (since `Object.create`
|
|
184
|
+
* bypasses them). Must stay in sync with the class-field declarations
|
|
185
|
+
* and the constructor body above.
|
|
186
|
+
*
|
|
187
|
+
* Pass the Proxy to `installUntrackedChangeTree` as the public identity
|
|
188
|
+
* so children set their parent to the Proxy, not the raw target.
|
|
189
|
+
*/
|
|
190
|
+
static initializeForDecoder<V = any>(): ArraySchema<V> {
|
|
191
|
+
const self: any = Object.create(ArraySchema.prototype);
|
|
192
|
+
self.items = [];
|
|
193
|
+
// `tmpItems` / `deletedIndexes` are encoder-only (consulted by the
|
|
194
|
+
// staged-snapshot path in `$getByIndex`, `$onEncodeEnd`, etc.). The
|
|
195
|
+
// decoder reads from `items` directly and never maintains them.
|
|
196
|
+
self.isMovingItems = false;
|
|
197
|
+
self[$childType] = undefined;
|
|
198
|
+
self[$proxyTarget] = self;
|
|
199
|
+
|
|
200
|
+
const proxy = new Proxy(self, ARRAY_PROXY_HANDLER);
|
|
201
|
+
installUntrackedChangeTree(self, proxy);
|
|
202
|
+
return proxy;
|
|
203
|
+
}
|
|
204
|
+
|
|
175
205
|
set length (newLength: number) {
|
|
176
206
|
if (newLength === 0) {
|
|
177
207
|
this.clear();
|
|
@@ -186,10 +216,22 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
186
216
|
return this.items.length;
|
|
187
217
|
}
|
|
188
218
|
|
|
189
|
-
|
|
190
|
-
|
|
219
|
+
// ────── Change tracking control (same API as Schema) ──────
|
|
220
|
+
pauseTracking(): void { this[$changes].pause(); }
|
|
221
|
+
resumeTracking(): void { this[$changes].resume(); }
|
|
222
|
+
untracked<T>(fn: () => T): T { return this[$changes].untracked(fn); }
|
|
223
|
+
get isTrackingPaused(): boolean { return this[$changes].paused; }
|
|
191
224
|
|
|
192
|
-
|
|
225
|
+
push(...values: V[]) {
|
|
226
|
+
// `this` is the Proxy when called from user code. Grab the underlying
|
|
227
|
+
// instance once so the body's field reads (items, tmpItems, $changes,
|
|
228
|
+
// $childType) skip the Proxy.get trap on every iteration.
|
|
229
|
+
const self = this[$proxyTarget];
|
|
230
|
+
const items = self.items;
|
|
231
|
+
const tmpItems = self.tmpItems;
|
|
232
|
+
const changeTree = self[$changes];
|
|
233
|
+
const childType = self[$childType];
|
|
234
|
+
let length = tmpItems.length;
|
|
193
235
|
|
|
194
236
|
for (let i = 0, l = values.length; i < l; i++, length++) {
|
|
195
237
|
const value = values[i];
|
|
@@ -198,19 +240,21 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
198
240
|
// skip null values
|
|
199
241
|
return;
|
|
200
242
|
|
|
201
|
-
} else if (typeof (value) === "object" &&
|
|
202
|
-
assertInstanceType(value as any,
|
|
243
|
+
} else if (typeof (value) === "object" && childType) {
|
|
244
|
+
assertInstanceType(value as any, childType as typeof Schema, self, i);
|
|
203
245
|
// TODO: move value[$changes]?.setParent() to this block.
|
|
204
246
|
}
|
|
205
247
|
|
|
206
|
-
changeTree.indexedOperation(length, OPERATION.ADD
|
|
248
|
+
changeTree.indexedOperation(length, OPERATION.ADD);
|
|
207
249
|
|
|
208
|
-
|
|
209
|
-
|
|
250
|
+
items.push(value);
|
|
251
|
+
tmpItems.push(value);
|
|
210
252
|
|
|
211
253
|
//
|
|
212
254
|
// set value's parent after the value is set
|
|
213
255
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
256
|
+
// Pass `this` (the Proxy) as parent — the Proxy is the public
|
|
257
|
+
// identity of the array; ChangeTree.parentRef compares by identity.
|
|
214
258
|
//
|
|
215
259
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
216
260
|
}
|
|
@@ -222,12 +266,15 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
222
266
|
* Removes the last element from an array and returns it.
|
|
223
267
|
*/
|
|
224
268
|
pop(): V | undefined {
|
|
269
|
+
// Unwrap Proxy once — see push() for rationale.
|
|
270
|
+
const self = this[$proxyTarget];
|
|
271
|
+
const tmpItems = self.tmpItems;
|
|
272
|
+
const deletedIndexes = self.deletedIndexes;
|
|
225
273
|
let index: number = -1;
|
|
226
274
|
|
|
227
275
|
// find last non-undefined index
|
|
228
|
-
for (let i =
|
|
229
|
-
|
|
230
|
-
if (this.deletedIndexes[i] !== true) {
|
|
276
|
+
for (let i = tmpItems.length - 1; i >= 0; i--) {
|
|
277
|
+
if (deletedIndexes[i] !== true) {
|
|
231
278
|
index = i;
|
|
232
279
|
break;
|
|
233
280
|
}
|
|
@@ -237,11 +284,10 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
237
284
|
return undefined;
|
|
238
285
|
}
|
|
239
286
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
this.deletedIndexes[index] = true;
|
|
287
|
+
self[$changes].delete(index);
|
|
288
|
+
deletedIndexes[index] = true;
|
|
243
289
|
|
|
244
|
-
return
|
|
290
|
+
return self.items.pop();
|
|
245
291
|
}
|
|
246
292
|
|
|
247
293
|
at(index: number) {
|
|
@@ -303,24 +349,25 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
303
349
|
}
|
|
304
350
|
|
|
305
351
|
clear() {
|
|
352
|
+
const self = this[$proxyTarget];
|
|
306
353
|
// skip if already clear
|
|
307
|
-
if (
|
|
354
|
+
if (self.items.length === 0) {
|
|
308
355
|
return;
|
|
309
356
|
}
|
|
310
357
|
|
|
311
358
|
// discard previous operations.
|
|
312
|
-
const changeTree =
|
|
359
|
+
const changeTree = self[$changes];
|
|
313
360
|
|
|
314
361
|
// remove children references
|
|
315
362
|
changeTree.forEachChild((childChangeTree, _) => {
|
|
316
363
|
changeTree.root?.remove(childChangeTree);
|
|
317
364
|
});
|
|
318
365
|
|
|
319
|
-
changeTree.discard(
|
|
366
|
+
changeTree.discard();
|
|
320
367
|
changeTree.operation(OPERATION.CLEAR);
|
|
321
368
|
|
|
322
|
-
|
|
323
|
-
|
|
369
|
+
self.items.length = 0;
|
|
370
|
+
self.tmpItems.length = 0;
|
|
324
371
|
}
|
|
325
372
|
|
|
326
373
|
/**
|
|
@@ -345,9 +392,10 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
345
392
|
*/
|
|
346
393
|
// @ts-ignore
|
|
347
394
|
reverse(): ArraySchema<V> {
|
|
348
|
-
this[$
|
|
349
|
-
|
|
350
|
-
|
|
395
|
+
const self = this[$proxyTarget];
|
|
396
|
+
self[$changes].operation(OPERATION.REVERSE);
|
|
397
|
+
self.items.reverse();
|
|
398
|
+
self.tmpItems.reverse();
|
|
351
399
|
return this;
|
|
352
400
|
}
|
|
353
401
|
|
|
@@ -355,19 +403,17 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
355
403
|
* Removes the first element from an array and returns it.
|
|
356
404
|
*/
|
|
357
405
|
shift(): V | undefined {
|
|
358
|
-
|
|
406
|
+
const self = this[$proxyTarget];
|
|
407
|
+
const items = self.items;
|
|
408
|
+
if (items.length === 0) { return undefined; }
|
|
359
409
|
|
|
360
|
-
const changeTree =
|
|
361
|
-
|
|
362
|
-
const index =
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
changeTree.delete(index, OPERATION.DELETE, allChangesIndex);
|
|
366
|
-
changeTree.shiftAllChangeIndexes(-1, allChangesIndex);
|
|
367
|
-
|
|
368
|
-
this.deletedIndexes[index] = true;
|
|
410
|
+
const changeTree = self[$changes];
|
|
411
|
+
const first = items[0];
|
|
412
|
+
const index = self.tmpItems.findIndex(item => item === first);
|
|
413
|
+
changeTree.delete(index, OPERATION.DELETE);
|
|
414
|
+
self.deletedIndexes[index] = true;
|
|
369
415
|
|
|
370
|
-
return
|
|
416
|
+
return items.shift();
|
|
371
417
|
}
|
|
372
418
|
|
|
373
419
|
/**
|
|
@@ -391,17 +437,18 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
391
437
|
* ```
|
|
392
438
|
*/
|
|
393
439
|
sort(compareFn: (a: V, b: V) => number = DEFAULT_SORT): this {
|
|
394
|
-
|
|
440
|
+
const self = this[$proxyTarget];
|
|
441
|
+
self.isMovingItems = true;
|
|
395
442
|
|
|
396
|
-
const changeTree =
|
|
397
|
-
const sortedItems =
|
|
443
|
+
const changeTree = self[$changes];
|
|
444
|
+
const sortedItems = self.items.sort(compareFn);
|
|
398
445
|
|
|
399
446
|
// wouldn't OPERATION.MOVE make more sense here?
|
|
400
447
|
sortedItems.forEach((_, i) => changeTree.change(i, OPERATION.REPLACE));
|
|
401
448
|
|
|
402
|
-
|
|
449
|
+
self.tmpItems.sort(compareFn);
|
|
403
450
|
|
|
404
|
-
|
|
451
|
+
self.isMovingItems = false;
|
|
405
452
|
return this;
|
|
406
453
|
}
|
|
407
454
|
|
|
@@ -416,16 +463,20 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
416
463
|
deleteCount?: number,
|
|
417
464
|
...insertItems: V[]
|
|
418
465
|
): V[] {
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
const
|
|
466
|
+
const self = this[$proxyTarget];
|
|
467
|
+
const changeTree = self[$changes];
|
|
468
|
+
const items = self.items;
|
|
469
|
+
const tmpItems = self.tmpItems;
|
|
470
|
+
const deletedIndexes = self.deletedIndexes;
|
|
471
|
+
|
|
472
|
+
const itemsLength = items.length;
|
|
473
|
+
const tmpItemsLength = tmpItems.length;
|
|
423
474
|
const insertCount = insertItems.length;
|
|
424
475
|
|
|
425
476
|
// build up-to-date list of indexes, excluding removed values.
|
|
426
477
|
const indexes: number[] = [];
|
|
427
478
|
for (let i = 0; i < tmpItemsLength; i++) {
|
|
428
|
-
if (
|
|
479
|
+
if (deletedIndexes[i] !== true) {
|
|
429
480
|
indexes.push(i);
|
|
430
481
|
}
|
|
431
482
|
}
|
|
@@ -442,7 +493,7 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
442
493
|
for (let i = start; i < start + deleteCount; i++) {
|
|
443
494
|
const index = indexes[i];
|
|
444
495
|
changeTree.delete(index, OPERATION.DELETE);
|
|
445
|
-
|
|
496
|
+
deletedIndexes[index] = true;
|
|
446
497
|
}
|
|
447
498
|
|
|
448
499
|
} else {
|
|
@@ -462,36 +513,19 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
462
513
|
|
|
463
514
|
changeTree.indexedOperation(
|
|
464
515
|
addIndex,
|
|
465
|
-
(
|
|
516
|
+
(deletedIndexes[addIndex])
|
|
466
517
|
? OPERATION.DELETE_AND_ADD
|
|
467
518
|
: OPERATION.ADD
|
|
468
519
|
);
|
|
469
520
|
|
|
470
|
-
// set value's parent/root
|
|
521
|
+
// set value's parent/root — use `this` (Proxy) as parent.
|
|
471
522
|
insertItems[i][$changes]?.setParent(this, changeTree.root, addIndex);
|
|
472
523
|
}
|
|
473
524
|
}
|
|
474
525
|
|
|
475
|
-
|
|
476
|
-
// delete exceeding indexes from "allChanges"
|
|
477
|
-
// (prevent .encodeAll() from encoding non-existing items)
|
|
478
|
-
//
|
|
479
|
-
if (deleteCount > insertCount) {
|
|
480
|
-
changeTree.shiftAllChangeIndexes(-(deleteCount - insertCount), indexes[start + insertCount]);
|
|
481
|
-
// debugChangeSet("AFTER SHIFT indexes", changeTree.allChanges);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
//
|
|
485
|
-
// FIXME: this code block is duplicated on ChangeTree
|
|
486
|
-
//
|
|
487
|
-
if (changeTree.filteredChanges !== undefined) {
|
|
488
|
-
changeTree.root?.enqueueChangeTree(changeTree, 'filteredChanges');
|
|
526
|
+
changeTree.root?.enqueueChangeTree(changeTree);
|
|
489
527
|
|
|
490
|
-
|
|
491
|
-
changeTree.root?.enqueueChangeTree(changeTree, 'changes');
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return this.items.splice(start, deleteCount, ...insertItems);
|
|
528
|
+
return items.splice(start, deleteCount, ...insertItems);
|
|
495
529
|
}
|
|
496
530
|
|
|
497
531
|
/**
|
|
@@ -499,28 +533,20 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
499
533
|
* @param items Elements to insert at the start of the Array.
|
|
500
534
|
*/
|
|
501
535
|
unshift(...items: V[]): number {
|
|
502
|
-
const
|
|
536
|
+
const self = this[$proxyTarget];
|
|
537
|
+
const changeTree = self[$changes];
|
|
503
538
|
|
|
504
|
-
// shift
|
|
539
|
+
// Existing items shift up — `shiftChangeIndexes` handles their
|
|
540
|
+
// relocation bookkeeping. The prepended `items` are genuinely new
|
|
541
|
+
// (no prior existence to MOVE), so each records an ADD.
|
|
505
542
|
changeTree.shiftChangeIndexes(items.length);
|
|
506
|
-
|
|
507
|
-
// new index
|
|
508
|
-
if (changeTree.isFiltered) {
|
|
509
|
-
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
510
|
-
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
511
|
-
} else {
|
|
512
|
-
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
513
|
-
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// FIXME: should we use OPERATION.MOVE here instead?
|
|
517
543
|
items.forEach((_, index) => {
|
|
518
544
|
changeTree.change(index, OPERATION.ADD)
|
|
519
545
|
});
|
|
520
546
|
|
|
521
|
-
|
|
547
|
+
self.tmpItems.unshift(...items);
|
|
522
548
|
|
|
523
|
-
return
|
|
549
|
+
return self.items.unshift(...items);
|
|
524
550
|
}
|
|
525
551
|
|
|
526
552
|
/**
|
|
@@ -648,13 +674,7 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
648
674
|
* length+end.
|
|
649
675
|
*/
|
|
650
676
|
fill(value: V, start?: number, end?: number): this {
|
|
651
|
-
//
|
|
652
|
-
// TODO
|
|
653
|
-
//
|
|
654
677
|
throw new Error("ArraySchema#fill() not implemented");
|
|
655
|
-
// this.$items.fill(value, start, end);
|
|
656
|
-
|
|
657
|
-
return this;
|
|
658
678
|
}
|
|
659
679
|
|
|
660
680
|
/**
|
|
@@ -667,11 +687,7 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
667
687
|
* @param end If not specified, length of the this object is used as its default value.
|
|
668
688
|
*/
|
|
669
689
|
copyWithin(target: number, start: number, end?: number): this {
|
|
670
|
-
//
|
|
671
|
-
// TODO
|
|
672
|
-
//
|
|
673
690
|
throw new Error("ArraySchema#copyWithin() not implemented");
|
|
674
|
-
return this;
|
|
675
691
|
}
|
|
676
692
|
|
|
677
693
|
/**
|
|
@@ -824,14 +840,13 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
824
840
|
return this;
|
|
825
841
|
}
|
|
826
842
|
|
|
843
|
+
/**
|
|
844
|
+
* Encoder-only. Reads the staged-snapshot (`tmpItems`) so the encoder can
|
|
845
|
+
* resolve a wire-index even after the user has mutated `items` mid-tick.
|
|
846
|
+
* The decoder reads `items[index]` directly — see `decodeArray` and
|
|
847
|
+
* `$deleteByIndex` below.
|
|
848
|
+
*/
|
|
827
849
|
[$getByIndex](index: number, isEncodeAll: boolean = false): any {
|
|
828
|
-
//
|
|
829
|
-
// TODO: avoid unecessary `this.tmpItems` check during decoding.
|
|
830
|
-
//
|
|
831
|
-
// ENCODING uses `this.tmpItems` (or `this.items` if `isEncodeAll` is true)
|
|
832
|
-
// DECODING uses `this.items`
|
|
833
|
-
//
|
|
834
|
-
|
|
835
850
|
return (isEncodeAll)
|
|
836
851
|
? this.items[index]
|
|
837
852
|
: this.deletedIndexes[index]
|
|
@@ -841,17 +856,15 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V>, IR
|
|
|
841
856
|
|
|
842
857
|
[$deleteByIndex](index: number): void {
|
|
843
858
|
this.items[index] = undefined;
|
|
844
|
-
this.tmpItems[index] = undefined; // TODO: do not try to get "tmpItems" at decoding time.
|
|
845
859
|
}
|
|
846
860
|
|
|
847
861
|
protected [$onEncodeEnd]() {
|
|
848
862
|
this.tmpItems = this.items.slice();
|
|
849
|
-
this.deletedIndexes =
|
|
863
|
+
this.deletedIndexes.length = 0;
|
|
850
864
|
}
|
|
851
865
|
|
|
852
866
|
protected [$onDecodeEnd]() {
|
|
853
867
|
this.items = this.items.filter((item) => item !== undefined);
|
|
854
|
-
this.tmpItems = this.items.slice(); // TODO: do no use "tmpItems" at decoding time.
|
|
855
868
|
}
|
|
856
869
|
|
|
857
870
|
toArray() {
|