Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions companion/lib/Controls/ActionRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { ControlEntityInstance } from './Entities/EntityInstance.js'
import LogController from '../Log/Controller.js'
import type { InternalController } from '../Internal/Controller.js'
import type { InstanceController } from '../Instance/Controller.js'
import type { VariableValue } from '@companion-app/shared/Model/Variables.js'

/**
* Class to handle execution of actions.
Expand Down Expand Up @@ -33,24 +34,26 @@ export class ActionRunner {
}

/**
* Run a single action
* Run a single action and return its result
*/
async #runAction(action: ControlEntityInstance, extras: RunActionExtras): Promise<void> {
async #runAction(action: ControlEntityInstance, extras: RunActionExtras): Promise<VariableValue> {
this.#logger.silly('Running action', action)

if (action.connectionId === 'internal') {
await this.#internalModule.executeAction(action, extras)
} else {
const instance = this.#instanceController.processManager.getConnectionChild(action.connectionId)
if (instance) {
const entityModel = action.asEntityModel(false)
if (entityModel.type !== EntityModelType.Action)
throw new Error(`Cannot execute entity of type "${entityModel.type}" as an action`)
await instance.actionRun(entityModel, extras)
} else {
this.#logger.silly('trying to run action on a missing instance.', action)
}
return undefined
}

const instance = this.#instanceController.processManager.getConnectionChild(action.connectionId)
if (instance) {
const entityModel = action.asEntityModel(false)
if (entityModel.type !== EntityModelType.Action)
throw new Error(`Cannot execute entity of type "${entityModel.type}" as an action`)
return instance.actionRun(entityModel, extras)
}

this.#logger.silly('trying to run action on a missing instance.', action)
return undefined
}

