@colyseus/schema 3.0.0-alpha.10 → 3.0.0-alpha.12

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.
@@ -942,6 +942,18 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
942
942
  }
943
943
  const type = changeTree.getType(field);
944
944
  const value = changeTree.getValue(field);
945
+ // try { throw new Error(); } catch (e) {
946
+ // // only print if not coming from Reflection.ts
947
+ // if (!e.stack.includes("src/Reflection.ts")) {
948
+ // console.log("encodeKeyValueOperation -> ", {
949
+ // ref: changeTree.ref.constructor.name,
950
+ // field,
951
+ // operation: OPERATION[operation],
952
+ // value: value?.toJSON(),
953
+ // items: ref.toJSON(),
954
+ // });
955
+ // }
956
+ // }
945
957
  // TODO: inline this function call small performance gain
946
958
  encodeValue(encoder, bytes, ref, type, value, field, operation, it);
947
959
  };
@@ -3381,9 +3393,8 @@ class Encoder {
3381
3393
  this.state = state;
3382
3394
  state[$changes].setRoot(this.root);
3383
3395
  }
3384
- encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes) {
3396
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees) {
3385
3397
  const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
3386
- const isEncodeAll = this.root.allChanges === changeTrees;
3387
3398
  const hasView = (view !== undefined);
3388
3399
  const rootChangeTree = this.state[$changes];
3389
3400
  const changeTreesIterator = changeTrees.entries();
@@ -3392,6 +3403,12 @@ class Encoder {
3392
3403
  const ctor = ref['constructor'];
3393
3404
  const encoder = ctor[$encoder];
3394
3405
  const filter = ctor[$filter];
3406
+ // try { throw new Error(); } catch (e) {
3407
+ // // only print if not coming from Reflection.ts
3408
+ // if (!e.stack.includes("src/Reflection.ts")) {
3409
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
3410
+ // }
3411
+ // }
3395
3412
  if (hasView) {
3396
3413
  if (!view.items.has(changeTree)) {
3397
3414
  view.invisible.add(changeTree);
@@ -3421,11 +3438,16 @@ class Encoder {
3421
3438
  // view?.invisible.add(changeTree);
3422
3439
  continue;
3423
3440
  }
3424
- // console.log("WILL ENCODE", {
3425
- // ref: changeTree.ref.constructor.name,
3426
- // fieldIndex,
3427
- // operation: OPERATION[operation],
3428
- // });
3441
+ // try { throw new Error(); } catch (e) {
3442
+ // // only print if not coming from Reflection.ts
3443
+ // if (!e.stack.includes("src/Reflection.ts")) {
3444
+ // // console.log("WILL ENCODE", {
3445
+ // // ref: changeTree.ref.constructor.name,
3446
+ // // fieldIndex,
3447
+ // // operation: OPERATION[operation],
3448
+ // // });
3449
+ // }
3450
+ // }
3429
3451
  encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3430
3452
  }
3431
3453
  }
@@ -3440,7 +3462,7 @@ class Encoder {
3440
3462
  if (buffer === this.sharedBuffer) {
3441
3463
  this.sharedBuffer = buffer;
3442
3464
  }
3443
- return this.encode({ offset: initialOffset }, view, buffer);
3465
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3444
3466
  }
3445
3467
  else {
3446
3468
  //
@@ -3460,14 +3482,14 @@ class Encoder {
3460
3482
  // Array.from(this.root.allChanges.entries()).map((item) => {
3461
3483
  // console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3462
3484
  // });
3463
- return this.encode(it, undefined, buffer, this.root.allChanges);
3485
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
3464
3486
  }
3465
3487
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3466
3488
  const viewOffset = it.offset;
3467
3489
  // console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3468
3490
  // this.debugAllFilteredChanges();
3469
3491
  // try to encode "filtered" changes
3470
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3492
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true);
3471
3493
  return Buffer.concat([
3472
3494
  bytes.subarray(0, sharedOffset),
3473
3495
  bytes.subarray(viewOffset, it.offset)
@@ -3937,6 +3959,238 @@ __decorate([
3937
3959
  type([ReflectionType])
3938
3960
  ], Reflection.prototype, "types", void 0);
3939
3961
 
3962
+ function getStateCallbacks(decoder) {
3963
+ const $root = decoder.root;
3964
+ const callbacks = $root.callbacks;
3965
+ let isTriggeringOnAdd = false;
3966
+ decoder.triggerChanges = function (allChanges) {
3967
+ const uniqueRefIds = new Set();
3968
+ for (let i = 0, l = allChanges.length; i < l; i++) {
3969
+ const change = allChanges[i];
3970
+ const refId = change.refId;
3971
+ const ref = change.ref;
3972
+ const $callbacks = callbacks[refId];
3973
+ if (!$callbacks) {
3974
+ continue;
3975
+ }
3976
+ //
3977
+ // trigger onRemove on child structure.
3978
+ //
3979
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE &&
3980
+ change.previousValue instanceof Schema) {
3981
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE];
3982
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
3983
+ deleteCallbacks[i]();
3984
+ }
3985
+ }
3986
+ if (ref instanceof Schema) {
3987
+ //
3988
+ // Handle schema instance
3989
+ //
3990
+ if (!uniqueRefIds.has(refId)) {
3991
+ try {
3992
+ // trigger onChange
3993
+ const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
3994
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
3995
+ replaceCallbacks[i]();
3996
+ }
3997
+ }
3998
+ catch (e) {
3999
+ console.error(e);
4000
+ }
4001
+ }
4002
+ try {
4003
+ if ($callbacks.hasOwnProperty(change.field)) {
4004
+ const fieldCallbacks = $callbacks[change.field];
4005
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4006
+ fieldCallbacks[i](change.value, change.previousValue);
4007
+ }
4008
+ }
4009
+ }
4010
+ catch (e) {
4011
+ //
4012
+ console.error(e);
4013
+ }
4014
+ }
4015
+ else {
4016
+ //
4017
+ // Handle collection of items
4018
+ //
4019
+ if (change.op === OPERATION.ADD && change.previousValue === undefined) {
4020
+ // triger onAdd
4021
+ isTriggeringOnAdd = true;
4022
+ const addCallbacks = $callbacks[OPERATION.ADD];
4023
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4024
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4025
+ }
4026
+ isTriggeringOnAdd = false;
4027
+ }
4028
+ else if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
4029
+ //
4030
+ // FIXME: `previousValue` should always be available.
4031
+ //
4032
+ if (change.previousValue !== undefined) {
4033
+ // triger onRemove
4034
+ const deleteCallbacks = $callbacks[OPERATION.DELETE];
4035
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4036
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4037
+ }
4038
+ }
4039
+ // Handle DELETE_AND_ADD operations
4040
+ // FIXME: should we set "isTriggeringOnAdd" here?
4041
+ if ((change.op & OPERATION.ADD) === OPERATION.ADD) {
4042
+ const addCallbacks = $callbacks[OPERATION.ADD];
4043
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4044
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4045
+ }
4046
+ }
4047
+ }
4048
+ // trigger onChange
4049
+ if (change.value !== change.previousValue) {
4050
+ const replaceCallbacks = $callbacks[OPERATION.REPLACE];
4051
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4052
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4053
+ }
4054
+ }
4055
+ }
4056
+ uniqueRefIds.add(refId);
4057
+ }
4058
+ };
4059
+ function getProxy(metadataOrType, context) {
4060
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4061
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4062
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4063
+ if (metadata && !isCollection) {
4064
+ const onAdd = function (ref, prop, callback, immediate) {
4065
+ // immediate trigger
4066
+ if (immediate &&
4067
+ context.instance[prop] !== undefined &&
4068
+ !isTriggeringOnAdd // FIXME: This is a workaround (https://github.com/colyseus/schema/issues/147)
4069
+ ) {
4070
+ callback(context.instance[prop], undefined);
4071
+ }
4072
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4073
+ };
4074
+ /**
4075
+ * Schema instances
4076
+ */
4077
+ return new Proxy({
4078
+ listen: function listen(prop, callback, immediate = true) {
4079
+ if (context.instance) {
4080
+ return onAdd(context.instance, prop, callback, immediate);
4081
+ }
4082
+ else {
4083
+ // collection instance not received yet
4084
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, prop, callback, immediate && existing));
4085
+ }
4086
+ },
4087
+ onChange: function onChange(callback) {
4088
+ return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, callback);
4089
+ },
4090
+ bindTo: function bindTo(targetObject, properties) {
4091
+ // return $root.addCallback(
4092
+ // $root.refIds.get(context.instance),
4093
+ // OPERATION.BIND,
4094
+ // callback
4095
+ // );
4096
+ console.log("bindTo", targetObject, properties);
4097
+ }
4098
+ }, {
4099
+ get(target, prop) {
4100
+ if (metadata[prop]) {
4101
+ const instance = context.instance?.[prop];
4102
+ const onInstanceAvailable = ((callback) => {
4103
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4104
+ callback(value, false);
4105
+ // FIXME: by "unbinding" the callback here,
4106
+ // it will not support when the server
4107
+ // re-instantiates the instance.
4108
+ //
4109
+ unbind?.();
4110
+ }, false);
4111
+ // has existing value
4112
+ if ($root.refIds.get(instance) !== undefined) {
4113
+ callback(instance, true);
4114
+ }
4115
+ });
4116
+ return getProxy(metadata[prop].type, {
4117
+ instance,
4118
+ parentInstance: context.instance,
4119
+ onInstanceAvailable,
4120
+ });
4121
+ }
4122
+ else {
4123
+ // accessing the function
4124
+ return target[prop];
4125
+ }
4126
+ },
4127
+ has(target, prop) { return metadata[prop] !== undefined; },
4128
+ set(_, _1, _2) { throw new Error("not allowed"); },
4129
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4130
+ });
4131
+ }
4132
+ else {
4133
+ /**
4134
+ * Collection instances
4135
+ */
4136
+ const onAdd = function (ref, callback, immediate) {
4137
+ // Trigger callback on existing items
4138
+ if (immediate) {
4139
+ ref.forEach((v, k) => callback(v, k));
4140
+ }
4141
+ return $root.addCallback($root.refIds.get(ref), OPERATION.ADD, callback);
4142
+ };
4143
+ const onRemove = function (ref, callback) {
4144
+ return $root.addCallback($root.refIds.get(ref), OPERATION.DELETE, callback);
4145
+ };
4146
+ return new Proxy({
4147
+ onAdd: function (callback, immediate = true) {
4148
+ //
4149
+ // https://github.com/colyseus/schema/issues/147
4150
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4151
+ //
4152
+ // FIXME: "isTriggeringOnAdd" is a workaround. We should find a better way to handle this.
4153
+ //
4154
+ if (context.onInstanceAvailable) {
4155
+ // collection instance not received yet
4156
+ context.onInstanceAvailable((ref, existing) => onAdd(ref, callback, immediate && existing && !isTriggeringOnAdd));
4157
+ }
4158
+ else if (context.instance) {
4159
+ onAdd(context.instance, callback, immediate && !isTriggeringOnAdd);
4160
+ }
4161
+ },
4162
+ onRemove: function (callback) {
4163
+ if (context.onInstanceAvailable) {
4164
+ // collection instance not received yet
4165
+ context.onInstanceAvailable((ref) => onRemove(ref, callback));
4166
+ }
4167
+ else if (context.instance) {
4168
+ onRemove(context.instance, callback);
4169
+ }
4170
+ },
4171
+ }, {
4172
+ get(target, prop) {
4173
+ if (!target[prop]) {
4174
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4175
+ }
4176
+ return target[prop];
4177
+ },
4178
+ has(target, prop) { return target[prop] !== undefined; },
4179
+ set(_, _1, _2) { throw new Error("not allowed"); },
4180
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4181
+ });
4182
+ }
4183
+ }
4184
+ function $(instance) {
4185
+ return getProxy(undefined, { instance });
4186
+ }
4187
+ return $(decoder.state);
4188
+ }
4189
+
4190
+ function getRawChangesCallback(decoder, callback) {
4191
+ decoder.triggerChanges = callback;
4192
+ }
4193
+
3940
4194
  class StateView {
3941
4195
  constructor() {
3942
4196
  /**
@@ -4130,5 +4384,5 @@ registerType("array", { constructor: ArraySchema });
4130
4384
  registerType("set", { constructor: SetSchema });
4131
4385
  registerType("collection", { constructor: CollectionSchema, });
4132
4386
 
4133
- export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, registerType, type, view };
4387
+ export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, getRawChangesCallback, getStateCallbacks, registerType, type, view };
4134
4388
  //# sourceMappingURL=index.mjs.map