Optimize media stream subscriptions by distance and movement
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
// Maintainer-controlled web client version.
|
// Maintainer-controlled web client version.
|
||||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||||
window.CHGRID_WEB_VERSION = "2026.02.22 R172";
|
window.CHGRID_WEB_VERSION = "2026.02.22 R173";
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ type EmitSpatialConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ITEM_EMIT_BASE_GAIN = 0.3;
|
const ITEM_EMIT_BASE_GAIN = 0.3;
|
||||||
|
const SUBSCRIBE_PRELOAD_SQUARES = 5;
|
||||||
|
const UNSUBSCRIBE_HYSTERESIS_SQUARES = 8;
|
||||||
|
|
||||||
/** Maps a 0-100 speed control to playback-rate range used by emitted audio. */
|
/** Maps a 0-100 speed control to playback-rate range used by emitted audio. */
|
||||||
function resolveEmitPlaybackRate(raw: unknown): number {
|
function resolveEmitPlaybackRate(raw: unknown): number {
|
||||||
@@ -60,6 +62,7 @@ function resolveEmitRates(item: WorldItem): { playbackRate: number; preservePitc
|
|||||||
export class ItemEmitRuntime {
|
export class ItemEmitRuntime {
|
||||||
private readonly outputs = new Map<string, EmitOutput>();
|
private readonly outputs = new Map<string, EmitOutput>();
|
||||||
private layerEnabled = true;
|
private layerEnabled = true;
|
||||||
|
private listenerPosition: { x: number; y: number } | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly audio: AudioEngine,
|
private readonly audio: AudioEngine,
|
||||||
@@ -86,30 +89,39 @@ export class ItemEmitRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setLayerEnabled(enabled: boolean, items: Iterable<WorldItem>): Promise<void> {
|
async setLayerEnabled(
|
||||||
|
enabled: boolean,
|
||||||
|
items: Iterable<WorldItem>,
|
||||||
|
listenerPosition: { x: number; y: number } | null = null,
|
||||||
|
): Promise<void> {
|
||||||
this.layerEnabled = enabled;
|
this.layerEnabled = enabled;
|
||||||
|
if (listenerPosition) {
|
||||||
|
this.listenerPosition = { ...listenerPosition };
|
||||||
|
}
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
this.cleanupAll();
|
this.cleanupAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.sync(items);
|
await this.sync(items, this.listenerPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync(items: Iterable<WorldItem>): Promise<void> {
|
async sync(items: Iterable<WorldItem>, listenerPosition: { x: number; y: number } | null = null): Promise<void> {
|
||||||
if (!this.layerEnabled) {
|
if (!this.layerEnabled) {
|
||||||
this.cleanupAll();
|
this.cleanupAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (listenerPosition) {
|
||||||
|
this.listenerPosition = { ...listenerPosition };
|
||||||
|
}
|
||||||
|
const listener = this.listenerPosition;
|
||||||
const validIds = new Set<string>();
|
const validIds = new Set<string>();
|
||||||
await this.audio.ensureContext();
|
let audioCtx = this.audio.context;
|
||||||
const audioCtx = this.audio.context;
|
|
||||||
if (!audioCtx) return;
|
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const emitSound = String(item.params.emitSound ?? item.emitSound ?? '').trim();
|
const emitSound = String(item.params.emitSound ?? item.emitSound ?? '').trim();
|
||||||
const enabled = item.params.enabled !== false;
|
const enabled = item.params.enabled !== false;
|
||||||
const soundUrl = enabled ? this.resolveSoundUrl(emitSound) : '';
|
const soundUrl = enabled ? this.resolveSoundUrl(emitSound) : '';
|
||||||
if (!soundUrl || item.carrierId) {
|
if (!soundUrl || item.carrierId || !this.shouldKeepRuntime(item, listener, this.outputs.has(item.id))) {
|
||||||
this.cleanup(item.id);
|
this.cleanup(item.id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -121,6 +133,13 @@ export class ItemEmitRuntime {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
this.cleanup(item.id);
|
this.cleanup(item.id);
|
||||||
}
|
}
|
||||||
|
if (!audioCtx) {
|
||||||
|
await this.audio.ensureContext();
|
||||||
|
audioCtx = this.audio.context;
|
||||||
|
}
|
||||||
|
if (!audioCtx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const element = new Audio(soundUrl);
|
const element = new Audio(soundUrl);
|
||||||
element.loop = true;
|
element.loop = true;
|
||||||
element.preload = 'none';
|
element.preload = 'none';
|
||||||
@@ -208,4 +227,16 @@ export class ItemEmitRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldKeepRuntime(
|
||||||
|
item: WorldItem,
|
||||||
|
listenerPosition: { x: number; y: number } | null,
|
||||||
|
currentlyActive: boolean,
|
||||||
|
): boolean {
|
||||||
|
if (!listenerPosition) return false;
|
||||||
|
const spatialConfig = this.getSpatialConfig(item);
|
||||||
|
const baseRange = Math.max(1, spatialConfig.range || HEARING_RADIUS);
|
||||||
|
const threshold = baseRange + (currentlyActive ? UNSUBSCRIBE_HYSTERESIS_SQUARES : SUBSCRIBE_PRELOAD_SQUARES);
|
||||||
|
return Math.hypot(item.x - listenerPosition.x, item.y - listenerPosition.y) <= threshold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,10 +162,14 @@ type RadioSpatialConfig = {
|
|||||||
facingDeg: number;
|
facingDeg: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SUBSCRIBE_PRELOAD_SQUARES = 5;
|
||||||
|
const UNSUBSCRIBE_HYSTERESIS_SQUARES = 8;
|
||||||
|
|
||||||
export class RadioStationRuntime {
|
export class RadioStationRuntime {
|
||||||
private readonly sharedRadioSources = new Map<string, SharedRadioSource>();
|
private readonly sharedRadioSources = new Map<string, SharedRadioSource>();
|
||||||
private readonly itemRadioOutputs = new Map<string, ItemRadioOutput>();
|
private readonly itemRadioOutputs = new Map<string, ItemRadioOutput>();
|
||||||
private layerEnabled = true;
|
private layerEnabled = true;
|
||||||
|
private listenerPosition: { x: number; y: number } | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly audio: AudioEngine,
|
private readonly audio: AudioEngine,
|
||||||
@@ -207,24 +211,39 @@ export class RadioStationRuntime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setLayerEnabled(enabled: boolean, items: Iterable<WorldItem>): Promise<void> {
|
async setLayerEnabled(
|
||||||
|
enabled: boolean,
|
||||||
|
items: Iterable<WorldItem>,
|
||||||
|
listenerPosition: { x: number; y: number } | null = null,
|
||||||
|
): Promise<void> {
|
||||||
this.layerEnabled = enabled;
|
this.layerEnabled = enabled;
|
||||||
|
if (listenerPosition) {
|
||||||
|
this.listenerPosition = { ...listenerPosition };
|
||||||
|
}
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
this.cleanupAll();
|
this.cleanupAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.sync(items);
|
await this.sync(items, this.listenerPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync(items: Iterable<WorldItem>): Promise<void> {
|
async sync(items: Iterable<WorldItem>, listenerPosition: { x: number; y: number } | null = null): Promise<void> {
|
||||||
if (!this.layerEnabled) {
|
if (!this.layerEnabled) {
|
||||||
this.cleanupAll();
|
this.cleanupAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (listenerPosition) {
|
||||||
|
this.listenerPosition = { ...listenerPosition };
|
||||||
|
}
|
||||||
|
const listener = this.listenerPosition;
|
||||||
const validIds = new Set<string>();
|
const validIds = new Set<string>();
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.type !== 'radio_station') continue;
|
if (item.type !== 'radio_station') continue;
|
||||||
validIds.add(item.id);
|
validIds.add(item.id);
|
||||||
|
if (!this.shouldKeepRuntime(item, listener, this.itemRadioOutputs.has(item.id))) {
|
||||||
|
this.cleanup(item.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await this.ensureRuntime(item);
|
await this.ensureRuntime(item);
|
||||||
}
|
}
|
||||||
for (const id of Array.from(this.itemRadioOutputs.keys())) {
|
for (const id of Array.from(this.itemRadioOutputs.keys())) {
|
||||||
@@ -386,4 +405,19 @@ export class RadioStationRuntime {
|
|||||||
panner,
|
panner,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldKeepRuntime(
|
||||||
|
item: WorldItem,
|
||||||
|
listenerPosition: { x: number; y: number } | null,
|
||||||
|
currentlyActive: boolean,
|
||||||
|
): boolean {
|
||||||
|
const streamUrl = String(item.params.streamUrl ?? '').trim();
|
||||||
|
if (!streamUrl || item.params.enabled === false || !listenerPosition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const spatialConfig = this.getSpatialConfig(item);
|
||||||
|
const baseRange = Math.max(1, spatialConfig.range || HEARING_RADIUS);
|
||||||
|
const threshold = baseRange + (currentlyActive ? UNSUBSCRIBE_HYSTERESIS_SQUARES : SUBSCRIBE_PRELOAD_SQUARES);
|
||||||
|
return Math.hypot(item.x - listenerPosition.x, item.y - listenerPosition.y) <= threshold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
EFFECT_IDS,
|
EFFECT_IDS,
|
||||||
EFFECT_SEQUENCE,
|
EFFECT_SEQUENCE,
|
||||||
clampEffectLevel,
|
clampEffectLevel,
|
||||||
type EffectId,
|
|
||||||
} from './audio/effects';
|
} from './audio/effects';
|
||||||
import {
|
import {
|
||||||
RadioStationRuntime,
|
RadioStationRuntime,
|
||||||
@@ -80,6 +79,7 @@ const MIC_INPUT_GAIN_STEP = 0.05;
|
|||||||
const HEARTBEAT_INTERVAL_MS = 10_000;
|
const HEARTBEAT_INTERVAL_MS = 10_000;
|
||||||
const RECONNECT_DELAY_MS = 5_000;
|
const RECONNECT_DELAY_MS = 5_000;
|
||||||
const RECONNECT_MAX_ATTEMPTS = 3;
|
const RECONNECT_MAX_ATTEMPTS = 3;
|
||||||
|
const AUDIO_SUBSCRIPTION_REFRESH_MS = 500;
|
||||||
const AUTO_RECONNECT_AFTER_RELOAD_KEY = 'chatGridAutoReconnectAfterReload';
|
const AUTO_RECONNECT_AFTER_RELOAD_KEY = 'chatGridAutoReconnectAfterReload';
|
||||||
const SELF_LIST_ENTRY_ID = '__self__';
|
const SELF_LIST_ENTRY_ID = '__self__';
|
||||||
|
|
||||||
@@ -200,7 +200,6 @@ let micGainLoopbackRestoreState: boolean | null = null;
|
|||||||
let helpViewerLines: string[] = [];
|
let helpViewerLines: string[] = [];
|
||||||
let helpViewerIndex = 0;
|
let helpViewerIndex = 0;
|
||||||
let heartbeatTimerId: number | null = null;
|
let heartbeatTimerId: number | null = null;
|
||||||
let heartbeatLastPongAt = 0;
|
|
||||||
let heartbeatNextPingId = -1;
|
let heartbeatNextPingId = -1;
|
||||||
let heartbeatAwaitingPong = false;
|
let heartbeatAwaitingPong = false;
|
||||||
let reconnectInFlight = false;
|
let reconnectInFlight = false;
|
||||||
@@ -213,6 +212,11 @@ let audioLayers: AudioLayerState = {
|
|||||||
media: true,
|
media: true,
|
||||||
world: true,
|
world: true,
|
||||||
};
|
};
|
||||||
|
let lastSubscriptionRefreshAt = 0;
|
||||||
|
let lastSubscriptionRefreshX = state.player.x;
|
||||||
|
let lastSubscriptionRefreshY = state.player.y;
|
||||||
|
let subscriptionRefreshInFlight = false;
|
||||||
|
let subscriptionRefreshPending = false;
|
||||||
|
|
||||||
const signalingProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
const signalingProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
const signalingUrl = `${signalingProtocol}://${window.location.host}/ws`;
|
const signalingUrl = `${signalingProtocol}://${window.location.host}/ws`;
|
||||||
@@ -551,8 +555,38 @@ async function applyAudioLayerState(): Promise<void> {
|
|||||||
} else {
|
} else {
|
||||||
peerManager.suspendRemoteAudio();
|
peerManager.suspendRemoteAudio();
|
||||||
}
|
}
|
||||||
await radioRuntime.setLayerEnabled(audioLayers.media, state.items.values());
|
const listenerPosition = { x: state.player.x, y: state.player.y };
|
||||||
await itemEmitRuntime.setLayerEnabled(audioLayers.item, state.items.values());
|
await radioRuntime.setLayerEnabled(audioLayers.media, state.items.values(), listenerPosition);
|
||||||
|
await itemEmitRuntime.setLayerEnabled(audioLayers.item, state.items.values(), listenerPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Refreshes distance-gated radio/item stream subscriptions on movement or timer cadence. */
|
||||||
|
async function refreshAudioSubscriptions(force = false): Promise<void> {
|
||||||
|
if (!state.running) return;
|
||||||
|
const now = Date.now();
|
||||||
|
const moved = state.player.x !== lastSubscriptionRefreshX || state.player.y !== lastSubscriptionRefreshY;
|
||||||
|
if (!force && !moved && now - lastSubscriptionRefreshAt < AUDIO_SUBSCRIPTION_REFRESH_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (subscriptionRefreshInFlight) {
|
||||||
|
subscriptionRefreshPending = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subscriptionRefreshInFlight = true;
|
||||||
|
lastSubscriptionRefreshAt = now;
|
||||||
|
lastSubscriptionRefreshX = state.player.x;
|
||||||
|
lastSubscriptionRefreshY = state.player.y;
|
||||||
|
const listenerPosition = { x: state.player.x, y: state.player.y };
|
||||||
|
try {
|
||||||
|
await radioRuntime.sync(state.items.values(), listenerPosition);
|
||||||
|
await itemEmitRuntime.sync(state.items.values(), listenerPosition);
|
||||||
|
} finally {
|
||||||
|
subscriptionRefreshInFlight = false;
|
||||||
|
if (subscriptionRefreshPending) {
|
||||||
|
subscriptionRefreshPending = false;
|
||||||
|
void refreshAudioSubscriptions(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Toggles a single audio layer and applies the change immediately. */
|
/** Toggles a single audio layer and applies the change immediately. */
|
||||||
@@ -1057,6 +1091,7 @@ function randomFootstepUrl(): string {
|
|||||||
function gameLoop(): void {
|
function gameLoop(): void {
|
||||||
if (!state.running) return;
|
if (!state.running) return;
|
||||||
handleMovement();
|
handleMovement();
|
||||||
|
void refreshAudioSubscriptions();
|
||||||
audio.updateSpatialAudio(peerManager.getPeers(), { x: state.player.x, y: state.player.y });
|
audio.updateSpatialAudio(peerManager.getPeers(), { x: state.player.x, y: state.player.y });
|
||||||
radioRuntime.updateSpatialAudio(state.items, { x: state.player.x, y: state.player.y });
|
radioRuntime.updateSpatialAudio(state.items, { x: state.player.x, y: state.player.y });
|
||||||
itemEmitRuntime.updateSpatialAudio(state.items, { x: state.player.x, y: state.player.y });
|
itemEmitRuntime.updateSpatialAudio(state.items, { x: state.player.x, y: state.player.y });
|
||||||
@@ -1100,6 +1135,7 @@ function handleMovement(): void {
|
|||||||
lastWallCollisionDirection = null;
|
lastWallCollisionDirection = null;
|
||||||
persistPlayerPosition();
|
persistPlayerPosition();
|
||||||
state.player.lastMoveTime = now;
|
state.player.lastMoveTime = now;
|
||||||
|
void refreshAudioSubscriptions(true);
|
||||||
void audio.playSample(randomFootstepUrl(), FOOTSTEP_GAIN);
|
void audio.playSample(randomFootstepUrl(), FOOTSTEP_GAIN);
|
||||||
signaling.send({ type: 'update_position', x: nextX, y: nextY });
|
signaling.send({ type: 'update_position', x: nextX, y: nextY });
|
||||||
|
|
||||||
@@ -1159,7 +1195,6 @@ function stopHeartbeat(): void {
|
|||||||
window.clearInterval(heartbeatTimerId);
|
window.clearInterval(heartbeatTimerId);
|
||||||
heartbeatTimerId = null;
|
heartbeatTimerId = null;
|
||||||
}
|
}
|
||||||
heartbeatLastPongAt = 0;
|
|
||||||
heartbeatAwaitingPong = false;
|
heartbeatAwaitingPong = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1173,7 +1208,6 @@ function sendHeartbeatPing(): void {
|
|||||||
/** Starts heartbeat timer for stale-connection detection. */
|
/** Starts heartbeat timer for stale-connection detection. */
|
||||||
function startHeartbeat(): void {
|
function startHeartbeat(): void {
|
||||||
stopHeartbeat();
|
stopHeartbeat();
|
||||||
heartbeatLastPongAt = Date.now();
|
|
||||||
heartbeatAwaitingPong = false;
|
heartbeatAwaitingPong = false;
|
||||||
sendHeartbeatPing();
|
sendHeartbeatPing();
|
||||||
heartbeatTimerId = window.setInterval(() => {
|
heartbeatTimerId = window.setInterval(() => {
|
||||||
@@ -1272,6 +1306,9 @@ function disconnect(): void {
|
|||||||
runDisconnectFlow(getConnectionFlowDeps());
|
runDisconnectFlow(getConnectionFlowDeps());
|
||||||
pendingEscapeDisconnect = false;
|
pendingEscapeDisconnect = false;
|
||||||
restoreLoopbackAfterMicGainEdit();
|
restoreLoopbackAfterMicGainEdit();
|
||||||
|
subscriptionRefreshPending = false;
|
||||||
|
subscriptionRefreshInFlight = false;
|
||||||
|
lastSubscriptionRefreshAt = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onAppMessage = createOnMessageHandler({
|
const onAppMessage = createOnMessageHandler({
|
||||||
@@ -1289,8 +1326,11 @@ const onAppMessage = createOnMessageHandler({
|
|||||||
dom,
|
dom,
|
||||||
signalingSend: (message) => signaling.send(message as OutgoingMessage),
|
signalingSend: (message) => signaling.send(message as OutgoingMessage),
|
||||||
peerManager,
|
peerManager,
|
||||||
radioRuntime,
|
refreshAudioSubscriptions,
|
||||||
itemEmitRuntime,
|
cleanupItemAudio: (itemId) => {
|
||||||
|
radioRuntime.cleanup(itemId);
|
||||||
|
itemEmitRuntime.cleanup(itemId);
|
||||||
|
},
|
||||||
applyAudioLayerState,
|
applyAudioLayerState,
|
||||||
gameLoop,
|
gameLoop,
|
||||||
sanitizeName,
|
sanitizeName,
|
||||||
@@ -1329,7 +1369,6 @@ const onAppMessage = createOnMessageHandler({
|
|||||||
/** Handles signaling packets with heartbeat/restart metadata before app-level dispatch. */
|
/** Handles signaling packets with heartbeat/restart metadata before app-level dispatch. */
|
||||||
async function onSignalingMessage(message: IncomingMessage): Promise<void> {
|
async function onSignalingMessage(message: IncomingMessage): Promise<void> {
|
||||||
if (message.type === 'pong' && message.clientSentAt < 0) {
|
if (message.type === 'pong' && message.clientSentAt < 0) {
|
||||||
heartbeatLastPongAt = Date.now();
|
|
||||||
heartbeatAwaitingPong = false;
|
heartbeatAwaitingPong = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1946,6 +1985,7 @@ function handleListModeInput(code: string, key: string): void {
|
|||||||
state.player.x = entry.x;
|
state.player.x = entry.x;
|
||||||
state.player.y = entry.y;
|
state.player.y = entry.y;
|
||||||
persistPlayerPosition();
|
persistPlayerPosition();
|
||||||
|
void refreshAudioSubscriptions(true);
|
||||||
void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN);
|
void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN);
|
||||||
signaling.send({ type: 'update_position', x: entry.x, y: entry.y });
|
signaling.send({ type: 'update_position', x: entry.x, y: entry.y });
|
||||||
state.mode = 'normal';
|
state.mode = 'normal';
|
||||||
@@ -1993,6 +2033,7 @@ function handleListItemsModeInput(code: string, key: string): void {
|
|||||||
state.player.x = item.x;
|
state.player.x = item.x;
|
||||||
state.player.y = item.y;
|
state.player.y = item.y;
|
||||||
persistPlayerPosition();
|
persistPlayerPosition();
|
||||||
|
void refreshAudioSubscriptions(true);
|
||||||
void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN);
|
void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN);
|
||||||
signaling.send({ type: 'update_position', x: item.x, y: item.y });
|
signaling.send({ type: 'update_position', x: item.x, y: item.y });
|
||||||
state.mode = 'normal';
|
state.mode = 'normal';
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ type MessageHandlerDeps = {
|
|||||||
setPeerNickname: (id: string, nickname: string) => void;
|
setPeerNickname: (id: string, nickname: string) => void;
|
||||||
removePeer: (id: string) => void;
|
removePeer: (id: string) => void;
|
||||||
};
|
};
|
||||||
radioRuntime: { sync: (items: Iterable<WorldItem>) => Promise<void>; cleanup: (itemId: string) => void };
|
refreshAudioSubscriptions: (force?: boolean) => Promise<void>;
|
||||||
itemEmitRuntime: { sync: (items: Iterable<WorldItem>) => Promise<void>; cleanup: (itemId: string) => void };
|
cleanupItemAudio: (itemId: string) => void;
|
||||||
applyAudioLayerState: () => Promise<void>;
|
applyAudioLayerState: () => Promise<void>;
|
||||||
gameLoop: () => void;
|
gameLoop: () => void;
|
||||||
sanitizeName: (value: string) => string;
|
sanitizeName: (value: string) => string;
|
||||||
@@ -106,8 +106,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
|||||||
carrierId: item.carrierId ?? null,
|
carrierId: item.carrierId ?? null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await deps.radioRuntime.sync(deps.state.items.values());
|
await deps.refreshAudioSubscriptions(true);
|
||||||
await deps.itemEmitRuntime.sync(deps.state.items.values());
|
|
||||||
await deps.applyAudioLayerState();
|
await deps.applyAudioLayerState();
|
||||||
deps.gameLoop();
|
deps.gameLoop();
|
||||||
break;
|
break;
|
||||||
@@ -208,16 +207,15 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
|
|||||||
deps.updateStatus(`${deps.itemPropertyLabel(key)}: ${deps.getItemPropertyValue(message.item, key)}`);
|
deps.updateStatus(`${deps.itemPropertyLabel(key)}: ${deps.getItemPropertyValue(message.item, key)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await deps.radioRuntime.sync(deps.state.items.values());
|
await deps.refreshAudioSubscriptions(true);
|
||||||
await deps.itemEmitRuntime.sync(deps.state.items.values());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'item_remove': {
|
case 'item_remove': {
|
||||||
deps.state.items.delete(message.itemId);
|
deps.state.items.delete(message.itemId);
|
||||||
deps.state.carriedItemId = deps.getCarriedItemId();
|
deps.state.carriedItemId = deps.getCarriedItemId();
|
||||||
deps.radioRuntime.cleanup(message.itemId);
|
deps.cleanupItemAudio(message.itemId);
|
||||||
deps.itemEmitRuntime.cleanup(message.itemId);
|
await deps.refreshAudioSubscriptions(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user