import { useCallback, useEffect, useRef, useState } from 'react'; import { ipcChannels } from '../../../shared/ipc/contracts'; import type { SceneNpcTokensSessionEvent, SceneNpcTokensSessionState, } from '../../../shared/types'; import { clampNpcTokenSessionScale, DEFAULT_NPC_TOKEN_SESSION_SCALE, } from '../../../shared/types/appPlayers'; import { getDndApi } from '../dndApi'; function withScale(state: SceneNpcTokensSessionState): SceneNpcTokensSessionState { return { ...state, scale: clampNpcTokenSessionScale(state.scale), }; } function applyEvent( prev: SceneNpcTokensSessionState, event: SceneNpcTokensSessionEvent, ): SceneNpcTokensSessionState { if (event.kind === 'clear') { return { revision: prev.revision + 1, byPlacementId: {}, scale: DEFAULT_NPC_TOKEN_SESSION_SCALE, }; } if (event.kind === 'setScale') { const scale = clampNpcTokenSessionScale(event.scale); if (prev.scale === scale) return prev; return { ...prev, revision: prev.revision + 1, scale }; } return { revision: prev.revision + 1, byPlacementId: { ...prev.byPlacementId, [event.placementId]: { nx: event.nx, ny: event.ny }, }, scale: prev.scale ?? DEFAULT_NPC_TOKEN_SESSION_SCALE, }; } export function useSceneNpcTokensSession(): [ SceneNpcTokensSessionState, { dispatch: (event: SceneNpcTokensSessionEvent) => void }, ] { const api = getDndApi(); const [state, setState] = useState({ revision: 0, byPlacementId: {}, scale: DEFAULT_NPC_TOKEN_SESSION_SCALE, }); const localRevRef = useRef(0); useEffect(() => { void api.invoke(ipcChannels.sceneNpcTokensSession.getState, {}).then((res) => { const next = withScale(res.state); // Не затираем более свежий optimistic/broadcast стейт устаревшим getState. if (next.revision < localRevRef.current) return; localRevRef.current = next.revision; setState(next); }); return api.on(ipcChannels.sceneNpcTokensSession.stateChanged, ({ state: incoming }) => { const next = withScale(incoming); if (next.revision < localRevRef.current) return; localRevRef.current = next.revision; setState(next); }); }, [api]); const dispatch = useCallback( (event: SceneNpcTokensSessionEvent) => { setState((prev) => { const next = applyEvent(prev, event); localRevRef.current = Math.max(localRevRef.current, next.revision); return next; }); void api.invoke(ipcChannels.sceneNpcTokensSession.dispatch, { event }); }, [api], ); return [state, { dispatch }]; }