UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

122 lines 4.11 kB
import { ProcessingNode } from '../ProcessingNode'; import { TimeUnit } from '../../utils'; import { TimeService } from '../../service'; import { PushError } from '../../graph/events'; /** * Merge data frames from two or more sources * using a certain merge key (e.g. source uid, parent uid, node uid). * @type {@link http://purl.org/poso/HighLevelFusion} * @category Flow shape */ export class MergeShape extends ProcessingNode { constructor(mergeFn, groupFn, options) { super(options); this._queue = new Map(); this._mergeKeyFn = mergeFn; this._groupFn = groupFn; // Merge timeout this.options.timeout = this.options.timeout || 100; this.options.timeoutUnit = this.options.timeoutUnit || TimeUnit.MILLISECOND; this._timeout = this.options.timeoutUnit.convert(this.options.timeout, TimeService.getUnit()); this.once('build', this._start.bind(this)); this.once('destroy', this._stop.bind(this)); } /** * Start the timeout timer * @returns {Promise<void>} Timer promise */ _start() { return new Promise(resolve => { this.options.minCount = this.options.minCount || this.inlets.length; this.options.maxCount = this.options.maxCount || this.inlets.length; const interval = this.options.checkInterval || TimeService.getUnit().convert(this._timeout, TimeUnit.MILLISECOND); if (this._timeout > 0) { this._timer = setInterval(this._timerTick.bind(this), interval); } resolve(); }); } _timerTick() { this._queue.forEach(queue => { this._purgeQueue(queue); }); } _purgeQueue(queue) { const currentTime = TimeService.now(); if (queue !== undefined && this._queue.has(queue.key) && currentTime - queue.timestamp >= this._timeout && queue.frames.size >= this.options.minCount) { const frames = Array.from(queue.frames.values()); try { // Merge node this.outlets.forEach(outlet => outlet.push(this.merge(frames, queue.key))); this._queue.delete(queue.key); // Resolve pending promises queue.promises.forEach(fn => { fn(undefined); }); } catch (ex) { this.emit('error', new PushError(frames[0].uid, this.uid, ex)); } return undefined; } else { return queue; } } _stop() { if (this._timer !== undefined) { clearInterval(this._timer); } } process(frame, options) { return new Promise(resolve => { if (this.options.maxCount === 1) { return resolve(frame); } // Merge key(s) const merge = this._mergeKeyFn(frame, options); if (merge === undefined) { return resolve(undefined); } (Array.isArray(merge) ? merge : [merge]).forEach(key => { let queue = this._purgeQueue(this._queue.get(key)); if (queue === undefined) { // Create a new queued data frame based on the key queue = new QueuedMerge(key); queue.promises.push(resolve); // Group the frames by the grouping function queue.frames.set(this._groupFn(frame, options), frame); this._queue.set(key, queue); } else { const groupKey = this._groupFn(frame, options); if (queue.frames.has(groupKey)) { // Merge frames queue.frames.set(groupKey, this.merge([queue.frames.get(groupKey), frame])); } else { queue.frames.set(groupKey, frame); } // Check if there are enough frames if (queue.frames.size >= this.options.maxCount) { this._queue.delete(key); const mergedFrame = this.merge(Array.from(queue.frames.values()), key); resolve(mergedFrame); queue.promises.forEach(fn => { fn(undefined); }); } else { queue.promises.push(resolve); } } }); }); } } /** * Queued merge */ class QueuedMerge { constructor(key) { this.frames = new Map(); this.promises = []; this.key = key; this.timestamp = TimeService.now(); } }