/**
Expand All @@ -60,25 +63,28 @@ export class ActionRunner {
actions0: ControlEntityInstance[],
extras: RunActionExtras,
executeSequential = false
): Promise<void> {
): Promise<VariableValue> {
const actions = actions0.filter((act) => act.type === EntityModelType.Action && !act.disabled)
if (actions.length === 0) return
if (actions.length === 0) return undefined

if (extras.abortDelayed.aborted) return
if (extras.abortDelayed.aborted) return undefined

if (executeSequential) {
// Future: abort on error?

for (const action of actions) {
if (extras.abortDelayed.aborted) break
await this.#runAction(action, extras).catch((e) => {
extras.previousResult = await this.#runAction(action, extras).catch((e) => {
this.#logger.silly(`Error executing action for ${action.connectionId}: ${e.message ?? e}`)
return undefined
})
}

return extras.previousResult
} else {
const groupedActions = this.#splitActionsAroundWaits(actions)

const ps: Promise<void>[] = []
const ps: Promise<VariableValue>[] = []

for (const { waitAction, actions } of groupedActions) {
if (extras.abortDelayed.aborted) break
Expand All @@ -97,13 +103,15 @@ export class ActionRunner {
ps.push(
this.#runAction(action, extras).catch((e) => {
this.#logger.silly(`Error executing action for ${action.connectionId}: ${e.message ?? e}`)
return undefined
})
)
}
}

// Await all the actions, so that the abort signal is respected and the promise is pending until all actions are done
await Promise.all(ps)
return undefined
}
}

Expand Down Expand Up @@ -155,7 +163,7 @@ export class ControlActionRunner {
async runActions(
actions: ControlEntityInstance[],
extras: Omit<RunActionExtras, 'controlId' | 'abortDelayed' | 'executionMode'>
): Promise<void> {
): Promise<VariableValue> {
const controller = new AbortController()

const chainId = nanoid()
Expand Down
9 changes: 5 additions & 4 deletions companion/lib/Controls/ControlStore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TriggerEvents } from './TriggerEvents.js'
import type { IControlStore } from './IControlStore.js'
import type { SomeControl } from './IControlFragments.js'
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariablesAndExpressionParser } from '../Variables/VariablesAndExpressionParser.js'
import type { NewFeedbackValue } from './Entities/Types.js'
import type { VariablesValues } from '../Variables/Values.js'
Expand Down Expand Up @@ -166,15 +166,16 @@ export class ControlStore implements IControlStore {

createVariablesAndExpressionParser(
controlId: string | null | undefined,
overrideVariableValues: VariableValues | null
overrideVariableValues: VariableValues | null,
previousResult: VariableValue
): VariablesAndExpressionParser {
const control = controlId && this.getControl(controlId)

// If the control exists and supports entities, use its parser for local variables
if (control && control.supportsEntities)
return control.entities.createVariablesAndExpressionParser(overrideVariableValues)
return control.entities.createVariablesAndExpressionParser(overrideVariableValues, previousResult)

// Otherwise create a generic one
return this.#variablesValues.createVariablesAndExpressionParser(null, null, overrideVariableValues)
return this.#variablesValues.createVariablesAndExpressionParser(null, null, overrideVariableValues, previousResult)
}
}
5 changes: 4 additions & 1 deletion companion/lib/Controls/ControlTypes/Button/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ export abstract class ButtonControlBase<TJson, TOptions extends ButtonOptionsBas
.createVariablesAndExpressionParser(
deps.pageStore.getLocationOfControlId(this.controlId),
this.entities.getLocalVariableEntities(),
injectedVariableValues ?? null
injectedVariableValues ?? null,
undefined
)
.executeExpression(expression, requiredType)
)
Expand Down Expand Up @@ -314,6 +315,7 @@ export abstract class ButtonControlBase<TJson, TOptions extends ButtonOptionsBas
.runActions(actions, {
surfaceId,
location,
previousResult: undefined,
})
.catch((e) => {
this.logger.error(`action execution failed: ${e}`)
Expand Down Expand Up @@ -360,6 +362,7 @@ export abstract class ButtonControlBase<TJson, TOptions extends ButtonOptionsBas
.runActions(actions, {
surfaceId,
location,
previousResult: undefined,
})
.catch((e) => {
this.logger.error(`action execution failed: ${e}`)
Expand Down
3 changes: 2 additions & 1 deletion companion/lib/Controls/ControlTypes/Button/Preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ export class ControlButtonPreset
.createVariablesAndExpressionParser(
deps.pageStore.getLocationOfControlId(this.controlId),
null, // This doesn't support local variables
injectedVariableValues ?? null
injectedVariableValues ?? null,
undefined
)
.executeExpression(expression, requiredType)
)
Expand Down
3 changes: 2 additions & 1 deletion companion/lib/Controls/ControlTypes/Button/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export function parseVariablesInButtonStyle(
const parser = deps.variableValues.createVariablesAndExpressionParser(
location,
entities.getLocalVariableEntities(),
overrideVariableValues
overrideVariableValues,
undefined
)

if (style.textExpression) {
Expand Down
1 change: 1 addition & 0 deletions companion/lib/Controls/ControlTypes/Triggers/Trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export class ControlTrigger
.runActions(actions, {
surfaceId: this.controlId,
location: undefined,
previousResult: undefined,
})
.catch((e) => {
this.logger.error(`Failed to run actions: ${e.message}`)
Expand Down
7 changes: 4 additions & 3 deletions companion/lib/Controls/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { createActionSetsTrpcRouter } from './ActionSetsTrpcRouter.js'
import { createControlsTrpcRouter } from './ControlsTrpcRouter.js'
import z from 'zod'
import type { SomeControlModel, UIControlUpdate } from '@companion-app/shared/Model/Controls.js'
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariablesAndExpressionParser } from '../Variables/VariablesAndExpressionParser.js'
import { ControlExpressionVariable } from './ControlTypes/ExpressionVariable.js'
import type {
Expand Down Expand Up @@ -662,8 +662,9 @@ export class ControlsController {

createVariablesAndExpressionParser(
controlId: string | null | undefined,
overrideVariableValues: VariableValues | null
overrideVariableValues: VariableValues | null,
previousResult: VariableValue
): VariablesAndExpressionParser {
return this.#store.createVariablesAndExpressionParser(controlId, overrideVariableValues)
return this.#store.createVariablesAndExpressionParser(controlId, overrideVariableValues, previousResult)
}
}
7 changes: 4 additions & 3 deletions companion/lib/Controls/Entities/EntityIsInvertedManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ControlEntityInstance } from '../../Controls/Entities/EntityInstan
import LogController, { type Logger } from '../../Log/Controller.js'
import type { NewIsInvertedValue } from './Types.js'
import { isExpressionOrValue } from '@companion-app/shared/Model/Options.js'
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariablesAndExpressionParser } from '../../Variables/VariablesAndExpressionParser.js'

interface EntityWrapper {
Expand All @@ -16,7 +16,8 @@ interface EntityWrapper {

export type UpdateIsInvertedValuesFn = (newValues: ReadonlyMap<string, NewIsInvertedValue>) => void
export type CreateVariablesAndExpressionParser = (
overrideVariableValues: VariableValues | null
overrideVariableValues: VariableValues | null,
previousResult: VariableValue
) => VariablesAndExpressionParser

/**
Expand Down Expand Up @@ -52,7 +53,7 @@ export class EntityPoolIsInvertedManager {

const updatedValues = new Map<string, NewIsInvertedValue>()

const parser = this.#createVariablesAndExpressionParser(null)
const parser = this.#createVariablesAndExpressionParser(null, undefined)

for (const [entityId, wrapper] of this.#entities) {
// Resolve the entity, and make sure it still exists
Expand Down
10 changes: 7 additions & 3 deletions companion/lib/Controls/Entities/EntityListPoolBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { InternalController } from '../../Internal/Controller.js'
import isEqual from 'fast-deep-equal'
import type { InstanceDefinitionsForEntity, NewFeedbackValue, NewIsInvertedValue } from './Types.js'
import type { ButtonStyleProperties } from '@companion-app/shared/Model/StyleModel.js'
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
import debounceFn from 'debounce-fn'
import type { VariablesValues } from '../../Variables/Values.js'
import { isLabelValid } from '@companion-app/shared/Label.js'
Expand Down Expand Up @@ -159,14 +159,18 @@ export abstract class ControlEntityListPoolBase {
if (changed) this.invalidateControl()
}

createVariablesAndExpressionParser(overrideVariableValues: VariableValues | null): VariablesAndExpressionParser {
createVariablesAndExpressionParser(
overrideVariableValues: VariableValues | null,
previousResult: VariableValue
): VariablesAndExpressionParser {
const controlLocation = this.#pageStore.getLocationOfControlId(this.controlId)
const variableEntities = this.getLocalVariableEntities()

return this.#variableValues.createVariablesAndExpressionParser(
controlLocation,
variableEntities,
overrideVariableValues
overrideVariableValues,
previousResult
)
}

Expand Down
5 changes: 3 additions & 2 deletions companion/lib/Controls/IControlStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
import type { SomeControl } from './IControlFragments.js'
import type { VariablesAndExpressionParser } from '../Variables/VariablesAndExpressionParser.js'
import type { NewFeedbackValue } from './Entities/Types.js'
Expand Down Expand Up @@ -47,7 +47,8 @@ export interface IControlStore {

createVariablesAndExpressionParser(
controlId: string | null | undefined,
overrideVariableValues: VariableValues | null
overrideVariableValues: VariableValues | null,
previousResult: VariableValue
): VariablesAndExpressionParser

/**
Expand Down
2 changes: 1 addition & 1 deletion companion/lib/ImportExport/Backups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export class BackupController {
await fs.mkdir(backupDir, { recursive: true })

// Generate backup filename
const parser = this.#variableValuesController.createVariablesAndExpressionParser(null, null, null)
const parser = this.#variableValuesController.createVariablesAndExpressionParser(null, null, null, undefined)
const backupName = parser.parseVariables(rule.backupNamePattern).text
if (!backupName) {
logger.info('No backup name generated, skipping backup')
Expand Down
2 changes: 1 addition & 1 deletion companion/lib/ImportExport/Export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ export class ExportController {
#generateFilename(filename: string, exportType: string, fileExt: string): string {
//If the user isn't using their default file name, don't append any extra info in file name since it was a manual choice
const useDefault = filename == this.#userConfigController.getKey('default_export_filename')
const parser = this.#variablesController.values.createVariablesAndExpressionParser(null, null, null)
const parser = this.#variablesController.values.createVariablesAndExpressionParser(null, null, null, undefined)
const parsedName = parser.parseVariables(filename).text

return parsedName && parsedName !== 'undefined'
Expand Down
4 changes: 3 additions & 1 deletion companion/lib/Instance/Connection/ChildHandlerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { ActionEntityModel, SomeEntityModel } from '@companion-app/shared/M
import type { ControlEntityInstance } from '../../Controls/Entities/EntityInstance.js'
import type { ExpressionableOptionsObject, SomeCompanionInputField } from '@companion-app/shared/Model/Options.js'
import type { ChildProcessHandlerBase } from '../ProcessManager.js'
import type { VariableValue } from '@companion-app/shared/Model/Variables.js'

export interface ConnectionChildHandlerDependencies {
readonly controls: IControlStore
Expand Down Expand Up @@ -87,7 +88,7 @@ export interface ConnectionChildHandlerApi extends ChildProcessHandlerBase {
/**
* Tell the child instance class to execute an action
*/
actionRun(action: ActionEntityModel, extras: RunActionExtras): Promise<void>
actionRun(action: ActionEntityModel, extras: RunActionExtras): Promise<VariableValue>

