import {SyncedState} from "../state/SyncedState"; import {isMapAction, MapAction} from "../actions/MapAction"; import {hexMapReducer} from "./HexMapReducer"; import {isUserAction, UserAction} from "../actions/UserAction"; import {userReducer} from "./UserReducer"; import {exhaustivenessCheck} from "../util/TypeUtils"; import {SyncableAction} from "../actions/ServerAction"; export function syncedStateReducer(state: SyncedState, action: (MapAction|UserAction)): SyncedState { if (isMapAction(action)) { const newMap = hexMapReducer(state.map, action) return newMap === state.map ? state : { ...state, map: newMap } } else if (isUserAction(action)) { const newUser = userReducer(state.user, action) return newUser === state.user ? state : { ...state, user: newUser } } exhaustivenessCheck(action) } interface SyncableActionApplicationResults { state: SyncedState appliedActions: readonly T[] } /** Applies a sequence of actions, skipping failed actions, and returns an array of the successful ones. */ export function applySyncableActions(state: SyncedState, actions: readonly T[]): SyncableActionApplicationResults { return actions.reduce(({state, appliedActions}: SyncableActionApplicationResults, action) => { // Save our breath by reusing the actions array if this is the last action in the list. const resultActions = (appliedActions.length === actions.length - 1) ? actions : [...appliedActions, action] try { const newState = syncedStateReducer(state, action) if (newState === state) { // Act as if it wasn't there - it did nothing. return { state, appliedActions } } return { state: newState, appliedActions: resultActions } } catch (e) { // Just skip it - continue as if it wasn't there, and don't let the exception escape. return { state, appliedActions } } }, {state: state, appliedActions: []}) }