diff --git a/src/events/app-data.ts b/src/events/app-data.ts index a212245..720d23c 100644 --- a/src/events/app-data.ts +++ b/src/events/app-data.ts @@ -1,32 +1,16 @@ import { writable } from "svelte/store"; import { commands, events, type UserData } from "$lib/bindings"; -import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; +import { createEventSource } from "./listener-utils"; export const appData = writable(null); -const subscription = createListenersSubscription(); - -/** - * Starts listening for app data refresh events. - * Initializes app data from the backend. - */ -export async function startAppData() { - try { - if (subscription.isListening()) return; +export const { start: startAppData, stop: stopAppData } = createEventSource( + async (addEventListener) => { appData.set(await commands.getAppData()); - const unlisten = await events.appDataRefreshed.listen((event) => { - appData.set(event.payload); - }); - subscription.addUnlisten(unlisten); - subscription.setListening(true); - } catch (error) { - console.error(error); - throw error; - } -} - -export function stopAppData() { - subscription.stop(); -} - -setupHmrCleanup(stopAppData); + addEventListener( + await events.appDataRefreshed.listen((event) => { + appData.set(event.payload); + }), + ); + }, +); diff --git a/src/events/cursor.ts b/src/events/cursor.ts index a4ca912..04a21ba 100644 --- a/src/events/cursor.ts +++ b/src/events/cursor.ts @@ -1,35 +1,17 @@ import { writable } from "svelte/store"; import { events, type CursorPositions } from "$lib/bindings"; -import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; +import { createEventSource } from "./listener-utils"; export const cursorPositionOnScreen = writable({ raw: { x: 0, y: 0 }, mapped: { x: 0, y: 0 }, }); -const subscription = createListenersSubscription(); - -/** - * Starts tracking the local cursor position. - * Initializes cursor position from the backend and listens for updates. - */ -export async function startCursorTracking() { - if (subscription.isListening()) return; - - try { - const unlisten = await events.cursorMoved.listen((event) => { - cursorPositionOnScreen.set(event.payload); - }); - subscription.addUnlisten(unlisten); - subscription.setListening(true); - } catch (err) { - console.error("Failed to initialize cursor tracking:", err); - throw err; - } -} - -export function stopCursorTracking() { - subscription.stop(); -} - -setupHmrCleanup(stopCursorTracking); +export const { start: startCursorTracking, stop: stopCursorTracking } = + createEventSource(async (addEventListener) => { + addEventListener( + await events.cursorMoved.listen((event) => { + cursorPositionOnScreen.set(event.payload); + }), + ); + }); diff --git a/src/events/friend-cursor.ts b/src/events/friend-cursor.ts index 340467f..731d14c 100644 --- a/src/events/friend-cursor.ts +++ b/src/events/friend-cursor.ts @@ -5,11 +5,7 @@ import { type DollDto, type OutgoingFriendCursorPayload, } from "$lib/bindings"; -import { - createListenersSubscription, - removeFromStore, - setupHmrCleanup, -} from "./listener-utils"; +import { createEventSource, removeFromStore } from "./listener-utils"; type FriendCursorData = { position: CursorPositions; @@ -21,87 +17,65 @@ export const friendsCursorPositions = writable>( ); export const friendsActiveDolls = writable>({}); -const subscription = createListenersSubscription(); +export const { + start: startFriendCursorTracking, + stop: stopFriendCursorTracking, +} = createEventSource(async (addEventListener) => { + let friendCursorState: Record = {}; + addEventListener( + await events.friendCursorPositionUpdated.listen((event) => { + const data: OutgoingFriendCursorPayload = event.payload; -let friendCursorState: Record = {}; + friendCursorState[data.userId] = { + position: data.position, + lastUpdated: Date.now(), + }; -/** - * Starts listening for friend cursor position and active doll changes. - * Also handles friend disconnection events. - */ -export async function startFriendCursorTracking() { - if (subscription.isListening()) return; - - try { - // TODO: Add initial sync for existing friends' cursors and dolls if needed - - const unlistenFriendCursor = - await events.friendCursorPositionUpdated.listen((event) => { - const data: OutgoingFriendCursorPayload = event.payload; - - friendCursorState[data.userId] = { - position: data.position, - lastUpdated: Date.now(), + friendsCursorPositions.update((current) => { + return { + ...current, + [data.userId]: data.position, }; - - friendsCursorPositions.update((current) => { - return { - ...current, - [data.userId]: data.position, - }; - }); }); - subscription.addUnlisten(unlistenFriendCursor); + }), + ); - const unlistenFriendDisconnected = await events.friendDisconnected.listen( - (event) => { - const data = event.payload; + addEventListener( + await events.friendDisconnected.listen((event) => { + const data = event.payload; - if (friendCursorState[data.userId]) { - delete friendCursorState[data.userId]; - } + if (friendCursorState[data.userId]) { + delete friendCursorState[data.userId]; + } + + friendsCursorPositions.update((current) => + removeFromStore(current, data.userId), + ); + }), + ); + + addEventListener( + await events.friendActiveDollChanged.listen((event) => { + const payload = event.payload; + + if (!payload.doll) { + friendsActiveDolls.update((current) => { + const next = { ...current }; + next[payload.friendId] = null; + return next; + }); friendsCursorPositions.update((current) => - removeFromStore(current, data.userId), + removeFromStore(current, payload.friendId), ); - }, - ); - subscription.addUnlisten(unlistenFriendDisconnected); - - const unlistenFriendActiveDollChanged = - await events.friendActiveDollChanged.listen((event) => { - const payload = event.payload; - - if (!payload.doll) { - friendsActiveDolls.update((current) => { - const next = { ...current }; - next[payload.friendId] = null; - return next; - }); - - friendsCursorPositions.update((current) => - removeFromStore(current, payload.friendId), - ); - } else { - friendsActiveDolls.update((current) => { - return { - ...current, - [payload.friendId]: payload.doll, - }; - }); - } - }); - subscription.addUnlisten(unlistenFriendActiveDollChanged); - - subscription.setListening(true); - } catch (err) { - console.error("Failed to initialize friend cursor tracking:", err); - throw err; - } -} - -export function stopFriendCursorTracking() { - subscription.stop(); -} - -setupHmrCleanup(stopFriendCursorTracking); + } else { + friendsActiveDolls.update((current) => { + return { + ...current, + [payload.friendId]: payload.doll, + }; + }); + } + }), + ); +}); diff --git a/src/events/interaction.ts b/src/events/interaction.ts index 7ccb24e..31ba820 100644 --- a/src/events/interaction.ts +++ b/src/events/interaction.ts @@ -1,6 +1,6 @@ import { writable } from "svelte/store"; import { events, type InteractionPayloadDto } from "$lib/bindings"; -import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; +import { createEventSource } from "./listener-utils"; export const receivedInteractions = writable< Map @@ -22,40 +22,20 @@ export function clearInteraction(userId: string) { }); } -const subscription = createListenersSubscription(); - -/** - * Starts listening for interaction events (received and delivery failed). - */ -export async function startInteraction() { - if (subscription.isListening()) return; - - try { - const unlistenReceived = await events.interactionReceived.listen( - (event) => { +export const { start: startInteraction, stop: stopInteraction } = + createEventSource(async (addEventListener) => { + addEventListener( + await events.interactionReceived.listen((event) => { addInteraction(event.payload); - }, + }), ); - subscription.addUnlisten(unlistenReceived); - const unlistenFailed = await events.interactionDeliveryFailed.listen( - (event) => { + addEventListener( + await events.interactionDeliveryFailed.listen((event) => { console.error("Interaction delivery failed:", event.payload); alert( `Failed to send message to user ${event.payload.recipientUserId}: ${event.payload.reason}`, ); - }, + }), ); - subscription.addUnlisten(unlistenFailed); - subscription.setListening(true); - } catch (err) { - console.error("Failed to initialize interaction listeners:", err); - throw err; - } -} - -export function stopInteraction() { - subscription.stop(); -} - -setupHmrCleanup(stopInteraction); + }); diff --git a/src/events/listener-utils.ts b/src/events/listener-utils.ts index a5dde4a..364b68d 100644 --- a/src/events/listener-utils.ts +++ b/src/events/listener-utils.ts @@ -4,7 +4,7 @@ export type ListenerSubscription = { stop: () => void; isListening: () => boolean; setListening: (value: boolean) => void; - addUnlisten: (unlisten: UnlistenFn | null) => void; + addEventListener: (unlisten: UnlistenFn | null) => void; }; export function createListenersSubscription( @@ -26,7 +26,7 @@ export function createListenersSubscription( setListening: (value) => { listening = value; }, - addUnlisten: (unlisten) => { + addEventListener: (unlisten) => { if (unlisten) { unlistens.push(unlisten); } @@ -34,12 +34,39 @@ export function createListenersSubscription( }; } -export function setupHmrCleanup(cleanup: () => void) { - if (import.meta.hot) { - import.meta.hot.dispose(() => { - cleanup(); - }); +export type EventSource = { + start: () => Promise; + stop: () => void; + isListening: () => boolean; +}; + +export function createEventSource( + setup: (addEventListener: (unlisten: UnlistenFn) => void) => Promise, + stopFn: () => void = () => {}, +): EventSource { + const subscription = createListenersSubscription(stopFn); + + async function start() { + if (subscription.isListening()) return; + try { + await setup((unlisten) => subscription.addEventListener(unlisten)); + subscription.setListening(true); + } catch (err) { + subscription.stop(); + console.error(`Failed to start:`, err); + throw err; + } } + + function stop() { + subscription.stop(); + } + + if (import.meta.hot) { + import.meta.hot.dispose(() => stop()); + } + + return { start, stop, isListening: () => subscription.isListening() }; } export function removeFromStore( diff --git a/src/events/scene-interactive.ts b/src/events/scene-interactive.ts index 650ebb6..637c2bd 100644 --- a/src/events/scene-interactive.ts +++ b/src/events/scene-interactive.ts @@ -1,33 +1,15 @@ import { writable } from "svelte/store"; import { commands, events } from "$lib/bindings"; -import { createListenersSubscription, setupHmrCleanup } from "./listener-utils"; +import { createEventSource } from "./listener-utils"; export const sceneInteractive = writable(false); -const subscription = createListenersSubscription(); - -/** - * Starts listening for scene interactive state changes. - * Initializes the scene interactive state from the backend. - */ -export async function startSceneInteractive() { - if (subscription.isListening()) return; - - try { +export const { start: startSceneInteractive, stop: stopSceneInteractive } = + createEventSource(async (addEventListener) => { sceneInteractive.set(await commands.getSceneInteractive()); - const unlisten = await events.sceneInteractiveChanged.listen((event) => { - sceneInteractive.set(Boolean(event.payload)); - }); - subscription.addUnlisten(unlisten); - subscription.setListening(true); - } catch (error) { - console.error("Failed to initialize scene interactive listener:", error); - throw error; - } -} - -export function stopSceneInteractive() { - subscription.stop(); -} - -setupHmrCleanup(stopSceneInteractive); + addEventListener( + await events.sceneInteractiveChanged.listen((event) => { + sceneInteractive.set(Boolean(event.payload)); + }), + ); + }); diff --git a/src/events/user-status.ts b/src/events/user-status.ts index 0f293d8..0ccd3cc 100644 --- a/src/events/user-status.ts +++ b/src/events/user-status.ts @@ -1,27 +1,16 @@ import { writable } from "svelte/store"; import { events, type UserStatusPayload } from "$lib/bindings"; -import { - createListenersSubscription, - removeFromStore, - setupHmrCleanup, -} from "./listener-utils"; +import { createEventSource, removeFromStore } from "./listener-utils"; export const friendsPresenceStates = writable< Record >({}); export const currentPresenceState = writable(null); -const subscription = createListenersSubscription(); - -/** - * Starts listening for user status changes and friend status updates. - */ -export async function startUserStatus() { - if (subscription.isListening()) return; - - try { - const unlistenStatus = await events.friendUserStatusChanged.listen( - (event) => { +export const { start: startUserStatus, stop: stopUserStatus } = + createEventSource(async (addEventListener) => { + addEventListener( + await events.friendUserStatusChanged.listen((event) => { const { userId, status } = event.payload; const hasValidName = @@ -35,36 +24,21 @@ export async function startUserStatus() { ...current, [userId]: status, })); - }, + }), ); - subscription.addUnlisten(unlistenStatus); - const unlistenUserStatusChanged = await events.userStatusChanged.listen( - (event) => { + addEventListener( + await events.userStatusChanged.listen((event) => { currentPresenceState.set(event.payload); - }, + }), ); - subscription.addUnlisten(unlistenUserStatusChanged); - const unlistenFriendDisconnected = await events.friendDisconnected.listen( - (event) => { + addEventListener( + await events.friendDisconnected.listen((event) => { const { userId } = event.payload; friendsPresenceStates.update((current) => removeFromStore(current, userId), ); - }, + }), ); - subscription.addUnlisten(unlistenFriendDisconnected); - - subscription.setListening(true); - } catch (error) { - console.error("Failed to initialize user status listeners", error); - throw error; - } -} - -export function stopUserStatus() { - subscription.stop(); -} - -setupHmrCleanup(stopUserStatus); + });