Skip to content

Commit e646622

Browse files
committed
feat: Allow action callbacks to set a result to be propagated to sequentially succeeding actions as $(this:result).
1 parent b046c44 commit e646622

File tree

27 files changed

+265
-186
lines changed

27 files changed

+265
-186
lines changed

companion/lib/Controls/ActionRunner.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { ControlEntityInstance } from './Entities/EntityInstance.js'
55
import LogController from '../Log/Controller.js'
66
import type { InternalController } from '../Internal/Controller.js'
77
import type { InstanceController } from '../Instance/Controller.js'
8+
import type { VariableValue } from '@companion-app/shared/Model/Variables.js'
89

910
/**
1011
* Class to handle execution of actions.
@@ -33,24 +34,26 @@ export class ActionRunner {
3334
}
3435

3536
/**
36-
* Run a single action
37+
* Run a single action and return its result
3738
*/
38-
async #runAction(action: ControlEntityInstance, extras: RunActionExtras): Promise<void> {
39+
async #runAction(action: ControlEntityInstance, extras: RunActionExtras): Promise<VariableValue> {
3940
this.#logger.silly('Running action', action)
4041

4142
if (action.connectionId === 'internal') {
4243
await this.#internalModule.executeAction(action, extras)
43-
} else {
44-
const instance = this.#instanceController.processManager.getConnectionChild(action.connectionId)
45-
if (instance) {
46-
const entityModel = action.asEntityModel(false)
47-
if (entityModel.type !== EntityModelType.Action)
48-
throw new Error(`Cannot execute entity of type "${entityModel.type}" as an action`)
49-
await instance.actionRun(entityModel, extras)
50-
} else {
51-
this.#logger.silly('trying to run action on a missing instance.', action)
52-
}
44+
return undefined
5345
}
46+
47+
const instance = this.#instanceController.processManager.getConnectionChild(action.connectionId)
48+
if (instance) {
49+
const entityModel = action.asEntityModel(false)
50+
if (entityModel.type !== EntityModelType.Action)
51+
throw new Error(`Cannot execute entity of type "${entityModel.type}" as an action`)
52+
return instance.actionRun(entityModel, extras)
53+
}
54+
55+
this.#logger.silly('trying to run action on a missing instance.', action)
56+
return undefined
5457
}
5558

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

67-
if (extras.abortDelayed.aborted) return
70+
if (extras.abortDelayed.aborted) return undefined
6871

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

7275
for (const action of actions) {
7376
if (extras.abortDelayed.aborted) break
74-
await this.#runAction(action, extras).catch((e) => {
77+
extras.previousResult = await this.#runAction(action, extras).catch((e) => {
7578
this.#logger.silly(`Error executing action for ${action.connectionId}: ${e.message ?? e}`)
79+
return undefined
7680
})
7781
}
82+
83+
return extras.previousResult
7884
} else {
7985
const groupedActions = this.#splitActionsAroundWaits(actions)
8086

81-
const ps: Promise<void>[] = []
87+
const ps: Promise<VariableValue>[] = []
8288

8389
for (const { waitAction, actions } of groupedActions) {
8490
if (extras.abortDelayed.aborted) break
@@ -97,13 +103,15 @@ export class ActionRunner {
97103
ps.push(
98104
this.#runAction(action, extras).catch((e) => {
99105
this.#logger.silly(`Error executing action for ${action.connectionId}: ${e.message ?? e}`)
106+
return undefined
100107
})
101108
)
102109
}
103110
}
104111

105112
// Await all the actions, so that the abort signal is respected and the promise is pending until all actions are done
106113
await Promise.all(ps)
114+
return undefined
107115
}
108116
}
109117

