Skip to content

Commit 48b0386

Browse files
committed
wip: make types happy
1 parent 650ba00 commit 48b0386

File tree

4 files changed

+105
-103
lines changed

4 files changed

+105
-103
lines changed

meteor/server/api/deviceTriggers/RundownsObserver.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,75 @@
11
import { Meteor } from 'meteor/meteor'
22
import { RundownId, RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
3-
import _ from 'underscore'
43
import { Rundowns } from '../../collections'
54
import { literal } from '@sofie-automation/corelib/dist/lib'
65
import { MongoFieldSpecifierOnesStrict } from '@sofie-automation/corelib/dist/mongo'
76
import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
7+
import { PromiseDebounce } from '../../publications/lib/debounce'
88

99
const REACTIVITY_DEBOUNCE = 20
1010

11-
type ChangedHandler = (rundownIds: RundownId[]) => () => void
11+
type ChangedHandler = (rundownIds: RundownId[]) => Promise<() => void>
1212

1313
type RundownFields = '_id'
1414
const rundownFieldSpecifier = literal<MongoFieldSpecifierOnesStrict<Pick<DBRundown, RundownFields>>>({
1515
_id: 1,
1616
})
1717

1818
export class RundownsObserver {
19-
#rundownsLiveQuery: Meteor.LiveQueryHandle
19+
#rundownsLiveQuery!: Meteor.LiveQueryHandle
2020
#rundownIds: Set<RundownId> = new Set<RundownId>()
2121
#changed: ChangedHandler | undefined
2222
#cleanup: (() => void) | undefined
2323

24-
constructor(activePlaylistId: RundownPlaylistId, onChanged: ChangedHandler) {
24+
readonly #triggerUpdateRundownContent = new PromiseDebounce(async () => {
25+
if (!this.#changed) return
26+
this.#cleanup?.()
27+
28+
const changed = this.#changed
29+
this.#cleanup = await changed(this.rundownIds)
30+
}, REACTIVITY_DEBOUNCE)
31+
32+
private constructor(onChanged: ChangedHandler) {
2533
this.#changed = onChanged
26-
this.#rundownsLiveQuery = Rundowns.observeChanges(
34+
}
35+
36+
static async create(playlistId: RundownPlaylistId, onChanged: ChangedHandler): Promise<RundownsObserver> {
37+
const observer = new RundownsObserver(onChanged)
38+
39+
await observer.init(playlistId)
40+
41+
return observer
42+
}
43+
44+
private async init(activePlaylistId: RundownPlaylistId) {
45+
this.#rundownsLiveQuery = await Rundowns.observeChanges(
2746
{
2847
playlistId: activePlaylistId,
2948
},
3049
{
3150
added: (rundownId) => {
3251
this.#rundownIds.add(rundownId)
33-
this.updateRundownContent()
52+
this.#triggerUpdateRundownContent.trigger()
3453
},
3554
removed: (rundownId) => {
3655
this.#rundownIds.delete(rundownId)
37-
this.updateRundownContent()
56+
this.#triggerUpdateRundownContent.trigger()
3857
},
3958
},
4059
{
4160
projection: rundownFieldSpecifier,
4261
}
4362
)
44-
this.updateRundownContent()
63+
64+
this.#triggerUpdateRundownContent.trigger()
4565
}
4666

4767
public get rundownIds(): RundownId[] {
4868
return Array.from(this.#rundownIds)
4969
}
5070

51-
private innerUpdateRundownContent = () => {
52-
if (!this.#changed) return
53-
this.#cleanup?.()
54-
55-
const changed = this.#changed
56-
this.#cleanup = changed(this.rundownIds)
57-
}
58-
59-
public updateRundownContent = _.debounce(
60-
Meteor.bindEnvironment(this.innerUpdateRundownContent),
61-
REACTIVITY_DEBOUNCE
62-
)
63-
6471
public stop = (): void => {
65-
this.updateRundownContent.cancel()
72+
this.#triggerUpdateRundownContent.cancelWaiting()
6673
this.#rundownsLiveQuery.stop()
6774
this.#changed = undefined
6875
this.#cleanup?.()

meteor/server/api/deviceTriggers/StudioObserver.ts

Lines changed: 55 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ContentCache } from './reactiveContentCache'
2020
import { RundownContentObserver } from './RundownContentObserver'
2121
import { RundownsObserver } from './RundownsObserver'
2222
import { RundownPlaylists, Rundowns, ShowStyleBases } from '../../collections'
23+
import { PromiseDebounce } from '../../publications/lib/debounce'
2324

2425
type ChangedHandler = (showStyleBaseId: ShowStyleBaseId, cache: ContentCache) => () => void
2526

@@ -146,77 +147,67 @@ export class StudioObserver extends EventEmitter {
146147
) as Promise<MongoCursor<Pick<DBShowStyleBase, ShowStyleBaseFields>>>)
147148
: null
148149
)
149-
.end(this.updateShowStyle)
150+
.end(this.updateShowStyle.call)
150151
}
151152

