Files
DndGamePlayer/app/renderer/shared/tokens/useSceneTokensSession.ts
T
2026-08-18 14:42:06 +08:00

83 lines
2.7 KiB
TypeScript

import { useEffect, useMemo, useRef, useState } from 'react';
import { ipcChannels } from '../../../shared/ipc/contracts';
import type { SceneTokensSessionEvent, SceneTokensSessionState } from '../../../shared/types';
import { getDndApi } from '../dndApi';
function applyEvent(
prev: SceneTokensSessionState | null,
event: SceneTokensSessionEvent,
): SceneTokensSessionState {
const base = prev ?? { revision: 0, byPlacementId: {} };
if (event.kind === 'clear') {
return { revision: base.revision + 1, byPlacementId: {} };
}
const placementId = String(event.placementId ?? '');
if (!placementId) return base;
const cur = base.byPlacementId[placementId];
if (event.kind === 'setSize') {
return {
revision: base.revision + 1,
byPlacementId: {
...base.byPlacementId,
[placementId]: {
...(typeof cur?.nx === 'number' ? { nx: cur.nx } : {}),
...(typeof cur?.ny === 'number' ? { ny: cur.ny } : {}),
sizeN: event.sizeN,
},
},
};
}
return {
revision: base.revision + 1,
byPlacementId: {
...base.byPlacementId,
[placementId]: {
nx: event.nx,
ny: event.ny,
...(cur?.sizeN !== undefined ? { sizeN: cur.sizeN } : {}),
},
},
};
}
export function useSceneTokensSession(): [
SceneTokensSessionState | null,
{ dispatch: (event: SceneTokensSessionEvent) => Promise<void> },
] {
const api = getDndApi();
const [state, setState] = useState<SceneTokensSessionState | null>(null);
const localRevisionRef = useRef(0);
useEffect(() => {
void api.invoke(ipcChannels.sceneTokensSession.getState, {}).then(({ state: s }) => {
localRevisionRef.current = Math.max(localRevisionRef.current, s.revision);
setState(s);
});
return api.on(ipcChannels.sceneTokensSession.stateChanged, ({ state: s }) => {
// Не затираем более свежий optimistic-стейт устаревшим broadcast
// (например, emit при смене сцены, пока последний move ещё в полёте).
if (localRevisionRef.current > s.revision) return;
localRevisionRef.current = s.revision;
setState(s);
});
}, [api]);
const apiWrap = useMemo(
() => ({
dispatch: async (event: SceneTokensSessionEvent) => {
setState((prev) => {
const next = applyEvent(prev, event);
localRevisionRef.current = Math.max(localRevisionRef.current, next.revision);
return next;
});
const res = await api.invoke(ipcChannels.sceneTokensSession.dispatch, { event });
void res;
},
}),
[api],
);
return [state, apiWrap];
}