Skip to content

Commit

Permalink
feat: atem command batching
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Dec 4, 2023
1 parent e1610a5 commit 83c66c8
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from 'timeline-state-resolver-types'
import { literal } from '../../../lib'
import { makeTimelineObjectResolved } from '../../../__mocks__/objects'
import { compareAtemCommands, createDevice, waitForConnection } from './util'
import { compareAtemCommands, createDevice, extractAllCommands, waitForConnection } from './util'
import { getDeviceContext } from '../../__tests__/testlib'

describe('Atem', () => {
Expand Down Expand Up @@ -163,9 +163,10 @@ describe('Atem', () => {
{
const commands = device.diffStates(undefined, deviceState1, myLayerMapping)

expect(commands).toHaveLength(2)
compareAtemCommands(commands[0].command, new AtemConnection.Commands.PreviewInputCommand(0, 2))
compareAtemCommands(commands[1].command, new AtemConnection.Commands.CutCommand(0))
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(2)
compareAtemCommands(allCommands[0], new AtemConnection.Commands.PreviewInputCommand(0, 2))
compareAtemCommands(allCommands[1], new AtemConnection.Commands.CutCommand(0))
}

const mockState2: Timeline.TimelineState<TSRTimelineContent> = {
Expand Down Expand Up @@ -199,9 +200,10 @@ describe('Atem', () => {
{
const commands = device.diffStates(deviceState1, deviceState2, myLayerMapping)

expect(commands).toHaveLength(2)
compareAtemCommands(commands[0].command, new AtemConnection.Commands.PreviewInputCommand(0, 3))
compareAtemCommands(commands[1].command, new AtemConnection.Commands.CutCommand(0))
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(2)
compareAtemCommands(allCommands[0], new AtemConnection.Commands.PreviewInputCommand(0, 3))
compareAtemCommands(allCommands[1], new AtemConnection.Commands.CutCommand(0))
}
})

Expand Down Expand Up @@ -239,7 +241,8 @@ describe('Atem', () => {

// Expect that a command has been scheduled
const commands = device.diffStates(undefined, deviceState, myLayerMapping)
expect(commands).toHaveLength(2)
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(2)

// Diff the same state, after the commands have been sent
const commands2 = device.diffStates(deviceState, deviceState, myLayerMapping)
Expand Down Expand Up @@ -281,9 +284,10 @@ describe('Atem', () => {
{
const commands = device.diffStates(undefined, deviceState1, myLayerMapping)

expect(commands).toHaveLength(2)
compareAtemCommands(commands[0].command, new AtemConnection.Commands.PreviewInputCommand(0, 2))
compareAtemCommands(commands[1].command, new AtemConnection.Commands.CutCommand(0))
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(2)
compareAtemCommands(allCommands[0], new AtemConnection.Commands.PreviewInputCommand(0, 2))
compareAtemCommands(allCommands[1], new AtemConnection.Commands.CutCommand(0))
}

const mockState2: Timeline.TimelineState<TSRTimelineContent> = {
Expand Down Expand Up @@ -317,15 +321,16 @@ describe('Atem', () => {
{
const commands = device.diffStates(deviceState1, deviceState2, myLayerMapping)

expect(commands).toHaveLength(5)
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(5)
const transitionPropertiesCommand = new AtemConnection.Commands.TransitionPropertiesCommand(0)
transitionPropertiesCommand.updateProps({ nextStyle: 1 })

compareAtemCommands(commands[0].command, transitionPropertiesCommand)
compareAtemCommands(commands[1].command, new AtemConnection.Commands.PreviewInputCommand(0, 3))
compareAtemCommands(commands[2].command, transitionPropertiesCommand) // TODO - why is this sent twice?
compareAtemCommands(commands[3].command, new AtemConnection.Commands.TransitionPositionCommand(0, 0))
compareAtemCommands(commands[4].command, new AtemConnection.Commands.AutoTransitionCommand(0))
compareAtemCommands(allCommands[0], transitionPropertiesCommand)
compareAtemCommands(allCommands[1], new AtemConnection.Commands.PreviewInputCommand(0, 3))
compareAtemCommands(allCommands[2], transitionPropertiesCommand) // TODO - why is this sent twice?
compareAtemCommands(allCommands[3], new AtemConnection.Commands.TransitionPositionCommand(0, 0))
compareAtemCommands(allCommands[4], new AtemConnection.Commands.AutoTransitionCommand(0))
}
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as AtemConnection from 'atem-connection'
import { compareAtemCommands, createDevice, expectIncludesAtemCommandName } from './util'
import { compareAtemCommands, createDevice, expectIncludesAtemCommandName, extractAllCommands } from './util'
import {
AtemTransitionStyle,
DeviceType,
Expand Down Expand Up @@ -40,8 +40,9 @@ describe('Diff States', () => {
diffOptions
)

expect(commands).toHaveLength(1)
compareAtemCommands(commands[0].command, new AtemConnection.Commands.ProgramInputCommand(0, 2))
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(1)
compareAtemCommands(allCommands[0], new AtemConnection.Commands.ProgramInputCommand(0, 2))
})

test('Simple diff against other state', async () => {
Expand All @@ -61,8 +62,9 @@ describe('Diff States', () => {
expect(diffStatesSpy).toHaveBeenCalledTimes(1)
expect(diffStatesSpy).toHaveBeenNthCalledWith(1, expect.anything(), state1, state2, diffOptions)

expect(commands).toHaveLength(1)
compareAtemCommands(commands[0].command, new AtemConnection.Commands.ProgramInputCommand(0, 3))
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(1)
compareAtemCommands(allCommands[0], new AtemConnection.Commands.ProgramInputCommand(0, 3))
})

test('Diff aux without mapping', async () => {
Expand Down Expand Up @@ -123,8 +125,9 @@ describe('Diff States', () => {
diffOptions
)

expect(commands).toHaveLength(1)
compareAtemCommands(commands[0].command, new AtemConnection.Commands.AuxSourceCommand(5, 10))
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(1)
compareAtemCommands(allCommands[0], new AtemConnection.Commands.AuxSourceCommand(5, 10))
})

test('Diff set input with transition', async () => {
Expand All @@ -150,8 +153,9 @@ describe('Diff States', () => {
{
const commands = device.diffStates(undefined, deviceState1, mappings)

expect(commands).toHaveLength(2)
expectIncludesAtemCommandName(commands, AtemConnection.Commands.CutCommand.name)
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(2)
expectIncludesAtemCommandName(allCommands, AtemConnection.Commands.CutCommand.name)
}

const deviceState2 = AtemConnection.AtemStateUtil.Create()
Expand All @@ -163,9 +167,10 @@ describe('Diff States', () => {
{
const commands = device.diffStates(deviceState1, deviceState2, mappings)

expect(commands).toHaveLength(4)
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(4)

expectIncludesAtemCommandName(commands, AtemConnection.Commands.AutoTransitionCommand.name)
expectIncludesAtemCommandName(allCommands, AtemConnection.Commands.AutoTransitionCommand.name)
}
})

Expand All @@ -191,7 +196,9 @@ describe('Diff States', () => {

{
const commands = device.diffStates(undefined, deviceState1, mappings)
expect(commands).toHaveLength(4)

const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(4)
}

const deviceState2 = AtemConnection.AtemStateUtil.Create()
Expand All @@ -203,9 +210,10 @@ describe('Diff States', () => {
{
const commands = device.diffStates(deviceState1, deviceState2, mappings)

expect(commands).toHaveLength(3)
const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(3)

expectIncludesAtemCommandName(commands, AtemConnection.Commands.AutoTransitionCommand.name)
expectIncludesAtemCommandName(allCommands, AtemConnection.Commands.AutoTransitionCommand.name)
}
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { promisify } from 'util'
import { AtemOptions } from 'timeline-state-resolver-types'
import { getDeviceContext } from '../../__tests__/testlib'
import { literal } from '../../../lib'
import { ISerializableCommand } from 'atem-connection/dist/commands'

const sleep = promisify(setTimeout)

Expand Down Expand Up @@ -41,21 +42,21 @@ export function compareAtemCommands(
}

export function expectIncludesAtemCommands(
received: AtemCommandWithContext[],
received: ISerializableCommand[],
expected: AtemConnection.Commands.ISerializableCommand
) {
const failedCommands: AtemConnection.Commands.ISerializableCommand[] = []
for (const candidate of received) {
if (candidate.command.constructor.name === expected.constructor.name) {
if (candidate.constructor.name === expected.constructor.name) {
if (
candidate.command
candidate
.serialize(AtemConnection.Enums.ProtocolVersion.V8_0)
.equals(expected.serialize(AtemConnection.Enums.ProtocolVersion.V8_0))
) {
// Buffer matched
return
} else {
failedCommands.push(candidate.command)
failedCommands.push(candidate)
}
}
}
Expand All @@ -64,7 +65,11 @@ export function expectIncludesAtemCommands(
expect(failedCommands).toBeFalsy()
}

export function expectIncludesAtemCommandName(received: AtemCommandWithContext[], expectedName: string) {
const commandNames = received.map((cmd) => cmd.command.constructor.name)
export function expectIncludesAtemCommandName(received: ISerializableCommand[], expectedName: string) {
const commandNames = received.map((cmd) => cmd.constructor.name)
expect(commandNames).toContain(expectedName)
}

export function extractAllCommands(commands: AtemCommandWithContext[]): ISerializableCommand[] {
return commands.flatMap((cmd) => cmd.command)
}
21 changes: 13 additions & 8 deletions packages/timeline-state-resolver/src/integrations/atem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,19 @@ export class AtemDevice extends Device<AtemOptions, AtemDeviceState, AtemCommand
oldAtemState = oldAtemState ?? this._atem.state ?? AtemStateUtil.Create()

const diffOptions = createDiffOptions(mappings)

return [
{
command: AtemState.diffStates(this._protocolVersion, oldAtemState, newAtemState, diffOptions),
context: '',
timelineObjId: '',
},
]
const commands = AtemState.diffStates(this._protocolVersion, oldAtemState, newAtemState, diffOptions)

if (commands.length > 0) {
return [
{
command: commands,
context: '',
timelineObjId: '',
},
]
} else {
return []
}
}

async sendCommand({ command, context, timelineObjId }: AtemCommandWithContext): Promise<void> {
Expand Down

0 comments on commit 83c66c8

Please sign in to comment.