@colyseus/schema
Version:
Binary state serializer with delta encoding for games
407 lines • 14.8 kB
JavaScript
"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