@colyseus/schema 3.0.52 → 3.0.53
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/build/cjs/index.js +969 -925
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +969 -925
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +969 -925
- package/lib/Schema.js +0 -1
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +2 -2
- package/lib/annotations.js +52 -5
- package/lib/annotations.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +1 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/package.json +1 -1
- package/src/Schema.ts +0 -1
- package/src/annotations.ts +52 -7
- package/src/types/custom/ArraySchema.ts +1 -1
package/build/cjs/index.js
CHANGED
|
@@ -2185,7 +2185,7 @@ class ArraySchema {
|
|
|
2185
2185
|
const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
|
|
2186
2186
|
changeTree.delete(index, exports.OPERATION.DELETE, allChangesIndex);
|
|
2187
2187
|
changeTree.shiftAllChangeIndexes(-1, allChangesIndex);
|
|
2188
|
-
|
|
2188
|
+
this.deletedIndexes[index] = true;
|
|
2189
2189
|
return this.items.shift();
|
|
2190
2190
|
}
|
|
2191
2191
|
/**
|
|
@@ -2820,448 +2820,174 @@ class MapSchema {
|
|
|
2820
2820
|
}
|
|
2821
2821
|
registerType("map", { constructor: MapSchema });
|
|
2822
2822
|
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2854
|
-
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2855
|
-
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2856
|
-
// ?? -1; // no fields defined
|
|
2857
|
-
// fieldIndex++;
|
|
2858
|
-
// if (
|
|
2859
|
-
// !parent && // the parent already initializes the `$changes` property
|
|
2860
|
-
// !Metadata.hasFields(context.metadata)
|
|
2861
|
-
// ) {
|
|
2862
|
-
// context.addInitializer(function (this: Ref) {
|
|
2863
|
-
// Object.defineProperty(this, $changes, {
|
|
2864
|
-
// value: new ChangeTree(this),
|
|
2865
|
-
// enumerable: false,
|
|
2866
|
-
// writable: true
|
|
2867
|
-
// });
|
|
2868
|
-
// });
|
|
2869
|
-
// }
|
|
2870
|
-
// Metadata.addField(context.metadata, fieldIndex, field, type);
|
|
2871
|
-
// const isArray = ArraySchema.is(type);
|
|
2872
|
-
// const isMap = !isArray && MapSchema.is(type);
|
|
2873
|
-
// // if (options && options.manual) {
|
|
2874
|
-
// // // do not declare getter/setter descriptor
|
|
2875
|
-
// // definition.descriptors[field] = {
|
|
2876
|
-
// // enumerable: true,
|
|
2877
|
-
// // configurable: true,
|
|
2878
|
-
// // writable: true,
|
|
2879
|
-
// // };
|
|
2880
|
-
// // return;
|
|
2881
|
-
// // }
|
|
2882
|
-
// return {
|
|
2883
|
-
// init(value) {
|
|
2884
|
-
// // TODO: may need to convert ArraySchema/MapSchema here
|
|
2885
|
-
// // do not flag change if value is undefined.
|
|
2886
|
-
// if (value !== undefined) {
|
|
2887
|
-
// this[$changes].change(fieldIndex);
|
|
2888
|
-
// // automaticallty transform Array into ArraySchema
|
|
2889
|
-
// if (isArray) {
|
|
2890
|
-
// if (!(value instanceof ArraySchema)) {
|
|
2891
|
-
// value = new ArraySchema(...value);
|
|
2892
|
-
// }
|
|
2893
|
-
// value[$childType] = Object.values(type)[0];
|
|
2894
|
-
// }
|
|
2895
|
-
// // automaticallty transform Map into MapSchema
|
|
2896
|
-
// if (isMap) {
|
|
2897
|
-
// if (!(value instanceof MapSchema)) {
|
|
2898
|
-
// value = new MapSchema(value);
|
|
2899
|
-
// }
|
|
2900
|
-
// value[$childType] = Object.values(type)[0];
|
|
2901
|
-
// }
|
|
2902
|
-
// // try to turn provided structure into a Proxy
|
|
2903
|
-
// if (value['$proxy'] === undefined) {
|
|
2904
|
-
// if (isMap) {
|
|
2905
|
-
// value = getMapProxy(value);
|
|
2906
|
-
// }
|
|
2907
|
-
// }
|
|
2908
|
-
// }
|
|
2909
|
-
// return value;
|
|
2910
|
-
// },
|
|
2911
|
-
// get() {
|
|
2912
|
-
// return get.call(this);
|
|
2913
|
-
// },
|
|
2914
|
-
// set(value: any) {
|
|
2915
|
-
// /**
|
|
2916
|
-
// * Create Proxy for array or map items
|
|
2917
|
-
// */
|
|
2918
|
-
// // skip if value is the same as cached.
|
|
2919
|
-
// if (value === get.call(this)) {
|
|
2920
|
-
// return;
|
|
2921
|
-
// }
|
|
2922
|
-
// if (
|
|
2923
|
-
// value !== undefined &&
|
|
2924
|
-
// value !== null
|
|
2925
|
-
// ) {
|
|
2926
|
-
// // automaticallty transform Array into ArraySchema
|
|
2927
|
-
// if (isArray) {
|
|
2928
|
-
// if (!(value instanceof ArraySchema)) {
|
|
2929
|
-
// value = new ArraySchema(...value);
|
|
2930
|
-
// }
|
|
2931
|
-
// value[$childType] = Object.values(type)[0];
|
|
2932
|
-
// }
|
|
2933
|
-
// // automaticallty transform Map into MapSchema
|
|
2934
|
-
// if (isMap) {
|
|
2935
|
-
// if (!(value instanceof MapSchema)) {
|
|
2936
|
-
// value = new MapSchema(value);
|
|
2937
|
-
// }
|
|
2938
|
-
// value[$childType] = Object.values(type)[0];
|
|
2939
|
-
// }
|
|
2940
|
-
// // try to turn provided structure into a Proxy
|
|
2941
|
-
// if (value['$proxy'] === undefined) {
|
|
2942
|
-
// if (isMap) {
|
|
2943
|
-
// value = getMapProxy(value);
|
|
2944
|
-
// }
|
|
2945
|
-
// }
|
|
2946
|
-
// // flag the change for encoding.
|
|
2947
|
-
// this[$changes].change(fieldIndex);
|
|
2948
|
-
// //
|
|
2949
|
-
// // call setParent() recursively for this and its child
|
|
2950
|
-
// // structures.
|
|
2951
|
-
// //
|
|
2952
|
-
// if (value[$changes]) {
|
|
2953
|
-
// value[$changes].setParent(
|
|
2954
|
-
// this,
|
|
2955
|
-
// this[$changes].root,
|
|
2956
|
-
// Metadata.getIndex(context.metadata, field),
|
|
2957
|
-
// );
|
|
2958
|
-
// }
|
|
2959
|
-
// } else if (get.call(this)) {
|
|
2960
|
-
// //
|
|
2961
|
-
// // Setting a field to `null` or `undefined` will delete it.
|
|
2962
|
-
// //
|
|
2963
|
-
// this[$changes].delete(field);
|
|
2964
|
-
// }
|
|
2965
|
-
// set.call(this, value);
|
|
2966
|
-
// },
|
|
2967
|
-
// };
|
|
2968
|
-
// }
|
|
2969
|
-
// }
|
|
2970
|
-
function view(tag = DEFAULT_VIEW_TAG) {
|
|
2971
|
-
return function (target, fieldName) {
|
|
2972
|
-
const constructor = target.constructor;
|
|
2973
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
2974
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
2975
|
-
// TODO: use Metadata.initialize()
|
|
2976
|
-
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2977
|
-
// const fieldIndex = metadata[fieldName];
|
|
2978
|
-
// if (!metadata[fieldIndex]) {
|
|
2979
|
-
// //
|
|
2980
|
-
// // detect index for this field, considering inheritance
|
|
2981
|
-
// //
|
|
2982
|
-
// metadata[fieldIndex] = {
|
|
2983
|
-
// type: undefined,
|
|
2984
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2985
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2986
|
-
// ?? -1) + 1 // no fields defined
|
|
2987
|
-
// }
|
|
2988
|
-
// }
|
|
2989
|
-
Metadata.setTag(metadata, fieldName, tag);
|
|
2990
|
-
};
|
|
2991
|
-
}
|
|
2992
|
-
function type(type, options) {
|
|
2993
|
-
return function (target, field) {
|
|
2994
|
-
const constructor = target.constructor;
|
|
2995
|
-
if (!type) {
|
|
2996
|
-
throw new Error(`${constructor.name}: @type() reference provided for "${field}" is undefined. Make sure you don't have any circular dependencies.`);
|
|
2997
|
-
}
|
|
2998
|
-
// for inheritance support
|
|
2999
|
-
TypeContext.register(constructor);
|
|
3000
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
3001
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
3002
|
-
const metadata = Metadata.initialize(constructor);
|
|
3003
|
-
let fieldIndex = metadata[field];
|
|
3004
|
-
/**
|
|
3005
|
-
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
3006
|
-
*/
|
|
3007
|
-
if (metadata[fieldIndex] !== undefined) {
|
|
3008
|
-
if (metadata[fieldIndex].deprecated) {
|
|
3009
|
-
// do not create accessors for deprecated properties.
|
|
3010
|
-
return;
|
|
3011
|
-
}
|
|
3012
|
-
else if (metadata[fieldIndex].type !== undefined) {
|
|
3013
|
-
// trying to define same property multiple times across inheritance.
|
|
3014
|
-
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
3015
|
-
try {
|
|
3016
|
-
throw new Error(`@colyseus/schema: Duplicate '${field}' definition on '${constructor.name}'.\nCheck @type() annotation`);
|
|
3017
|
-
}
|
|
3018
|
-
catch (e) {
|
|
3019
|
-
const definitionAtLine = e.stack.split("\n")[4].trim();
|
|
3020
|
-
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
3021
|
-
}
|
|
3022
|
-
}
|
|
2823
|
+
var _a$2, _b$2;
|
|
2824
|
+
class CollectionSchema {
|
|
2825
|
+
static { this[_a$2] = encodeKeyValueOperation; }
|
|
2826
|
+
static { this[_b$2] = decodeKeyValueOperation; }
|
|
2827
|
+
/**
|
|
2828
|
+
* Determine if a property must be filtered.
|
|
2829
|
+
* - If returns false, the property is NOT going to be encoded.
|
|
2830
|
+
* - If returns true, the property is going to be encoded.
|
|
2831
|
+
*
|
|
2832
|
+
* Encoding with "filters" happens in two steps:
|
|
2833
|
+
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2834
|
+
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2835
|
+
*/
|
|
2836
|
+
static [(_a$2 = $encoder, _b$2 = $decoder, $filter)](ref, index, view) {
|
|
2837
|
+
return (!view ||
|
|
2838
|
+
typeof (ref[$childType]) === "string" ||
|
|
2839
|
+
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
2840
|
+
}
|
|
2841
|
+
static is(type) {
|
|
2842
|
+
return type['collection'] !== undefined;
|
|
2843
|
+
}
|
|
2844
|
+
constructor(initialValues) {
|
|
2845
|
+
this.$items = new Map();
|
|
2846
|
+
this.$indexes = new Map();
|
|
2847
|
+
this.deletedItems = {};
|
|
2848
|
+
this.$refId = 0;
|
|
2849
|
+
this[$changes] = new ChangeTree(this);
|
|
2850
|
+
this[$changes].indexes = {};
|
|
2851
|
+
if (initialValues) {
|
|
2852
|
+
initialValues.forEach((v) => this.add(v));
|
|
3023
2853
|
}
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
2854
|
+
Object.defineProperty(this, $childType, {
|
|
2855
|
+
value: undefined,
|
|
2856
|
+
enumerable: false,
|
|
2857
|
+
writable: true,
|
|
2858
|
+
configurable: true,
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
add(value) {
|
|
2862
|
+
// set "index" for reference.
|
|
2863
|
+
const index = this.$refId++;
|
|
2864
|
+
const isRef = (value[$changes]) !== undefined;
|
|
2865
|
+
if (isRef) {
|
|
2866
|
+
value[$changes].setParent(this, this[$changes].root, index);
|
|
3032
2867
|
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
writable: true,
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
3041
|
-
else {
|
|
3042
|
-
const complexTypeKlass = (Array.isArray(type))
|
|
3043
|
-
? getType("array")
|
|
3044
|
-
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
3045
|
-
const childType = (complexTypeKlass)
|
|
3046
|
-
? Object.values(type)[0]
|
|
3047
|
-
: type;
|
|
3048
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
3049
|
-
}
|
|
3050
|
-
};
|
|
3051
|
-
}
|
|
3052
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
3053
|
-
return {
|
|
3054
|
-
get: function () { return this[fieldCached]; },
|
|
3055
|
-
set: function (value) {
|
|
3056
|
-
const previousValue = this[fieldCached] ?? undefined;
|
|
3057
|
-
// skip if value is the same as cached.
|
|
3058
|
-
if (value === previousValue) {
|
|
3059
|
-
return;
|
|
3060
|
-
}
|
|
3061
|
-
if (value !== undefined &&
|
|
3062
|
-
value !== null) {
|
|
3063
|
-
if (complexTypeKlass) {
|
|
3064
|
-
// automaticallty transform Array into ArraySchema
|
|
3065
|
-
if (complexTypeKlass.constructor === ArraySchema && !(value instanceof ArraySchema)) {
|
|
3066
|
-
value = new ArraySchema(...value);
|
|
3067
|
-
}
|
|
3068
|
-
// automaticallty transform Map into MapSchema
|
|
3069
|
-
if (complexTypeKlass.constructor === MapSchema && !(value instanceof MapSchema)) {
|
|
3070
|
-
value = new MapSchema(value);
|
|
3071
|
-
}
|
|
3072
|
-
value[$childType] = type;
|
|
3073
|
-
}
|
|
3074
|
-
else if (typeof (type) !== "string") {
|
|
3075
|
-
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
3076
|
-
}
|
|
3077
|
-
else {
|
|
3078
|
-
assertType(value, type, this, fieldCached.substring(1));
|
|
3079
|
-
}
|
|
3080
|
-
const changeTree = this[$changes];
|
|
3081
|
-
//
|
|
3082
|
-
// Replacing existing "ref", remove it from root.
|
|
3083
|
-
//
|
|
3084
|
-
if (previousValue !== undefined && previousValue[$changes]) {
|
|
3085
|
-
changeTree.root?.remove(previousValue[$changes]);
|
|
3086
|
-
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.DELETE_AND_ADD);
|
|
3087
|
-
}
|
|
3088
|
-
else {
|
|
3089
|
-
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
3090
|
-
}
|
|
3091
|
-
//
|
|
3092
|
-
// call setParent() recursively for this and its child
|
|
3093
|
-
// structures.
|
|
3094
|
-
//
|
|
3095
|
-
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
3096
|
-
}
|
|
3097
|
-
else if (previousValue !== undefined) {
|
|
3098
|
-
//
|
|
3099
|
-
// Setting a field to `null` or `undefined` will delete it.
|
|
3100
|
-
//
|
|
3101
|
-
this[$changes].delete(fieldIndex);
|
|
3102
|
-
}
|
|
3103
|
-
this[fieldCached] = value;
|
|
3104
|
-
},
|
|
3105
|
-
enumerable: true,
|
|
3106
|
-
configurable: true
|
|
3107
|
-
};
|
|
3108
|
-
}
|
|
3109
|
-
/**
|
|
3110
|
-
* `@deprecated()` flag a field as deprecated.
|
|
3111
|
-
* The previous `@type()` annotation should remain along with this one.
|
|
3112
|
-
*/
|
|
3113
|
-
function deprecated(throws = true) {
|
|
3114
|
-
return function (klass, field) {
|
|
3115
|
-
//
|
|
3116
|
-
// FIXME: the following block of code is repeated across `@type()`, `@deprecated()` and `@unreliable()` decorators.
|
|
3117
|
-
//
|
|
3118
|
-
const constructor = klass.constructor;
|
|
3119
|
-
const parentClass = Object.getPrototypeOf(constructor);
|
|
3120
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
3121
|
-
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3122
|
-
const fieldIndex = metadata[field];
|
|
3123
|
-
// if (!metadata[field]) {
|
|
3124
|
-
// //
|
|
3125
|
-
// // detect index for this field, considering inheritance
|
|
3126
|
-
// //
|
|
3127
|
-
// metadata[field] = {
|
|
3128
|
-
// type: undefined,
|
|
3129
|
-
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3130
|
-
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3131
|
-
// ?? -1) + 1 // no fields defined
|
|
3132
|
-
// }
|
|
3133
|
-
// }
|
|
3134
|
-
metadata[fieldIndex].deprecated = true;
|
|
3135
|
-
if (throws) {
|
|
3136
|
-
metadata[$descriptors] ??= {};
|
|
3137
|
-
metadata[$descriptors][field] = {
|
|
3138
|
-
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
3139
|
-
set: function (value) { },
|
|
3140
|
-
enumerable: false,
|
|
3141
|
-
configurable: true
|
|
3142
|
-
};
|
|
3143
|
-
}
|
|
3144
|
-
// flag metadata[field] as non-enumerable
|
|
3145
|
-
Object.defineProperty(metadata, fieldIndex, {
|
|
3146
|
-
value: metadata[fieldIndex],
|
|
3147
|
-
enumerable: false,
|
|
3148
|
-
configurable: true
|
|
3149
|
-
});
|
|
3150
|
-
};
|
|
3151
|
-
}
|
|
3152
|
-
function defineTypes(target, fields, options) {
|
|
3153
|
-
for (let field in fields) {
|
|
3154
|
-
type(fields[field], options)(target.prototype, field);
|
|
2868
|
+
this[$changes].indexes[index] = index;
|
|
2869
|
+
this.$indexes.set(index, index);
|
|
2870
|
+
this.$items.set(index, value);
|
|
2871
|
+
this[$changes].change(index);
|
|
2872
|
+
return index;
|
|
3155
2873
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
const
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
2874
|
+
at(index) {
|
|
2875
|
+
const key = Array.from(this.$items.keys())[index];
|
|
2876
|
+
return this.$items.get(key);
|
|
2877
|
+
}
|
|
2878
|
+
entries() {
|
|
2879
|
+
return this.$items.entries();
|
|
2880
|
+
}
|
|
2881
|
+
delete(item) {
|
|
2882
|
+
const entries = this.$items.entries();
|
|
2883
|
+
let index;
|
|
2884
|
+
let entry;
|
|
2885
|
+
while (entry = entries.next()) {
|
|
2886
|
+
if (entry.done) {
|
|
2887
|
+
break;
|
|
3168
2888
|
}
|
|
3169
|
-
if (value[
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
: value['view'];
|
|
2889
|
+
if (item === entry.value[1]) {
|
|
2890
|
+
index = entry.value[0];
|
|
2891
|
+
break;
|
|
3173
2892
|
}
|
|
3174
|
-
fields[fieldName] = value;
|
|
3175
|
-
}
|
|
3176
|
-
else if (typeof (value) === "function") {
|
|
3177
|
-
methods[fieldName] = value;
|
|
3178
2893
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
2894
|
+
if (index === undefined) {
|
|
2895
|
+
return false;
|
|
3181
2896
|
}
|
|
2897
|
+
this.deletedItems[index] = this[$changes].delete(index);
|
|
2898
|
+
this.$indexes.delete(index);
|
|
2899
|
+
return this.$items.delete(index);
|
|
3182
2900
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
}
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
2901
|
+
clear() {
|
|
2902
|
+
const changeTree = this[$changes];
|
|
2903
|
+
// discard previous operations.
|
|
2904
|
+
changeTree.discard(true);
|
|
2905
|
+
changeTree.indexes = {};
|
|
2906
|
+
// remove children references
|
|
2907
|
+
changeTree.forEachChild((childChangeTree, _) => {
|
|
2908
|
+
changeTree.root?.remove(childChangeTree);
|
|
2909
|
+
});
|
|
2910
|
+
// clear previous indexes
|
|
2911
|
+
this.$indexes.clear();
|
|
2912
|
+
// clear items
|
|
2913
|
+
this.$items.clear();
|
|
2914
|
+
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3191
2915
|
}
|
|
3192
|
-
|
|
3193
|
-
|
|
2916
|
+
has(value) {
|
|
2917
|
+
return Array.from(this.$items.values()).some((v) => v === value);
|
|
3194
2918
|
}
|
|
3195
|
-
|
|
3196
|
-
|
|
2919
|
+
forEach(callbackfn) {
|
|
2920
|
+
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3197
2921
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
|
-
function getIndent(level) {
|
|
3203
|
-
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
3204
|
-
}
|
|
3205
|
-
function dumpChanges(schema) {
|
|
3206
|
-
const $root = schema[$changes].root;
|
|
3207
|
-
const dump = {
|
|
3208
|
-
ops: {},
|
|
3209
|
-
refs: []
|
|
3210
|
-
};
|
|
3211
|
-
// for (const refId in $root.changes) {
|
|
3212
|
-
let current = $root.changes.next;
|
|
3213
|
-
while (current) {
|
|
3214
|
-
const changeTree = current.changeTree;
|
|
3215
|
-
// skip if ChangeTree is undefined
|
|
3216
|
-
if (changeTree === undefined) {
|
|
3217
|
-
current = current.next;
|
|
3218
|
-
continue;
|
|
3219
|
-
}
|
|
3220
|
-
const changes = changeTree.indexedOperations;
|
|
3221
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3222
|
-
for (const index in changes) {
|
|
3223
|
-
const op = changes[index];
|
|
3224
|
-
const opName = exports.OPERATION[op];
|
|
3225
|
-
if (!dump.ops[opName]) {
|
|
3226
|
-
dump.ops[opName] = 0;
|
|
3227
|
-
}
|
|
3228
|
-
dump.ops[exports.OPERATION[op]]++;
|
|
3229
|
-
}
|
|
3230
|
-
current = current.next;
|
|
2922
|
+
values() {
|
|
2923
|
+
return this.$items.values();
|
|
3231
2924
|
}
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
var _a$2, _b$2;
|
|
3236
|
-
/**
|
|
3237
|
-
* Schema encoder / decoder
|
|
3238
|
-
*/
|
|
3239
|
-
class Schema {
|
|
3240
|
-
static { this[_a$2] = encodeSchemaOperation; }
|
|
3241
|
-
static { this[_b$2] = decodeSchemaOperation; }
|
|
3242
|
-
/**
|
|
3243
|
-
* Assign the property descriptors required to track changes on this instance.
|
|
3244
|
-
* @param instance
|
|
3245
|
-
*/
|
|
3246
|
-
static initialize(instance) {
|
|
3247
|
-
Object.defineProperty(instance, $changes, {
|
|
3248
|
-
value: new ChangeTree(instance),
|
|
3249
|
-
enumerable: false,
|
|
3250
|
-
writable: true
|
|
3251
|
-
});
|
|
3252
|
-
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2925
|
+
get size() {
|
|
2926
|
+
return this.$items.size;
|
|
3253
2927
|
}
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
// return metadata && Object.prototype.hasOwnProperty.call(metadata, -1);
|
|
2928
|
+
/** Iterator */
|
|
2929
|
+
[Symbol.iterator]() {
|
|
2930
|
+
return this.$items.values();
|
|
3258
2931
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
2932
|
+
setIndex(index, key) {
|
|
2933
|
+
this.$indexes.set(index, key);
|
|
2934
|
+
}
|
|
2935
|
+
getIndex(index) {
|
|
2936
|
+
return this.$indexes.get(index);
|
|
2937
|
+
}
|
|
2938
|
+
[$getByIndex](index) {
|
|
2939
|
+
return this.$items.get(this.$indexes.get(index));
|
|
2940
|
+
}
|
|
2941
|
+
[$deleteByIndex](index) {
|
|
2942
|
+
const key = this.$indexes.get(index);
|
|
2943
|
+
this.$items.delete(key);
|
|
2944
|
+
this.$indexes.delete(index);
|
|
2945
|
+
}
|
|
2946
|
+
[$onEncodeEnd]() {
|
|
2947
|
+
this.deletedItems = {};
|
|
2948
|
+
}
|
|
2949
|
+
toArray() {
|
|
2950
|
+
return Array.from(this.$items.values());
|
|
2951
|
+
}
|
|
2952
|
+
toJSON() {
|
|
2953
|
+
const values = [];
|
|
2954
|
+
this.forEach((value, key) => {
|
|
2955
|
+
values.push((typeof (value['toJSON']) === "function")
|
|
2956
|
+
? value['toJSON']()
|
|
2957
|
+
: value);
|
|
2958
|
+
});
|
|
2959
|
+
return values;
|
|
2960
|
+
}
|
|
2961
|
+
//
|
|
2962
|
+
// Decoding utilities
|
|
2963
|
+
//
|
|
2964
|
+
clone(isDecoding) {
|
|
2965
|
+
let cloned;
|
|
2966
|
+
if (isDecoding) {
|
|
2967
|
+
// client-side
|
|
2968
|
+
cloned = Object.assign(new CollectionSchema(), this);
|
|
2969
|
+
}
|
|
2970
|
+
else {
|
|
2971
|
+
// server-side
|
|
2972
|
+
cloned = new CollectionSchema();
|
|
2973
|
+
this.forEach((value) => {
|
|
2974
|
+
if (value[$changes]) {
|
|
2975
|
+
cloned.add(value['clone']());
|
|
2976
|
+
}
|
|
2977
|
+
else {
|
|
2978
|
+
cloned.add(value);
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
return cloned;
|
|
3264
2983
|
}
|
|
2984
|
+
}
|
|
2985
|
+
registerType("collection", { constructor: CollectionSchema, });
|
|
2986
|
+
|
|
2987
|
+
var _a$1, _b$1;
|
|
2988
|
+
class SetSchema {
|
|
2989
|
+
static { this[_a$1] = encodeKeyValueOperation; }
|
|
2990
|
+
static { this[_b$1] = decodeKeyValueOperation; }
|
|
3265
2991
|
/**
|
|
3266
2992
|
* Determine if a property must be filtered.
|
|
3267
2993
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -3271,418 +2997,652 @@ class Schema {
|
|
|
3271
2997
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
3272
2998
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
3273
2999
|
*/
|
|
3274
|
-
static [$filter](ref, index, view) {
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
// shared pass/encode: encode if doesn't have a tag
|
|
3279
|
-
return tag === undefined;
|
|
3280
|
-
}
|
|
3281
|
-
else if (tag === undefined) {
|
|
3282
|
-
// view pass: no tag
|
|
3283
|
-
return true;
|
|
3284
|
-
}
|
|
3285
|
-
else if (tag === DEFAULT_VIEW_TAG) {
|
|
3286
|
-
// view pass: default tag
|
|
3287
|
-
return view.isChangeTreeVisible(ref[$changes]);
|
|
3288
|
-
}
|
|
3289
|
-
else {
|
|
3290
|
-
// view pass: custom tag
|
|
3291
|
-
const tags = view.tags?.get(ref[$changes]);
|
|
3292
|
-
return tags && tags.has(tag);
|
|
3293
|
-
}
|
|
3000
|
+
static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
|
|
3001
|
+
return (!view ||
|
|
3002
|
+
typeof (ref[$childType]) === "string" ||
|
|
3003
|
+
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
3294
3004
|
}
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
if (
|
|
3306
|
-
|
|
3005
|
+
static is(type) {
|
|
3006
|
+
return type['set'] !== undefined;
|
|
3007
|
+
}
|
|
3008
|
+
constructor(initialValues) {
|
|
3009
|
+
this.$items = new Map();
|
|
3010
|
+
this.$indexes = new Map();
|
|
3011
|
+
this.deletedItems = {};
|
|
3012
|
+
this.$refId = 0;
|
|
3013
|
+
this[$changes] = new ChangeTree(this);
|
|
3014
|
+
this[$changes].indexes = {};
|
|
3015
|
+
if (initialValues) {
|
|
3016
|
+
initialValues.forEach((v) => this.add(v));
|
|
3307
3017
|
}
|
|
3018
|
+
Object.defineProperty(this, $childType, {
|
|
3019
|
+
value: undefined,
|
|
3020
|
+
enumerable: false,
|
|
3021
|
+
writable: true,
|
|
3022
|
+
configurable: true,
|
|
3023
|
+
});
|
|
3308
3024
|
}
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3025
|
+
add(value) {
|
|
3026
|
+
// immediatelly return false if value already added.
|
|
3027
|
+
if (this.has(value)) {
|
|
3028
|
+
return false;
|
|
3029
|
+
}
|
|
3030
|
+
// set "index" for reference.
|
|
3031
|
+
const index = this.$refId++;
|
|
3032
|
+
if ((value[$changes]) !== undefined) {
|
|
3033
|
+
value[$changes].setParent(this, this[$changes].root, index);
|
|
3034
|
+
}
|
|
3035
|
+
const operation = this[$changes].indexes[index]?.op ?? exports.OPERATION.ADD;
|
|
3036
|
+
this[$changes].indexes[index] = index;
|
|
3037
|
+
this.$indexes.set(index, index);
|
|
3038
|
+
this.$items.set(index, value);
|
|
3039
|
+
this[$changes].change(index, operation);
|
|
3040
|
+
return index;
|
|
3312
3041
|
}
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
* @param instance Schema instance
|
|
3316
|
-
* @param property string representing the property name, or number representing the index of the property.
|
|
3317
|
-
* @param operation OPERATION to perform (detected automatically)
|
|
3318
|
-
*/
|
|
3319
|
-
setDirty(property, operation) {
|
|
3320
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
3321
|
-
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
3042
|
+
entries() {
|
|
3043
|
+
return this.$items.entries();
|
|
3322
3044
|
}
|
|
3323
|
-
|
|
3324
|
-
const
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
for (const fieldIndex in metadata) {
|
|
3331
|
-
// const field = metadata[metadata[fieldIndex]].name;
|
|
3332
|
-
const field = metadata[fieldIndex].name;
|
|
3333
|
-
if (typeof (this[field]) === "object" &&
|
|
3334
|
-
typeof (this[field]?.clone) === "function") {
|
|
3335
|
-
// deep clone
|
|
3336
|
-
cloned[field] = this[field].clone();
|
|
3045
|
+
delete(item) {
|
|
3046
|
+
const entries = this.$items.entries();
|
|
3047
|
+
let index;
|
|
3048
|
+
let entry;
|
|
3049
|
+
while (entry = entries.next()) {
|
|
3050
|
+
if (entry.done) {
|
|
3051
|
+
break;
|
|
3337
3052
|
}
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3053
|
+
if (item === entry.value[1]) {
|
|
3054
|
+
index = entry.value[0];
|
|
3055
|
+
break;
|
|
3341
3056
|
}
|
|
3342
3057
|
}
|
|
3343
|
-
|
|
3058
|
+
if (index === undefined) {
|
|
3059
|
+
return false;
|
|
3060
|
+
}
|
|
3061
|
+
this.deletedItems[index] = this[$changes].delete(index);
|
|
3062
|
+
this.$indexes.delete(index);
|
|
3063
|
+
return this.$items.delete(index);
|
|
3344
3064
|
}
|
|
3345
|
-
|
|
3346
|
-
const
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3065
|
+
clear() {
|
|
3066
|
+
const changeTree = this[$changes];
|
|
3067
|
+
// discard previous operations.
|
|
3068
|
+
changeTree.discard(true);
|
|
3069
|
+
changeTree.indexes = {};
|
|
3070
|
+
// clear previous indexes
|
|
3071
|
+
this.$indexes.clear();
|
|
3072
|
+
// clear items
|
|
3073
|
+
this.$items.clear();
|
|
3074
|
+
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3075
|
+
}
|
|
3076
|
+
has(value) {
|
|
3077
|
+
const values = this.$items.values();
|
|
3078
|
+
let has = false;
|
|
3079
|
+
let entry;
|
|
3080
|
+
while (entry = values.next()) {
|
|
3081
|
+
if (entry.done) {
|
|
3082
|
+
break;
|
|
3083
|
+
}
|
|
3084
|
+
if (value === entry.value) {
|
|
3085
|
+
has = true;
|
|
3086
|
+
break;
|
|
3355
3087
|
}
|
|
3356
3088
|
}
|
|
3357
|
-
return
|
|
3089
|
+
return has;
|
|
3358
3090
|
}
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3091
|
+
forEach(callbackfn) {
|
|
3092
|
+
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3093
|
+
}
|
|
3094
|
+
values() {
|
|
3095
|
+
return this.$items.values();
|
|
3096
|
+
}
|
|
3097
|
+
get size() {
|
|
3098
|
+
return this.$items.size;
|
|
3099
|
+
}
|
|
3100
|
+
/** Iterator */
|
|
3101
|
+
[Symbol.iterator]() {
|
|
3102
|
+
return this.$items.values();
|
|
3103
|
+
}
|
|
3104
|
+
setIndex(index, key) {
|
|
3105
|
+
this.$indexes.set(index, key);
|
|
3106
|
+
}
|
|
3107
|
+
getIndex(index) {
|
|
3108
|
+
return this.$indexes.get(index);
|
|
3365
3109
|
}
|
|
3366
3110
|
[$getByIndex](index) {
|
|
3367
|
-
|
|
3368
|
-
return this[metadata[index].name];
|
|
3111
|
+
return this.$items.get(this.$indexes.get(index));
|
|
3369
3112
|
}
|
|
3370
3113
|
[$deleteByIndex](index) {
|
|
3371
|
-
const
|
|
3372
|
-
this
|
|
3114
|
+
const key = this.$indexes.get(index);
|
|
3115
|
+
this.$items.delete(key);
|
|
3116
|
+
this.$indexes.delete(index);
|
|
3373
3117
|
}
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
*
|
|
3377
|
-
* @param ref Schema instance
|
|
3378
|
-
* @param showContents display JSON contents of the instance
|
|
3379
|
-
* @returns
|
|
3380
|
-
*/
|
|
3381
|
-
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3382
|
-
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3383
|
-
const changeTree = ref[$changes];
|
|
3384
|
-
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
3385
|
-
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3386
|
-
// log reference count if > 1
|
|
3387
|
-
const refCount = (root?.refCount?.[refId] > 1)
|
|
3388
|
-
? ` [×${root.refCount[refId]}]`
|
|
3389
|
-
: '';
|
|
3390
|
-
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3391
|
-
changeTree.forEachChild((childChangeTree, indexOrKey) => {
|
|
3392
|
-
let key = indexOrKey;
|
|
3393
|
-
if (typeof indexOrKey === 'number' && ref['$indexes']) {
|
|
3394
|
-
// MapSchema
|
|
3395
|
-
key = ref['$indexes'].get(indexOrKey) ?? indexOrKey;
|
|
3396
|
-
}
|
|
3397
|
-
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3398
|
-
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3399
|
-
});
|
|
3400
|
-
return output;
|
|
3118
|
+
[$onEncodeEnd]() {
|
|
3119
|
+
this.deletedItems = {};
|
|
3401
3120
|
}
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
let current = ref[$changes].root[changeSet].next;
|
|
3405
|
-
while (current) {
|
|
3406
|
-
if (current.changeTree) {
|
|
3407
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3408
|
-
}
|
|
3409
|
-
current = current.next;
|
|
3410
|
-
}
|
|
3411
|
-
return encodeOrder;
|
|
3121
|
+
toArray() {
|
|
3122
|
+
return Array.from(this.$items.values());
|
|
3412
3123
|
}
|
|
3413
|
-
|
|
3414
|
-
|
|
3124
|
+
toJSON() {
|
|
3125
|
+
const values = [];
|
|
3126
|
+
this.forEach((value, key) => {
|
|
3127
|
+
values.push((typeof (value['toJSON']) === "function")
|
|
3128
|
+
? value['toJSON']()
|
|
3129
|
+
: value);
|
|
3130
|
+
});
|
|
3131
|
+
return values;
|
|
3415
3132
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
static debugChanges(instance, isEncodeAll = false) {
|
|
3425
|
-
const changeTree = instance[$changes];
|
|
3426
|
-
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3427
|
-
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3428
|
-
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3429
|
-
function dumpChangeSet(changeSet) {
|
|
3430
|
-
changeSet.operations
|
|
3431
|
-
.filter(op => op)
|
|
3432
|
-
.forEach((index) => {
|
|
3433
|
-
const operation = changeTree.indexedOperations[index];
|
|
3434
|
-
console.log({ index, operation });
|
|
3435
|
-
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3436
|
-
});
|
|
3437
|
-
}
|
|
3438
|
-
dumpChangeSet(changeSet);
|
|
3439
|
-
// display filtered changes
|
|
3440
|
-
if (!isEncodeAll &&
|
|
3441
|
-
changeTree.filteredChanges &&
|
|
3442
|
-
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3443
|
-
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3444
|
-
dumpChangeSet(changeTree.filteredChanges);
|
|
3133
|
+
//
|
|
3134
|
+
// Decoding utilities
|
|
3135
|
+
//
|
|
3136
|
+
clone(isDecoding) {
|
|
3137
|
+
let cloned;
|
|
3138
|
+
if (isDecoding) {
|
|
3139
|
+
// client-side
|
|
3140
|
+
cloned = Object.assign(new SetSchema(), this);
|
|
3445
3141
|
}
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
(
|
|
3450
|
-
|
|
3451
|
-
|
|
3142
|
+
else {
|
|
3143
|
+
// server-side
|
|
3144
|
+
cloned = new SetSchema();
|
|
3145
|
+
this.forEach((value) => {
|
|
3146
|
+
if (value[$changes]) {
|
|
3147
|
+
cloned.add(value['clone']());
|
|
3148
|
+
}
|
|
3149
|
+
else {
|
|
3150
|
+
cloned.add(value);
|
|
3151
|
+
}
|
|
3152
|
+
});
|
|
3452
3153
|
}
|
|
3453
|
-
return
|
|
3154
|
+
return cloned;
|
|
3454
3155
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3156
|
+
}
|
|
3157
|
+
registerType("set", { constructor: SetSchema });
|
|
3158
|
+
|
|
3159
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
3160
|
+
function entity(constructor) {
|
|
3161
|
+
TypeContext.register(constructor);
|
|
3162
|
+
return constructor;
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
3166
|
+
*
|
|
3167
|
+
* Annotate a Schema property to be serializeable.
|
|
3168
|
+
* \@type()'d fields are automatically flagged as "dirty" for the next patch.
|
|
3169
|
+
*
|
|
3170
|
+
* @example Standard usage, with automatic change tracking.
|
|
3171
|
+
* ```
|
|
3172
|
+
* \@type("string") propertyName: string;
|
|
3173
|
+
* ```
|
|
3174
|
+
*
|
|
3175
|
+
* @example You can provide the "manual" option if you'd like to manually control your patches via .setDirty().
|
|
3176
|
+
* ```
|
|
3177
|
+
* \@type("string", { manual: true })
|
|
3178
|
+
* ```
|
|
3179
|
+
*/
|
|
3180
|
+
// export function type(type: DefinitionType, options?: TypeOptions) {
|
|
3181
|
+
// return function ({ get, set }, context: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<Schema, any> {
|
|
3182
|
+
// if (context.kind !== "accessor") {
|
|
3183
|
+
// throw new Error("@type() is only supported for class accessor properties");
|
|
3184
|
+
// }
|
|
3185
|
+
// const field = context.name.toString();
|
|
3186
|
+
// //
|
|
3187
|
+
// // detect index for this field, considering inheritance
|
|
3188
|
+
// //
|
|
3189
|
+
// const parent = Object.getPrototypeOf(context.metadata);
|
|
3190
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
3191
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
3192
|
+
// ?? -1; // no fields defined
|
|
3193
|
+
// fieldIndex++;
|
|
3194
|
+
// if (
|
|
3195
|
+
// !parent && // the parent already initializes the `$changes` property
|
|
3196
|
+
// !Metadata.hasFields(context.metadata)
|
|
3197
|
+
// ) {
|
|
3198
|
+
// context.addInitializer(function (this: Ref) {
|
|
3199
|
+
// Object.defineProperty(this, $changes, {
|
|
3200
|
+
// value: new ChangeTree(this),
|
|
3201
|
+
// enumerable: false,
|
|
3202
|
+
// writable: true
|
|
3203
|
+
// });
|
|
3204
|
+
// });
|
|
3205
|
+
// }
|
|
3206
|
+
// Metadata.addField(context.metadata, fieldIndex, field, type);
|
|
3207
|
+
// const isArray = ArraySchema.is(type);
|
|
3208
|
+
// const isMap = !isArray && MapSchema.is(type);
|
|
3209
|
+
// // if (options && options.manual) {
|
|
3210
|
+
// // // do not declare getter/setter descriptor
|
|
3211
|
+
// // definition.descriptors[field] = {
|
|
3212
|
+
// // enumerable: true,
|
|
3213
|
+
// // configurable: true,
|
|
3214
|
+
// // writable: true,
|
|
3215
|
+
// // };
|
|
3216
|
+
// // return;
|
|
3217
|
+
// // }
|
|
3218
|
+
// return {
|
|
3219
|
+
// init(value) {
|
|
3220
|
+
// // TODO: may need to convert ArraySchema/MapSchema here
|
|
3221
|
+
// // do not flag change if value is undefined.
|
|
3222
|
+
// if (value !== undefined) {
|
|
3223
|
+
// this[$changes].change(fieldIndex);
|
|
3224
|
+
// // automaticallty transform Array into ArraySchema
|
|
3225
|
+
// if (isArray) {
|
|
3226
|
+
// if (!(value instanceof ArraySchema)) {
|
|
3227
|
+
// value = new ArraySchema(...value);
|
|
3228
|
+
// }
|
|
3229
|
+
// value[$childType] = Object.values(type)[0];
|
|
3230
|
+
// }
|
|
3231
|
+
// // automaticallty transform Map into MapSchema
|
|
3232
|
+
// if (isMap) {
|
|
3233
|
+
// if (!(value instanceof MapSchema)) {
|
|
3234
|
+
// value = new MapSchema(value);
|
|
3235
|
+
// }
|
|
3236
|
+
// value[$childType] = Object.values(type)[0];
|
|
3237
|
+
// }
|
|
3238
|
+
// // try to turn provided structure into a Proxy
|
|
3239
|
+
// if (value['$proxy'] === undefined) {
|
|
3240
|
+
// if (isMap) {
|
|
3241
|
+
// value = getMapProxy(value);
|
|
3242
|
+
// }
|
|
3243
|
+
// }
|
|
3244
|
+
// }
|
|
3245
|
+
// return value;
|
|
3246
|
+
// },
|
|
3247
|
+
// get() {
|
|
3248
|
+
// return get.call(this);
|
|
3249
|
+
// },
|
|
3250
|
+
// set(value: any) {
|
|
3251
|
+
// /**
|
|
3252
|
+
// * Create Proxy for array or map items
|
|
3253
|
+
// */
|
|
3254
|
+
// // skip if value is the same as cached.
|
|
3255
|
+
// if (value === get.call(this)) {
|
|
3256
|
+
// return;
|
|
3257
|
+
// }
|
|
3258
|
+
// if (
|
|
3259
|
+
// value !== undefined &&
|
|
3260
|
+
// value !== null
|
|
3261
|
+
// ) {
|
|
3262
|
+
// // automaticallty transform Array into ArraySchema
|
|
3263
|
+
// if (isArray) {
|
|
3264
|
+
// if (!(value instanceof ArraySchema)) {
|
|
3265
|
+
// value = new ArraySchema(...value);
|
|
3266
|
+
// }
|
|
3267
|
+
// value[$childType] = Object.values(type)[0];
|
|
3268
|
+
// }
|
|
3269
|
+
// // automaticallty transform Map into MapSchema
|
|
3270
|
+
// if (isMap) {
|
|
3271
|
+
// if (!(value instanceof MapSchema)) {
|
|
3272
|
+
// value = new MapSchema(value);
|
|
3273
|
+
// }
|
|
3274
|
+
// value[$childType] = Object.values(type)[0];
|
|
3275
|
+
// }
|
|
3276
|
+
// // try to turn provided structure into a Proxy
|
|
3277
|
+
// if (value['$proxy'] === undefined) {
|
|
3278
|
+
// if (isMap) {
|
|
3279
|
+
// value = getMapProxy(value);
|
|
3280
|
+
// }
|
|
3281
|
+
// }
|
|
3282
|
+
// // flag the change for encoding.
|
|
3283
|
+
// this[$changes].change(fieldIndex);
|
|
3284
|
+
// //
|
|
3285
|
+
// // call setParent() recursively for this and its child
|
|
3286
|
+
// // structures.
|
|
3287
|
+
// //
|
|
3288
|
+
// if (value[$changes]) {
|
|
3289
|
+
// value[$changes].setParent(
|
|
3290
|
+
// this,
|
|
3291
|
+
// this[$changes].root,
|
|
3292
|
+
// Metadata.getIndex(context.metadata, field),
|
|
3293
|
+
// );
|
|
3294
|
+
// }
|
|
3295
|
+
// } else if (get.call(this)) {
|
|
3296
|
+
// //
|
|
3297
|
+
// // Setting a field to `null` or `undefined` will delete it.
|
|
3298
|
+
// //
|
|
3299
|
+
// this[$changes].delete(field);
|
|
3300
|
+
// }
|
|
3301
|
+
// set.call(this, value);
|
|
3302
|
+
// },
|
|
3303
|
+
// };
|
|
3304
|
+
// }
|
|
3305
|
+
// }
|
|
3306
|
+
function view(tag = DEFAULT_VIEW_TAG) {
|
|
3307
|
+
return function (target, fieldName) {
|
|
3308
|
+
const constructor = target.constructor;
|
|
3309
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3310
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3311
|
+
// TODO: use Metadata.initialize()
|
|
3312
|
+
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3313
|
+
// const fieldIndex = metadata[fieldName];
|
|
3314
|
+
// if (!metadata[fieldIndex]) {
|
|
3315
|
+
// //
|
|
3316
|
+
// // detect index for this field, considering inheritance
|
|
3317
|
+
// //
|
|
3318
|
+
// metadata[fieldIndex] = {
|
|
3319
|
+
// type: undefined,
|
|
3320
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3321
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3322
|
+
// ?? -1) + 1 // no fields defined
|
|
3323
|
+
// }
|
|
3324
|
+
// }
|
|
3325
|
+
Metadata.setTag(metadata, fieldName, tag);
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
function type(type, options) {
|
|
3329
|
+
return function (target, field) {
|
|
3330
|
+
const constructor = target.constructor;
|
|
3331
|
+
if (!type) {
|
|
3332
|
+
throw new Error(`${constructor.name}: @type() reference provided for "${field}" is undefined. Make sure you don't have any circular dependencies.`);
|
|
3333
|
+
}
|
|
3334
|
+
// for inheritance support
|
|
3335
|
+
TypeContext.register(constructor);
|
|
3336
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3337
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3338
|
+
const metadata = Metadata.initialize(constructor);
|
|
3339
|
+
let fieldIndex = metadata[field];
|
|
3340
|
+
/**
|
|
3341
|
+
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
3342
|
+
*/
|
|
3343
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
3344
|
+
if (metadata[fieldIndex].deprecated) {
|
|
3345
|
+
// do not create accessors for deprecated properties.
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
3349
|
+
// trying to define same property multiple times across inheritance.
|
|
3350
|
+
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
3351
|
+
try {
|
|
3352
|
+
throw new Error(`@colyseus/schema: Duplicate '${field}' definition on '${constructor.name}'.\nCheck @type() annotation`);
|
|
3353
|
+
}
|
|
3354
|
+
catch (e) {
|
|
3355
|
+
const definitionAtLine = e.stack.split("\n")[4].trim();
|
|
3356
|
+
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
3357
|
+
}
|
|
3467
3358
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3359
|
+
}
|
|
3360
|
+
else {
|
|
3361
|
+
//
|
|
3362
|
+
// detect index for this field, considering inheritance
|
|
3363
|
+
//
|
|
3364
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
3365
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3366
|
+
?? -1; // no fields defined
|
|
3367
|
+
fieldIndex++;
|
|
3368
|
+
}
|
|
3369
|
+
if (options && options.manual) {
|
|
3370
|
+
Metadata.addField(metadata, fieldIndex, field, type, {
|
|
3371
|
+
// do not declare getter/setter descriptor
|
|
3372
|
+
enumerable: true,
|
|
3373
|
+
configurable: true,
|
|
3374
|
+
writable: true,
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
else {
|
|
3378
|
+
const complexTypeKlass = (Array.isArray(type))
|
|
3379
|
+
? getType("array")
|
|
3380
|
+
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
3381
|
+
const childType = (complexTypeKlass)
|
|
3382
|
+
? Object.values(type)[0]
|
|
3383
|
+
: type;
|
|
3384
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
3385
|
+
}
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
3389
|
+
return {
|
|
3390
|
+
get: function () { return this[fieldCached]; },
|
|
3391
|
+
set: function (value) {
|
|
3392
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
3393
|
+
// skip if value is the same as cached.
|
|
3394
|
+
if (value === previousValue) {
|
|
3395
|
+
return;
|
|
3473
3396
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3397
|
+
if (value !== undefined &&
|
|
3398
|
+
value !== null) {
|
|
3399
|
+
if (complexTypeKlass) {
|
|
3400
|
+
// automaticallty transform Array into ArraySchema
|
|
3401
|
+
if (complexTypeKlass.constructor === ArraySchema && !(value instanceof ArraySchema)) {
|
|
3402
|
+
value = new ArraySchema(...value);
|
|
3480
3403
|
}
|
|
3481
|
-
|
|
3404
|
+
// automaticallty transform Map into MapSchema
|
|
3405
|
+
if (complexTypeKlass.constructor === MapSchema && !(value instanceof MapSchema)) {
|
|
3406
|
+
value = new MapSchema(value);
|
|
3407
|
+
}
|
|
3408
|
+
value[$childType] = type;
|
|
3482
3409
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
instanceRefIds.push(changeTree.refId);
|
|
3486
|
-
totalOperations += Object.keys(changes).length;
|
|
3487
|
-
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3488
|
-
}
|
|
3489
|
-
}
|
|
3490
|
-
output += "---\n";
|
|
3491
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3492
|
-
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3493
|
-
output += `Total changes: ${totalOperations}\n`;
|
|
3494
|
-
output += "---\n";
|
|
3495
|
-
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
3496
|
-
const visitedParents = new WeakSet();
|
|
3497
|
-
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3498
|
-
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3499
|
-
if (!visitedParents.has(parentChangeTree)) {
|
|
3500
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3501
|
-
visitedParents.add(parentChangeTree);
|
|
3410
|
+
else if (typeof (type) !== "string") {
|
|
3411
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
3502
3412
|
}
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3413
|
+
else {
|
|
3414
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
3415
|
+
}
|
|
3416
|
+
const changeTree = this[$changes];
|
|
3417
|
+
//
|
|
3418
|
+
// Replacing existing "ref", remove it from root.
|
|
3419
|
+
//
|
|
3420
|
+
if (previousValue !== undefined && previousValue[$changes]) {
|
|
3421
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
3422
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.DELETE_AND_ADD);
|
|
3423
|
+
}
|
|
3424
|
+
else {
|
|
3425
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
3426
|
+
}
|
|
3427
|
+
//
|
|
3428
|
+
// call setParent() recursively for this and its child
|
|
3429
|
+
// structures.
|
|
3430
|
+
//
|
|
3431
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
3512
3432
|
}
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3433
|
+
else if (previousValue !== undefined) {
|
|
3434
|
+
//
|
|
3435
|
+
// Setting a field to `null` or `undefined` will delete it.
|
|
3436
|
+
//
|
|
3437
|
+
this[$changes].delete(fieldIndex);
|
|
3438
|
+
}
|
|
3439
|
+
this[fieldCached] = value;
|
|
3440
|
+
},
|
|
3441
|
+
enumerable: true,
|
|
3442
|
+
configurable: true
|
|
3443
|
+
};
|
|
3516
3444
|
}
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3445
|
+
/**
|
|
3446
|
+
* `@deprecated()` flag a field as deprecated.
|
|
3447
|
+
* The previous `@type()` annotation should remain along with this one.
|
|
3448
|
+
*/
|
|
3449
|
+
function deprecated(throws = true) {
|
|
3450
|
+
return function (klass, field) {
|
|
3451
|
+
//
|
|
3452
|
+
// FIXME: the following block of code is repeated across `@type()`, `@deprecated()` and `@unreliable()` decorators.
|
|
3453
|
+
//
|
|
3454
|
+
const constructor = klass.constructor;
|
|
3455
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
3456
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3457
|
+
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
3458
|
+
const fieldIndex = metadata[field];
|
|
3459
|
+
// if (!metadata[field]) {
|
|
3460
|
+
// //
|
|
3461
|
+
// // detect index for this field, considering inheritance
|
|
3462
|
+
// //
|
|
3463
|
+
// metadata[field] = {
|
|
3464
|
+
// type: undefined,
|
|
3465
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
3466
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
3467
|
+
// ?? -1) + 1 // no fields defined
|
|
3468
|
+
// }
|
|
3469
|
+
// }
|
|
3470
|
+
metadata[fieldIndex].deprecated = true;
|
|
3471
|
+
if (throws) {
|
|
3472
|
+
metadata[$descriptors] ??= {};
|
|
3473
|
+
metadata[$descriptors][field] = {
|
|
3474
|
+
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
3475
|
+
set: function (value) { },
|
|
3476
|
+
enumerable: false,
|
|
3477
|
+
configurable: true
|
|
3478
|
+
};
|
|
3548
3479
|
}
|
|
3549
|
-
|
|
3550
|
-
|
|
3480
|
+
// flag metadata[field] as non-enumerable
|
|
3481
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
3482
|
+
value: metadata[fieldIndex],
|
|
3551
3483
|
enumerable: false,
|
|
3552
|
-
|
|
3553
|
-
configurable: true,
|
|
3484
|
+
configurable: true
|
|
3554
3485
|
});
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
function defineTypes(target, fields, options) {
|
|
3489
|
+
for (let field in fields) {
|
|
3490
|
+
type(fields[field], options)(target.prototype, field);
|
|
3555
3491
|
}
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3492
|
+
return target;
|
|
3493
|
+
}
|
|
3494
|
+
function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
3495
|
+
const fields = {};
|
|
3496
|
+
const methods = {};
|
|
3497
|
+
const defaultValues = {};
|
|
3498
|
+
const viewTagFields = {};
|
|
3499
|
+
for (let fieldName in fieldsAndMethods) {
|
|
3500
|
+
const value = fieldsAndMethods[fieldName];
|
|
3501
|
+
if (typeof (value) === "object") {
|
|
3502
|
+
if (value['view'] !== undefined) {
|
|
3503
|
+
viewTagFields[fieldName] = (typeof (value['view']) === "boolean")
|
|
3504
|
+
? DEFAULT_VIEW_TAG
|
|
3505
|
+
: value['view'];
|
|
3506
|
+
}
|
|
3507
|
+
fields[fieldName] = value;
|
|
3508
|
+
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3509
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3510
|
+
if (Array.isArray(value) || value['array'] !== undefined) {
|
|
3511
|
+
// Collection: Array → new ArraySchema()
|
|
3512
|
+
defaultValues[fieldName] = new ArraySchema();
|
|
3513
|
+
}
|
|
3514
|
+
else if (value['map'] !== undefined) {
|
|
3515
|
+
// Collection: Map → new MapSchema()
|
|
3516
|
+
defaultValues[fieldName] = new MapSchema();
|
|
3517
|
+
}
|
|
3518
|
+
else if (value['collection'] !== undefined) {
|
|
3519
|
+
// Collection: Collection → new CollectionSchema()
|
|
3520
|
+
defaultValues[fieldName] = new CollectionSchema();
|
|
3521
|
+
}
|
|
3522
|
+
else if (value['set'] !== undefined) {
|
|
3523
|
+
// Collection: Set → new SetSchema()
|
|
3524
|
+
defaultValues[fieldName] = new SetSchema();
|
|
3525
|
+
}
|
|
3526
|
+
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3527
|
+
// Direct Schema type: Type → new Type()
|
|
3528
|
+
defaultValues[fieldName] = new value['type']();
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
else {
|
|
3532
|
+
defaultValues[fieldName] = value['default'];
|
|
3533
|
+
}
|
|
3562
3534
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
}
|
|
3569
|
-
at(index) {
|
|
3570
|
-
const key = Array.from(this.$items.keys())[index];
|
|
3571
|
-
return this.$items.get(key);
|
|
3572
|
-
}
|
|
3573
|
-
entries() {
|
|
3574
|
-
return this.$items.entries();
|
|
3575
|
-
}
|
|
3576
|
-
delete(item) {
|
|
3577
|
-
const entries = this.$items.entries();
|
|
3578
|
-
let index;
|
|
3579
|
-
let entry;
|
|
3580
|
-
while (entry = entries.next()) {
|
|
3581
|
-
if (entry.done) {
|
|
3582
|
-
break;
|
|
3535
|
+
else if (typeof (value) === "function") {
|
|
3536
|
+
if (Schema.is(value)) {
|
|
3537
|
+
// Direct Schema type: Type → new Type()
|
|
3538
|
+
defaultValues[fieldName] = new value();
|
|
3539
|
+
fields[fieldName] = value;
|
|
3583
3540
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
break;
|
|
3541
|
+
else {
|
|
3542
|
+
methods[fieldName] = value;
|
|
3587
3543
|
}
|
|
3588
3544
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3545
|
+
else {
|
|
3546
|
+
fields[fieldName] = value;
|
|
3591
3547
|
}
|
|
3592
|
-
this.deletedItems[index] = this[$changes].delete(index);
|
|
3593
|
-
this.$indexes.delete(index);
|
|
3594
|
-
return this.$items.delete(index);
|
|
3595
|
-
}
|
|
3596
|
-
clear() {
|
|
3597
|
-
const changeTree = this[$changes];
|
|
3598
|
-
// discard previous operations.
|
|
3599
|
-
changeTree.discard(true);
|
|
3600
|
-
changeTree.indexes = {};
|
|
3601
|
-
// remove children references
|
|
3602
|
-
changeTree.forEachChild((childChangeTree, _) => {
|
|
3603
|
-
changeTree.root?.remove(childChangeTree);
|
|
3604
|
-
});
|
|
3605
|
-
// clear previous indexes
|
|
3606
|
-
this.$indexes.clear();
|
|
3607
|
-
// clear items
|
|
3608
|
-
this.$items.clear();
|
|
3609
|
-
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3610
|
-
}
|
|
3611
|
-
has(value) {
|
|
3612
|
-
return Array.from(this.$items.values()).some((v) => v === value);
|
|
3613
|
-
}
|
|
3614
|
-
forEach(callbackfn) {
|
|
3615
|
-
this.$items.forEach((value, key, _) => callbackfn(value, key, this));
|
|
3616
|
-
}
|
|
3617
|
-
values() {
|
|
3618
|
-
return this.$items.values();
|
|
3619
3548
|
}
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
}
|
|
3641
|
-
|
|
3642
|
-
|
|
3549
|
+
const getDefaultValues = () => {
|
|
3550
|
+
const defaults = {};
|
|
3551
|
+
for (const fieldName in defaultValues) {
|
|
3552
|
+
const defaultValue = defaultValues[fieldName];
|
|
3553
|
+
// If the default value has a clone method, use it to get a fresh instance
|
|
3554
|
+
if (defaultValue && typeof defaultValue.clone === 'function') {
|
|
3555
|
+
defaults[fieldName] = defaultValue.clone();
|
|
3556
|
+
}
|
|
3557
|
+
else {
|
|
3558
|
+
// Otherwise, use the value as-is (for primitives and non-cloneable objects)
|
|
3559
|
+
defaults[fieldName] = defaultValue;
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
return defaults;
|
|
3563
|
+
};
|
|
3564
|
+
const klass = Metadata.setFields(class extends inherits {
|
|
3565
|
+
constructor(...args) {
|
|
3566
|
+
args[0] = Object.assign({}, getDefaultValues(), args[0]);
|
|
3567
|
+
super(...args);
|
|
3568
|
+
}
|
|
3569
|
+
}, fields);
|
|
3570
|
+
for (let fieldName in viewTagFields) {
|
|
3571
|
+
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
3643
3572
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3573
|
+
for (let methodName in methods) {
|
|
3574
|
+
klass.prototype[methodName] = methods[methodName];
|
|
3646
3575
|
}
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
this.forEach((value, key) => {
|
|
3650
|
-
values.push((typeof (value['toJSON']) === "function")
|
|
3651
|
-
? value['toJSON']()
|
|
3652
|
-
: value);
|
|
3653
|
-
});
|
|
3654
|
-
return values;
|
|
3576
|
+
if (name) {
|
|
3577
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
3655
3578
|
}
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3579
|
+
klass.extends = (fields, name) => schema(fields, name, klass);
|
|
3580
|
+
return klass;
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
function getIndent(level) {
|
|
3584
|
+
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
3585
|
+
}
|
|
3586
|
+
function dumpChanges(schema) {
|
|
3587
|
+
const $root = schema[$changes].root;
|
|
3588
|
+
const dump = {
|
|
3589
|
+
ops: {},
|
|
3590
|
+
refs: []
|
|
3591
|
+
};
|
|
3592
|
+
// for (const refId in $root.changes) {
|
|
3593
|
+
let current = $root.changes.next;
|
|
3594
|
+
while (current) {
|
|
3595
|
+
const changeTree = current.changeTree;
|
|
3596
|
+
// skip if ChangeTree is undefined
|
|
3597
|
+
if (changeTree === undefined) {
|
|
3598
|
+
current = current.next;
|
|
3599
|
+
continue;
|
|
3664
3600
|
}
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
}
|
|
3675
|
-
});
|
|
3601
|
+
const changes = changeTree.indexedOperations;
|
|
3602
|
+
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3603
|
+
for (const index in changes) {
|
|
3604
|
+
const op = changes[index];
|
|
3605
|
+
const opName = exports.OPERATION[op];
|
|
3606
|
+
if (!dump.ops[opName]) {
|
|
3607
|
+
dump.ops[opName] = 0;
|
|
3608
|
+
}
|
|
3609
|
+
dump.ops[exports.OPERATION[op]]++;
|
|
3676
3610
|
}
|
|
3677
|
-
|
|
3611
|
+
current = current.next;
|
|
3678
3612
|
}
|
|
3613
|
+
return dump;
|
|
3679
3614
|
}
|
|
3680
|
-
registerType("collection", { constructor: CollectionSchema, });
|
|
3681
3615
|
|
|
3682
3616
|
var _a, _b;
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3617
|
+
/**
|
|
3618
|
+
* Schema encoder / decoder
|
|
3619
|
+
*/
|
|
3620
|
+
class Schema {
|
|
3621
|
+
static { this[_a] = encodeSchemaOperation; }
|
|
3622
|
+
static { this[_b] = decodeSchemaOperation; }
|
|
3623
|
+
/**
|
|
3624
|
+
* Assign the property descriptors required to track changes on this instance.
|
|
3625
|
+
* @param instance
|
|
3626
|
+
*/
|
|
3627
|
+
static initialize(instance) {
|
|
3628
|
+
Object.defineProperty(instance, $changes, {
|
|
3629
|
+
value: new ChangeTree(instance),
|
|
3630
|
+
enumerable: false,
|
|
3631
|
+
writable: true
|
|
3632
|
+
});
|
|
3633
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3634
|
+
}
|
|
3635
|
+
static is(type) {
|
|
3636
|
+
return typeof (type[Symbol.metadata]) === "object";
|
|
3637
|
+
// const metadata = type[Symbol.metadata];
|
|
3638
|
+
// return metadata && Object.prototype.hasOwnProperty.call(metadata, -1);
|
|
3639
|
+
}
|
|
3640
|
+
/**
|
|
3641
|
+
* Track property changes
|
|
3642
|
+
*/
|
|
3643
|
+
static [(_a = $encoder, _b = $decoder, $track)](changeTree, index, operation = exports.OPERATION.ADD) {
|
|
3644
|
+
changeTree.change(index, operation);
|
|
3645
|
+
}
|
|
3686
3646
|
/**
|
|
3687
3647
|
* Determine if a property must be filtered.
|
|
3688
3648
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -3692,164 +3652,248 @@ class SetSchema {
|
|
|
3692
3652
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
3693
3653
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
3694
3654
|
*/
|
|
3695
|
-
static [
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
return type['set'] !== undefined;
|
|
3702
|
-
}
|
|
3703
|
-
constructor(initialValues) {
|
|
3704
|
-
this.$items = new Map();
|
|
3705
|
-
this.$indexes = new Map();
|
|
3706
|
-
this.deletedItems = {};
|
|
3707
|
-
this.$refId = 0;
|
|
3708
|
-
this[$changes] = new ChangeTree(this);
|
|
3709
|
-
this[$changes].indexes = {};
|
|
3710
|
-
if (initialValues) {
|
|
3711
|
-
initialValues.forEach((v) => this.add(v));
|
|
3712
|
-
}
|
|
3713
|
-
Object.defineProperty(this, $childType, {
|
|
3714
|
-
value: undefined,
|
|
3715
|
-
enumerable: false,
|
|
3716
|
-
writable: true,
|
|
3717
|
-
configurable: true,
|
|
3718
|
-
});
|
|
3719
|
-
}
|
|
3720
|
-
add(value) {
|
|
3721
|
-
// immediatelly return false if value already added.
|
|
3722
|
-
if (this.has(value)) {
|
|
3723
|
-
return false;
|
|
3724
|
-
}
|
|
3725
|
-
// set "index" for reference.
|
|
3726
|
-
const index = this.$refId++;
|
|
3727
|
-
if ((value[$changes]) !== undefined) {
|
|
3728
|
-
value[$changes].setParent(this, this[$changes].root, index);
|
|
3729
|
-
}
|
|
3730
|
-
const operation = this[$changes].indexes[index]?.op ?? exports.OPERATION.ADD;
|
|
3731
|
-
this[$changes].indexes[index] = index;
|
|
3732
|
-
this.$indexes.set(index, index);
|
|
3733
|
-
this.$items.set(index, value);
|
|
3734
|
-
this[$changes].change(index, operation);
|
|
3735
|
-
return index;
|
|
3736
|
-
}
|
|
3737
|
-
entries() {
|
|
3738
|
-
return this.$items.entries();
|
|
3739
|
-
}
|
|
3740
|
-
delete(item) {
|
|
3741
|
-
const entries = this.$items.entries();
|
|
3742
|
-
let index;
|
|
3743
|
-
let entry;
|
|
3744
|
-
while (entry = entries.next()) {
|
|
3745
|
-
if (entry.done) {
|
|
3746
|
-
break;
|
|
3747
|
-
}
|
|
3748
|
-
if (item === entry.value[1]) {
|
|
3749
|
-
index = entry.value[0];
|
|
3750
|
-
break;
|
|
3751
|
-
}
|
|
3655
|
+
static [$filter](ref, index, view) {
|
|
3656
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
3657
|
+
const tag = metadata[index]?.tag;
|
|
3658
|
+
if (view === undefined) {
|
|
3659
|
+
// shared pass/encode: encode if doesn't have a tag
|
|
3660
|
+
return tag === undefined;
|
|
3752
3661
|
}
|
|
3753
|
-
if (
|
|
3754
|
-
|
|
3662
|
+
else if (tag === undefined) {
|
|
3663
|
+
// view pass: no tag
|
|
3664
|
+
return true;
|
|
3755
3665
|
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
changeTree.indexes = {};
|
|
3765
|
-
// clear previous indexes
|
|
3766
|
-
this.$indexes.clear();
|
|
3767
|
-
// clear items
|
|
3768
|
-
this.$items.clear();
|
|
3769
|
-
changeTree.operation(exports.OPERATION.CLEAR);
|
|
3770
|
-
}
|
|
3771
|
-
has(value) {
|
|
3772
|
-
const values = this.$items.values();
|
|
3773
|
-
let has = false;
|
|
3774
|
-
let entry;
|
|
3775
|
-
while (entry = values.next()) {
|
|
3776
|
-
if (entry.done) {
|
|
3777
|
-
break;
|
|
3778
|
-
}
|
|
3779
|
-
if (value === entry.value) {
|
|
3780
|
-
has = true;
|
|
3781
|
-
break;
|
|
3782
|
-
}
|
|
3666
|
+
else if (tag === DEFAULT_VIEW_TAG) {
|
|
3667
|
+
// view pass: default tag
|
|
3668
|
+
return view.isChangeTreeVisible(ref[$changes]);
|
|
3669
|
+
}
|
|
3670
|
+
else {
|
|
3671
|
+
// view pass: custom tag
|
|
3672
|
+
const tags = view.tags?.get(ref[$changes]);
|
|
3673
|
+
return tags && tags.has(tag);
|
|
3783
3674
|
}
|
|
3784
|
-
return has;
|
|
3785
3675
|
}
|
|
3786
|
-
|
|
3787
|
-
|
|
3676
|
+
// allow inherited classes to have a constructor
|
|
3677
|
+
constructor(...args) {
|
|
3678
|
+
//
|
|
3679
|
+
// inline
|
|
3680
|
+
// Schema.initialize(this);
|
|
3681
|
+
//
|
|
3682
|
+
Schema.initialize(this);
|
|
3683
|
+
//
|
|
3684
|
+
// Assign initial values
|
|
3685
|
+
//
|
|
3686
|
+
if (args[0]) {
|
|
3687
|
+
Object.assign(this, args[0]);
|
|
3688
|
+
}
|
|
3788
3689
|
}
|
|
3789
|
-
|
|
3790
|
-
|
|
3690
|
+
assign(props) {
|
|
3691
|
+
Object.assign(this, props);
|
|
3692
|
+
return this;
|
|
3791
3693
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
3694
|
+
/**
|
|
3695
|
+
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3696
|
+
* @param instance Schema instance
|
|
3697
|
+
* @param property string representing the property name, or number representing the index of the property.
|
|
3698
|
+
* @param operation OPERATION to perform (detected automatically)
|
|
3699
|
+
*/
|
|
3700
|
+
setDirty(property, operation) {
|
|
3701
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3702
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
3794
3703
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3704
|
+
clone() {
|
|
3705
|
+
const cloned = new (this.constructor);
|
|
3706
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3707
|
+
//
|
|
3708
|
+
// TODO: clone all properties, not only annotated ones
|
|
3709
|
+
//
|
|
3710
|
+
// for (const field in this) {
|
|
3711
|
+
for (const fieldIndex in metadata) {
|
|
3712
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3713
|
+
const field = metadata[fieldIndex].name;
|
|
3714
|
+
if (typeof (this[field]) === "object" &&
|
|
3715
|
+
typeof (this[field]?.clone) === "function") {
|
|
3716
|
+
// deep clone
|
|
3717
|
+
cloned[field] = this[field].clone();
|
|
3718
|
+
}
|
|
3719
|
+
else {
|
|
3720
|
+
// primitive values
|
|
3721
|
+
cloned[field] = this[field];
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
return cloned;
|
|
3798
3725
|
}
|
|
3799
|
-
|
|
3800
|
-
|
|
3726
|
+
toJSON() {
|
|
3727
|
+
const obj = {};
|
|
3728
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3729
|
+
for (const index in metadata) {
|
|
3730
|
+
const field = metadata[index];
|
|
3731
|
+
const fieldName = field.name;
|
|
3732
|
+
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
3733
|
+
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
3734
|
+
? this[fieldName]['toJSON']()
|
|
3735
|
+
: this[fieldName];
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
return obj;
|
|
3801
3739
|
}
|
|
3802
|
-
|
|
3803
|
-
|
|
3740
|
+
/**
|
|
3741
|
+
* Used in tests only
|
|
3742
|
+
* @internal
|
|
3743
|
+
*/
|
|
3744
|
+
discardAllChanges() {
|
|
3745
|
+
this[$changes].discardAll();
|
|
3804
3746
|
}
|
|
3805
3747
|
[$getByIndex](index) {
|
|
3806
|
-
|
|
3748
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3749
|
+
return this[metadata[index].name];
|
|
3807
3750
|
}
|
|
3808
3751
|
[$deleteByIndex](index) {
|
|
3809
|
-
const
|
|
3810
|
-
this
|
|
3811
|
-
this.$indexes.delete(index);
|
|
3752
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3753
|
+
this[metadata[index].name] = undefined;
|
|
3812
3754
|
}
|
|
3813
|
-
|
|
3814
|
-
|
|
3755
|
+
/**
|
|
3756
|
+
* Inspect the `refId` of all Schema instances in the tree. Optionally display the contents of the instance.
|
|
3757
|
+
*
|
|
3758
|
+
* @param ref Schema instance
|
|
3759
|
+
* @param showContents display JSON contents of the instance
|
|
3760
|
+
* @returns
|
|
3761
|
+
*/
|
|
3762
|
+
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3763
|
+
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3764
|
+
const changeTree = ref[$changes];
|
|
3765
|
+
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
3766
|
+
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3767
|
+
// log reference count if > 1
|
|
3768
|
+
const refCount = (root?.refCount?.[refId] > 1)
|
|
3769
|
+
? ` [×${root.refCount[refId]}]`
|
|
3770
|
+
: '';
|
|
3771
|
+
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3772
|
+
changeTree.forEachChild((childChangeTree, indexOrKey) => {
|
|
3773
|
+
let key = indexOrKey;
|
|
3774
|
+
if (typeof indexOrKey === 'number' && ref['$indexes']) {
|
|
3775
|
+
// MapSchema
|
|
3776
|
+
key = ref['$indexes'].get(indexOrKey) ?? indexOrKey;
|
|
3777
|
+
}
|
|
3778
|
+
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3779
|
+
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3780
|
+
});
|
|
3781
|
+
return output;
|
|
3815
3782
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
3783
|
+
static debugRefIdEncodingOrder(ref, changeSet = 'allChanges') {
|
|
3784
|
+
let encodeOrder = [];
|
|
3785
|
+
let current = ref[$changes].root[changeSet].next;
|
|
3786
|
+
while (current) {
|
|
3787
|
+
if (current.changeTree) {
|
|
3788
|
+
encodeOrder.push(current.changeTree.refId);
|
|
3789
|
+
}
|
|
3790
|
+
current = current.next;
|
|
3791
|
+
}
|
|
3792
|
+
return encodeOrder;
|
|
3818
3793
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
this.forEach((value, key) => {
|
|
3822
|
-
values.push((typeof (value['toJSON']) === "function")
|
|
3823
|
-
? value['toJSON']()
|
|
3824
|
-
: value);
|
|
3825
|
-
});
|
|
3826
|
-
return values;
|
|
3794
|
+
static debugRefIdsFromDecoder(decoder) {
|
|
3795
|
+
return this.debugRefIds(decoder.state, false, 0, decoder);
|
|
3827
3796
|
}
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3797
|
+
/**
|
|
3798
|
+
* Return a string representation of the changes on a Schema instance.
|
|
3799
|
+
* The list of changes is cleared after each encode.
|
|
3800
|
+
*
|
|
3801
|
+
* @param instance Schema instance
|
|
3802
|
+
* @param isEncodeAll Return "full encode" instead of current change set.
|
|
3803
|
+
* @returns
|
|
3804
|
+
*/
|
|
3805
|
+
static debugChanges(instance, isEncodeAll = false) {
|
|
3806
|
+
const changeTree = instance[$changes];
|
|
3807
|
+
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3808
|
+
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3809
|
+
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3810
|
+
function dumpChangeSet(changeSet) {
|
|
3811
|
+
changeSet.operations
|
|
3812
|
+
.filter(op => op)
|
|
3813
|
+
.forEach((index) => {
|
|
3814
|
+
const operation = changeTree.indexedOperations[index];
|
|
3815
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3816
|
+
});
|
|
3836
3817
|
}
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3818
|
+
dumpChangeSet(changeSet);
|
|
3819
|
+
// display filtered changes
|
|
3820
|
+
if (!isEncodeAll &&
|
|
3821
|
+
changeTree.filteredChanges &&
|
|
3822
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3823
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3824
|
+
dumpChangeSet(changeTree.filteredChanges);
|
|
3825
|
+
}
|
|
3826
|
+
// display filtered changes
|
|
3827
|
+
if (isEncodeAll &&
|
|
3828
|
+
changeTree.allFilteredChanges &&
|
|
3829
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3830
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3831
|
+
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3832
|
+
}
|
|
3833
|
+
return output;
|
|
3834
|
+
}
|
|
3835
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3836
|
+
let output = "";
|
|
3837
|
+
const rootChangeTree = ref[$changes];
|
|
3838
|
+
const root = rootChangeTree.root;
|
|
3839
|
+
const changeTrees = new Map();
|
|
3840
|
+
const instanceRefIds = [];
|
|
3841
|
+
let totalOperations = 0;
|
|
3842
|
+
// TODO: FIXME: this method is not working as expected
|
|
3843
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3844
|
+
const changeTree = root.changeTrees[refId];
|
|
3845
|
+
if (!changeTree) {
|
|
3846
|
+
continue;
|
|
3847
|
+
}
|
|
3848
|
+
let includeChangeTree = false;
|
|
3849
|
+
let parentChangeTrees = [];
|
|
3850
|
+
let parentChangeTree = changeTree.parent?.[$changes];
|
|
3851
|
+
if (changeTree === rootChangeTree) {
|
|
3852
|
+
includeChangeTree = true;
|
|
3853
|
+
}
|
|
3854
|
+
else {
|
|
3855
|
+
while (parentChangeTree !== undefined) {
|
|
3856
|
+
parentChangeTrees.push(parentChangeTree);
|
|
3857
|
+
if (parentChangeTree.ref === ref) {
|
|
3858
|
+
includeChangeTree = true;
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
parentChangeTree = parentChangeTree.parent?.[$changes];
|
|
3843
3862
|
}
|
|
3844
|
-
|
|
3845
|
-
|
|
3863
|
+
}
|
|
3864
|
+
if (includeChangeTree) {
|
|
3865
|
+
instanceRefIds.push(changeTree.refId);
|
|
3866
|
+
totalOperations += Object.keys(changes).length;
|
|
3867
|
+
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
output += "---\n";
|
|
3871
|
+
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3872
|
+
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3873
|
+
output += `Total changes: ${totalOperations}\n`;
|
|
3874
|
+
output += "---\n";
|
|
3875
|
+
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
3876
|
+
const visitedParents = new WeakSet();
|
|
3877
|
+
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3878
|
+
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3879
|
+
if (!visitedParents.has(parentChangeTree)) {
|
|
3880
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3881
|
+
visitedParents.add(parentChangeTree);
|
|
3846
3882
|
}
|
|
3847
3883
|
});
|
|
3884
|
+
const changes = changeTree.indexedOperations;
|
|
3885
|
+
const level = parentChangeTrees.length;
|
|
3886
|
+
const indent = getIndent(level);
|
|
3887
|
+
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3888
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3889
|
+
for (const index in changes) {
|
|
3890
|
+
const operation = changes[index];
|
|
3891
|
+
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3892
|
+
}
|
|
3848
3893
|
}
|
|
3849
|
-
return
|
|
3894
|
+
return `${output}`;
|
|
3850
3895
|
}
|
|
3851
3896
|
}
|
|
3852
|
-
registerType("set", { constructor: SetSchema });
|
|
3853
3897
|
|
|
3854
3898
|
/******************************************************************************
|
|
3855
3899
|
Copyright (c) Microsoft Corporation.
|