UNPKG

@tamgl/colyseus-schema

Version:

Binary state serializer with delta encoding for games

255 lines 9.72 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeArray = exports.decodeKeyValueOperation = exports.decodeSchemaOperation = exports.DEFINITION_MISMATCH = void 0; exports.decodeValue = decodeValue; const spec_1 = require("../encoding/spec"); const Schema_1 = require("../Schema"); const decode_1 = require("../encoding/decode"); const symbols_1 = require("../types/symbols"); const registry_1 = require("../types/registry"); exports.DEFINITION_MISMATCH = -1; function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) { const $root = decoder.root; const previousValue = ref[symbols_1.$getByIndex](index); let value; if ((operation & spec_1.OPERATION.DELETE) === spec_1.OPERATION.DELETE) { // Flag `refId` for garbage collection. const previousRefId = $root.refIds.get(previousValue); if (previousRefId !== undefined) { $root.removeRef(previousRefId); } // // Delete operations // if (operation !== spec_1.OPERATION.DELETE_AND_ADD) { ref[symbols_1.$deleteByIndex](index); } value = undefined; } if (operation === spec_1.OPERATION.DELETE) { // // Don't do anything // } else if (Schema_1.Schema.is(type)) { const refId = decode_1.decode.number(bytes, it); value = $root.refs.get(refId); if ((operation & spec_1.OPERATION.ADD) === spec_1.OPERATION.ADD) { const childType = decoder.getInstanceType(bytes, it, type); if (!value) { value = decoder.createInstanceOfType(childType); } $root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed (operation === spec_1.OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again )); } } else if (typeof (type) === "string") { // // primitive value (number, string, boolean, etc) // value = decode_1.decode[type](bytes, it); } else { const typeDef = (0, registry_1.getType)(Object.keys(type)[0]); const refId = decode_1.decode.number(bytes, it); const valueRef = ($root.refs.has(refId)) ? previousValue || $root.refs.get(refId) : new typeDef.constructor(); value = valueRef.clone(true); value[symbols_1.$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema if (previousValue) { let previousRefId = $root.refIds.get(previousValue); if (previousRefId !== undefined && refId !== previousRefId) { // // enqueue onRemove if structure has been replaced. // const entries = previousValue.entries(); let iter; while ((iter = entries.next()) && !iter.done) { const [key, value] = iter.value; // if value is a schema, remove its reference if (typeof (value) === "object") { previousRefId = $root.refIds.get(value); $root.removeRef(previousRefId); } allChanges.push({ ref: previousValue, refId: previousRefId, op: spec_1.OPERATION.DELETE, field: key, value: undefined, previousValue: value, }); } } } $root.addRef(refId, value, (valueRef !== previousValue || (operation === spec_1.OPERATION.DELETE_AND_ADD && valueRef === previousValue))); } return { value, previousValue }; } const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) { const first_byte = bytes[it.offset++]; const metadata = ref.constructor[Symbol.metadata]; // "compressed" index + operation const operation = (first_byte >> 6) << 6; const index = first_byte % (operation || 255); // skip early if field is not defined const field = metadata[index]; if (field === undefined) { console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata }); return exports.DEFINITION_MISMATCH; } const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges); if (value !== null && value !== undefined) { ref[field.name] = value; } // add change if (previousValue !== value) { allChanges.push({ ref, refId: decoder.currentRefId, op: operation, field: field.name, value, previousValue, }); } }; exports.decodeSchemaOperation = decodeSchemaOperation; const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) { // "uncompressed" index + operation (array/map items) const operation = bytes[it.offset++]; if (operation === spec_1.OPERATION.CLEAR) { // // When decoding: // - enqueue items for DELETE callback. // - flag child items for garbage collection. // decoder.removeChildRefs(ref, allChanges); ref.clear(); return; } const index = decode_1.decode.number(bytes, it); const type = ref[symbols_1.$childType]; let dynamicIndex; if ((operation & spec_1.OPERATION.ADD) === spec_1.OPERATION.ADD) { // ADD or DELETE_AND_ADD if (typeof (ref['set']) === "function") { dynamicIndex = decode_1.decode.string(bytes, it); // MapSchema ref['setIndex'](index, dynamicIndex); } else { dynamicIndex = index; // ArraySchema } } else { // get dynamic index from "ref" dynamicIndex = ref['getIndex'](index); } const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges); if (value !== null && value !== undefined) { if (typeof (ref['set']) === "function") { // MapSchema ref['$items'].set(dynamicIndex, value); } else if (typeof (ref['$setAt']) === "function") { // ArraySchema ref['$setAt'](index, value, operation); } else if (typeof (ref['add']) === "function") { // CollectionSchema && SetSchema const index = ref.add(value); if (typeof (index) === "number") { ref['setIndex'](index, index); } } } // add change if (previousValue !== value) { allChanges.push({ ref, refId: decoder.currentRefId, op: operation, field: "", // FIXME: remove this dynamicIndex, value, previousValue, }); } }; exports.decodeKeyValueOperation = decodeKeyValueOperation; const decodeArray = function (decoder, bytes, it, ref, allChanges) { // "uncompressed" index + operation (array/map items) let operation = bytes[it.offset++]; let index; if (operation === spec_1.OPERATION.CLEAR) { // // When decoding: // - enqueue items for DELETE callback. // - flag child items for garbage collection. // decoder.removeChildRefs(ref, allChanges); ref.clear(); return; } else if (operation === spec_1.OPERATION.REVERSE) { ref.reverse(); return; } else if (operation === spec_1.OPERATION.DELETE_BY_REFID) { // TODO: refactor here, try to follow same flow as below const refId = decode_1.decode.number(bytes, it); const previousValue = decoder.root.refs.get(refId); index = ref.findIndex((value) => value === previousValue); ref[symbols_1.$deleteByIndex](index); allChanges.push({ ref, refId: decoder.currentRefId, op: spec_1.OPERATION.DELETE, field: "", // FIXME: remove this dynamicIndex: index, value: undefined, previousValue, }); return; } else if (operation === spec_1.OPERATION.ADD_BY_REFID) { const refId = decode_1.decode.number(bytes, it); const itemByRefId = decoder.root.refs.get(refId); // if item already exists, use existing index if (itemByRefId) { index = ref.findIndex((value) => value === itemByRefId); } // fallback to use last index if (index === -1 || index === undefined) { index = ref.length; } } else { index = decode_1.decode.number(bytes, it); } const type = ref[symbols_1.$childType]; let dynamicIndex = index; const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges); if (value !== null && value !== undefined && value !== previousValue // avoid setting same value twice (if index === 0 it will result in a "unshift" for ArraySchema) ) { // ArraySchema ref['$setAt'](index, value, operation); } // add change if (previousValue !== value) { allChanges.push({ ref, refId: decoder.currentRefId, op: operation, field: "", // FIXME: remove this dynamicIndex, value, previousValue, }); } }; exports.decodeArray = decodeArray; //# sourceMappingURL=DecodeOperation.js.map