Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Action Recorder #1001 #2125

Merged
merged 19 commits into from
Sep 28, 2022
460 changes: 460 additions & 0 deletions lib/Controls/ActionRecorder.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/Controls/ActionRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default class ActionRunner extends CoreBase {
* @param {Registry} registry - the application core
*/
constructor(registry) {
super(registry, 'action-runner', 'Control/ActionRuner')
super(registry, 'action-runner', 'Control/ActionRunner')
}

/**
Expand Down
50 changes: 50 additions & 0 deletions lib/Controls/Button/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,56 @@ export default class ButtonControlBase extends ControlBase {
return false
}

/**
* Append some actions to this button
* @param {string} setId the action_set id to update
* @param {Array} newActions actions to append
* @access public
*/
actionAppend(setId, newActions) {
const action_set = this.action_sets[setId]
if (action_set) {
// Add new actions
for (const action of newActions) {
this.action_sets[setId].push(action)

this.#actionSubscribe(action)
}

return true
}

return false
}

/**
* Replace all the actions in a set
* @param {string} setId the action_set id to update
* @param {Array} newActions actions to populate
* @access public
*/
actionReplaceAll(setId, newActions) {
const action_set = this.action_sets[setId]
if (action_set) {
// Remove the old actions
for (const action of action_set) {
this.cleanupAction(action)
}
this.action_sets[setId] = []

// Add new actions
for (const action of newActions) {
this.action_sets[setId].push(action)

this.#actionSubscribe(action)
}

return true
}

return false
}

/**
* Set the delay of an action
* @param {string} setId the action_set id
Expand Down
11 changes: 11 additions & 0 deletions lib/Controls/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ControlButtonPageUp from './Button/PageUp.js'
import { CreateBankControlId, ParseControlId } from '../Resources/Util.js'
import { ControlConfigRoom } from './ControlBase.js'
import ActionRunner from './ActionRunner.js'
import ActionRecorder from './ActionRecorder.js'

/**
* The class that manages the controls
Expand Down Expand Up @@ -39,6 +40,13 @@ class ControlsController extends CoreBase {
*/
actions

/**
* Actions recorder
* @type {ActionRecorder}
* @access public
*/
actionRecorder

/**
* The currently configured controls
* @access private
Expand All @@ -52,6 +60,7 @@ class ControlsController extends CoreBase {
super(registry, 'controls', 'Controls/Controller')

this.actions = new ActionRunner(registry)
this.actionRecorder = new ActionRecorder(registry)

// Init all the control classes
const config = this.db.getKey('controls', {})
Expand Down Expand Up @@ -81,6 +90,8 @@ class ControlsController extends CoreBase {
* @access public
*/
clientConnect(client) {
this.actionRecorder.clientConnect(client)

client.onPromise('controls:subscribe', (controlId) => {
client.join(ControlConfigRoom(controlId))

Expand Down
89 changes: 21 additions & 68 deletions lib/Graphics/Preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import CoreBase from '../Core/Base.js'
import Registry from '../Registry.js'
import { ControlConfigRoom } from '../Controls/ControlBase.js'

function PreviewPageRoom(page) {
return `preview:page:${page}`
}

/**
* The class that manages bank preview generation/relay for interfaces
*
Expand Down Expand Up @@ -49,10 +53,21 @@ class GraphicsPreview extends CoreBase {
clientConnect(client) {
this.clients[client.id] = client

client.on('bank_preview_page', this.handlePreviewPage.bind(this, client))
client.onPromise('web_buttons', this.handleWebButtons.bind(this, client))
client.onPromise('preview:page:subscribe', (page) => {
client.join(PreviewPageRoom(page))

client.onPromise('web_buttons_page', this.handleWebButtonsPage.bind(this, client))
const renders = {}
for (let i = 1; i <= global.MAX_BUTTONS; ++i) {
renders[i] = this.graphics.getBank(page, i).buffer
}

return renders
})
client.onPromise('preview:page:unsubscribe', (page) => {
client.leave(PreviewPageRoom(page))
})

client.onPromise('web_buttons', this.handleWebButtons.bind(this, client))

client.on('disconnect', () => {
delete this.clients[client.id]
Expand All @@ -77,28 +92,6 @@ class GraphicsPreview extends CoreBase {
return newPage
}

/**
* Send updated previews to the client for page edit
* @param {Socket} client - the client connection
* @param {number} page - the page number
* @param {Object} cache - last update information from the UI
* @access protected
*/
handlePreviewPage(client, page, cache) {
let result = {}

client._previewPage = page

for (let i = 0; i < global.MAX_BUTTONS; ++i) {
const image = this.graphics.getBank(page, i + 1)
if (cache === undefined || cache[i + 1] === undefined || cache[i + 1] != image.updated) {
result[i + 1] = image
}
}

client.emit('preview_page_data', result)
}

/**
* Get all the pages for web buttons
* @param {Socket} client - the client connection
Expand All @@ -117,33 +110,6 @@ class GraphicsPreview extends CoreBase {
return pages
}

/**
* Get a page for web buttons
* @param {Socket} client - the client connection
* @param {number} page - the page number
* @param {Object} cache - last update information from the UI
* @param {?function} answer - the response function to the UI
* @access protected
*/
handleWebButtonsPage(client, page, cache) {
this.logger.silly('handleWebButtonsPage()', page)

let result = {}

if (cache === null) {
return
}

for (let i = 0; i < global.MAX_BUTTONS; ++i) {
const image = this.graphics.getBank(page, i + 1)
if (cache === undefined || cache[i + 1] === undefined || cache[i + 1] != image.updated) {
result[i + 1] = image
}
}

return result
}

/**
* Send a bank update to the UIs
* @param {number} page - the page number
Expand All @@ -158,22 +124,9 @@ class GraphicsPreview extends CoreBase {
this.io.emitToRoom(controlRoom, `controls:preview-${controlId}`, render.buffer)
}

for (const key in this.clients) {
let client = this.clients[key]

if (client.web_buttons) {
let result = {}
result[bank] = render

client.emit('buttons_bank_data', page, result)
} else if (client._previewPage !== undefined) {
if (client._previewPage == page) {
let result = {}
result[bank] = render

client.emit('preview_page_data', result)
}
}
const previewRoom = PreviewPageRoom(page)
if (this.io.countRoomMembers(previewRoom) > 0) {
this.io.emitToRoom(previewRoom, `preview:page-bank`, page, bank, render.buffer)
}
}

Expand Down
20 changes: 14 additions & 6 deletions lib/Instance/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class Instance extends CoreBase {
this.store.db = this.db.getKey('instance', {})

// Prepare for clients already
this.doSave()
this.commitChanges()

this.registry.api_router.get('/help/module/:module_id/*', (req, res, next) => {
const module_id = req.params.module_id.replace(/\.\.+/g, '')
Expand Down Expand Up @@ -206,7 +206,7 @@ class Instance extends CoreBase {
this.definitions.updateVariablePrefixesForLabel(id, newLabel)
}

this.doSave()
this.commitChanges()

const instance = this.instance.moduleHost.getChild(id)
if (newLabel) {
Expand Down Expand Up @@ -270,7 +270,7 @@ class Instance extends CoreBase {
this.activate_module(id, true)

this.logger.silly('instance_add', id)
this.doSave()
this.commitChanges()

return id
}
Expand Down Expand Up @@ -313,7 +313,7 @@ class Instance extends CoreBase {
this.activate_module(id)
}

this.doSave()
this.commitChanges()
} else {
if (state === true) {
this.logger.warn(id, 'warn', `Attempting to enable connection "${label}" that is already enabled`)
Expand All @@ -337,7 +337,7 @@ class Instance extends CoreBase {
this.status.forgetInstanceStatus(id)
delete this.store.db[id]

this.doSave()
this.commitChanges()

// forward cleanup elsewhere
this.definitions.forgetInstance(id)
Expand Down Expand Up @@ -393,7 +393,7 @@ class Instance extends CoreBase {
* Save the instances config to the db, and inform clients
* @access protected
*/
doSave() {
commitChanges() {
this.db.setKey('instance', this.store.db)

const newJson = cloneDeep(this.getClientJson())
Expand Down Expand Up @@ -556,6 +556,14 @@ class Instance extends CoreBase {
instance_type: config.instance_type,
label: config.label,
enabled: config.enabled,

// Runtime properties
hasRecordActionsHandler: false,
}

const instance = this.moduleHost.getChild(id)
if (instance) {
result[id].hasRecordActionsHandler = instance.hasRecordActionsHandler
}
}

Expand Down
8 changes: 8 additions & 0 deletions lib/Instance/Host.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ class ModuleHost {
.init(config)
.then(() => {
child.restartCount = 0

// Make sure clients are informed about any runtime properties
this.registry.instance.commitChanges()

// Inform action recorder
this.registry.controls.actionRecorder.instanceStatusChange(connectionId, true)
})
.catch((e) => {
this.logger.warn(`Instance "${config.label || child.connectionId}" failed to init: ${e} ${e?.stack}`)
Expand Down Expand Up @@ -350,6 +356,8 @@ class ModuleHost {
child.crashed ? 'Crashed' : 'Stopped'
)
this.logger.debug(`Connection "${config.label}" stopped`)

this.registry.controls.actionRecorder.instanceStatusChange(connectionId, false)
})
monitor.on('crash', () => {
this.instanceStatus.updateInstanceStatus(connectionId, null, 'Crashed')
Expand Down
29 changes: 29 additions & 0 deletions lib/Instance/Wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class SocketEventsHandler {
this.socket = socket
this.connectionId = connectionId
this.hasHttpHandler = false
this.hasRecordActionsHandler = false

this.unsubListeners = this.#listenToEvents({
'log-message': this.#handleLogMessage.bind(this),
Expand All @@ -41,6 +42,7 @@ class SocketEventsHandler {
'send-osc': this.#handleSendOsc.bind(this),
parseVariablesInString: this.#handleParseVariablesInString.bind(this),
upgradedItems: this.#handleUpgradedItems.bind(this),
recordAction: this.#handleRecordAction.bind(this),
})
}

Expand Down Expand Up @@ -109,6 +111,7 @@ class SocketEventsHandler {

// Save the resulting values
this.hasHttpHandler = !!msg.hasHttpHandler
this.hasRecordActionsHandler = !!msg.hasRecordActionsHandler
config.lastUpgradeIndex = msg.newUpgradeIndex
this.registry.instance.setInstanceLabelAndConfig(this.connectionId, null, msg.updatedConfig, true)
}
Expand Down Expand Up @@ -599,6 +602,22 @@ class SocketEventsHandler {
}
}

/**
* Handle action recorded by the instance
*/
async #handleRecordAction(msg) {
try {
this.registry.controls.actionRecorder.receiveAction(
this.connectionId,
msg.actionId,
msg.options,
msg.uniquenessId
)
} catch (e) {
this.logger.error(`Record action failed: ${e}`)
}
}

/**
* Handle the module informing us of some actions/feedbacks which have been run through upgrade scripts
*/
Expand Down Expand Up @@ -646,6 +665,16 @@ class SocketEventsHandler {
}
}
}

/**
* Inform the child instance class to start or stop recording actions
* @param {boolean} recording
*/
async startStopRecordingActions(recording) {
await socketEmit(this.socket, 'startStopRecordActions', {
recording: recording,
})
}
}

export default SocketEventsHandler
Loading