@tamgl/colyseus-schema
Version:
Binary state serializer with delta encoding for games
204 lines • 7.27 kB
JavaScript
"use strict";
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapSchema = void 0;
const symbols_1 = require("../symbols");
const ChangeTree_1 = require("../../encoder/ChangeTree");
const spec_1 = require("../../encoding/spec");
const registry_1 = require("../registry");
const DecodeOperation_1 = require("../../decoder/DecodeOperation");
const EncodeOperation_1 = require("../../encoder/EncodeOperation");
const assert_1 = require("../../encoding/assert");
class MapSchema {
static { this[_a] = EncodeOperation_1.encodeKeyValueOperation; }
static { this[_b] = DecodeOperation_1.decodeKeyValueOperation; }
/**
* Determine if a property must be filtered.
* - If returns false, the property is NOT going to be encoded.
* - If returns true, the property is going to be encoded.
*
* Encoding with "filters" happens in two steps:
* - First, the encoder iterates over all "not owned" properties and encodes them.
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
*/
static [(_a = symbols_1.$encoder, _b = symbols_1.$decoder, symbols_1.$filter)](ref, index, view) {
return (!view ||
typeof (ref[symbols_1.$childType]) === "string" ||
view.isChangeTreeVisible((ref[symbols_1.$getByIndex](index) ?? ref.deletedItems[index])[symbols_1.$changes]));
}
static is(type) {
return type['map'] !== undefined;
}
constructor(initialValues) {
this.$items = new Map();
this.$indexes = new Map();
this.deletedItems = {};
this[symbols_1.$changes] = new ChangeTree_1.ChangeTree(this);
this[symbols_1.$changes].indexes = {};
if (initialValues) {
if (initialValues instanceof Map ||
initialValues instanceof MapSchema) {
initialValues.forEach((v, k) => this.set(k, v));
}
else {
for (const k in initialValues) {
this.set(k, initialValues[k]);
}
}
}
Object.defineProperty(this, symbols_1.$childType, {
value: undefined,
enumerable: false,
writable: true,
configurable: true,
});
}
/** Iterator */
[Symbol.iterator]() { return this.$items[Symbol.iterator](); }
get [Symbol.toStringTag]() { return this.$items[Symbol.toStringTag]; }
static get [Symbol.species]() { return MapSchema; }
set(key, value) {
if (value === undefined || value === null) {
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
}
else if (typeof (value) === "object" && this[symbols_1.$childType]) {
(0, assert_1.assertInstanceType)(value, this[symbols_1.$childType], this, key);
}
// Force "key" as string
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
key = key.toString();
const changeTree = this[symbols_1.$changes];
const isRef = (value[symbols_1.$changes]) !== undefined;
let index;
let operation;
// IS REPLACE?
if (typeof (changeTree.indexes[key]) !== "undefined") {
index = changeTree.indexes[key];
operation = spec_1.OPERATION.REPLACE;
const previousValue = this.$items.get(key);
if (previousValue === value) {
// if value is the same, avoid re-encoding it.
return;
}
else if (isRef) {
// if is schema, force ADD operation if value differ from previous one.
operation = spec_1.OPERATION.DELETE_AND_ADD;
// remove reference from previous value
if (previousValue !== undefined) {
previousValue[symbols_1.$changes].root?.remove(previousValue[symbols_1.$changes]);
}
}
}
else {
index = changeTree.indexes[symbols_1.$numFields] ?? 0;
operation = spec_1.OPERATION.ADD;
this.$indexes.set(index, key);
changeTree.indexes[key] = index;
changeTree.indexes[symbols_1.$numFields] = index + 1;
}
this.$items.set(key, value);
changeTree.change(index, operation);
//
// set value's parent after the value is set
// (to avoid encoding "refId" operations before parent's "ADD" operation)
//
if (isRef) {
value[symbols_1.$changes].setParent(this, changeTree.root, index);
}
return this;
}
get(key) {
return this.$items.get(key);
}
delete(key) {
const index = this[symbols_1.$changes].indexes[key];
this.deletedItems[index] = this[symbols_1.$changes].delete(index);
return this.$items.delete(key);
}
clear() {
const changeTree = this[symbols_1.$changes];
// discard previous operations.
changeTree.discard(true);
changeTree.indexes = {};
// remove children references
changeTree.forEachChild((childChangeTree, _) => {
changeTree.root?.remove(childChangeTree);
});
// clear previous indexes
this.$indexes.clear();
// clear items
this.$items.clear();
changeTree.operation(spec_1.OPERATION.CLEAR);
}
has(key) {
return this.$items.has(key);
}
forEach(callbackfn) {
this.$items.forEach(callbackfn);
}
entries() {
return this.$items.entries();
}
keys() {
return this.$items.keys();
}
values() {
return this.$items.values();
}
get size() {
return this.$items.size;
}
setIndex(index, key) {
this.$indexes.set(index, key);
}
getIndex(index) {
return this.$indexes.get(index);
}
[symbols_1.$getByIndex](index) {
return this.$items.get(this.$indexes.get(index));
}
[symbols_1.$deleteByIndex](index) {
const key = this.$indexes.get(index);
this.$items.delete(key);
this.$indexes.delete(index);
}
[symbols_1.$onEncodeEnd]() {
this.deletedItems = {};
}
toJSON() {
const map = {};
this.forEach((value, key) => {
map[key] = (typeof (value['toJSON']) === "function")
? value['toJSON']()
: value;
});
return map;
}
//
// Decoding utilities
//
// @ts-ignore
clone(isDecoding) {
let cloned;
if (isDecoding) {
// client-side
cloned = Object.assign(new MapSchema(), this);
}
else {
// server-side
cloned = new MapSchema();
this.forEach((value, key) => {
if (value[symbols_1.$changes]) {
cloned.set(key, value['clone']());
}
else {
cloned.set(key, value);
}
});
}
return cloned;
}
}
exports.MapSchema = MapSchema;
(0, registry_1.registerType)("map", { constructor: MapSchema });
//# sourceMappingURL=MapSchema.js.map