UNPKG

atem-connection

Version:

Typescript Node.js library for connecting with an ATEM switcher.

242 lines 10.1 kB
"use strict"; var _DataTransferLockingQueue_storeId, _DataTransferLockingQueue_sendLockCommand; Object.defineProperty(exports, "__esModule", { value: true }); exports.DataTransferSimpleQueue = exports.DataTransferLockingQueue = exports.DataTransferQueueBase = void 0; const tslib_1 = require("tslib"); const dataTransfer_1 = require("./dataTransfer"); const DataTransfer_1 = require("../commands/DataTransfer"); const p_queue_1 = require("p-queue"); const debug0 = require("debug"); const debug = debug0('atem-connection:data-transfer:upload-buffer'); const queueHighPriority = 99; class DataTransferQueueBase { constructor(nextTransferId) { this.taskQueue = []; this.handleCommandQueue = new p_queue_1.default({ concurrency: 1 }); // this.queueCommands = queueCommands this.nextTransferId = nextTransferId; } get currentTransferId() { return this.activeTransfer?.id ?? null; } /** Clear the pending queue, and abort any in-progress transfers */ clearQueueAndAbort(reason) { for (const transfer of this.taskQueue) { transfer.abort(reason); } this.taskQueue.splice(0, this.taskQueue.length); this.handleCommandQueue.clear(); this.transferCompleted(); if (this.activeTransfer) { this.activeTransfer.job.abort(reason); this.activeTransfer = undefined; } } /** Pop some queued commands from the active transfer */ popQueuedCommands(maxCount) { if (this.activeTransfer) { if (this.activeTransfer.queuedCommands.length === 0 && this.activeTransfer.state === dataTransfer_1.DataTransferState.Finished) { // This has now truely finished, so fire up the next thing // Transfer reports as having finished, so clear tracker and start the next this.transferCompleted(); this.activeTransfer = undefined; this.dequeueAndRun(); return []; } else { return this.activeTransfer.queuedCommands.splice(0, maxCount); } } else { return null; } } /** Queue a transfer to be performed */ async enqueue(transfer) { this.taskQueue.push(transfer); this.dequeueAndRun(); return transfer.promise; } dequeueAndRun() { if (this.activeTransfer) return; const newTransfer = this.taskQueue.shift(); if (!newTransfer) return; // Anything in the queue is rubbish now TODO - is this true? what about lock changes? this.handleCommandQueue.clear(); const transferId = this.nextTransferId(); this.activeTransfer = { id: transferId, state: dataTransfer_1.DataTransferState.Pending, job: newTransfer, queuedCommands: [], }; this.tryStartTransfer(); } /** * Try and start the 'activeTransfer' if it is sat at pending * This is done in the queue, and calls back out to this.startTransfer */ tryStartTransfer() { if (this.activeTransfer) { this.handleCommandQueue .add(async () => { const transfer = this.activeTransfer; if (!transfer || transfer.state !== dataTransfer_1.DataTransferState.Pending) { // No transfer to start return; } const result = await this.startTransfer(transfer.job, transfer.id); // TODO - this is rather duplicated.. if (this.activeTransfer?.id !== transfer.id) { throw new Error('Transfer changed mid-handle!'); } // Store the result transfer.state = result.newState; transfer.queuedCommands.push(...result.commands); // if (transfer.state === DataTransferState.) if (transfer.state === dataTransfer_1.DataTransferState.Finished && transfer.queuedCommands.length === 0) { // Transfer reports as having finished, so clear tracker and start the next this.transferCompleted(); this.activeTransfer = undefined; this.dequeueAndRun(); } else { // Looks to be progressing along } }, { priority: queueHighPriority }) .catch((e) => { // TODO - better console.error(`startTransfer failed: ${e}`); }); } } /** * Try and abort the 'activeTransfer' if there is one */ tryAbortTransfer(reason) { if (this.activeTransfer) { const transferId = this.activeTransfer.id; this.handleCommandQueue .add(async () => { const transfer = this.activeTransfer; if (!transfer || transfer.id !== transferId) { // Wrong transfer to abort return; } transfer.job.abort(reason); this.transferCompleted(); this.activeTransfer = undefined; this.dequeueAndRun(); }, { priority: queueHighPriority }) .catch((e) => { // TODO - better console.error(`abortTransfer failed: ${e}`); }); } } handleCommand(command) { this.handleCommandQueue .add(async () => { const transfer = this.activeTransfer; if (!transfer || transfer.id !== command.properties.transferId) { // The id changed while this was queued. Hopefully safe to discard it return; } if (transfer.state === dataTransfer_1.DataTransferState.Pending) { // Transfer has not sent its init, so ignore return; } const oldState = transfer.state; const result = await transfer.job.handleCommand(command, oldState); if (this.activeTransfer?.id !== transfer.id) { throw new Error('Transfer changed mid-handle!'); } // Store the result transfer.state = result.newState; transfer.queuedCommands.push(...result.commands); if (transfer.state !== dataTransfer_1.DataTransferState.Finished && transfer.queuedCommands.length === 0) { // // Transfer reports as having finished, so clear tracker and start the next // this.transferCompleted() // this.activeTransfer = undefined // this.dequeueAndRun() } else { // Looks to be progressing along // If the transfer provided a new id, track it if (result.newId !== undefined) { transfer.id = result.newId; } } }) .catch((e) => { // TODO - better console.error(`Queue error: ${e}`); }); } } exports.DataTransferQueueBase = DataTransferQueueBase; class DataTransferLockingQueue extends DataTransferQueueBase { constructor(storeId, sendLockCommand, nextTransferId) { super(nextTransferId); _DataTransferLockingQueue_storeId.set(this, void 0); _DataTransferLockingQueue_sendLockCommand.set(this, void 0); tslib_1.__classPrivateFieldSet(this, _DataTransferLockingQueue_storeId, storeId, "f"); tslib_1.__classPrivateFieldSet(this, _DataTransferLockingQueue_sendLockCommand, sendLockCommand, "f"); this.isLocked = false; } async startTransfer(transfer, transferId) { debug(`Starting transfer ${transferId} (Already locked = ${this.isLocked})`); if (this.isLocked) { // Get the transfer going immediately return transfer.startTransfer(transferId); } else { tslib_1.__classPrivateFieldGet(this, _DataTransferLockingQueue_sendLockCommand, "f").call(this, new DataTransfer_1.LockStateCommand(tslib_1.__classPrivateFieldGet(this, _DataTransferLockingQueue_storeId, "f"), true)); // We need to lock the pool first return { newState: dataTransfer_1.DataTransferState.Pending, commands: [], }; } } /** We have obtained the lock! */ lockObtained() { this.isLocked = true; // Get the transfer started this.tryStartTransfer(); } /** The status of the lock has changed */ updateLock(locked) { const wasLocked = this.isLocked; this.isLocked = locked; if (wasLocked && !locked) { // only abort when it becomes unlocked unexpectedly, otherwise we're likely waiting to // obtain lock for a transfer queued shortly after sending unlock command this.tryAbortTransfer(new Error('Lost lock mid-transfer')); } } transferCompleted() { if (this.isLocked && !this.taskQueue.length) { // unlock only when queue is empty this.isLocked = false; debug(`Completing transfer`); // Unlock the pool tslib_1.__classPrivateFieldGet(this, _DataTransferLockingQueue_sendLockCommand, "f").call(this, new DataTransfer_1.LockStateCommand(tslib_1.__classPrivateFieldGet(this, _DataTransferLockingQueue_storeId, "f"), false)); } } } exports.DataTransferLockingQueue = DataTransferLockingQueue; _DataTransferLockingQueue_storeId = new WeakMap(), _DataTransferLockingQueue_sendLockCommand = new WeakMap(); class DataTransferSimpleQueue extends DataTransferQueueBase { async startTransfer(transfer, transferId) { return transfer.startTransfer(transferId); } transferCompleted() { // Nothing to do } } exports.DataTransferSimpleQueue = DataTransferSimpleQueue; //# sourceMappingURL=dataTransferQueue.js.map