UNPKG

@colyseus/schema

Version:

Binary state serializer with delta encoding for games

407 lines 14.8 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); const util = require("node:util"); require("./symbol.shim"); const _1 = require("."); const $callback = { $onCreate: Symbol('$onCreate'), $onDelete: Symbol('$onDelete'), $onUpdate: Symbol('$onUpdate'), }; function logSingleCall(label, callback) { const time = Date.now(); const res = callback(); console.log(`${label}:`, Date.now() - time); return res; } function logTime(label, callback) { const time = Date.now(); for (let i = 0; i < 500000; i++) { callback(); } console.log(`${label}:`, Date.now() - time); } // // @ts-ignore // globalThis.perform = function perform() { // for (let i = 0; i < 500000; i++) { // encoder.encodeAll(); // } // } // const timeout = setInterval(() => {}, 1000); // function decorate({ get, set }, context: ClassAccessorDecoratorContext): ClassAccessorDecoratorResult<any, any> { // const field = context.name.toString(); // // const fieldIndex = Metadata.addField(context.metadata, field, type); // const parent = Object.getPrototypeOf(context.metadata); // let lastIndex = (parent && parent[-1] as number) ?? -1; // lastIndex++; // context.metadata[field] = { type: "number" }; // Object.defineProperty(context.metadata, lastIndex, { // value: field, // enumerable: false, // configurable: true, // }); // Object.defineProperty(context.metadata, -1, { // value: lastIndex, // enumerable: false, // configurable: true // }); // return { // init(value) { return value; }, // get() { return get.call(this); }, // set(value: any) { set.call(this, value); }, // }; // } // class Fruit { // @decorate accessor frutose: number = 1; // } // class Banana extends Fruit { // @decorate accessor potassium: number = 10; // } // class Berry extends Fruit { // @decorate accessor antioxidants: number = 10; // } // class Strawberry extends Berry { // @decorate accessor fiber: number = 10; // } // class Grape extends Berry { // @decorate accessor vitaminc: number = 5; // } // console.log("fruit:", Fruit[Symbol.metadata], Object.keys(Fruit[Symbol.metadata])); // console.log("banana:", Banana[Symbol.metadata], Object.keys(Banana[Symbol.metadata])); // console.log("strawberry:", Strawberry[Symbol.metadata], Object.keys(Strawberry[Symbol.metadata])); // console.log("grape:", Grape[Symbol.metadata], Object.keys(Grape[Symbol.metadata])); // console.log("GRAPE =>"); // function printFields(metadata) { // let i = 0; // const len = metadata[-1] // console.log({ len }); // for (let i = 0; i <= len; i++) { // console.log("over len...", i, metadata[i]) // } // } // console.log("Grape..."); // printFields(Grape[Symbol.metadata]); // console.log("Banana..."); // printFields(Banana[Symbol.metadata]); // class Item extends Schema { // @type("string") accessor name: string; // } // class RootState extends Schema { // @type([Item]) accessor items = new ArraySchema<Item>(); // } // const s = new RootState(); // s.items.push(new Item().assign({ name: "hello" })); // const encoder = new Encoder(s); // const encoded = encoder.encode(); // const decoder = new Decoder(new RootState()); // decoder.decode(encoded); function log(message) { console.log(util.inspect(message, false, 10, true)); } // // No need to extend Schema! // class Vec3 { // x: number; // y: number; // z: number; // constructor() { // // Need to initialize property descriptors // Schema.initialize(this); // } // } // // Define fields to encode/decode // Metadata.setFields(Vec3, { // x: "number", // y: "number", // z: "number", // }); // // // Vec3[$track] = function ( // changeTree: ChangeTree, // index: number, // operation: OPERATION = OPERATION.ADD // ) { // changeTree.change(index, operation); // }; // Vec3[$encoder] = encodeSchemaOperation; // Vec3[$decoder] = decodeSchemaOperation; // // @ts-ignore // if (!Vec3.prototype.toJSON) { Vec3.prototype.toJSON = Schema.prototype.toJSON; } // ------------------------------------------------------------------------------- class Vec3 extends _1.Schema { } __decorate([ (0, _1.type)("number") ], Vec3.prototype, "x", void 0); __decorate([ (0, _1.type)("number") ], Vec3.prototype, "y", void 0); __decorate([ (0, _1.type)("number") ], Vec3.prototype, "z", void 0); // Vec3[$track] = function (changeTree, index) { // changeTree.change(0, OPERATION.ADD); // }; // Vec3[$encoder] = function (encoder, bytes, changeTree, index, operation, it) { // encode.number(bytes, changeTree.ref.x, it); // encode.number(bytes, changeTree.ref.y, it); // encode.number(bytes, changeTree.ref.z, it); // }; // Vec3[$decoder] = function ( // decoder: Decoder<any>, // bytes: number[], // it: decode.Iterator, // ref: Vec3, // allChanges: DataChange[] // ) { // ref.x = decode.number(bytes, it); // ref.y = decode.number(bytes, it); // ref.z = decode.number(bytes, it); // }; class Base extends _1.Schema { } class Entity extends _1.Schema { constructor() { super(...arguments); this.position = new Vec3().assign({ x: 0, y: 0, z: 0 }); } } __decorate([ (0, _1.type)(Vec3) ], Entity.prototype, "position", void 0); class Card extends _1.Schema { } __decorate([ (0, _1.type)("string") ], Card.prototype, "suit", void 0); __decorate([ (0, _1.type)("number") ], Card.prototype, "num", void 0); class Player extends Entity { constructor() { super(...arguments); this.rotation = new Vec3().assign({ x: 0, y: 0, z: 0 }); this.secret = "private info only for this player"; this.cards = new _1.ArraySchema(new Card().assign({ suit: "Hearts", num: 1 }), new Card().assign({ suit: "Spaces", num: 2 }), new Card().assign({ suit: "Diamonds", num: 3 })); } [$callback.$onCreate]() { } } __decorate([ (0, _1.type)(Vec3) ], Player.prototype, "rotation", void 0); __decorate([ (0, _1.type)("string") ], Player.prototype, "secret", void 0); __decorate([ (0, _1.type)([Card]) ], Player.prototype, "cards", void 0); class Team extends _1.Schema { constructor() { super(...arguments); this.entities = new _1.MapSchema(); } } __decorate([ (0, _1.type)({ map: Entity }) ], Team.prototype, "entities", void 0); class State extends _1.Schema { constructor() { super(...arguments); this.num = 0; this.str = "Hello world!"; this.teams = new _1.ArraySchema(); // @type({ map: Entity }) entities = new MapSchema<Entity>(); // @type(Entity) entity1 = new Player().assign({ // position: new Vec3().assign({ x: 1, y: 2, z: 3 }), // rotation: new Vec3().assign({ x: 4, y: 5, z: 6 }), // }); // @type(Entity) entity2 = new Player().assign({ // position: new Vec3().assign({ x: 1, y: 2, z: 3 }), // rotation: new Vec3().assign({ x: 4, y: 5, z: 6 }), // }); } } __decorate([ (0, _1.type)("number") ], State.prototype, "num", void 0); __decorate([ (0, _1.type)("string") ], State.prototype, "str", void 0); __decorate([ (0, _1.view)(), (0, _1.type)([Team]) ], State.prototype, "teams", void 0); const state = new State(); // for (let i=0;i<1000;i++) { // state.entities.set("one" + i, new Player().assign({ // position: new Vec3().assign({ x: 1, y: 2, z: 3 }), // rotation: new Vec3().assign({ x: 4, y: 5, z: 6 }), // })); // } // state.entities.set("one", new Player().assign({ // position: new Vec3().assign({ x: 1, y: 2, z: 3 }), // rotation: new Vec3().assign({ x: 4, y: 5, z: 6 }), // })); // state.entities.set("two", new Player().assign({ // position: new Vec3().assign({ x: 10, y: 10, z: 3 }), // rotation: new Vec3().assign({ x: 4, y: 5, z: 6 }), // })); function addTeam() { const team = new Team(); team.entities.set("one", new Player().assign({ position: new Vec3().assign({ x: 1, y: 2, z: 3 }), rotation: new Vec3().assign({ x: 4, y: 5, z: 6 }), })); team.entities.set("two", new Player().assign({ position: new Vec3().assign({ x: 7, y: 8, z: 9 }), rotation: new Vec3().assign({ x: 2, y: 3, z: 4 }), })); state.teams.push(team); } addTeam(); addTeam(); const it = { offset: 0 }; const encoder = new _1.Encoder(state); // logTime("encode time", () => encoder.encodeAll()); // const encoded = encoder.encode(it); console.log("> will encode all...", state.toJSON()); const encoded = encoder.encode(it); console.log("HEAP TOTAL:", process.memoryUsage().heapTotal / 1024 / 1024, "MB"); console.log("HEAP USED:", process.memoryUsage().heapUsed / 1024 / 1024, "MB"); // console.log("encoded.buffer =>", `(${encoded.byteLength} bytes)`); const sharedOffset = it.offset; // const team1View = new StateView<State>(); // team1View.owns(state.teams[0]); const view1 = new _1.StateView(); view1.add(state.teams[0]); // view1['owned'].add(state[$changes]); // view1['owned'].add(state.teams[$changes]); // view1.owns(state.teams[0]); // view1.owns(state.entities); // view1.owns(state.entities.get("one")); const view2 = new _1.StateView(); console.log(">>> VIEW 2"); view2.add(state.teams[1]); // view2.owns(state.entities.get("two")); console.log("> will encode view 1..."); const viewEncoded1 = encoder.encodeView(view1, sharedOffset, it, encoder.sharedBuffer); console.log("done. view1 encoded =>", `(${viewEncoded1.byteLength} bytes)`); console.log("> will encode view 2..."); const viewEncoded2 = encoder.encodeView(view2, sharedOffset, it, encoder.sharedBuffer); console.log("done. view2 encoded =>", `(${viewEncoded2.byteLength} bytes)`); // setTimeout(() => { // for (let i = 0; i < 500000; i++) { // encoder.encodeAll(); // } // }, 1000) // logTime("encode time", () => encoder.encodeAll()); // console.log(`encode: (${encoded.length})`, encoded); console.log("----------------------------------- ENCODE reflection..."); const encodedReflection = _1.Reflection.encode(state, encoder.context); console.log("----------------------------------- DECODE reflection..."); const decodedState = _1.Reflection.decode(encodedReflection); // const decodedState = new State(); const decoder = new _1.Decoder(decodedState); // $(decoder.state).teams.onAdd((team, index) => { // // room.$state.bind(team, frontendTeam); // $(team).entities.onAdd((entity, entityId) => { // $(entity as Player).listen("secret", (value, previousValue) => { // }); // $(entity).position.onChange(() => { // }); // }); // }); console.log("> will decode..."); // decoder.decode(encoded); decoder.decode(viewEncoded1); console.log("Decoded =>", decoder.state.toJSON()); // decoder.decode(viewEncoded2); // log(decodedState.toJSON()); // console.log("encoder.$root.changes =>", encoder.$root.changes.length); // console.log("encoder.$root.filteredChanges =>", encoder.$root.filteredChanges.length); // state.teams[0].entities.get("one").position.x = 100; // // encoder. // const it2 = { offset: 0 }; // // const encoded = encoder.encode(it); // const encoded2 = encoder.encode(it2); // console.log(`> shared encode... (${encoded2.byteLength} bytes)`); // const viewEncoded3 = encoder.encodeView(view1, sharedOffset, it, encoder.sharedBuffer); // console.log(`> view1... (${viewEncoded3.byteLength} bytes)`); // log(decoder.root.toJSON()); // logTime("decode time", () => decoder.decode(encoded)); // console.log("changes =>", changes); // const rotation = state.entity.rotation; // rotation.x = 100; // state.entity.rotation = undefined; // const encoded2 = encoder.encode(); // console.log({ encoded2 }); // decoder.decode(encoded2); // console.log("decoded =>", decoded.toJSON()); // console.profile(); // const time = Date.now(); // for (let i = 0; i < 300000; i++) { // const state = new State(); // encoder['setRoot'](state); // encoder.encode(); // } // console.log("encode time:", Date.now() - time); // console.profileEnd(); // state.players.set("entity", new Entity()); // state.players.set("one", new Player()); // console.log("state:", state); // console.log("state.players", state.players); // console.log("state.players.one", state.players.get("one")); // console.log("state.players.one.position", state.players.get("one").position); // const encoder = new Encoder(state); // // encoder.change() // let encoded = encoder.encodeAll(); // console.log({ encoded }) // const decoder = new Decoder(new State()); // decoder.decode(encoded); // log(decoder['root'].toJSON()); // const reflection = Reflection.encode(state); // console.log("encoded =>", reflection); // log(Reflection.decode(reflection)) ///////............................................... // function type (type: string) { // return function (_: any, context: ClassFieldDecoratorContext) { // context.addInitializer(function() { // console.log("INITIALIZER!", this); // setTimeout(() => { // context.access.set(this, "WHAT"); // }, 1000); // }); // context.access.get = function(object) { // return object[context.name]; // } // context.access.set = function(object, value) { // console.log("setter...", object, value); // object[context.name] = `Setter[${value}]`; // }; // return function(value) { // console.log("SET", this, value); // if (typeof(value) === "string") { // value = `Initializer[${value}]`; // } else { // value = value * 100; // } // return value; // }; // } // } // class MyState { // @type("string") name: string = "Hello world!"; // @type("number") num: number = 1; // } // const state = new MyState(); // console.log("name: ", state.name); // console.log("num: ", state.num); // state.name = `${state.name} modified...`; // setTimeout(() => console.log(state.name), 1100); //# sourceMappingURL=v3_experiment.js.map