diff --git a/js/hang/src/publish/audio/encoder.ts b/js/hang/src/publish/audio/encoder.ts index a7d9c5b3d..6ff5d0541 100644 --- a/js/hang/src/publish/audio/encoder.ts +++ b/js/hang/src/publish/audio/encoder.ts @@ -73,11 +73,9 @@ export class Encoder { } #runSource(effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const source = effect.get(this.source); - if (!source) return; + const values = effect.getAll([this.enabled, this.source]); + if (!values) return; + const [_, source] = values; const settings = source.getSettings(); @@ -153,12 +151,9 @@ export class Encoder { } serve(track: Moq.Track, effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const values = effect.getAll([this.#worklet, this.#config]); + const values = effect.getAll([this.enabled, this.#worklet, this.#config]); if (!values) return; - const [worklet, config] = values; + const [_, worklet, config] = values; effect.set(this.active, true, false); diff --git a/js/hang/src/publish/broadcast.ts b/js/hang/src/publish/broadcast.ts index bb998267c..bc0731f32 100644 --- a/js/hang/src/publish/broadcast.ts +++ b/js/hang/src/publish/broadcast.ts @@ -53,12 +53,12 @@ export class Broadcast { } #run(effect: Effect) { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const values = effect.getAll([this.connection, this.path]); + const values = effect.getAll([this.enabled, this.connection]); if (!values) return; - const [connection, path] = values; + const [_enabled, connection] = values; + + const path = effect.get(this.path); + if (path === undefined) return; const broadcast = new Moq.Broadcast(); effect.cleanup(() => broadcast.close()); diff --git a/js/hang/src/publish/location/peers.ts b/js/hang/src/publish/location/peers.ts index 9811431ac..ec4dcfed6 100644 --- a/js/hang/src/publish/location/peers.ts +++ b/js/hang/src/publish/location/peers.ts @@ -32,11 +32,9 @@ export class Peers { } serve(track: Moq.Track, effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const positions = effect.get(this.positions); - if (!positions) return; + const values = effect.getAll([this.enabled, this.positions]); + if (!values) return; + const [_, positions] = values; Zod.write(track, positions, Catalog.PeersSchema); } diff --git a/js/hang/src/publish/location/window.ts b/js/hang/src/publish/location/window.ts index dd72bbe02..95377154e 100644 --- a/js/hang/src/publish/location/window.ts +++ b/js/hang/src/publish/location/window.ts @@ -45,11 +45,9 @@ export class Window { } serve(track: Moq.Track, effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const position = effect.get(this.position); - if (!position) return; + const values = effect.getAll([this.enabled, this.position]); + if (!values) return; + const [_, position] = values; Zod.write(track, position, Catalog.PositionSchema); } diff --git a/js/hang/src/publish/preview.ts b/js/hang/src/publish/preview.ts index 91df96d1f..c9d5c80cc 100644 --- a/js/hang/src/publish/preview.ts +++ b/js/hang/src/publish/preview.ts @@ -30,10 +30,9 @@ export class Preview { } serve(track: Moq.Track, effect: Effect): void { - if (!effect.get(this.enabled)) return; - - const info = effect.get(this.info); - if (!info) return; + const values = effect.getAll([this.enabled, this.info]); + if (!values) return; + const [_, info] = values; track.writeJson(info); } diff --git a/js/hang/src/publish/source/file.ts b/js/hang/src/publish/source/file.ts index 2e908ca41..6d592ccb6 100644 --- a/js/hang/src/publish/source/file.ts +++ b/js/hang/src/publish/source/file.ts @@ -19,12 +19,9 @@ export class File { this.file = Signal.from(config.file); this.signals.effect((effect) => { - const file = effect.get(this.file); - const enabled = effect.get(this.enabled); - - if (!file || !enabled) { - return; - } + const values = effect.getAll([this.file, this.enabled]); + if (!values) return; + const [file] = values; this.#decode(file, effect).catch((err) => { console.error("Failed to decode file:", err); diff --git a/js/hang/src/publish/video/encoder.ts b/js/hang/src/publish/video/encoder.ts index b263894dd..900829e3e 100644 --- a/js/hang/src/publish/video/encoder.ts +++ b/js/hang/src/publish/video/encoder.ts @@ -127,11 +127,9 @@ export class Encoder { // Returns the catalog for the configured settings. #runCatalog(effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const config = effect.get(this.#config); - if (!config) return; + const values = effect.getAll([this.enabled, this.#config]); + if (!values) return; + const [_, config] = values; const catalog: Catalog.VideoConfig = { codec: config.codec, @@ -147,14 +145,11 @@ export class Encoder { } #runConfig(effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - // NOTE: dimensions already factors in user provided maxPixels. // It's a separate effect in order to deduplicate. - const values = effect.getAll([this.source, this.#dimensions]); + const values = effect.getAll([this.enabled, this.source, this.#dimensions]); if (!values) return; - const [source, dimensions] = values; + const [_, source, dimensions] = values; const settings = source.getSettings(); const framerate = settings.frameRate ?? 30; diff --git a/js/hang/src/watch/audio/source.ts b/js/hang/src/watch/audio/source.ts index 7265c4295..0c1c1c9be 100644 --- a/js/hang/src/watch/audio/source.ts +++ b/js/hang/src/watch/audio/source.ts @@ -130,11 +130,9 @@ export class Source { } #runEnabled(effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const context = effect.get(this.#context); - if (!context) return; + const values = effect.getAll([this.enabled, this.#context]); + if (!values) return; + const [_enabled, context] = values; context.resume(); @@ -142,12 +140,9 @@ export class Source { } #runDecoder(effect: Effect): void { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const values = effect.getAll([this.catalog, this.broadcast, this.config, this.active]); + const values = effect.getAll([this.enabled, this.catalog, this.broadcast, this.config, this.active]); if (!values) return; - const [catalog, broadcast, config, active] = values; + const [_enabled, catalog, broadcast, config, active] = values; const sub = broadcast.subscribe(active, catalog.priority); effect.cleanup(() => sub.close()); diff --git a/js/hang/src/watch/broadcast.ts b/js/hang/src/watch/broadcast.ts index 7527883fa..4c9ca0cce 100644 --- a/js/hang/src/watch/broadcast.ts +++ b/js/hang/src/watch/broadcast.ts @@ -115,11 +115,12 @@ export class Broadcast { } #runBroadcast(effect: Effect): void { - if (!effect.get(this.enabled) || !effect.get(this.#active)) return; - - const values = effect.getAll([this.connection, this.path]); + const values = effect.getAll([this.enabled, this.#active, this.connection]); if (!values) return; - const [conn, path] = values; + const [_enabled, _active, conn] = values; + + const path = effect.get(this.path); + if (path === undefined) return; const broadcast = conn.consume(path); effect.cleanup(() => broadcast.close()); @@ -128,10 +129,9 @@ export class Broadcast { } #runCatalog(effect: Effect): void { - if (!effect.get(this.enabled)) return; - - const broadcast = effect.get(this.#broadcast); - if (!broadcast) return; + const values = effect.getAll([this.enabled, this.#broadcast]); + if (!values) return; + const [_enabled, broadcast] = values; this.status.set("loading"); diff --git a/js/hang/src/watch/chat/message.ts b/js/hang/src/watch/chat/message.ts index a4d5e5417..686d4356d 100644 --- a/js/hang/src/watch/chat/message.ts +++ b/js/hang/src/watch/chat/message.ts @@ -39,13 +39,9 @@ export class Message { } #run(effect: Effect) { - if (!effect.get(this.enabled)) return; - - const catalog = effect.get(this.#catalog); - if (!catalog) return; - - const broadcast = effect.get(this.broadcast); - if (!broadcast) return; + const values = effect.getAll([this.enabled, this.#catalog, this.broadcast]); + if (!values) return; + const [_, catalog, broadcast] = values; const track = broadcast.subscribe(catalog.name, catalog.priority); effect.cleanup(() => track.close()); diff --git a/js/hang/src/watch/chat/typing.ts b/js/hang/src/watch/chat/typing.ts index 39139d582..a5ce2a0f4 100644 --- a/js/hang/src/watch/chat/typing.ts +++ b/js/hang/src/watch/chat/typing.ts @@ -37,13 +37,9 @@ export class Typing { } #run(effect: Effect) { - if (!effect.get(this.enabled)) return; - - const catalog = effect.get(this.#catalog); - if (!catalog) return; - - const broadcast = effect.get(this.broadcast); - if (!broadcast) return; + const values = effect.getAll([this.enabled, this.#catalog, this.broadcast]); + if (!values) return; + const [_, catalog, broadcast] = values; const track = broadcast.subscribe(catalog.name, catalog.priority); effect.cleanup(() => track.close()); diff --git a/js/hang/src/watch/location/peers.ts b/js/hang/src/watch/location/peers.ts index 56acbbdce..283648110 100644 --- a/js/hang/src/watch/location/peers.ts +++ b/js/hang/src/watch/location/peers.ts @@ -32,14 +32,9 @@ export class Peers { } #run(effect: Effect) { - const enabled = effect.get(this.enabled); - if (!enabled) return; - - const catalog = effect.get(this.#catalog); - if (!catalog) return; - - const broadcast = effect.get(this.broadcast); - if (!broadcast) return; + const values = effect.getAll([this.enabled, this.#catalog, this.broadcast]); + if (!values) return; + const [_, catalog, broadcast] = values; const track = broadcast.subscribe(catalog.name, catalog.priority); effect.cleanup(() => track.close()); diff --git a/js/hang/src/watch/preview.ts b/js/hang/src/watch/preview.ts index bfa0bf523..653cdf440 100644 --- a/js/hang/src/watch/preview.ts +++ b/js/hang/src/watch/preview.ts @@ -28,13 +28,9 @@ export class Preview { }); this.#signals.effect((effect) => { - if (!effect.get(this.enabled)) return; - - const broadcast = effect.get(this.broadcast); - if (!broadcast) return; - - const catalog = effect.get(this.#catalog); - if (!catalog) return; + const values = effect.getAll([this.enabled, this.broadcast, this.#catalog]); + if (!values) return; + const [_, broadcast, catalog] = values; // Subscribe to the preview.json track directly const track = broadcast.subscribe(catalog.name, catalog.priority); diff --git a/js/signals/package.json b/js/signals/package.json index 155b04f87..c903908d0 100644 --- a/js/signals/package.json +++ b/js/signals/package.json @@ -1,7 +1,7 @@ { "name": "@moq/signals", "type": "module", - "version": "0.1.1", + "version": "0.1.2", "description": "Reactive and safe signals", "license": "(MIT OR Apache-2.0)", "repository": "github:moq-dev/moq", diff --git a/js/signals/src/index.ts b/js/signals/src/index.ts index 00ff9b516..578e8aa29 100644 --- a/js/signals/src/index.ts +++ b/js/signals/src/index.ts @@ -158,6 +158,10 @@ export class Signal implements Getter, Setter { type SetterType = S extends Setter ? T : never; type GetterType = G extends Getter ? T : never; +// Excludes common falsy values from a type +type Falsy = false | 0 | "" | null | undefined; +type Truthy = Exclude; + // TODO Make this a single instance of an Effect, so close() can work correctly from async code. export class Effect { // Sanity check to make sure roots are being disposed on dev. @@ -414,17 +418,17 @@ export class Effect { this.#dispose.push(() => effect.close()); } - // Get the values of multiple signals, returning undefined if any are null/undefined. + // Get the values of multiple signals, returning undefined if any are falsy. getAll[]>( signals: [...S], - ): { [K in keyof S]: NonNullable> } | undefined { + ): { [K in keyof S]: Truthy> } | undefined { const values: unknown[] = []; for (const signal of signals) { const value = this.get(signal); - if (value == null) return undefined; + if (!value) return undefined; values.push(value); } - return values as { [K in keyof S]: NonNullable> }; + return values as { [K in keyof S]: Truthy> }; } // A helper to call a function when a signal changes.