import type { PositionEntry } from './protocol'; export interface Coalescer { /** Buffer the latest position for `deviceId`. Replaces any earlier in-flight value. */ push: (p: PositionEntry) => void; /** Drop the buffer and cancel any pending flush. Used on event-switch and shutdown. */ cancel: () => void; } /** * Per-frame coalescer at the WebSocket boundary. * * Every incoming `position` message lands in a per-device buffer; the * latest wins. One `requestAnimationFrame` tick flushes the snapshot to * the consumer (typically `usePositionStore.getState().applyPositions`). * That caps the dispatch rate at the browser's frame rate (~60 Hz) * regardless of how fast positions arrive. * * This is the discipline traccar-web lacks: per-message Redux dispatch * cascades through selectors and rebuilds full feature collections at * every position arrival, which is the most likely cause of its observed * lag at high update rates. */ export function createCoalescer(onFlush: (snapshot: PositionEntry[]) => void): Coalescer { const buffer = new Map(); let rafId: number | null = null; function flush(): void { rafId = null; if (buffer.size === 0) return; const snapshot = Array.from(buffer.values()); buffer.clear(); onFlush(snapshot); } return { push(p) { buffer.set(p.deviceId, p); if (rafId === null) { rafId = requestAnimationFrame(flush); } }, cancel() { buffer.clear(); if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; } }, }; }