From 1b3a56427f76a6e44539c25735afdd3b9552d2ed Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 21 Oct 2025 00:27:18 -0400 Subject: [PATCH] Document generateKeyed$ more thoroughly --- src/state/CallViewModel.ts | 2 ++ src/utils/observable.ts | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index e1f92ffa..0868b474 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -762,6 +762,8 @@ export class CallViewModel { MediaItem, MediaItem[] >( + // Generate a collection of MediaItems from the list of expected (whether + // present or missing) LiveKit participants. combineLatest([this.participantsByRoom$, duplicateTiles.value$]), ([participantsByRoom, duplicateTiles], createOrGet) => { const items: MediaItem[] = []; diff --git a/src/utils/observable.ts b/src/utils/observable.ts index 8b141ef4..eb817991 100644 --- a/src/utils/observable.ts +++ b/src/utils/observable.ts @@ -125,6 +125,14 @@ export function pauseWhen(pause$: Behavior) { * automatically created when their key is requested for the first time, reused * when the same key is requested at a later time, and destroyed (have their * scope ended) when the key is no longer requested. + * + * @param input$ The input value to be mapped. + * @param project A function mapping input values to output values. This + * function receives an additional callback `createOrGet` which can be used + * within the function body to request that an item be generated for a certain + * key. The caller provides a factory which will be used to create the item if + * it is being requested for the first time. Otherwise, the item previously + * existing under that key will be returned. */ export function generateKeyed$( input$: Observable, @@ -137,6 +145,7 @@ export function generateKeyed$( ) => Out, ): Observable { return input$.pipe( + // Keep track of the existing items over time, so we can reuse them scan< In, { @@ -150,22 +159,28 @@ export function generateKeyed$( string, { item: Item; scope: ObservableScope } >(); + const output = project(data, (key, factory) => { let item = state.items.get(key); if (item === undefined) { + // First time requesting the key; create the item const scope = new ObservableScope(); item = { item: factory(scope), scope }; } nextItems.set(key, item); return item.item; }); + + // Destroy all items that are no longer being requested for (const [key, { scope }] of state.items) if (!nextItems.has(key)) scope.end(); + return { items: nextItems, output }; }, { items: new Map() }, ), finalizeValue((state) => { + // Destroy all remaining items when no longer subscribed for (const { scope } of state.items.values()) scope.end(); }), map(({ output }) => output),