@colyseus/schema 3.0.20 → 3.0.21

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/src/Reflection.ts CHANGED
@@ -44,71 +44,104 @@ export class Reflection extends Schema {
44
44
  const rootType = context.schemas.get(encoder.state.constructor);
45
45
  if (rootType > 0) { reflection.rootType = rootType; }
46
46
 
47
- const buildType = (currentType: ReflectionType, metadata: Metadata) => {
48
- for (const fieldIndex in metadata) {
49
- const index = Number(fieldIndex);
50
- const fieldName = metadata[index].name;
51
-
52
- // skip fields from parent classes
53
- if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
54
- continue;
47
+ const includedTypeIds = new Set<number>();
48
+ const pendingReflectionTypes: { [typeid: number]: ReflectionType[] } = {};
49
+
50
+ // add type to reflection in a way that respects inheritance
51
+ // (parent types should be added before their children)
52
+ const addType = (type: ReflectionType) => {
53
+ if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
54
+ includedTypeIds.add(type.id);
55
+
56
+ reflection.types.push(type);
57
+
58
+ const deps = pendingReflectionTypes[type.id];
59
+ if (deps !== undefined) {
60
+ delete pendingReflectionTypes[type.id];
61
+ deps.forEach((childType) => addType(childType));
55
62
  }
63
+ } else {
64
+ if (pendingReflectionTypes[type.extendsId] === undefined) {
65
+ pendingReflectionTypes[type.extendsId] = [];
66
+ }
67
+ pendingReflectionTypes[type.extendsId].push(type);
68
+ }
69
+ };
56
70
 
57
- const field = new ReflectionField();
58
- field.name = fieldName;
71
+ context.schemas.forEach((typeid, klass) => {
72
+ const type = new ReflectionType();
73
+ type.id = Number(typeid);
59
74
 
60
- let fieldType: string;
75
+ // support inheritance
76
+ const inheritFrom = Object.getPrototypeOf(klass);
77
+ if (inheritFrom !== Schema) {
78
+ type.extendsId = context.schemas.get(inheritFrom);
79
+ }
61
80
 
62
- const type = metadata[index].type;
81
+ const metadata = klass[Symbol.metadata];
63
82
 
64
- if (typeof (type) === "string") {
65
- fieldType = type;
83
+ //
84
+ // FIXME: this is a workaround for inherited types without additional fields
85
+ // if metadata is the same reference as the parent class - it means the class has no own metadata
86
+ //
87
+ if (metadata !== inheritFrom[Symbol.metadata]) {
88
+ for (const fieldIndex in metadata) {
89
+ const index = Number(fieldIndex);
90
+ const fieldName = metadata[index].name;
66
91
 
67
- } else {
68
- let childTypeSchema: typeof Schema;
92
+ // skip fields from parent classes
93
+ if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
94
+ continue;
95
+ }
96
+
97
+ const reflectionField = new ReflectionField();
98
+ reflectionField.name = fieldName;
99
+
100
+ let fieldType: string;
69
101
 
70
- //
71
- // TODO: refactor below.
72
- //
73
- if (Schema.is(type)) {
74
- fieldType = "ref";
75
- childTypeSchema = type as typeof Schema;
102
+ const field = metadata[index];
103
+
104
+ if (typeof (field.type) === "string") {
105
+ fieldType = field.type;
76
106
 
77
107
  } else {
78
- fieldType = Object.keys(type)[0];
108
+ let childTypeSchema: typeof Schema;
79
109
 
80
- if (typeof(type[fieldType]) === "string") {
81
- fieldType += ":" + type[fieldType]; // array:string
110
+ //
111
+ // TODO: refactor below.
112
+ //
113
+ if (Schema.is(field.type)) {
114
+ fieldType = "ref";
115
+ childTypeSchema = field.type as typeof Schema;
82
116
 
83
117
  } else {
84
- childTypeSchema = type[fieldType];
118
+ fieldType = Object.keys(field.type)[0];
119
+
120
+ if (typeof (field.type[fieldType]) === "string") {
121
+ fieldType += ":" + field.type[fieldType]; // array:string
122
+
123
+ } else {
124
+ childTypeSchema = field.type[fieldType];
125
+ }
85
126
  }
127
+
128
+ reflectionField.referencedType = (childTypeSchema)
129
+ ? context.getTypeId(childTypeSchema)
130
+ : -1;
86
131
  }
87
132
 
88
- field.referencedType = (childTypeSchema)
89
- ? context.getTypeId(childTypeSchema)
90
- : -1;
133
+ reflectionField.type = fieldType;
134
+ type.fields.push(reflectionField);
91
135
  }
92
-
93
- field.type = fieldType;
94
- currentType.fields.push(field);
95
136
  }
96
137
 
97
- reflection.types.push(currentType);
98
- }
99
-
100
- for (let typeid in context.types) {
101
- const klass = context.types[typeid];
102
- const type = new ReflectionType();
103
- type.id = Number(typeid);
104
-
105
- // support inheritance
106
- const inheritFrom = Object.getPrototypeOf(klass);
107
- if (inheritFrom !== Schema) {
108
- type.extendsId = context.schemas.get(inheritFrom);
109
- }
138
+ addType(type);
139
+ });
110
140
 
111
- buildType(type, klass[Symbol.metadata]);
141
+ // in case there are types that were not added due to inheritance
142
+ for (const typeid in pendingReflectionTypes) {
143
+ pendingReflectionTypes[typeid].forEach((type) =>
144
+ reflection.types.push(type))
112
145
  }
113
146
 
114
147
  const buf = reflectionEncoder.encodeAll(it);
@@ -23,10 +23,12 @@ export class Encoder<T extends Schema = any> {
23
23
 
24
24
  constructor(state: T) {
25
25
  //
26
- // TODO: cache and restore "Context" based on root schema
27
- // (to avoid creating a new context for every new room)
26
+ // Use .cache() here to avoid re-creating a new context for every new room instance.
28
27
  //
29
- this.context = new TypeContext(state.constructor as typeof Schema);
28
+ // We may need to make this optional in case of dynamically created
29
+ // schemas - which would lead to memory leaks
30
+ //
31
+ this.context = TypeContext.cache(state.constructor as typeof Schema);
30
32
  this.root = new Root(this.context);
31
33
 
32
34
  this.setState(state);
@@ -14,6 +14,7 @@ export class TypeContext {
14
14
  * Keeps track of which classes extends which. (parent -> children)
15
15
  */
16
16
  static inheritedTypes = new Map<typeof Schema, Set<typeof Schema>>();
17
+ static cachedContexts = new Map<typeof Schema, TypeContext>();
17
18
 
18
19
  static register(target: typeof Schema) {
19
20
  const parent = Object.getPrototypeOf(target);
@@ -27,13 +28,17 @@ export class TypeContext {
27
28
  }
28
29
  }
29
30
 
31
+ static cache (rootClass: typeof Schema) {
32
+ let context = TypeContext.cachedContexts.get(rootClass);
33
+ if (!context) {
34
+ context = new TypeContext(rootClass);
35
+ TypeContext.cachedContexts.set(rootClass, context);
36
+ }
37
+ return context;
38
+ }
39
+
30
40
  constructor(rootClass?: typeof Schema) {
31
41
  if (rootClass) {
32
- //
33
- // TODO:
34
- // cache "discoverTypes" results for each rootClass
35
- // to avoid re-discovering types for each new context/room
36
- //
37
42
  this.discoverTypes(rootClass);
38
43
  }
39
44
  }