/**
*
Expand All @@ -106,4 +107,5 @@ export interface RunActionExtras {
location: ControlLocation | undefined
abortDelayed: AbortSignal
executionMode: 'sequential' | 'concurrent'
previousResult: VariableValue
}
13 changes: 10 additions & 3 deletions companion/lib/Instance/Connection/ChildHandlerLegacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ export class ConnectionChildHandlerLegacy implements ChildProcessHandlerBase, Co
/**
* Tell the child instance class to execute an action
*/
async actionRun(action: ActionEntityModel, extras: RunActionExtras): Promise<void> {
async actionRun(action: ActionEntityModel, extras: RunActionExtras): Promise<undefined> {
if (action.connectionId !== this.connectionId) throw new Error(`Action is for a different connection`)

try {
Expand All @@ -584,7 +584,11 @@ export class ConnectionChildHandlerLegacy implements ChildProcessHandlerBase, Co
if (!actionDefinition) throw new Error(`Failed to find action definition for ${action.definitionId}`)

// Note: for actions, this doesn't need to be reactive
const parser = this.#deps.controls.createVariablesAndExpressionParser(extras.controlId, null)
const parser = this.#deps.controls.createVariablesAndExpressionParser(
extras.controlId,
null,
extras.previousResult
)
const parseRes = parser.parseEntityOptions(actionDefinition, action.options)
if (!parseRes.ok) {
this.logger.warn(
Expand Down Expand Up @@ -621,6 +625,9 @@ export class ConnectionChildHandlerLegacy implements ChildProcessHandlerBase, Co

throw e
}

// Legacy instance actions can't return values.
return undefined
}

/**
Expand Down Expand Up @@ -931,7 +938,7 @@ export class ConnectionChildHandlerLegacy implements ChildProcessHandlerBase, Co
msg: ParseVariablesInStringMessage
): Promise<ParseVariablesInStringResponseMessage> {
try {
const parser = this.#deps.controls.createVariablesAndExpressionParser(msg.controlId, null)
const parser = this.#deps.controls.createVariablesAndExpressionParser(msg.controlId, null, undefined)
const result = parser.parseVariables(msg.text)

return {
Expand Down
Loading
Loading