import { EventEnvelope } from "@/api_fsb"; import { RootState } from "@/store"; import { createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit'; const eventAdapter = createEntityAdapter({ selectId: (model: EventEnvelope) => model.event_uuid!, sortComparer: (a, b) => { // ASC by timestamp return a.timestamp!.localeCompare(b.timestamp!); }, }); const grlEventsSlice = createSlice({ name: 'grlEvents', initialState: eventAdapter.getInitialState(), reducers: { addEvent: eventAdapter.addOne, addEvents: eventAdapter.addMany, upsertEvent: eventAdapter.upsertOne, // Add or Update } }) export const { addEvent, addEvents, upsertEvent } = grlEventsSlice.actions; export const eventSelectors = eventAdapter.getSelectors( (state: RootState) => state.events ); export const selectAllEvents = (state: RootState): EventEnvelope[] => eventSelectors.selectAll(state); const BASE_SPEED = 40; // px/sec at idle const MAX_SPEED = 380; // px/sec at surge const RATE_WINDOW_MS = 10_000; // ms window to measure message frequency const RATE_TO_SPEED = 55; // px/sec added per msg/sec // --- Speed: derived from arrival rate of recent items --- export const selectCurrentSpeed = createSelector(selectAllEvents, (items) => { const now = Date.now(); const recentCount = items.filter((m) => { const ts = new Date(m.timestamp!).getTime(); return now - ts < RATE_WINDOW_MS; }).length; const rate = recentCount / (RATE_WINDOW_MS / 1_000); // msgs/sec return Math.min(BASE_SPEED + rate * RATE_TO_SPEED, MAX_SPEED); }); export default grlEventsSlice.reducer