UNPKG

@inglorious/engine

Version:

A JavaScript game engine written with global state, immutability, and pure functions in mind. Have fun(ctional programming) with it!

92 lines (76 loc) 2.78 kB
import { extend } from "@inglorious/utils/data-structures/objects.js" import { coreEvents } from "../core-events.js" // A constant for the server's WebSocket URL. const DEFAULT_SERVER_URL = `ws://${window.location.hostname}:3000` const DEFAULT_RECONNECTION_DELAY = 1000 /** * Creates and returns the multiplayer middleware. * @returns {Function} The middleware function. */ export function multiplayerMiddleware(config = {}) { const serverUrl = config.serverUrl ?? DEFAULT_SERVER_URL const reconnectionDelay = config.reconnectionDelay ?? DEFAULT_RECONNECTION_DELAY let ws = null const localQueue = [] // The middleware function that will be applied to the store. return (api) => (next) => (event) => { if (coreEvents.includes(event.type)) { return next(event) } // Establish the connection on the first event. if (!ws) { establishConnection(api) } // Only send the event to the server if it didn't come from the server. if (!event.fromServer) { if (ws?.readyState === WebSocket.OPEN) { // If the connection is open, send the event immediately. ws.send(JSON.stringify(event)) } else { // If the connection is not open, queue the event for later. localQueue.push(event) } } // Pass the event to the next middleware in the chain, // which is eventually the store's original dispatch function. return next(event) } /** * Attempts to establish a WebSocket connection to the server. */ function establishConnection(api) { // If a connection already exists, close it first. if (ws) { ws.close() } ws = new WebSocket(serverUrl) // ===================================================================== // WebSocket Event Handlers // ===================================================================== ws.onopen = () => { // Send any queued events to the server. while (localQueue.length) { ws.send(JSON.stringify(localQueue.shift())) } } ws.onmessage = (event) => { const serverEvent = JSON.parse(event.data) if (serverEvent.type === "initialState") { // Merge the server's initial state with the client's local state. const nextState = extend(api.getState(), serverEvent.payload) api.setState(nextState) } else { // Dispatch the event to the local store to update the client's state. api.dispatch({ ...serverEvent, fromServer: true }) } } ws.onclose = () => { // Attempt to reconnect after a short delay. setTimeout(() => establishConnection(api), reconnectionDelay) } ws.onerror = () => { ws.close() // The 'onclose' handler will trigger the reconnect. } } }