@tamgl/colyseus-schema
Version:
Binary state serializer with delta encoding for games
118 lines (93 loc) • 4.35 kB
text/typescript
import { OPERATION } from "../encoding/spec";
import { TypeContext } from "../types/TypeContext";
import { spliceOne } from "../types/utils";
import { ChangeTree, enqueueChangeTree, setOperationAtIndex } from "./ChangeTree";
export class Root {
protected nextUniqueId: number = 0;
refCount: {[id: number]: number} = {};
changeTrees: {[refId: number]: ChangeTree} = {};
// all changes
allChanges: ChangeTree[] = [];
allFilteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
// pending changes to be encoded
changes: ChangeTree[] = [];
filteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
constructor(public types: TypeContext) { }
getNextUniqueId() {
return this.nextUniqueId++;
}
add(changeTree: ChangeTree) {
// FIXME: move implementation of `ensureRefId` to `Root` class
changeTree.ensureRefId();
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
if (isNewChangeTree) { this.changeTrees[changeTree.refId] = changeTree; }
const previousRefCount = this.refCount[changeTree.refId];
if (previousRefCount === 0) {
//
// When a ChangeTree is re-added, it means that it was previously removed.
// We need to re-add all changes to the `changes` map.
//
const ops = changeTree.allChanges.operations;
let len = ops.length;
while (len--) {
changeTree.indexedOperations[ops[len]] = OPERATION.ADD;
setOperationAtIndex(changeTree.changes, len);
}
}
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
return isNewChangeTree;
}
remove(changeTree: ChangeTree) {
const refCount = (this.refCount[changeTree.refId]) - 1;
if (refCount <= 0) {
//
// Only remove "root" reference if it's the last reference
//
changeTree.root = undefined;
delete this.changeTrees[changeTree.refId];
this.removeChangeFromChangeSet("allChanges", changeTree);
this.removeChangeFromChangeSet("changes", changeTree);
if (changeTree.filteredChanges) {
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
this.removeChangeFromChangeSet("filteredChanges", changeTree);
}
this.refCount[changeTree.refId] = 0;
changeTree.forEachChild((child, _) => this.remove(child));
} else {
this.refCount[changeTree.refId] = refCount;
//
// When losing a reference to an instance, it is best to move the
// ChangeTree to the end of the encoding queue.
//
// This way, at decoding time, the instance that contains the
// ChangeTree will be available before the ChangeTree itself. If the
// containing instance is not available, the Decoder will throw
// "refId not found" error.
//
if (changeTree.filteredChanges !== undefined) {
this.removeChangeFromChangeSet("filteredChanges", changeTree);
enqueueChangeTree(this, changeTree, "filteredChanges");
} else {
this.removeChangeFromChangeSet("changes", changeTree);
enqueueChangeTree(this, changeTree, "changes");
}
}
return refCount;
}
removeChangeFromChangeSet(changeSetName: "allChanges" | "changes" | "filteredChanges" | "allFilteredChanges", changeTree: ChangeTree) {
const changeSet = this[changeSetName];
const changeSetIndex = changeSet.indexOf(changeTree);
if (changeSetIndex !== -1) {
changeTree[changeSetName].queueRootIndex = -1;
changeSet[changeSetIndex] = undefined;
return true;
}
// if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
// changeTree[changeSetName].queueRootIndex = -1;
// return true;
// }
}
clear() {
this.changes.length = 0;
}
}