UNPKG

diffusion

Version:

Diffusion JavaScript client

244 lines (243 loc) 9.64 kB
"use strict"; /** * @module MessageQueue */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RecoveryBuffer = void 0; var array_1 = require("./../util/array"); var function_1 = require("./../util/function"); var math_1 = require("./../util/math"); /** * How many bits of a timestamp to discard. The default rounds to approximately 1 second. * * @see {@link roundTimeStamp} */ var TIMESTAMP_ROUNDING = 10; /** * Reduce the accuracy of a timestamp * * @param timestamp the time stamp to round * @return the reduced accuracy time stamp */ function roundTimeStamp(timestamp) { /* eslint-disable-next-line no-bitwise */ return timestamp >> TIMESTAMP_ROUNDING; } /** * Apply a bitmask to a number to calculate the modulo of a power of two. This * is used to determine the position in a circular buffer * * @param p the number to mask * @param capacity the capacity, must be a power of two * @return the reduced value */ function mask(p, capacity) { /* eslint-disable-next-line no-bitwise */ return p & capacity - 1; } /** * Port of the Java RecoveryBuffer implementation, with a bounded number of entries. * * Timestamps are recorded in a timestamp index. The timestamp index also has a * fixed upper size bound. * * This implementation expects timestamp parameters in milliseconds. It rounds * timestamps to reduce the required index size and the number of index updates. * * The recovery buffer is implemented as a bounded circular buffer of * (timestamp, messages index) pairs. Each entry states that messages with * earlier indexes than its index were added at its timestamp or earlier. This * serves to timestamp ranges of {@link .messages}, allowing us to flush stale * entries. * * Each call to markTime adds an entry, possibly evicting messages referred to * by an older entry for which there is no longer room. * * * Some invariants: * * Valid entries have a pair of values at the same index in the timestamps & * indicies arrays. The index is in the range `[timesHead, timesTail)`. * * `indices[timesHead] == -1` <=> the timestamp index is empty. * * `timesHead == timesTail` <=> either the timestamp index is empty, or * `indices[timesHead] >= 0` and the timestamp index isfull. * * `size == 0` => the timestamp index is empty. * * For each valid entry at index `i`, `indices[i] >= 0` and is either equal to * tail or points to a non-null entry in messages. * * No entry points to the head of messages, i.e. the message at tail - size. * * Later entries have a messages index that is at least as great (modulo * messages.length) as earlier entries. * * @author Peter Hughes * @since 5.8 */ var RecoveryBuffer = /** @class */ (function () { /** * Create a RecoveryBuffer * * @param minimumMessages the minimum number of messages * @param minimumTimeIndex the minimum number of time indices */ function RecoveryBuffer(minimumMessages, minimumTimeIndex) { this.messagesLength = math_1.findNextPowerOfTwo(minimumMessages); this.indexCapacity = math_1.findNextPowerOfTwo(minimumTimeIndex); this.messagesMask = function_1.curryR(mask, this.messagesLength); this.indexMask = function_1.curryR(mask, this.indexCapacity); this.messages = array_1.ofSize(this.messagesLength); this.indices = array_1.ofSize(this.indexCapacity, -1); this.times = array_1.ofSize(this.indexCapacity); this.timesHead = 0; this.timesTail = 0; this.messagesTail = this.messagesMask(0); this.messagesSize = 0; } /** * Get the number of messages * * @return the number of messages that can be recovered */ RecoveryBuffer.prototype.size = function () { return this.messagesSize; }; /** * Add a message to the buffer. If the buffer is full, the oldest message is discarded * * @param message the message */ RecoveryBuffer.prototype.put = function (message) { this.messages[this.messagesTail] = message; this.messagesTail = this.messagesMask(this.messagesTail + 1); if (this.messagesSize < this.messagesLength) { this.messagesSize = this.messagesSize + 1; } else { while (this.indices[this.timesHead] === this.messagesTail) { this.indices[this.timesHead] = -1; this.timesHead = this.indexMask(this.timesHead + 1); } } }; /** * If there are at least N messages in the buffer, pass the N most recent * messages to the consumer function and return true. Otherwise return * false. * * This is a non-destructive operation. * * @param n the number of messages to recover * @param consumer the consumer function to be called with recovered messages (in order) * @returns `false` if there are not N messages, in which case consumer will not be called. */ RecoveryBuffer.prototype.recover = function (n, consumer) { if (n < 0 || n > this.messagesSize) { return false; } // this differs from the Java implementation, since we want to return messages in the order they were added for (var i = n; i >= 1; --i) { consumer(this.messages[this.messagesMask(this.messagesTail - i)]); } return true; }; /** * Clears this buffer */ RecoveryBuffer.prototype.clear = function () { array_1.fill(this.messages, null); array_1.fill(this.indices, -1); this.timesHead = 0; this.timesTail = 0; this.messagesSize = 0; }; /** * Remove elements from the buffer * * @param the new index of the messages head in the circular buffer */ RecoveryBuffer.prototype.removeElements = function (newElementsHead) { var messagesHead = this.messagesMask(this.messagesTail - this.messagesSize); var newSize; if (messagesHead < newElementsHead) { array_1.fill(this.messages, null, messagesHead, newElementsHead); newSize = this.messagesSize - newElementsHead + messagesHead; } else if (messagesHead > newElementsHead) { array_1.fill(this.messages, null, messagesHead, this.messagesLength); array_1.fill(this.messages, null, 0, newElementsHead); newSize = this.messagesSize - messagesHead + newElementsHead; } else { array_1.fill(this.messages, null); newSize = 0; } this.messagesSize = newSize; }; /** * Inform this recovery buffer of the current timestamp. Subsequent messages * put in the buffer are recorded as older than this timestamp. * * @param newElementsHead a timestamp, typically "unix time". * Larger values are later. The value does not * matter, so long as the sequence is consistently * used in calls to markTime and flush. */ RecoveryBuffer.prototype.markTime = function (timestamp) { if (this.messagesSize > 0) { var h = this.timesHead; var t = this.timesTail; var firstIndex = this.indices[h]; var roundedTimestamp = roundTimeStamp(timestamp); if (firstIndex >= 0) { var indexLast = this.indexMask(t - 1); if (this.indices[indexLast] === this.messagesTail) { return; } if (this.times[indexLast] === roundedTimestamp) { this.indices[indexLast] = this.messagesTail; return; } } this.timesTail = this.indexMask(t + 1); this.times[t] = roundedTimestamp; this.indices[t] = this.messagesTail; if (h === t && firstIndex >= 0) { this.removeElements(firstIndex); this.timesHead = this.timesTail; } } }; /** * Remove all elements added at or before the timestamp. * * @param timestamp the timestamp. Follows the same properties as used in * RecoveryBuffer#markTime */ RecoveryBuffer.prototype.flush = function (timestamp) { // due to time adjustments, there is no guarantee that a timestamp // supplied to markTime is later than a previous value. Treat // a more recently supplied timestamp as more accurate, iterate // from the tail of the index until we cross the threshold, and // flush everything else. if (this.messagesSize === 0 || this.indices[this.timesHead] < 0) { return; } var i = this.timesTail; var roundedTimestamp = roundTimeStamp(timestamp); do { var previousI = i; i = this.indexMask(i - 1); if (this.times[i] <= roundedTimestamp) { this.removeElements(this.indices[i]); if (this.timesHead <= previousI) { array_1.fill(this.indices, -1, this.timesHead, previousI); } else { array_1.fill(this.indices, -1, this.timesHead, this.indexCapacity); array_1.fill(this.indices, -1, 0, previousI); } this.timesHead = previousI; return; } } while (i !== this.timesHead); }; return RecoveryBuffer; }()); exports.RecoveryBuffer = RecoveryBuffer;