152-
private updateShowStyle = _.debounce(
153-
Meteor.bindEnvironment(
154-
(
155-
state: {
156-
currentRundown: Pick<DBRundown, RundownFields>
157-
showStyleBase: Pick<DBShowStyleBase, ShowStyleBaseFields>
158-
} | null
159-
) => {
160-
const showStyleBaseId = state?.showStyleBase._id
161-
162-
if (
163-
showStyleBaseId === undefined ||
164-
!this.nextProps?.activePlaylistId ||
165-
!this.nextProps?.activationId
166-
) {
167-
this.currentProps = undefined
168-
this.#rundownsLiveQuery?.stop()
169-
this.#rundownsLiveQuery = undefined
170-
this.showStyleBaseId = showStyleBaseId
171-
return
153+
private readonly updateShowStyle = new PromiseDebounce<
154+
void,
155+
[
156+
{
157+
currentRundown: Pick<DBRundown, RundownFields>
158+
showStyleBase: Pick<DBShowStyleBase, ShowStyleBaseFields>
159+
} | null
160+
]
161+
>(async (state): Promise<void> => {
162+
const showStyleBaseId = state?.showStyleBase._id
163+
164+
if (showStyleBaseId === undefined || !this.nextProps?.activePlaylistId || !this.nextProps?.activationId) {
165+
this.currentProps = undefined
166+
this.#rundownsLiveQuery?.stop()
167+
this.#rundownsLiveQuery = undefined
168+
this.showStyleBaseId = showStyleBaseId
169+
return
170+
}
171+
172+
if (
173+
showStyleBaseId === this.showStyleBaseId &&
174+
this.nextProps?.activationId === this.currentProps?.activationId &&
175+
this.nextProps?.activePlaylistId === this.currentProps?.activePlaylistId &&
176+
this.nextProps?.currentRundownId === this.currentProps?.currentRundownId
177+
)
178+
return
179+
180+
this.#rundownsLiveQuery?.stop()
181+
this.#rundownsLiveQuery = undefined
182+
183+
this.currentProps = this.nextProps
184+
this.nextProps = undefined
185+
186+
const { activePlaylistId } = this.currentProps
187+
188+
this.showStyleBaseId = showStyleBaseId
189+
190+
let cleanupChanges: (() => void) | undefined = undefined
191+
192+
this.#rundownsLiveQuery = await RundownsObserver.create(activePlaylistId, async (rundownIds) => {
193+
logger.silly(`Creating new RundownContentObserver`)
194+
const obs1 = await RundownContentObserver.create(activePlaylistId, showStyleBaseId, rundownIds, (cache) => {
195+
cleanupChanges = this.#changed(showStyleBaseId, cache)
196+
197+
return () => {
198+
void 0
172199
}
200+
})
173201

174-
if (
175-
showStyleBaseId === this.showStyleBaseId &&
176-
this.nextProps?.activationId === this.currentProps?.activationId &&
177-
this.nextProps?.activePlaylistId === this.currentProps?.activePlaylistId &&
178-
this.nextProps?.currentRundownId === this.currentProps?.currentRundownId
179-
)
180-
return
181-
182-
this.#rundownsLiveQuery?.stop()
183-
this.#rundownsLiveQuery = undefined
184-
185-
this.currentProps = this.nextProps
186-
this.nextProps = undefined
187-
188-
const { activePlaylistId } = this.currentProps
189-
190-
this.showStyleBaseId = showStyleBaseId
191-
192-
let cleanupChanges: (() => void) | undefined = undefined
193-
194-
this.#rundownsLiveQuery = new RundownsObserver(activePlaylistId, async (rundownIds) => {
195-
logger.silly(`Creating new RundownContentObserver`)
196-
const obs1 = await RundownContentObserver.create(
197-
activePlaylistId,
198-
showStyleBaseId,
199-
rundownIds,
200-
(cache) => {
201-
cleanupChanges = this.#changed(showStyleBaseId, cache)
202-
203-
return () => {
204-
void 0
205-
}
206-
}
207-
)
208-
209-
return () => {
210-
obs1.stop()
211-
cleanupChanges?.()
212-
}
213-
})
202+
return () => {
203+
obs1.stop()
204+
cleanupChanges?.()
214205
}
215-
),
216-
REACTIVITY_DEBOUNCE
217-
)
206+
})
207+
}, REACTIVITY_DEBOUNCE)
218208

