mute-structs
Version:
NodeJS module providing an implementation of the LogootSplit CRDT algorithm
263 lines (259 loc) • 10.8 kB
JavaScript
/*
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