Skip to content

Commit

Permalink
feat: timeline datastore prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
mint-dewit committed May 3, 2022
1 parent efce5e2 commit e122e8b
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 3 deletions.
4 changes: 2 additions & 2 deletions packages/timeline-state-resolver-types/src/atem.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Mapping } from './mapping'
import { TSRTimelineObjBase, DeviceType } from '.'
import { TSRTimelineObjBase, DeviceType, DatastoreValue } from '.'

export interface MappingAtem extends Mapping {
device: DeviceType.ATEM
Expand Down Expand Up @@ -134,7 +134,7 @@ export interface TimelineObjAtemME extends TimelineObjAtemBase {
type: TimelineContentTypeAtem.ME
me: XOR<
{
input: number
input: DatastoreValue<number>
transition: AtemTransitionStyle
},
{
Expand Down
2 changes: 2 additions & 0 deletions packages/timeline-state-resolver-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export enum DeviceType {
OBS = 21,
}

export type DatastoreValue<T> = T | { _datastoreKey: string; default: T }

export interface TSRTimelineKeyframe<T> extends Timeline.TimelineKeyframe {
content: Partial<T>
}
Expand Down
119 changes: 118 additions & 1 deletion packages/timeline-state-resolver/src/conductor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ export class Conductor extends EventEmitter<ConductorEvents> {
private _timelineSize: number | undefined = undefined
private _mappings: Mappings = {}

private _datastore: { [key: string]: { value: any; modified: number } } = {}
private _deviceStates: {
[deviceId: string]: {
state: TimelineState
mappings: Mappings
time: number
dependencies: string[]
}[]
} = {}

private _options: ConductorOptions

private devices = new Map<string, DeviceContainer<DeviceOptionsBase<any>>>()
Expand Down Expand Up @@ -946,7 +956,8 @@ export class Conductor extends EventEmitter<ConductorEvents> {

// Pass along the state to the device, it will generate its commands and execute them:
try {
await device.device.handleState(removeParentFromState(subState), this._mappings)
// await device.device.handleState(removeParentFromState(subState), this._mappings)
await this._setDeviceState(device.deviceId, tlState.time, removeParentFromState(subState), this._mappings)
} catch (e) {
this.emit('error', 'Error in device "' + device.deviceId + '"' + e + ' ' + (e as Error).stack)
}
Expand Down Expand Up @@ -1062,6 +1073,112 @@ export class Conductor extends EventEmitter<ConductorEvents> {
}
return nextResolveTime
}
private _setDeviceState(deviceId: string, time: number, state: TimelineState, mappings: Mappings) {
if (!this._deviceStates[deviceId]) this._deviceStates[deviceId] = []

const dependencies: string[] = []
const setDepencencies = (content: Record<string, any>) => {
Object.values(content).forEach((val) => {
if (typeof val === 'object') {
if ('_datastoreKey' in val) {
dependencies.push(val._datastoreKey)
} else {
// todo - can we do a tighter check
setDepencencies(val)
}
}
})
}
Object.values(state.layers).forEach((layer) => {
setDepencencies(layer.content)
})

this._deviceStates[deviceId] = [
// todo - this will be a memory leak
...this._deviceStates[deviceId].filter((s) => s.time < time),
{
time,
state,
dependencies,
mappings,
},
]

const filledState: typeof state = JSON.parse(JSON.stringify(state))
const fillState = (content: Record<string, any>) => {
Object.entries(content).forEach(([index, val]) => {
if (typeof val === 'object') {
if ('_datastoreKey' in val) {
content[index] = this._datastore[val._datastoreKey] ?? val.default
} else {
// todo - can we do a tighter check
fillState(val)
}
}
})
}
Object.values(filledState.layers).forEach((layer) => {
fillState(layer.content)
})

return this.getDevice(deviceId)?.device.handleState(filledState, mappings)
}
setDatastore(newStore: Record<string, any>) {
const allKeys = new Set([...Object.keys(newStore), ...Object.keys(this._datastore)])

const changed: string[] = []
for (const key of allKeys) {
if (this._datastore[key] !== newStore[key]) {
// it changed! let's sift through our dependencies to see if we need to do anything
Object.entries(this._deviceStates).forEach(([deviceId, states]) => {
if (states.find((state) => state.dependencies.find((deps) => deps === 'key'))) {
changed.push(deviceId)
}
})
}
}

this._datastore = newStore

for (const deviceId of changed) {
let hasOneBefore = false
const toBeFilled = this._deviceStates[deviceId]
.reverse()
.filter((s) => {
if (s.time > this.getCurrentTime()) {
return true
} else if (hasOneBefore === false) {
hasOneBefore = true
return true
}
return false
})
.reverse()

for (const s of toBeFilled) {
const filledState: typeof s.state = JSON.parse(JSON.stringify(s.state))
const fillState = (content: Record<string, any>) => {
Object.entries(content).forEach(([index, val]) => {
if (typeof val === 'object') {
if ('_datastoreKey' in val) {
content[index] = this._datastore[val._datastoreKey] ?? val.default
} else {
// todo - can we do a tighter check
fillState(val)
}
}
})
}
Object.values(filledState.layers).forEach((layer) => {
fillState(layer.content)
})

this.getDevice(deviceId)
?.device.handleState(filledState, s.mappings)
.catch((e) => this.emit('error', 'resolveTimeline' + e + '\nStack: ' + (e as Error).stack))
}
}
}
getTimelineSize(): number {
if (this._timelineSize === undefined) {
// Update the cache:
Expand Down

0 comments on commit e122e8b

Please sign in to comment.