@colyseus/schema 4.0.20 → 5.0.1
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 +56 -2
- package/build/Reflection.d.ts +28 -34
- package/build/Schema.d.ts +70 -9
- package/build/annotations.d.ts +64 -17
- package/build/codegen/cli.cjs +84 -67
- 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 +5258 -1549
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +7 -3
- package/build/index.js +5258 -1549
- package/build/index.mjs +5249 -1549
- 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 +7453 -0
- package/build/input/index.cjs.map +1 -0
- package/build/input/index.d.ts +3 -0
- package/build/input/index.mjs +7450 -0
- package/build/input/index.mjs.map +1 -0
- package/build/types/HelperTypes.d.ts +67 -9
- package/build/types/TypeContext.d.ts +9 -0
- package/build/types/builder.d.ts +192 -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 +9 -3
- package/src/Metadata.ts +259 -31
- package/src/Reflection.ts +15 -13
- package/src/Schema.ts +176 -134
- package/src/annotations.ts +365 -252
- 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/languages/csharp.ts +0 -24
- 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 +121 -24
- package/src/types/TypeContext.ts +14 -2
- package/src/types/builder.ts +331 -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 +93 -6
- package/src/utils.ts +4 -6
package/src/Schema.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { DEFAULT_VIEW_TAG, type DefinitionType } from "./annotations.js";
|
|
|
3
3
|
|
|
4
4
|
import { AssignableProps, NonFunctionPropNames, ToJSON } from './types/HelperTypes.js';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { $changes, $decoder, $deleteByIndex, $
|
|
6
|
+
import { ChangeTree, installUntrackedChangeTree, IRef, Ref } from './encoder/ChangeTree.js';
|
|
7
|
+
import { $changes, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $numFields, $refId, $track, $values } from './types/symbols.js';
|
|
8
8
|
import { StateView } from './encoder/StateView.js';
|
|
9
9
|
|
|
10
10
|
import { encodeSchemaOperation } from './encoder/EncodeOperation.js';
|
|
@@ -23,28 +23,71 @@ export class Schema<C = any> implements IRef {
|
|
|
23
23
|
static [$decoder] = decodeSchemaOperation;
|
|
24
24
|
|
|
25
25
|
[$refId]?: number;
|
|
26
|
+
[$values]: any[];
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* Initialize change tracking on this instance.
|
|
30
|
+
* Field accessor descriptors (getter/setter) live on the prototype,
|
|
31
|
+
* installed once at class-definition time. Per-instance work is limited
|
|
32
|
+
* to allocating a ChangeTree and a values array.
|
|
30
33
|
*/
|
|
31
34
|
static initialize(instance: any) {
|
|
35
|
+
// $changes MUST be non-enumerable: tests use assert.deepStrictEqual on
|
|
36
|
+
// Schema instances (e.g. arrayOfPlayers.toArray()), which walks
|
|
37
|
+
// enumerable own Symbol properties. ChangeTree has circular refs
|
|
38
|
+
// (root → changeTrees → other ChangeTrees), so a visible $changes
|
|
39
|
+
// would send deepStrictEqual into exponential recursion. Plain
|
|
40
|
+
// assignment of a Symbol key would be enumerable: true — hence we
|
|
41
|
+
// keep defineProperty here.
|
|
32
42
|
Object.defineProperty(instance, $changes, {
|
|
33
43
|
value: new ChangeTree(instance),
|
|
34
44
|
enumerable: false,
|
|
35
45
|
writable: true
|
|
36
46
|
});
|
|
47
|
+
instance[$values] = [];
|
|
48
|
+
}
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Decoder-side factory. Skips the user subclass ctor entirely —
|
|
52
|
+
* decoder-built instances are passive mirrors of server state, so any
|
|
53
|
+
* field initializer / ctor body work would be overwritten by the
|
|
54
|
+
* decoded ADDs immediately after. Assignment order matches
|
|
55
|
+
* {@link Schema.initialize} so V8 assigns the same hidden class
|
|
56
|
+
* ($changes, then $values), keeping decode-path ICs monomorphic even
|
|
57
|
+
* when tracked and untracked instances coexist.
|
|
58
|
+
*
|
|
59
|
+
* The `this:` constraint pins the return type to the concrete subclass
|
|
60
|
+
* when called as `Player.initializeForDecoder()`, not the base Schema.
|
|
61
|
+
*/
|
|
62
|
+
static initializeForDecoder<T extends Schema = Schema>(this: { prototype: T } & typeof Schema): T {
|
|
63
|
+
const inst: any = Object.create(this.prototype);
|
|
64
|
+
installUntrackedChangeTree(inst);
|
|
65
|
+
inst[$values] = [];
|
|
66
|
+
return inst;
|
|
39
67
|
}
|
|
40
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Check whether `type` describes a Schema *class* (a subclass
|
|
71
|
+
* constructor carrying `Symbol.metadata`, as installed by `@type`).
|
|
72
|
+
* Returns false for primitive type strings like `"number"`, descriptor
|
|
73
|
+
* objects like `{ map: Player }`, and Schema *instances*.
|
|
74
|
+
*
|
|
75
|
+
* For the instance-level check — "is this value a Schema instance?" —
|
|
76
|
+
* see {@link Schema.isSchema}.
|
|
77
|
+
*/
|
|
41
78
|
static is(type: DefinitionType) {
|
|
42
79
|
return typeof((type as typeof Schema)[Symbol.metadata]) === "object";
|
|
43
80
|
}
|
|
44
81
|
|
|
45
82
|
/**
|
|
46
|
-
* Check if a value is an instance of Schema.
|
|
47
|
-
*
|
|
83
|
+
* Check if a value is an *instance* of Schema. Uses duck-typing on
|
|
84
|
+
* `.assign` to work across multiple `@colyseus/schema` versions that
|
|
85
|
+
* may be loaded in the same process (e.g. bundled server types vs.
|
|
86
|
+
* client types in a p2p setup).
|
|
87
|
+
*
|
|
88
|
+
* For the class-level check — "is this type a Schema subclass?" —
|
|
89
|
+
* see {@link Schema.is}.
|
|
90
|
+
*
|
|
48
91
|
* @param obj Value to check
|
|
49
92
|
* @returns true if the value is a Schema instance
|
|
50
93
|
*/
|
|
@@ -53,7 +96,11 @@ export class Schema<C = any> implements IRef {
|
|
|
53
96
|
}
|
|
54
97
|
|
|
55
98
|
/**
|
|
56
|
-
* Track property changes
|
|
99
|
+
* Track property changes. Exposed as an override point so downstream
|
|
100
|
+
* tools (debuggers, transparent proxies, custom instrumentation) can
|
|
101
|
+
* intercept per-field writes. Hot-path code in `annotations.ts` calls
|
|
102
|
+
* `(this.constructor as typeof Schema)[$track](...)` rather than
|
|
103
|
+
* `changeTree.change(...)` directly so any subclass override wins.
|
|
57
104
|
*/
|
|
58
105
|
static [$track] (changeTree: ChangeTree, index: number, operation: OPERATION = OPERATION.ADD) {
|
|
59
106
|
changeTree.change(index, operation);
|
|
@@ -86,24 +133,15 @@ export class Schema<C = any> implements IRef {
|
|
|
86
133
|
|
|
87
134
|
} else {
|
|
88
135
|
// view pass: custom tag
|
|
89
|
-
|
|
90
|
-
return tags && tags.has(tag);
|
|
136
|
+
return view.hasTagOnTree(ref[$changes], tag);
|
|
91
137
|
}
|
|
92
138
|
}
|
|
93
139
|
|
|
94
140
|
// allow inherited classes to have a constructor
|
|
95
141
|
constructor(arg?: C) {
|
|
96
|
-
//
|
|
97
|
-
// inline
|
|
98
|
-
// Schema.initialize(this);
|
|
99
|
-
//
|
|
100
142
|
Schema.initialize(this);
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
// Assign initial values
|
|
104
|
-
//
|
|
105
143
|
if (arg) {
|
|
106
|
-
|
|
144
|
+
Schema.assignProps(this, arg);
|
|
107
145
|
}
|
|
108
146
|
}
|
|
109
147
|
|
|
@@ -113,10 +151,36 @@ export class Schema<C = any> implements IRef {
|
|
|
113
151
|
* @returns
|
|
114
152
|
*/
|
|
115
153
|
public assign<T extends Partial<this>>(props: AssignableProps<T>,): this {
|
|
116
|
-
|
|
154
|
+
Schema.assignProps(this, props);
|
|
117
155
|
return this;
|
|
118
156
|
}
|
|
119
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Metadata-driven property assignment.
|
|
160
|
+
* Reads tracked fields via property access (works with prototype accessors),
|
|
161
|
+
* then copies any remaining own properties for non-tracked fields.
|
|
162
|
+
*/
|
|
163
|
+
protected static assignProps(target: any, source: any) {
|
|
164
|
+
const metadata: Metadata = target.constructor[Symbol.metadata];
|
|
165
|
+
if (metadata && metadata[$numFields] !== undefined) {
|
|
166
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
167
|
+
const field = metadata[i];
|
|
168
|
+
if (!field) { continue; }
|
|
169
|
+
const value = source[field.name];
|
|
170
|
+
if (value !== undefined) {
|
|
171
|
+
target[field.name] = value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Copy non-tracked own properties (e.g. `notSynched: true`).
|
|
176
|
+
const keys = Object.keys(source);
|
|
177
|
+
for (let i = 0; i < keys.length; i++) {
|
|
178
|
+
const key = keys[i];
|
|
179
|
+
if (metadata && metadata[key] !== undefined) { continue; }
|
|
180
|
+
target[key] = source[key];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
120
184
|
/**
|
|
121
185
|
* Restore the instance from JSON data.
|
|
122
186
|
* @param jsonData JSON data to restore the instance from
|
|
@@ -194,6 +258,50 @@ export class Schema<C = any> implements IRef {
|
|
|
194
258
|
);
|
|
195
259
|
}
|
|
196
260
|
|
|
261
|
+
// ────────────────────────────────────────────────────────────────────
|
|
262
|
+
// Change-tracking control API
|
|
263
|
+
//
|
|
264
|
+
// By default, every mutation to a @type() property is automatically
|
|
265
|
+
// recorded as a change. These methods let you opt out for bulk-load
|
|
266
|
+
// scenarios or custom batching.
|
|
267
|
+
//
|
|
268
|
+
// @example
|
|
269
|
+
// // Bulk-load without emitting changes:
|
|
270
|
+
// player.untracked(() => {
|
|
271
|
+
// player.hp = 100;
|
|
272
|
+
// player.name = "alice";
|
|
273
|
+
// });
|
|
274
|
+
//
|
|
275
|
+
// // Pause / resume pattern:
|
|
276
|
+
// player.pauseTracking();
|
|
277
|
+
// player.hp = 100; // not tracked
|
|
278
|
+
// player.resumeTracking();
|
|
279
|
+
// player.hp = 50; // tracked
|
|
280
|
+
// ────────────────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
/** Stop recording mutations until resumeTracking() is called. */
|
|
283
|
+
public pauseTracking(): void {
|
|
284
|
+
this[$changes].pause();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Re-enable automatic change tracking. */
|
|
288
|
+
public resumeTracking(): void {
|
|
289
|
+
this[$changes].resume();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Run `fn` with change tracking paused, then resume.
|
|
294
|
+
* Returns the function's return value. Safe to nest.
|
|
295
|
+
*/
|
|
296
|
+
public untracked<T>(fn: () => T): T {
|
|
297
|
+
return this[$changes].untracked(fn);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** True while tracking is paused. */
|
|
301
|
+
public get isTrackingPaused(): boolean {
|
|
302
|
+
return this[$changes].paused;
|
|
303
|
+
}
|
|
304
|
+
|
|
197
305
|
clone (): this {
|
|
198
306
|
// Create instance without calling custom constructor
|
|
199
307
|
const cloned = Object.create(this.constructor.prototype);
|
|
@@ -291,15 +399,45 @@ export class Schema<C = any> implements IRef {
|
|
|
291
399
|
return output;
|
|
292
400
|
}
|
|
293
401
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
402
|
+
/**
|
|
403
|
+
* @param changeSet
|
|
404
|
+
* - "changes": iterate the current-tick dirty queue (per-tick encode order)
|
|
405
|
+
* - "allChanges" / "allFilteredChanges" (legacy): structurally walk the
|
|
406
|
+
* tree in DFS preorder (matches the order in which full-sync emits
|
|
407
|
+
* trees). The two legacy modes differ by which side of the filter
|
|
408
|
+
* split they include.
|
|
409
|
+
*/
|
|
410
|
+
static debugRefIdEncodingOrder<T extends Ref>(
|
|
411
|
+
ref: T,
|
|
412
|
+
changeSet: "changes" | "allChanges" | "allFilteredChanges" = 'allChanges'
|
|
413
|
+
) {
|
|
414
|
+
const encodeOrder: number[] = [];
|
|
415
|
+
const rootChangeTree = ref[$changes];
|
|
416
|
+
|
|
417
|
+
if (changeSet === "changes") {
|
|
418
|
+
let current = rootChangeTree.root.changes?.next;
|
|
419
|
+
while (current) {
|
|
420
|
+
if (current.changeTree) {
|
|
421
|
+
encodeOrder.push(current.changeTree.ref[$refId]);
|
|
422
|
+
}
|
|
423
|
+
current = current.next;
|
|
300
424
|
}
|
|
301
|
-
|
|
425
|
+
return encodeOrder;
|
|
302
426
|
}
|
|
427
|
+
|
|
428
|
+
// Full-sync modes: DFS preorder from root, filtered by tree's
|
|
429
|
+
// filter-status to match the unfiltered / filtered split.
|
|
430
|
+
const wantFiltered = (changeSet === "allFilteredChanges");
|
|
431
|
+
const visited = new Set<ChangeTree>();
|
|
432
|
+
const walk = (changeTree: ChangeTree) => {
|
|
433
|
+
if (visited.has(changeTree)) return;
|
|
434
|
+
visited.add(changeTree);
|
|
435
|
+
if (changeTree.isFiltered === wantFiltered) {
|
|
436
|
+
encodeOrder.push(changeTree.ref[$refId]);
|
|
437
|
+
}
|
|
438
|
+
changeTree.forEachChild((child, _) => walk(child));
|
|
439
|
+
};
|
|
440
|
+
walk(rootChangeTree);
|
|
303
441
|
return encodeOrder;
|
|
304
442
|
}
|
|
305
443
|
|
|
@@ -317,118 +455,22 @@ export class Schema<C = any> implements IRef {
|
|
|
317
455
|
*/
|
|
318
456
|
static debugChanges<T extends Ref>(instance: T, isEncodeAll: boolean = false) {
|
|
319
457
|
const changeTree: ChangeTree = instance[$changes];
|
|
458
|
+
const label = isEncodeAll ? "allChanges" : "changes";
|
|
459
|
+
let output = `${instance.constructor.name} (${instance[$refId]}) -> .${label}:\n`;
|
|
320
460
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
.forEach((index) => {
|
|
330
|
-
const operation = changeTree.indexedOperations[index];
|
|
331
|
-
output += `- [${index}]: ${OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
dumpChangeSet(changeSet);
|
|
336
|
-
|
|
337
|
-
// display filtered changes
|
|
338
|
-
if (
|
|
339
|
-
!isEncodeAll &&
|
|
340
|
-
changeTree.filteredChanges &&
|
|
341
|
-
(changeTree.filteredChanges.operations).filter(op => op).length > 0
|
|
342
|
-
) {
|
|
343
|
-
output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
|
|
344
|
-
dumpChangeSet(changeTree.filteredChanges);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// display filtered changes
|
|
348
|
-
if (
|
|
349
|
-
isEncodeAll &&
|
|
350
|
-
changeTree.allFilteredChanges &&
|
|
351
|
-
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0
|
|
352
|
-
) {
|
|
353
|
-
output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
|
|
354
|
-
dumpChangeSet(changeTree.allFilteredChanges);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return output;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
static debugChangesDeep<T extends Schema>(ref: T, changeSetName: "changes" | "allChanges" | "allFilteredChanges" | "filteredChanges" = "changes") {
|
|
361
|
-
let output = "";
|
|
362
|
-
|
|
363
|
-
const rootChangeTree: ChangeTree = ref[$changes];
|
|
364
|
-
const root = rootChangeTree.root;
|
|
365
|
-
const changeTrees: Map<ChangeTree, ChangeTree[]> = new Map();
|
|
366
|
-
|
|
367
|
-
const instanceRefIds = [];
|
|
368
|
-
let totalOperations = 0;
|
|
369
|
-
|
|
370
|
-
// TODO: FIXME: this method is not working as expected
|
|
371
|
-
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
372
|
-
const changeTree = root.changeTrees[refId as any as number];
|
|
373
|
-
if (!changeTree) { continue; }
|
|
374
|
-
|
|
375
|
-
let includeChangeTree = false;
|
|
376
|
-
let parentChangeTrees: ChangeTree[] = [];
|
|
377
|
-
let parentChangeTree = changeTree.parent?.[$changes];
|
|
378
|
-
|
|
379
|
-
if (changeTree === rootChangeTree) {
|
|
380
|
-
includeChangeTree = true;
|
|
381
|
-
|
|
382
|
-
} else {
|
|
383
|
-
while (parentChangeTree !== undefined) {
|
|
384
|
-
parentChangeTrees.push(parentChangeTree);
|
|
385
|
-
if (parentChangeTree.ref === ref) {
|
|
386
|
-
includeChangeTree = true;
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
parentChangeTree = parentChangeTree.parent?.[$changes];
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (includeChangeTree) {
|
|
394
|
-
instanceRefIds.push(changeTree.ref[$refId]);
|
|
395
|
-
totalOperations += Object.keys(changes).length;
|
|
396
|
-
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
output += "---\n"
|
|
401
|
-
output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
|
|
402
|
-
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
403
|
-
output += `Total changes: ${totalOperations}\n`;
|
|
404
|
-
output += "---\n"
|
|
405
|
-
|
|
406
|
-
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
407
|
-
const visitedParents = new WeakSet<ChangeTree>();
|
|
408
|
-
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
409
|
-
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
410
|
-
if (!visitedParents.has(parentChangeTree)) {
|
|
411
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
|
|
412
|
-
visitedParents.add(parentChangeTree);
|
|
413
|
-
}
|
|
461
|
+
if (isEncodeAll) {
|
|
462
|
+
changeTree.forEachLive((index) => {
|
|
463
|
+
output += `- [${index}]: ADD (${JSON.stringify(changeTree.getValue(Number(index), true))})\n`;
|
|
464
|
+
});
|
|
465
|
+
} else {
|
|
466
|
+
changeTree.forEach((index, op) => {
|
|
467
|
+
if (index < 0 || !op) return;
|
|
468
|
+
output += `- [${index}]: ${OPERATION[op]} (${JSON.stringify(changeTree.getValue(Number(index), false))})\n`;
|
|
414
469
|
});
|
|
415
|
-
|
|
416
|
-
const changes = changeTree.indexedOperations;
|
|
417
|
-
const level = parentChangeTrees.length;
|
|
418
|
-
const indent = getIndent(level);
|
|
419
|
-
|
|
420
|
-
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
421
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.ref[$refId]}) - changes: ${Object.keys(changes).length}\n`;
|
|
422
|
-
|
|
423
|
-
for (const index in changes) {
|
|
424
|
-
const operation = changes[index];
|
|
425
|
-
output += `${getIndent(level + 1)}${OPERATION[operation]}: ${index}\n`;
|
|
426
|
-
}
|
|
427
470
|
}
|
|
428
471
|
|
|
429
|
-
return
|
|
472
|
+
return output;
|
|
430
473
|
}
|
|
431
474
|
|
|
432
|
-
|
|
433
475
|
}
|
|
434
476
|
|