219209
public stop = (): void => {
210+
this.updateShowStyle.cancelWaiting()
220211
this.#playlistInStudioLiveQuery.stop()
221212
this.updatePlaylistInStudio.cancel()
222213
}

meteor/server/publications/lib/debounce.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ import { Meteor } from 'meteor/meteor'
55
* With additional features:
66
* - `cancel` method
77
*/
8-
export class PromiseDebounce<TResult> {
9-
readonly #fn: () => Promise<TResult>
8+
export class PromiseDebounce<TResult = void, TArgs extends unknown[] = []> {
9+
readonly #fn: (...args: TArgs) => Promise<TResult>
1010
readonly #wait: number
1111

1212
/** If an execution timeout has passed while */
13-
#isPending = false
13+
#pendingArgs: TArgs | null = null
1414
#timeout: number | undefined
1515

1616
#isExecuting = false
1717
#waitingListeners: Listener<TResult>[] = []
1818

19-
constructor(fn: () => Promise<TResult>, wait: number) {
19+
constructor(fn: (...args: TArgs) => Promise<TResult>, wait: number) {
2020
this.#fn = fn
2121
this.#wait = wait
2222
}
@@ -25,22 +25,25 @@ export class PromiseDebounce<TResult> {
2525
* Trigger an execution, and get the result.
2626
* @returns A promise that resolves with the result of the function
2727
*/
28-
async call(): Promise<TResult> {
28+
async call(...args: TArgs): Promise<TResult> {
2929
return new Promise<TResult>((resolve, reject) => {
3030
const listener: Listener<TResult> = { resolve, reject }
3131
this.#waitingListeners.push(listener)
3232

3333
// Trigger an execution
34-
this.trigger()
34+
this.trigger(...args)
3535
})
3636
}
3737

3838
/**
3939
* Trigger an execution, but don't report the result.
4040
*/
41-
trigger(): void {
41+
trigger(...args: TArgs): void {
4242
// If an execution is 'imminent', don't do anything
43-
if (this.#isPending) return
43+
if (this.#pendingArgs) {
44+
this.#pendingArgs = args
45+
return
46+
}
4447

4548
// Clear an existing timeout
4649
if (this.#timeout) Meteor.clearTimeout(this.#timeout)
@@ -49,26 +52,26 @@ export class PromiseDebounce<TResult> {
4952
this.#timeout = Meteor.setTimeout(() => {
5053
this.#timeout = undefined
5154

52-
this.#executeFn()
55+
this.#executeFn(args)
5356
}, this.#wait)
5457
}
5558

56-
#executeFn(): void {
59+
#executeFn(args: TArgs): void {
5760
// If an execution is still in progress, mark as pending and stop
5861
if (this.#isExecuting) {
59-
this.#isPending = true
62+
this.#pendingArgs = args
6063
return
6164
}
6265

6366
// We have the clear to begin executing
6467
this.#isExecuting = true
65-
this.#isPending = false
68+
this.#pendingArgs = null
6669

6770
// Collect up the listeners for this execution
6871
const listeners = this.#waitingListeners
6972
this.#waitingListeners = []
7073

71-
this.#fn()
74+
this.#fn(...args)
7275
.then((result) => {
7376
for (const listener of listeners) {
7477
try {
@@ -91,8 +94,9 @@ export class PromiseDebounce<TResult> {
9194
this.#isExecuting = false
9295

9396
// If there is a pending execution, run that soon
94-
if (this.#isPending) {
95-
Meteor.setTimeout(() => this.#executeFn(), 0)
97+
if (this.#pendingArgs) {
98+
const args = this.#pendingArgs
99+
Meteor.setTimeout(() => this.#executeFn(args), 0)
96100
}
97101
})
98102
}
@@ -101,7 +105,7 @@ export class PromiseDebounce<TResult> {
101105
* Cancel any waiting execution
102106
*/
103107
cancelWaiting(error?: Error): void {
104-
this.#isPending = false
108+
this.#pendingArgs = null
105109

106110
if (this.#timeout) {
107111
Meteor.clearTimeout(this.#timeout)

meteor/server/publications/lib/rundownsObserver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class RundownsObserver implements Meteor.LiveQueryHandle {
1717
#changed: ChangedHandler | undefined
1818
#cleanup: (() => void) | undefined
1919

20-
readonly #triggerUpdateRundownContent = new PromiseDebounce<void>(async () => {
20+
readonly #triggerUpdateRundownContent = new PromiseDebounce(async () => {
2121
if (!this.#changed) return
2222
this.#cleanup?.()
2323

0 commit comments

Comments
 (0)