@colyseus/schema 3.0.20 → 3.0.22
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 +91 -56
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +91 -56
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +91 -56
- package/lib/Reflection.js +69 -41
- package/lib/Reflection.js.map +1 -1
- package/lib/decoder/ReferenceTracker.js +2 -3
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/encoder/Encoder.js +7 -4
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/StateView.js +4 -4
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/types/TypeContext.d.ts +2 -0
- package/lib/types/TypeContext.js +9 -5
- package/lib/types/TypeContext.js.map +1 -1
- package/package.json +1 -1
- package/src/Reflection.ts +79 -46
- package/src/decoder/ReferenceTracker.ts +2 -2
- package/src/encoder/Encoder.ts +7 -4
- package/src/encoder/StateView.ts +5 -5
- package/src/types/TypeContext.ts +10 -5
package/build/esm/index.mjs
CHANGED
|
@@ -598,6 +598,7 @@ class TypeContext {
|
|
|
598
598
|
* Keeps track of which classes extends which. (parent -> children)
|
|
599
599
|
*/
|
|
600
600
|
static { this.inheritedTypes = new Map(); }
|
|
601
|
+
static { this.cachedContexts = new Map(); }
|
|
601
602
|
static register(target) {
|
|
602
603
|
const parent = Object.getPrototypeOf(target);
|
|
603
604
|
if (parent !== Schema) {
|
|
@@ -609,17 +610,20 @@ class TypeContext {
|
|
|
609
610
|
inherits.add(target);
|
|
610
611
|
}
|
|
611
612
|
}
|
|
613
|
+
static cache(rootClass) {
|
|
614
|
+
let context = TypeContext.cachedContexts.get(rootClass);
|
|
615
|
+
if (!context) {
|
|
616
|
+
context = new TypeContext(rootClass);
|
|
617
|
+
TypeContext.cachedContexts.set(rootClass, context);
|
|
618
|
+
}
|
|
619
|
+
return context;
|
|
620
|
+
}
|
|
612
621
|
constructor(rootClass) {
|
|
613
622
|
this.types = {};
|
|
614
623
|
this.schemas = new Map();
|
|
615
624
|
this.hasFilters = false;
|
|
616
625
|
this.parentFiltered = {};
|
|
617
626
|
if (rootClass) {
|
|
618
|
-
//
|
|
619
|
-
// TODO:
|
|
620
|
-
// cache "discoverTypes" results for each rootClass
|
|
621
|
-
// to avoid re-discovering types for each new context/room
|
|
622
|
-
//
|
|
623
627
|
this.discoverTypes(rootClass);
|
|
624
628
|
}
|
|
625
629
|
}
|
|
@@ -3765,10 +3769,12 @@ class Encoder {
|
|
|
3765
3769
|
constructor(state) {
|
|
3766
3770
|
this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
3767
3771
|
//
|
|
3768
|
-
//
|
|
3769
|
-
//
|
|
3772
|
+
// Use .cache() here to avoid re-creating a new context for every new room instance.
|
|
3773
|
+
//
|
|
3774
|
+
// We may need to make this optional in case of dynamically created
|
|
3775
|
+
// schemas - which would lead to memory leaks
|
|
3770
3776
|
//
|
|
3771
|
-
this.context =
|
|
3777
|
+
this.context = TypeContext.cache(state.constructor);
|
|
3772
3778
|
this.root = new Root(this.context);
|
|
3773
3779
|
this.setState(state);
|
|
3774
3780
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
@@ -3915,13 +3921,14 @@ class Encoder {
|
|
|
3915
3921
|
const changeTree = this.root.changeTrees[refId];
|
|
3916
3922
|
if (changeTree === undefined) {
|
|
3917
3923
|
// detached instance, remove from view and skip.
|
|
3924
|
+
// console.log("detached instance, remove from view and skip.", refId);
|
|
3918
3925
|
view.changes.delete(refId);
|
|
3919
3926
|
continue;
|
|
3920
3927
|
}
|
|
3921
3928
|
const keys = Object.keys(changes);
|
|
3922
3929
|
if (keys.length === 0) {
|
|
3923
3930
|
// FIXME: avoid having empty changes if no changes were made
|
|
3924
|
-
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3931
|
+
// console.log("changes.size === 0, skip", refId, changeTree.ref.constructor.name);
|
|
3925
3932
|
continue;
|
|
3926
3933
|
}
|
|
3927
3934
|
const ref = changeTree.ref;
|
|
@@ -4067,7 +4074,7 @@ class ReferenceTracker {
|
|
|
4067
4074
|
//
|
|
4068
4075
|
// Ensure child schema instances have their references removed as well.
|
|
4069
4076
|
//
|
|
4070
|
-
if (
|
|
4077
|
+
if (ref.constructor[Symbol.metadata] !== undefined) {
|
|
4071
4078
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4072
4079
|
for (const index in metadata) {
|
|
4073
4080
|
const field = metadata[index].name;
|
|
@@ -4078,7 +4085,7 @@ class ReferenceTracker {
|
|
|
4078
4085
|
}
|
|
4079
4086
|
}
|
|
4080
4087
|
else {
|
|
4081
|
-
if (typeof (
|
|
4088
|
+
if (typeof (ref[$childType]) === "function") {
|
|
4082
4089
|
Array.from(ref.values())
|
|
4083
4090
|
.forEach((child) => {
|
|
4084
4091
|
const childRefId = this.refIds.get(child);
|
|
@@ -4273,50 +4280,28 @@ class Reflection extends Schema {
|
|
|
4273
4280
|
if (rootType > 0) {
|
|
4274
4281
|
reflection.rootType = rootType;
|
|
4275
4282
|
}
|
|
4276
|
-
const
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
const
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
if (typeof (type) === "string") {
|
|
4289
|
-
fieldType = type;
|
|
4283
|
+
const includedTypeIds = new Set();
|
|
4284
|
+
const pendingReflectionTypes = {};
|
|
4285
|
+
// add type to reflection in a way that respects inheritance
|
|
4286
|
+
// (parent types should be added before their children)
|
|
4287
|
+
const addType = (type) => {
|
|
4288
|
+
if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
|
|
4289
|
+
includedTypeIds.add(type.id);
|
|
4290
|
+
reflection.types.push(type);
|
|
4291
|
+
const deps = pendingReflectionTypes[type.id];
|
|
4292
|
+
if (deps !== undefined) {
|
|
4293
|
+
delete pendingReflectionTypes[type.id];
|
|
4294
|
+
deps.forEach((childType) => addType(childType));
|
|
4290
4295
|
}
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
//
|
|
4296
|
-
if (Schema.is(type)) {
|
|
4297
|
-
fieldType = "ref";
|
|
4298
|
-
childTypeSchema = type;
|
|
4299
|
-
}
|
|
4300
|
-
else {
|
|
4301
|
-
fieldType = Object.keys(type)[0];
|
|
4302
|
-
if (typeof (type[fieldType]) === "string") {
|
|
4303
|
-
fieldType += ":" + type[fieldType]; // array:string
|
|
4304
|
-
}
|
|
4305
|
-
else {
|
|
4306
|
-
childTypeSchema = type[fieldType];
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
|
-
field.referencedType = (childTypeSchema)
|
|
4310
|
-
? context.getTypeId(childTypeSchema)
|
|
4311
|
-
: -1;
|
|
4296
|
+
}
|
|
4297
|
+
else {
|
|
4298
|
+
if (pendingReflectionTypes[type.extendsId] === undefined) {
|
|
4299
|
+
pendingReflectionTypes[type.extendsId] = [];
|
|
4312
4300
|
}
|
|
4313
|
-
|
|
4314
|
-
currentType.fields.push(field);
|
|
4301
|
+
pendingReflectionTypes[type.extendsId].push(type);
|
|
4315
4302
|
}
|
|
4316
|
-
reflection.types.push(currentType);
|
|
4317
4303
|
};
|
|
4318
|
-
|
|
4319
|
-
const klass = context.types[typeid];
|
|
4304
|
+
context.schemas.forEach((typeid, klass) => {
|
|
4320
4305
|
const type = new ReflectionType();
|
|
4321
4306
|
type.id = Number(typeid);
|
|
4322
4307
|
// support inheritance
|
|
@@ -4324,7 +4309,57 @@ class Reflection extends Schema {
|
|
|
4324
4309
|
if (inheritFrom !== Schema) {
|
|
4325
4310
|
type.extendsId = context.schemas.get(inheritFrom);
|
|
4326
4311
|
}
|
|
4327
|
-
|
|
4312
|
+
const metadata = klass[Symbol.metadata];
|
|
4313
|
+
//
|
|
4314
|
+
// FIXME: this is a workaround for inherited types without additional fields
|
|
4315
|
+
// if metadata is the same reference as the parent class - it means the class has no own metadata
|
|
4316
|
+
//
|
|
4317
|
+
if (metadata !== inheritFrom[Symbol.metadata]) {
|
|
4318
|
+
for (const fieldIndex in metadata) {
|
|
4319
|
+
const index = Number(fieldIndex);
|
|
4320
|
+
const fieldName = metadata[index].name;
|
|
4321
|
+
// skip fields from parent classes
|
|
4322
|
+
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
4323
|
+
continue;
|
|
4324
|
+
}
|
|
4325
|
+
const reflectionField = new ReflectionField();
|
|
4326
|
+
reflectionField.name = fieldName;
|
|
4327
|
+
let fieldType;
|
|
4328
|
+
const field = metadata[index];
|
|
4329
|
+
if (typeof (field.type) === "string") {
|
|
4330
|
+
fieldType = field.type;
|
|
4331
|
+
}
|
|
4332
|
+
else {
|
|
4333
|
+
let childTypeSchema;
|
|
4334
|
+
//
|
|
4335
|
+
// TODO: refactor below.
|
|
4336
|
+
//
|
|
4337
|
+
if (Schema.is(field.type)) {
|
|
4338
|
+
fieldType = "ref";
|
|
4339
|
+
childTypeSchema = field.type;
|
|
4340
|
+
}
|
|
4341
|
+
else {
|
|
4342
|
+
fieldType = Object.keys(field.type)[0];
|
|
4343
|
+
if (typeof (field.type[fieldType]) === "string") {
|
|
4344
|
+
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4345
|
+
}
|
|
4346
|
+
else {
|
|
4347
|
+
childTypeSchema = field.type[fieldType];
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
reflectionField.referencedType = (childTypeSchema)
|
|
4351
|
+
? context.getTypeId(childTypeSchema)
|
|
4352
|
+
: -1;
|
|
4353
|
+
}
|
|
4354
|
+
reflectionField.type = fieldType;
|
|
4355
|
+
type.fields.push(reflectionField);
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
addType(type);
|
|
4359
|
+
});
|
|
4360
|
+
// in case there are types that were not added due to inheritance
|
|
4361
|
+
for (const typeid in pendingReflectionTypes) {
|
|
4362
|
+
pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
|
|
4328
4363
|
}
|
|
4329
4364
|
const buf = reflectionEncoder.encodeAll(it);
|
|
4330
4365
|
return Buffer.from(buf, 0, it.offset);
|
|
@@ -4693,7 +4728,7 @@ class StateView {
|
|
|
4693
4728
|
}
|
|
4694
4729
|
// TODO: allow to set multiple tags at once
|
|
4695
4730
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
4696
|
-
if (!obj[$changes]) {
|
|
4731
|
+
if (!obj?.[$changes]) {
|
|
4697
4732
|
console.warn("StateView#add(), invalid object:", obj);
|
|
4698
4733
|
return this;
|
|
4699
4734
|
}
|
|
@@ -4815,7 +4850,7 @@ class StateView {
|
|
|
4815
4850
|
}
|
|
4816
4851
|
this.items.delete(changeTree);
|
|
4817
4852
|
const ref = changeTree.ref;
|
|
4818
|
-
const metadata = ref.constructor[Symbol.metadata];
|
|
4853
|
+
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
4819
4854
|
let changes = this.changes.get(changeTree.refId);
|
|
4820
4855
|
if (changes === undefined) {
|
|
4821
4856
|
changes = {};
|
|
@@ -4836,12 +4871,12 @@ class StateView {
|
|
|
4836
4871
|
}
|
|
4837
4872
|
else {
|
|
4838
4873
|
// delete all "tagged" properties.
|
|
4839
|
-
metadata[$viewFieldIndexes].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4874
|
+
metadata?.[$viewFieldIndexes].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4840
4875
|
}
|
|
4841
4876
|
}
|
|
4842
4877
|
else {
|
|
4843
4878
|
// delete only tagged properties
|
|
4844
|
-
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4879
|
+
metadata?.[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4845
4880
|
}
|
|
4846
4881
|
// remove tag
|
|
4847
4882
|
if (this.tags && this.tags.has(changeTree)) {
|