UNPKG

mute-structs

Version:

NodeJS module providing an implementation of the LogootSplit CRDT algorithm

263 lines (259 loc) 10.8 kB
/* This file is part of MUTE-structs. Copyright (C) 2017 Matthieu Nicolas, Victorien Elvinger This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ import { isObject } from "./data-validation"; import { IdentifierInterval } from "./identifierinterval"; import { isInt32 } from "./int32"; import { LogootSBlock } from "./logootsblock"; /** * @param aNode may be null * @returns Height of aNode or 0 if aNode is null */ function heightOf(aNode) { if (aNode !== null) { return aNode.height; } else { return 0; } } /** * @param aNode may be null * @returns size of aNode (including children sizes) or 0 if aNode is null */ function subtreeSizeOf(aNode) { if (aNode !== null) { return aNode.sizeNodeAndChildren; } else { return 0; } } export function mkNodeAt(id, length) { console.assert(isInt32(length), "length ∈ int32"); console.assert(length > 0, "length > 0"); var idi = new IdentifierInterval(id, length - 1); var newBlock = new LogootSBlock(idi, 0); return RopesNodes.leaf(newBlock, 0, length); } var RopesNodes = /** @class */ (function () { // Creation function RopesNodes(block, actualBegin, length, left, right) { console.assert(isInt32(actualBegin), "actualBegin ∈ int32"); console.assert(block.idInterval.begin <= actualBegin, "actualBegin must be greater than or equal to idInterval.begin"); this.block = block; this.actualBegin = actualBegin; this.length = length; this.left = left; this.right = right; this.height = Math.max(heightOf(left), heightOf(right)) + 1; this.sizeNodeAndChildren = length + subtreeSizeOf(left) + subtreeSizeOf(right); } RopesNodes.fromPlain = function (o) { if (isObject(o) && isInt32(o.actualBegin) && isInt32(o.length) && o.length >= 0) { var block = LogootSBlock.fromPlain(o.block); if (block !== null && block.idInterval.begin <= o.actualBegin && (block.idInterval.end - block.idInterval.begin) >= o.length - 1) { var right = RopesNodes.fromPlain(o.right); var left = RopesNodes.fromPlain(o.left); return new RopesNodes(block, o.actualBegin, o.length, left, right); } } return null; }; RopesNodes.leaf = function (block, offset, lenth) { console.assert(isInt32(offset), "aOffset ∈ int32"); console.assert(isInt32(lenth), "lenth ∈ int32"); console.assert(lenth > 0, "lenth > 0"); block.addBlock(offset, lenth); // Mutation return new RopesNodes(block, offset, lenth, null, null); }; Object.defineProperty(RopesNodes.prototype, "actualEnd", { get: function () { return this.actualBegin + this.length - 1; }, enumerable: true, configurable: true }); RopesNodes.prototype.getIdBegin = function () { return this.block.idInterval.getBaseId(this.actualBegin); }; RopesNodes.prototype.getIdEnd = function () { return this.block.idInterval.getBaseId(this.actualEnd); }; Object.defineProperty(RopesNodes.prototype, "max", { get: function () { if (this.right !== null) { return this.right.max; } return this.getIdEnd(); }, enumerable: true, configurable: true }); Object.defineProperty(RopesNodes.prototype, "min", { get: function () { if (this.left !== null) { return this.left.min; } return this.getIdBegin(); }, enumerable: true, configurable: true }); RopesNodes.prototype.addString = function (length) { console.assert(isInt32(length), "length ∈ int32"); // `length" may be negative this.sizeNodeAndChildren += length; }; RopesNodes.prototype.appendEnd = function (length) { console.assert(isInt32(length), "length ∈ int32"); console.assert(length > 0, "" + length, " > 0"); var b = this.actualEnd + 1; this.length += length; this.block.addBlock(b, length); return this.block.idInterval.getBaseId(b); }; RopesNodes.prototype.appendBegin = function (length) { console.assert(isInt32(length), "length ∈ int32"); console.assert(length > 0, "" + length, " > 0"); this.actualBegin -= length; this.length += length; this.block.addBlock(this.actualBegin, length); return this.getIdBegin(); }; /** * Delete a interval of identifiers belonging to this node * Reduces the node"s {@link RopesNodes#length} and/or shifts its {@link RopesNodes#offset} * May also trigger a split of the current node if the deletion cuts it in two parts * * @param {number} begin The start of the interval to delete * @param {number} end The end of the interval to delete * @returns {RopesNodes | null} The resulting block if a split occured, null otherwise */ RopesNodes.prototype.deleteOffsets = function (begin, end) { console.assert(isInt32(begin), "begin ∈ int32"); console.assert(isInt32(end), "end ∈ int32"); console.assert(begin <= end, "begin <= end: " + begin, " <= " + end); console.assert(this.block.idInterval.begin <= begin, "this.block.idInterval.begin <= to begin: " + this.block.idInterval.begin, " <= " + begin); console.assert(end <= this.block.idInterval.end, "end <= this.block.idInterval.end: " + end, " <= " + this.block.idInterval.end); var ret = null; // Some identifiers may have already been deleted by a previous operation // Need to update the range of the deletion accordingly // NOTE: actualEnd can be < to actualBegin if all the range has previously been deleted var actualBegin = Math.max(this.actualBegin, begin); var actualEnd = Math.min(this.actualEnd, end); if (actualBegin <= actualEnd) { var sizeToDelete = actualEnd - actualBegin + 1; this.block.delBlock(sizeToDelete); if (sizeToDelete !== this.length) { if (actualBegin === this.actualBegin) { // Deleting the beginning of the block this.actualBegin = actualEnd + 1; } else if (actualEnd !== this.actualEnd) { // Deleting the middle of the block ret = this.split(actualEnd - this.actualBegin + 1, null); } } this.length = this.length - sizeToDelete; } return ret; }; RopesNodes.prototype.split = function (size, node) { var newRight = new RopesNodes(this.block, this.actualBegin + size, this.length - size, node, this.right); this.length = size; this.right = newRight; this.height = Math.max(this.height, newRight.height); return newRight; }; RopesNodes.prototype.leftSubtreeSize = function () { return subtreeSizeOf(this.left); }; RopesNodes.prototype.rightSubtreeSize = function () { return subtreeSizeOf(this.right); }; RopesNodes.prototype.sumDirectChildren = function () { this.height = Math.max(heightOf(this.left), heightOf(this.right)) + 1; this.sizeNodeAndChildren = this.leftSubtreeSize() + this.rightSubtreeSize() + this.length; }; RopesNodes.prototype.replaceChildren = function (node, by) { if (this.left === node) { this.left = by; } else if (this.right === node) { this.right = by; } }; RopesNodes.prototype.balanceScore = function () { return heightOf(this.right) - heightOf(this.left); }; RopesNodes.prototype.become = function (node) { this.sizeNodeAndChildren = -this.length + node.length; this.length = node.length; this.actualBegin = node.actualBegin; this.block = node.block; }; RopesNodes.prototype.isAppendableAfter = function (replicaNumber, length) { return this.block.isMine(replicaNumber) && this.block.idInterval.end === this.actualEnd && this.block.idInterval.idEnd.hasPlaceAfter(length); }; RopesNodes.prototype.isAppendableBefore = function (replicaNumber, length) { return this.block.isMine(replicaNumber) && this.block.idInterval.begin === this.actualBegin && this.block.idInterval.idBegin.hasPlaceBefore(length); }; RopesNodes.prototype.toString = function () { var current = this.getIdentifierInterval().toString(); var leftToString = (this.left !== null) ? this.left.toString() : "\t#"; var rightToString = (this.right !== null) ? this.right.toString() : "\t#"; return rightToString.replace(/(\t+)/g, "\t$1") + "\n" + "\t" + current + "\n" + leftToString.replace(/(\t+)/g, "\t$1"); }; /** * @return linear representation */ RopesNodes.prototype.toList = function () { var idInterval = this.getIdentifierInterval(); var leftList = (this.left !== null) ? this.left.toList() : []; var rightList = (this.right !== null) ? this.right.toList() : []; return leftList.concat(idInterval, rightList); }; RopesNodes.prototype.getIdentifierInterval = function () { return new IdentifierInterval(this.getIdBegin(), this.actualEnd); }; /** * @return list of blocks (potentially with occurrences) */ RopesNodes.prototype.getBlocks = function () { var result = [this.block]; var left = this.left; if (left !== null) { result = result.concat(left.getBlocks()); } var right = this.right; if (right !== null) { result = result.concat(right.getBlocks()); } return result; }; return RopesNodes; }()); export { RopesNodes }; //# sourceMappingURL=ropesnodes.js.map