@@ -155,7 +163,7 @@ export class ControlActionRunner {
155163
async runActions(
156164
actions: ControlEntityInstance[],
157165
extras: Omit<RunActionExtras, 'controlId' | 'abortDelayed' | 'executionMode'>
158-
): Promise<void> {
166+
): Promise<VariableValue> {
159167
const controller = new AbortController()
160168

161169
const chainId = nanoid()

companion/lib/Controls/ControlStore.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { TriggerEvents } from './TriggerEvents.js'
22
import type { IControlStore } from './IControlStore.js'
33
import type { SomeControl } from './IControlFragments.js'
4-
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
4+
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
55
import type { VariablesAndExpressionParser } from '../Variables/VariablesAndExpressionParser.js'
66
import type { NewFeedbackValue } from './Entities/Types.js'
77
import type { VariablesValues } from '../Variables/Values.js'
@@ -166,15 +166,16 @@ export class ControlStore implements IControlStore {
166166

167167
createVariablesAndExpressionParser(
168168
controlId: string | null | undefined,
169-
overrideVariableValues: VariableValues | null
169+
overrideVariableValues: VariableValues | null,
170+
previousResult: VariableValue
170171
): VariablesAndExpressionParser {
171172
const control = controlId && this.getControl(controlId)
172173

173174
// If the control exists and supports entities, use its parser for local variables
174175
if (control && control.supportsEntities)
175-
return control.entities.createVariablesAndExpressionParser(overrideVariableValues)
176+
return control.entities.createVariablesAndExpressionParser(overrideVariableValues, previousResult)
176177

177178
// Otherwise create a generic one
178-
return this.#variablesValues.createVariablesAndExpressionParser(null, null, overrideVariableValues)
179+
return this.#variablesValues.createVariablesAndExpressionParser(null, null, overrideVariableValues, previousResult)
179180
}
180181
}

companion/lib/Controls/ControlTypes/Button/Base.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ export abstract class ButtonControlBase<TJson, TOptions extends ButtonOptionsBas
8787
.createVariablesAndExpressionParser(
8888
deps.pageStore.getLocationOfControlId(this.controlId),
8989
this.entities.getLocalVariableEntities(),
90-
injectedVariableValues ?? null
90+
injectedVariableValues ?? null,
91+
undefined
9192
)
9293
.executeExpression(expression, requiredType)
9394
)
@@ -314,6 +315,7 @@ export abstract class ButtonControlBase<TJson, TOptions extends ButtonOptionsBas
314315
.runActions(actions, {
315316
surfaceId,
316317
location,
318+
previousResult: undefined,
317319
})
318320
.catch((e) => {
319321
this.logger.error(`action execution failed: ${e}`)
@@ -360,6 +362,7 @@ export abstract class ButtonControlBase<TJson, TOptions extends ButtonOptionsBas
360362
.runActions(actions, {
361363
surfaceId,
362364
location,
365+
previousResult: undefined,
363366
})
364367
.catch((e) => {
365368
this.logger.error(`action execution failed: ${e}`)

companion/lib/Controls/ControlTypes/Button/Preset.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ export class ControlButtonPreset
125125
.createVariablesAndExpressionParser(
126126
deps.pageStore.getLocationOfControlId(this.controlId),
127127
null, // This doesn't support local variables
128-
injectedVariableValues ?? null
128+
injectedVariableValues ?? null,
129+
undefined
129130
)
130131
.executeExpression(expression, requiredType)
131132
)

companion/lib/Controls/ControlTypes/Button/Util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export function parseVariablesInButtonStyle(
2525
const parser = deps.variableValues.createVariablesAndExpressionParser(
2626
location,
2727
entities.getLocalVariableEntities(),
28-
overrideVariableValues
28+
overrideVariableValues,
29+
undefined
2930
)
3031

3132
if (style.textExpression) {

companion/lib/Controls/ControlTypes/Triggers/Trigger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ export class ControlTrigger
249249
.runActions(actions, {
250250
surfaceId: this.controlId,
251251
location: undefined,
252+
previousResult: undefined,
252253
})
253254
.catch((e) => {
254255
this.logger.error(`Failed to run actions: ${e.message}`)

companion/lib/Controls/Controller.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { createActionSetsTrpcRouter } from './ActionSetsTrpcRouter.js'
3030
import { createControlsTrpcRouter } from './ControlsTrpcRouter.js'
3131
import z from 'zod'
3232
import type { SomeControlModel, UIControlUpdate } from '@companion-app/shared/Model/Controls.js'
33-
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
33+
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
3434
import type { VariablesAndExpressionParser } from '../Variables/VariablesAndExpressionParser.js'
3535
import { ControlExpressionVariable } from './ControlTypes/ExpressionVariable.js'
3636
import type {
@@ -662,8 +662,9 @@ export class ControlsController {
662662

663663
createVariablesAndExpressionParser(
664664
controlId: string | null | undefined,
665-
overrideVariableValues: VariableValues | null
665+
overrideVariableValues: VariableValues | null,
666+
previousResult: VariableValue
666667
): VariablesAndExpressionParser {
667-
return this.#store.createVariablesAndExpressionParser(controlId, overrideVariableValues)
668+
return this.#store.createVariablesAndExpressionParser(controlId, overrideVariableValues, previousResult)
668669
}
669670
}

companion/lib/Controls/Entities/EntityIsInvertedManager.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ControlEntityInstance } from '../../Controls/Entities/EntityInstan
33
import LogController, { type Logger } from '../../Log/Controller.js'
44
import type { NewIsInvertedValue } from './Types.js'
55
import { isExpressionOrValue } from '@companion-app/shared/Model/Options.js'
6-
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
6+
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
77
import type { VariablesAndExpressionParser } from '../../Variables/VariablesAndExpressionParser.js'
88

99
interface EntityWrapper {
@@ -16,7 +16,8 @@ interface EntityWrapper {
1616

1717
export type UpdateIsInvertedValuesFn = (newValues: ReadonlyMap<string, NewIsInvertedValue>) => void
1818
export type CreateVariablesAndExpressionParser = (
19-
overrideVariableValues: VariableValues | null
19+
overrideVariableValues: VariableValues | null,
20+
previousResult: VariableValue
2021
) => VariablesAndExpressionParser
2122

2223
/**
@@ -52,7 +53,7 @@ export class EntityPoolIsInvertedManager {
5253

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

55-
const parser = this.#createVariablesAndExpressionParser(null)
56+
const parser = this.#createVariablesAndExpressionParser(null, undefined)
5657

5758
for (const [entityId, wrapper] of this.#entities) {
5859
// Resolve the entity, and make sure it still exists

companion/lib/Controls/Entities/EntityListPoolBase.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { InternalController } from '../../Internal/Controller.js'
1313
import isEqual from 'fast-deep-equal'
1414
import type { InstanceDefinitionsForEntity, NewFeedbackValue, NewIsInvertedValue } from './Types.js'
1515
import type { ButtonStyleProperties } from '@companion-app/shared/Model/StyleModel.js'
16-
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
16+
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
1717
import debounceFn from 'debounce-fn'
1818
import type { VariablesValues } from '../../Variables/Values.js'
1919
import { isLabelValid } from '@companion-app/shared/Label.js'
@@ -159,14 +159,18 @@ export abstract class ControlEntityListPoolBase {
159159
if (changed) this.invalidateControl()
160160
}
161161

162-
createVariablesAndExpressionParser(overrideVariableValues: VariableValues | null): VariablesAndExpressionParser {
162+
createVariablesAndExpressionParser(
163+
overrideVariableValues: VariableValues | null,
164+
previousResult: VariableValue
165+
): VariablesAndExpressionParser {
163166
const controlLocation = this.#pageStore.getLocationOfControlId(this.controlId)
164167
const variableEntities = this.getLocalVariableEntities()
165168

166169
return this.#variableValues.createVariablesAndExpressionParser(
167170
controlLocation,
168171
variableEntities,
169-
overrideVariableValues
172+
overrideVariableValues,
173+
previousResult
170174
)
171175
}
172176

companion/lib/Controls/IControlStore.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { VariableValues } from '@companion-app/shared/Model/Variables.js'
1+
import type { VariableValue, VariableValues } from '@companion-app/shared/Model/Variables.js'
22
import type { SomeControl } from './IControlFragments.js'
33
import type { VariablesAndExpressionParser } from '../Variables/VariablesAndExpressionParser.js'
44
import type { NewFeedbackValue } from './Entities/Types.js'
@@ -47,7 +47,8 @@ export interface IControlStore {
4747

4848
createVariablesAndExpressionParser(
4949
controlId: string | null | undefined,
50-
overrideVariableValues: VariableValues | null
50+
overrideVariableValues: VariableValues | null,
51+
previousResult: VariableValue
5152
): VariablesAndExpressionParser
5253

5354
/**

0 commit comments

Comments
 (0)