Skip to content

Commit 43b20e0

Browse files
committed
fix
1 parent 443a67a commit 43b20e0

File tree

5 files changed

+110
-6
lines changed

5 files changed

+110
-6
lines changed

packages/meteor-lib/src/triggers/triggersContext.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import { TFunction } from 'i18next'
1919
import { ProtectedString } from '@sofie-automation/corelib/dist/protectedString'
2020
import { MongoQuery } from '@sofie-automation/corelib/dist/mongo'
2121

22+
/**
23+
* A opaque type that is used in the meteor-lib api instead of implementation specific computations.
24+
* This should be treated as equivalent to the Meteor `Tracker.Computation` type.
25+
*/
2226
export type TriggerTrackerComputation = { __internal: true }
2327

2428
export interface TriggersAsyncCollection<DBInterface extends { _id: ProtectedString<any> }> {
@@ -71,8 +75,19 @@ export interface TriggersContext {
7175
_okMessage?: string
7276
): void
7377

78+
/**
79+
* Equivalent to the Meteor `Tracker.withComputation` function, but implementation specific.
80+
* Use this to ensure that a function is run as part of the provided computation.
81+
*/
7482
withComputation<T>(computation: TriggerTrackerComputation | null, func: () => Promise<T>): Promise<T>
7583

84+
/**
85+
* Create a reactive computation that will be run independently of the outer one. If the same function (using the same
86+
* name and parameters) will be used again, this computation will only be computed once on invalidation and it's
87+
* result will be memoized and reused on every other call.
88+
*
89+
* This will be run as part of the provided computation, and passes the inner computation to the function.
90+
*/
7691
memoizedIsolatedAutorun<TArgs extends any[], TRes>(
7792
computation: TriggerTrackerComputation | null,
7893
fnc: (computation: TriggerTrackerComputation | null, ...args: TArgs) => Promise<TRes>,

packages/webui/src/client/lib/memoizedIsolatedAutorun.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { isPromise } from '@sofie-automation/shared-lib/dist/lib/lib'
12
import { Meteor } from 'meteor/meteor'
23
import { Tracker } from 'meteor/tracker'
34
import _ from 'underscore'
5+
import { getRandomString } from './tempLib'
46

57
const isolatedAutorunsMem: {
68
[key: string]: {
@@ -78,3 +80,86 @@ export function memoizedIsolatedAutorun<T extends (...args: any) => any>(
7880
// @ts-expect-error it is assigned by the tracker
7981
return result
8082
}
83+
84+
interface IsolatedAsyncAutorunState {
85+
computationId: string
86+
dependancy: Tracker.Dependency
87+
value: any
88+
}
89+
90+
const isolatedAsyncAutorunsMem: {
91+
[key: string]: IsolatedAsyncAutorunState
92+
} = {}
93+
94+
export async function memoizedIsolatedAutorunAsync<TArgs extends any[], TRes>(
95+
parentComputation: Tracker.Computation | null,
96+
fnc: (computation: Tracker.Computation, ...args: TArgs) => Promise<TRes>,
97+
functionName: string,
98+
...params: TArgs
99+
): Promise<TRes> {
100+
function hashFncAndParams(fName: string, p: any): string {
101+
return fName + '_' + JSON.stringify(p)
102+
}
103+
104+
const fId = hashFncAndParams(functionName, params)
105+
// Computation is already running, depend on it
106+
if (isolatedAsyncAutorunsMem[fId]) {
107+
const result = isolatedAsyncAutorunsMem[fId].value
108+
isolatedAsyncAutorunsMem[fId].dependancy.depend(parentComputation)
109+
110+
return result
111+
}
112+
113+
// Setup the computation
114+
const computationId = getRandomString()
115+
const dep = new Tracker.Dependency()
116+
dep.depend(parentComputation)
117+
const computation = Tracker.nonreactive(() => {
118+
const computationState: IsolatedAsyncAutorunState = {
119+
computationId,
120+
dependancy: dep,
121+
value: null, // Filled in later
122+
}
123+
124+
const computation = Tracker.autorun(async (innerComputation) => {
125+
// Start executing the function
126+
const rawValue: Promise<TRes> = fnc(innerComputation, ...params)
127+
128+
// Fetch the previous value and the new value
129+
const oldValue = computationState.value
130+
const newValue = await rawValue
131+
132+
// If the old value is an unresolved promise, we can't compare it
133+
const oldRealValue = isPromise(oldValue) ? null : oldValue
134+
135+
// If the values are different, invalidate the dependancy
136+
// Do this even for the first run, as other listeners might have joined while the promise was resolving
137+
if (!_.isEqual(oldRealValue, newValue)) {
138+
dep.changed()
139+
}
140+
141+
return newValue as void // Tracker.autorun isn't generic
142+
})
143+
computation.onStop(() => {
144+
// Only delete if it is this computation that is stopping
145+
if (isolatedAsyncAutorunsMem[fId]?.computationId === computationId) {
146+
delete isolatedAsyncAutorunsMem[fId]
147+
}
148+
})
149+
150+
// Store the first value
151+
computationState.value = computation.firstRunPromise
152+
isolatedAsyncAutorunsMem[fId] = computationState
153+
154+
return computation
155+
})
156+
const gc = Meteor.setInterval(() => {
157+
if (!dep.hasDependents()) {
158+
Meteor.clearInterval(gc)
159+
computation.stop()
160+
}
161+
}, 5000)
162+
163+
// Return the promise of the first value
164+
return computation.firstRunPromise as TRes // Tracker.autorun isn't generic
165+
}

packages/webui/src/client/lib/triggers/triggersContext.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { hashSingleUseToken } from '../lib'
77
import { MeteorCall } from '../meteorApi'
88
import { IBaseFilterLink } from '@sofie-automation/blueprints-integration'
99
import { doUserAction } from '../clientUserAction'
10-
import { memoizedIsolatedAutorun as libMemoizedIsolatedAutorun } from '../memoizedIsolatedAutorun'
1110
import { Tracker } from 'meteor/tracker'
1211
import {
1312
AdLibActions,
@@ -27,6 +26,8 @@ import { ProtectedString } from '../tempLib'
2726
import { ReactiveVar as MeteorReactiveVar } from 'meteor/reactive-var'
2827
import { TriggerReactiveVar } from '@sofie-automation/meteor-lib/dist/triggers/reactive-var'
2928
import { FindOptions, MongoQuery } from '@sofie-automation/corelib/dist/mongo'
29+
import _ from 'underscore'
30+
import { memoizedIsolatedAutorunAsync } from '../memoizedIsolatedAutorun'
3031

3132
class UiTriggersCollectionWrapper<DBInterface extends { _id: ProtectedString<any> }>
3233
implements TriggersAsyncCollection<DBInterface>
@@ -88,9 +89,12 @@ export const UiTriggersContext: TriggersContext = {
8889
functionName: string,
8990
...params: TArgs
9091
): Promise<TRes> => {
91-
return Tracker.withComputation(computation as Tracker.Computation | null, async () => {
92-
return libMemoizedIsolatedAutorun(fnc, functionName, computation, ...params)
93-
})
92+
return memoizedIsolatedAutorunAsync(
93+
computation as Tracker.Computation | null,
94+
async (innerComputation, ...params2) => fnc(toTriggersComputation(innerComputation), ...params2),
95+
functionName,
96+
...params
97+
)
9498
},
9599

96100
async createContextForRundownPlaylistChain(

packages/webui/src/client/ui/RundownView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ const RundownViewContent = translateWithTracker<IPropsWithReady, IState, ITracke
13881388
if (playlist) {
13891389
studio = UIStudios.findOne({ _id: playlist.studioId })
13901390
rundowns = memoizedIsolatedAutorun(
1391-
(_playlistId) => RundownPlaylistCollectionUtil.getRundownsOrdered(playlist),
1391+
(_playlistId: RundownPlaylistId) => RundownPlaylistCollectionUtil.getRundownsOrdered(playlist),
13921392
'playlist.getRundowns',
13931393
playlistId
13941394
)

packages/webui/src/meteor/tracker.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export namespace Tracker {
7676
* Returns true if the computation is a new dependent of `dependency` rather than an existing one.
7777
* @param fromComputation An optional computation declared to depend on `dependency` instead of the current computation.
7878
*/
79-
depend(fromComputation?: Computation): boolean
79+
depend(fromComputation?: Computation | null): boolean
8080
/**
8181
* True if this Dependency has one or more dependent Computations, which would be invalidated if this Dependency were to change.
8282
*/

0 commit comments

Comments
 (0)