timeline-state-resolver
Version:
Have timeline, control stuff
166 lines • 7.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MultiOSCMessageDevice = void 0;
const _ = require("underscore");
const timeline_state_resolver_types_1 = require("timeline-state-resolver-types");
const device_1 = require("../../service/device");
const deviceConnection_1 = require("./deviceConnection");
/**
* This is a generic wrapper for any osc-enabled device.
*/
class MultiOSCMessageDevice extends device_1.Device {
constructor() {
super(...arguments);
this.actions = {};
this._connections = {};
this._commandQueue = [];
}
async init(initOptions, testOptions) {
this._timeBetweenCommands = initOptions.timeBetweenCommands;
for (const connOptions of initOptions.connections) {
const connectionId = connOptions.connectionId;
const connection = new deviceConnection_1.OSCConnection();
connection.on('error', (err) => this.context.logger.error('Error in MultiOSC connection ' + connectionId, err));
connection.on('debug', (...args) => this.context.logger.debug('from connection ' + connectionId, ...args));
this._connections[connectionId] = connection;
if (!connection) {
this.context.logger.error('Could not initialise device', new Error('Connection ' + connOptions.connectionId + ' not initialised'));
continue;
}
await connection.connect({
...connOptions,
oscSender: testOptions?.oscSenders?.[connOptions.connectionId] || undefined,
});
}
// note - we reset here but might still be missing some connections from tcp devices, not worth fixing right now
this.context
.resetToState(Object.fromEntries(Object.keys(this._connections).map((id) => [id, {}])))
.catch((e) => this.context.logger.warning('Failed to reset state: ' + e));
return true;
}
async terminate() {
for (const connection of Object.values(this._connections)) {
connection.dispose();
}
}
get connected() {
return false;
}
getStatus() {
const status = {
statusCode: timeline_state_resolver_types_1.StatusCode.GOOD,
messages: [],
};
for (const conn of Object.values(this._connections)) {
if (!conn.connected) {
status.statusCode = timeline_state_resolver_types_1.StatusCode.BAD;
status.messages.push(`${conn.connectionId} is disconnected`);
}
}
return status;
}
/**
* Transform the timeline state into a device state, which is in this case also
* a timeline state.
* @param state
*/
convertTimelineStateToDeviceState(state, mappings) {
const addrToOSCMessage = Object.fromEntries(Object.keys(this._connections).map((id) => [id, {}]));
const addrToPriority = Object.fromEntries(Object.keys(this._connections).map((id) => [id, {}]));
for (const layer of Object.values(state.layers)) {
const mapping = mappings[layer.layer];
if (!mapping)
continue;
const connectionState = addrToOSCMessage[mapping.options.connectionId];
if (!connectionState)
continue;
if (layer.content.deviceType === timeline_state_resolver_types_1.DeviceType.OSC) {
const content = {
...layer.content,
connectionId: mapping.options.connectionId,
fromTlObject: layer.id,
};
if ((connectionState[content.path] &&
addrToPriority[mapping.options.connectionId][content.path] <= (layer.priority || 0)) ||
!connectionState[content.path]) {
connectionState[content.path] = content;
addrToPriority[mapping.options.connectionId][content.path] = layer.priority || 0;
}
}
}
return addrToOSCMessage;
}
/**
* Compares the new timeline-state with the old one, and generates commands to account for the difference
* @param oldOscSendState The assumed current state
* @param newOscSendState The desired state of the device
*/
diffStates(oldOscSendState, newOscSendState) {
// in this oscSend class, let's just cheat:
const commands = [];
for (const connectionId of Object.keys(this._connections)) {
const oldConnectionState = oldOscSendState?.[connectionId];
const newConnectionState = newOscSendState[connectionId] ?? {};
for (const [address, newCommandContent] of Object.entries(newConnectionState)) {
if (!newCommandContent)
continue;
const oldLayer = oldConnectionState?.[address];
if (!oldLayer) {
// added!
commands.push({
context: `added: ${newCommandContent.fromTlObject}`,
timelineObjId: newCommandContent.fromTlObject,
command: {
// commandName: 'added',
...newCommandContent,
connectionId: newCommandContent.connectionId,
},
});
}
else {
// changed?
if (!_.isEqual(oldLayer, newCommandContent)) {
// changed!
commands.push({
context: `changed: ${newCommandContent.fromTlObject}`,
timelineObjId: newCommandContent.fromTlObject,
command: {
// commandName: 'changed',
...newCommandContent,
connectionId: newCommandContent.connectionId,
},
});
}
}
}
}
return commands;
}
async sendCommand(command) {
this.context.logger.debug(command);
this._commandQueue.push(command);
this._processQueue();
}
_processQueue() {
if (this._commandQueueTimer)
return;
const nextCommand = this._commandQueue.shift();
if (!nextCommand)
return;
try {
this._connections[nextCommand.command.connectionId]?.sendOsc({
address: nextCommand.command.path,
args: nextCommand.command.values,
});
}
catch (e) {
this.context.commandError(new Error('Command failed: ' + e), { ...nextCommand, command: nextCommand });
}
this._commandQueueTimer = setTimeout(() => {
this._commandQueueTimer = undefined;
this._processQueue();
}, this._timeBetweenCommands || 0);
}
}
exports.MultiOSCMessageDevice = MultiOSCMessageDevice;
//# sourceMappingURL=index.js.map