diff --git a/example/heaps/src/Container.hx b/example/heaps/src/Container.hx new file mode 100644 index 0000000..06ad120 --- /dev/null +++ b/example/heaps/src/Container.hx @@ -0,0 +1,19 @@ +// +// THIS FILE HAS BEEN GENERATED AUTOMATICALLY +// DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING +// +// GENERATED USING @colyseus/schema 1.0.1 +// + + +import io.colyseus.serializer.schema.Schema; +import io.colyseus.serializer.schema.types.*; + +class Container extends Schema { + @:type("map", "string") + public var testMap: MapSchema = new MapSchema(); + + @:type("array", "number") + public var testArray: ArraySchema = new ArraySchema(); + +} diff --git a/example/heaps/src/Main.hx b/example/heaps/src/Main.hx index 4579687..3dfece5 100644 --- a/example/heaps/src/Main.hx +++ b/example/heaps/src/Main.hx @@ -14,16 +14,16 @@ class Main extends hxd.App { // this triggers only when map or array are created with `new` // when new value appended it is also triggered but traces just empty array - room.state.onChange = function(v) trace('Root.onChange', v); + room.state.container.onChange = function(v) trace('Root.onChange', v); // following callbacks are never triggered - room.state.testMap.onChange = function(v, k) trace('Map.onChange', v, 'key', k); - room.state.testMap.onAdd = function(v, k) trace('Map.onAdd', v, 'key', k); - room.state.testMap.onRemove = function(v, k) trace('Map.onRemove', v, 'key', k); + room.state.container.testMap.onChange = function(v, k) trace('Map.onChange', v, 'key', k); + room.state.container.testMap.onAdd = function(v, k) trace('Map.onAdd', v, 'key', k); + room.state.container.testMap.onRemove = function(v, k) trace('Map.onRemove', v, 'key', k); - room.state.testArray.onChange = function(v, k) trace('Array.onChange', v, 'key', k); - room.state.testArray.onAdd = function(v, k) trace('Array.onAdd', v, 'key', k); - room.state.testArray.onRemove = function(v, k) trace('Array.onRemove', v, 'key', k); + room.state.container.testArray.onChange = function(v, k) trace('Array.onChange', v, 'key', k); + room.state.container.testArray.onAdd = function(v, k) trace('Array.onAdd', v, 'key', k); + room.state.container.testArray.onRemove = function(v, k) trace('Array.onRemove', v, 'key', k); this.room = room; }); diff --git a/example/heaps/src/State.hx b/example/heaps/src/State.hx index 2752089..a2e8510 100644 --- a/example/heaps/src/State.hx +++ b/example/heaps/src/State.hx @@ -1,19 +1,16 @@ -// +// // THIS FILE HAS BEEN GENERATED AUTOMATICALLY // DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING -// +// // GENERATED USING @colyseus/schema 1.0.1 -// +// import io.colyseus.serializer.schema.Schema; import io.colyseus.serializer.schema.types.*; class State extends Schema { - @:type("map", "string") - public var testMap: MapSchema = new MapSchema(); - - @:type("array", "number") - public var testArray: ArraySchema = new ArraySchema(); + @:type("ref", Container) + public var container: Container = new Container(); } diff --git a/example/server/package.json b/example/server/package.json index 2c79079..77754db 100644 --- a/example/server/package.json +++ b/example/server/package.json @@ -4,7 +4,8 @@ "description": "Usage Examples of Colyseus Game Server", "main": "index.js", "scripts": { - "start": "ts-node index.ts" + "start": "ts-node index.ts", + "schema-codegen": "schema-codegen rooms/TestRoom.ts --haxe --output ../heaps/src/" }, "engines": { "node": "8.9.1" diff --git a/example/server/rooms/TestRoom.ts b/example/server/rooms/TestRoom.ts index 04e75fa..d994b04 100644 --- a/example/server/rooms/TestRoom.ts +++ b/example/server/rooms/TestRoom.ts @@ -1,11 +1,15 @@ import { Schema, type, MapSchema, ArraySchema } from "@colyseus/schema"; import { Room } from "colyseus"; -class State extends Schema { +class Container extends Schema { @type({ map: "string" }) testMap = new MapSchema(); @type(["number"]) testArray = new ArraySchema(); } +class State extends Schema { + @type(Container) container = new Container(); +} + export class TestRoom extends Room { async onCreate(options) { @@ -14,14 +18,14 @@ export class TestRoom extends Room { let int: number = 0; this.clock.setInterval(() => { - this.state.testMap.set(String(int % 3), String(int)); - this.state.testArray.push(int); + this.state.container.testMap.set(String(int % 3), String(int)); + this.state.container.testArray.push(int); int++; if (int % 10 == 0) { - this.state.testMap = new MapSchema(); - this.state.testArray = new ArraySchema(); + this.state.container.testMap = new MapSchema(); + this.state.container.testArray = new ArraySchema(); console.log("RESET"); } }, 1000); diff --git a/haxelib.json b/haxelib.json index 08484a8..4cbe5f1 100644 --- a/haxelib.json +++ b/haxelib.json @@ -4,9 +4,9 @@ "license": "MIT", "tags": ["multiplayer", "networking", "websockets", "netcode"], "description": "Multiplayer Game Client for Haxe", - "version": "0.14.6", + "version": "0.14.7", "classPath": "src/", - "releasenote": "avoid calling onChange() with empty changeset", + "releasenote": "fixes keeping nested schema callback handlers.", "contributors": ["endel"], "dependencies": { "colyseus-websocket": "1.0.7" diff --git a/src/io/colyseus/serializer/schema/Schema.hx b/src/io/colyseus/serializer/schema/Schema.hx index 5c92999..4c1818e 100644 --- a/src/io/colyseus/serializer/schema/Schema.hx +++ b/src/io/colyseus/serializer/schema/Schema.hx @@ -371,7 +371,7 @@ class Schema implements IRef { return Reflect.setField(this, this._indexes.get(fieldIndex), value); } - public function getByIndex(fieldIndex: Int) { + public function getByIndex(fieldIndex: Int): Any { return Reflect.getProperty(this, this._indexes.get(fieldIndex)); } @@ -382,6 +382,20 @@ class Schema implements IRef { public function setIndex(fieldIndex: Int, dynamicIndex: Int) {} public function getIndex(fieldIndex: Int, dynamicIndex: Int) {} + public function moveEventHandlers (previousInstance: Dynamic) { + var previousSchemaInstance = (previousInstance: Schema); + + this.onChange = previousSchemaInstance.onChange; + this.onRemove = previousSchemaInstance.onRemove; + + for (fieldIndex => _ in this._childTypes) { + var childType = this.getByIndex(fieldIndex); + if (Std.isOfType(childType, IRef)) { + (childType : IRef).moveEventHandlers(previousSchemaInstance.getByIndex(fieldIndex)); + } + } + } + public function decode(bytes:Bytes, it:It = null, refs: ReferenceTracker = null) { if (it == null) { it = {offset: 0}; } if (refs == null) { refs = (this._refs != null) ? this._refs : new ReferenceTracker(); } @@ -537,8 +551,7 @@ class Schema implements IRef { value.__refId = refId; if (previousValue != null) { - value.onChange = previousValue.onChange; - value.onRemove = previousValue.onRemove; + (value : Schema).moveEventHandlers(previousValue); if (previousValue.__refId > 0 && refId != previousValue.__refId) { refs.remove(previousValue.__refId); diff --git a/src/io/colyseus/serializer/schema/types/IRef.hx b/src/io/colyseus/serializer/schema/types/IRef.hx index 17520b5..0c3d98a 100644 --- a/src/io/colyseus/serializer/schema/types/IRef.hx +++ b/src/io/colyseus/serializer/schema/types/IRef.hx @@ -5,4 +5,5 @@ interface IRef { public function setByIndex(fieldIndex: Int, dynamicIndex: Dynamic, value: Dynamic): Void; public function getByIndex(fieldIndex: Int): Dynamic; public function deleteByIndex(fieldIndex: Int): Void; + public function moveEventHandlers(previousInstance: Dynamic): Void; } \ No newline at end of file diff --git a/src/io/colyseus/serializer/schema/types/ISchemaCollection.hx b/src/io/colyseus/serializer/schema/types/ISchemaCollection.hx index 1e98e24..f30db7d 100644 --- a/src/io/colyseus/serializer/schema/types/ISchemaCollection.hx +++ b/src/io/colyseus/serializer/schema/types/ISchemaCollection.hx @@ -16,5 +16,4 @@ interface ISchemaCollection extends IRef { public function clear(refs: ReferenceTracker): Void; public function clone(): ISchemaCollection; - public function moveEventHandlers(previousInstance: Dynamic): Void; } \ No newline at end of file diff --git a/tests/SchemaSerializerTestCase.hx b/tests/SchemaSerializerTestCase.hx index fb08374..93c80d7 100644 --- a/tests/SchemaSerializerTestCase.hx +++ b/tests/SchemaSerializerTestCase.hx @@ -14,6 +14,7 @@ import schema.backwardsforwards.StateV1; import schema.backwardsforwards.StateV2; import schema.filteredtypes.State in FilteredTypesState; import schema.instancesharingtypes.State in InstanceSharingTypes; +import schema.callbacks.CallbacksState; class SchemaSerializerTestCase extends haxe.unit.TestCase { @@ -317,6 +318,114 @@ class SchemaSerializerTestCase extends haxe.unit.TestCase { assertEquals(3, refs.count()); } + public function testCallbacks() { + var state = new CallbacksState(); + + var containerOnChange = 0; + var containerOnChangeCallback = function(changes) {containerOnChange++;}; + state.container.onChange = containerOnChangeCallback; + + var arrayOfSchemasOnAdd = 0; + var arrayOfSchemasOnChange = 0; + var arrayOfSchemasOnRemove = 0; + + var arrayOfSchemasOnAddCallback = function(item, key) { + // trace("state.container.arrayOfSchemas.onAdd"); + arrayOfSchemasOnAdd++; + }; + var arrayOfSchemasOnChangeCallback = function(item, key) { + // trace("state.container.arrayOfSchemas.onChange"); + arrayOfSchemasOnChange++; + }; + var arrayOfSchemasOnRemoveCallback = function(item, key) { + // trace("state.container.arrayOfSchemas.onRemove"); + arrayOfSchemasOnRemove++; + }; + + state.container.arrayOfSchemas.onAdd = arrayOfSchemasOnAddCallback; + state.container.arrayOfSchemas.onChange = arrayOfSchemasOnChangeCallback; + state.container.arrayOfSchemas.onRemove = arrayOfSchemasOnRemoveCallback; + + var arrayOfNumbersOnAdd = 0; + var arrayOfNumbersOnChange = 0; + var arrayOfNumbersOnRemove = 0; + + var arrayOfNumbersOnAddCallback = function(item, key) { + // trace("state.container.arrayOfNumbers.onAdd"); + arrayOfNumbersOnAdd++; + }; + var arrayOfNumbersOnChangeCallback = function(item, key) { + // trace("state.container.arrayOfNumbers.onChange"); + arrayOfNumbersOnChange++; + }; + var arrayOfNumbersOnRemoveCallback = function(item, key) { + // trace("state.container.arrayOfNumbers.onRemove"); + arrayOfNumbersOnRemove++; + }; + + state.container.arrayOfNumbers.onAdd = arrayOfNumbersOnAddCallback; + state.container.arrayOfNumbers.onChange = arrayOfNumbersOnChangeCallback; + state.container.arrayOfNumbers.onRemove = arrayOfNumbersOnRemoveCallback; + + var arrayOfStringsOnAdd = 0; + var arrayOfStringsOnChange = 0; + var arrayOfStringsOnRemove = 0; + + var arrayOfStringsOnAddCallback = function(item, key) { + // trace("state.container.arrayOfStrings.onAdd"); + arrayOfStringsOnAdd++; + }; + var arrayOfStringsOnChangeCallback = function(item, key) { + // trace("state.container.arrayOfStrings.onChange"); + arrayOfStringsOnChange++; + }; + var arrayOfStringsOnRemoveCallback = function(item, key) { + // trace("state.container.arrayOfStrings.onRemove"); + arrayOfStringsOnRemove++; + }; + + state.container.arrayOfStrings.onAdd = arrayOfStringsOnAddCallback; + state.container.arrayOfStrings.onChange = arrayOfStringsOnChangeCallback; + state.container.arrayOfStrings.onRemove = arrayOfStringsOnRemoveCallback; + + state.decode(getBytes([128, 1, 255, 1, 130, 2, 131, 3, 132, 4, 133, 5])); + assertEquals(containerOnChange, 1); + assertEquals(arrayOfSchemasOnAdd, 0); + assertEquals(arrayOfSchemasOnChange, 0); + assertEquals(arrayOfSchemasOnRemove, 0); + assertEquals(arrayOfNumbersOnAdd, 0); + assertEquals(arrayOfNumbersOnChange, 0); + assertEquals(arrayOfNumbersOnRemove, 0); + assertEquals(arrayOfStringsOnAdd, 0); + assertEquals(arrayOfStringsOnChange, 0); + assertEquals(arrayOfStringsOnRemove, 0); + + state.decode(getBytes([255, 1, 128, 1, 129, 163, 111, 110, 101, 255, 2, 128, 1, 255, 3, 128, 0, 6, 255, 4, 128, 0, 1, 255, 5, 128, 0, 163, 111, 110, 101, 255, 6, 128, 2])); + assertEquals(containerOnChange, 2); + assertEquals(arrayOfSchemasOnAdd, 1); + assertEquals(arrayOfSchemasOnChange, 0); + assertEquals(arrayOfSchemasOnRemove, 0); + assertEquals(arrayOfNumbersOnAdd, 1); + assertEquals(arrayOfNumbersOnChange, 0); + assertEquals(arrayOfNumbersOnRemove, 0); + assertEquals(arrayOfStringsOnAdd, 1); + assertEquals(arrayOfStringsOnChange, 0); + assertEquals(arrayOfStringsOnRemove, 0); + + state.decode(getBytes([128, 7, 255, 7, 130, 8, 131, 9, 132, 10, 133, 11, 128, 2, 129, 163, 116, 119, 111, 255, 8, 128, 2, 255, 9, 128, 0, 12, 255, 10, 128, 0, 2, 255, 11, 128, 0, 163, 116, 119, 111, 255, 12, 128, 4])); + assertEquals(containerOnChange, 3); + assertEquals(arrayOfSchemasOnAdd, 2); + assertEquals(arrayOfSchemasOnChange, 0); + assertEquals(arrayOfSchemasOnRemove, 0); // FIXME: ideally, this should be 1 + assertEquals(arrayOfNumbersOnAdd, 2); + assertEquals(arrayOfNumbersOnChange, 0); + assertEquals(arrayOfNumbersOnRemove, 0); // FIXME: ideally, this should be 1 + assertEquals(arrayOfStringsOnAdd, 2); + assertEquals(arrayOfStringsOnChange, 0); + assertEquals(arrayOfStringsOnRemove, 0); // FIXME: ideally, this should be 1 + + } + } diff --git a/tests/schema/callbacks/CallbacksState.hx b/tests/schema/callbacks/CallbacksState.hx new file mode 100644 index 0000000..831eddf --- /dev/null +++ b/tests/schema/callbacks/CallbacksState.hx @@ -0,0 +1,16 @@ +// +// THIS FILE HAS BEEN GENERATED AUTOMATICALLY +// DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING +// +// GENERATED USING @colyseus/schema 1.0.1 +// + +package schema.callbacks; +import io.colyseus.serializer.schema.Schema; +import io.colyseus.serializer.schema.types.*; + +class CallbacksState extends Schema { + @:type("ref", Container) + public var container: Container = new Container(); + +} diff --git a/tests/schema/callbacks/Container.hx b/tests/schema/callbacks/Container.hx new file mode 100644 index 0000000..3187811 --- /dev/null +++ b/tests/schema/callbacks/Container.hx @@ -0,0 +1,31 @@ +// +// THIS FILE HAS BEEN GENERATED AUTOMATICALLY +// DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING +// +// GENERATED USING @colyseus/schema 1.0.1 +// + +package schema.callbacks; +import io.colyseus.serializer.schema.Schema; +import io.colyseus.serializer.schema.types.*; + +class Container extends Schema { + @:type("number") + public var num: Dynamic = 0; + + @:type("string") + public var str: String = ""; + + @:type("ref", Ref) + public var ref: Ref = new Ref(); + + @:type("array", Ref) + public var arrayOfSchemas: ArraySchema = new ArraySchema(); + + @:type("array", "number") + public var arrayOfNumbers: ArraySchema = new ArraySchema(); + + @:type("array", "string") + public var arrayOfStrings: ArraySchema = new ArraySchema(); + +} diff --git a/tests/schema/callbacks/Ref.hx b/tests/schema/callbacks/Ref.hx new file mode 100644 index 0000000..adedc48 --- /dev/null +++ b/tests/schema/callbacks/Ref.hx @@ -0,0 +1,16 @@ +// +// THIS FILE HAS BEEN GENERATED AUTOMATICALLY +// DO NOT CHANGE IT MANUALLY UNLESS YOU KNOW WHAT YOU'RE DOING +// +// GENERATED USING @colyseus/schema 1.0.1 +// + +package schema.callbacks; +import io.colyseus.serializer.schema.Schema; +import io.colyseus.serializer.schema.types.*; + +class Ref extends Schema { + @:type("number") + public var num: Dynamic = 0; + +}