'use strict';
/**
* @class
* @description Optional sequencer plugin. Manages creating sequence ids and registering callback handlers to them.
*/
class Sequencer {
/**
* @constructor
* @param {Object} opts Sequencer id settings (range, start, etc.)
* @param {Number} opts.min The minimum sequence id
* @param {Number} opts.max The maximum sequence id
* @param {String|Number} [opts.start] Can be set to 'random' or a number to start the sequence id with
* @param {Adapter} adapter The adapter object associated with this sequencer
*
*/
constructor(opts, adapter) {
this._opts = opts;
this._min = opts.min;
this._max = opts.max;
this._adapter = adapter;
this._handlers = {};
this._format = this._adapter.opts.format;
// init seqId
if (opts.start === 'random') {
this._seqId = this.random();
} else if (typeof opts.start === 'number') {
if (opts.start < opts.min || opts.start > opts.max) {
throw new Error('Sequencer start is out of range');
}
this._seqId = opts.start;
} else {
this._seqId = this._min;
}
}
/**
* @description Get the next sequence id
* @param {Function} [callback] Optional callback handler. Will be called if transport receives data with the corresponding sequence id
* @param {Object} [scope] Scope to call callback with
* @returns {Number}
*/
next(callback, scope) {
let num = this._seqId;
if (typeof callback === 'function') {
this.registerInternal(num, callback, scope);
}
if (this._seqId < this._max) {
this._seqId++;
} else {
this._seqId = this._min;
}
return num;
}
/**
* @description Processed received data from transport
* @param {Buffer} data The data received
* @private
* @returns {Boolean} True if sequencer handled the data
*/
received(data) {
this._adapter.bridge.k4.logging.debug('Sequencer received', data.toString(this._format));
let num = this._adapter.getSequence(data);
let handler = this._handlers[num];
if (!handler) {
num = `external_${num}`;
handler = this._handlers[num];
}
if (handler) {
this._adapter.k4.logging.debug(`Sequencer received seqId ${num}`);
delete this._handlers[num];
// NOTE: The reference to the callback is still accessible because
// it was stored earlier, even though the handler has been deleted
// from the map. This allows the callback to re-register a handler
// to the sequencer for the same sequence id without that handler
// being immediately deleted.
handler.callback.apply(handler.scope, [data]);
return true;
}
}
/**
* @description Generates a random sequence id between min and max configs
* @private
* @returns {Number}
*/
random() {
return Math.floor(Math.random() * (this._max - this._min + 1) + this._min);
}
/**
* @description Indicates if a particular sequencer id has already been
* registered and has neither been resolved nor cancelled
* @param {String/Number} id
* @returns {Array} First element is a Boolean indicating if the id has been
* registered with handler. The second element is an Array of types of seqId
* usages (internal, external) where the specified id is being used.
*/
checkIfHandlerForIdExists(id) {
const placesWhereUsed = [];
if (this._handlers[id]) {
placesWhereUsed.push('internal');
}
if (this._handlers[`external_${id}`]) {
placesWhereUsed.push('external');
}
if (placesWhereUsed.length > 0) {
return [true, placesWhereUsed];
}
return [false, placesWhereUsed];
}
/**
* @description Registers a handler for the specified internal sequence id.
* Normally, next() would both obtain a sequence id and register a handler
* (and this method would not need to be called). However, this method
* allows next() to be called without a callback, and then a handler can be
* manually registered here corresponding to the sequence id returned by that next() call.
* @param {Number} num The sequence id
* @param {Function} callback Callback handler
* @param {Object} [scope] Scope to call callback with
*/
registerInternal(num, callback, scope) {
if (typeof callback === 'function') {
this._handlers[num] = {
callback: callback,
scope: scope
};
}
}
/**
* @description Register an external sequence id. External id numbers must
* not overlap internal id numbers.
* @param {String/Number} id The sequence id
* @param {Function} callback Callback handler
* @param {Object} [scope] Scope to call callback with
*/
registerExternal(id, callback, scope) {
if (typeof callback === 'function') {
this._handlers[`external_${id}`] = {
callback: callback,
scope: scope
};
}
}
/**
* @description Cancel a pending internal sequence id
* @param {Number} id The sequence id
*/
cancelInternal(id) {
if (this._handlers[id]) {
delete this._handlers[id];
}
}
/**
* @description Cancel a pending external sequence id
* @param {String/Number} id The sequence id
*/
cancelExternal(id) {
if (this._handlers[`external_${id}`]) {
delete this._handlers[`external_${id}`];
}
}
};
module.exports = Sequencer;