From eb03c48e3fa9fa72b5e6ce74f9e674bd559e1745 Mon Sep 17 00:00:00 2001 From: Jage9 Date: Sat, 28 Feb 2026 02:57:50 -0500 Subject: [PATCH] Preserve emit loop delay schedule across range cleanup --- client/public/version.js | 2 +- client/src/audio/itemEmitRuntime.ts | 42 ++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/client/public/version.js b/client/public/version.js index b6efac7..0902ea8 100644 --- a/client/public/version.js +++ b/client/public/version.js @@ -1,5 +1,5 @@ // Maintainer-controlled web client version. // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) -window.CHGRID_WEB_VERSION = "2026.02.28 R306"; +window.CHGRID_WEB_VERSION = "2026.02.28 R307"; // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. window.CHGRID_TIME_ZONE = "America/Detroit"; diff --git a/client/src/audio/itemEmitRuntime.ts b/client/src/audio/itemEmitRuntime.ts index acfbfd2..6660e92 100644 --- a/client/src/audio/itemEmitRuntime.ts +++ b/client/src/audio/itemEmitRuntime.ts @@ -87,20 +87,24 @@ export class ItemEmitRuntime { private readonly getSpatialConfig: (item: WorldItem) => EmitSpatialConfig, ) {} - cleanup(itemId: string): void { + cleanup(itemId: string, options?: { preserveSchedule?: boolean }): void { + const preserveSchedule = options?.preserveSchedule === true; const output = this.outputs.get(itemId); - if (!output) return; - output.element.pause(); - output.element.removeEventListener('ended', output.onEnded); - output.element.src = ''; - output.source.disconnect(); - output.effectInput.disconnect(); - disconnectEffectRuntime(output.effectRuntime); - output.gain.disconnect(); - output.panner?.disconnect(); - this.outputs.delete(itemId); + if (output) { + output.element.pause(); + output.element.removeEventListener('ended', output.onEnded); + output.element.src = ''; + output.source.disconnect(); + output.effectInput.disconnect(); + disconnectEffectRuntime(output.effectRuntime); + output.gain.disconnect(); + output.panner?.disconnect(); + this.outputs.delete(itemId); + } this.pendingEmitStarts.delete(itemId); - this.nextEmitStartAtMs.delete(itemId); + if (!preserveSchedule) { + this.nextEmitStartAtMs.delete(itemId); + } this.emitStartFailureCount.delete(itemId); } @@ -139,16 +143,22 @@ export class ItemEmitRuntime { } const listeners = this.listenerPositions; const validIds = new Set(); + const seenItemIds = new Set(); let audioCtx = this.audio.context; for (const item of items) { + seenItemIds.add(item.id); const emitSound = String(item.params.emitSound ?? item.emitSound ?? '').trim(); const enabled = item.params.enabled !== false; const soundUrl = enabled ? this.resolveSoundUrl(emitSound) : ''; - if (!soundUrl || !this.shouldKeepRuntime(item, listeners, this.outputs.has(item.id))) { + if (!soundUrl) { this.cleanup(item.id); continue; } + if (!this.shouldKeepRuntime(item, listeners, this.outputs.has(item.id))) { + this.cleanup(item.id, { preserveSchedule: true }); + continue; + } validIds.add(item.id); const existing = this.outputs.get(item.id); if (existing && existing.soundUrl === soundUrl) { @@ -214,6 +224,12 @@ export class ItemEmitRuntime { this.cleanup(itemId); } } + + for (const itemId of Array.from(this.nextEmitStartAtMs.keys())) { + if (!seenItemIds.has(itemId)) { + this.nextEmitStartAtMs.delete(itemId); + } + } } updateSpatialAudio(items: Map, playerPosition: { x: number; y: number }): void {