import { Callbag } from "callbag"; import { INPUT_FREQUENCY, LockstepProcessor, LockstepState } from "../Ecs/Lockstep"; export const enum MessageTypes { RESET = 0, INPUT = 1, } export type Packet = { t: TypeId } & Payload; export type ClientMessage = | Packet }>; export type ServerMessage = | Packet }> | Packet; type Server = Callbag, ServerMessage>; export abstract class LockstepClient { private state: LockstepState; public constructor( public readonly engine: LockstepProcessor, ) { const initialState = this.initState({}); this.state = new LockstepState(initialState, engine); } public abstract initState(init: Partial): State; public abstract gatherInput(): Input; /** * Connect to a [perhaps emulated] server and return a disconnect callback */ public connect(server: Server): () => void { let serverTalkback: Server | null = null; const sampleInput = () => { if (serverTalkback) { const input = this.gatherInput(); this.state.addLocalInput(input); serverTalkback(1, {t: MessageTypes.INPUT, i: input}); setTimeout(sampleInput, INPUT_FREQUENCY); } }; // connect to server server(0, (mode: number, data: Server | ServerMessage) => { if (mode == 0) { serverTalkback = data as Server; // kickoff input sender setTimeout(sampleInput, INPUT_FREQUENCY); } else if (mode == 1) { // server message const message = data as ServerMessage; switch(message.t) { case MessageTypes.RESET: const resetState = this.initState(message.s); this.state = new LockstepState(resetState, this.engine); break; case MessageTypes.INPUT: this.state.addCanonInput(message.i); break; } } else if (mode == 2) { // disconnected console.log("Disconnected from server", data); serverTalkback = null; } }); // disposal return () => { serverTalkback?.(2); serverTalkback = null; }; } }