Not a tutorial, but I thought I would share what I recently discussed elsewhere regarding my engine layout and setup:
The application “shell” is mainly used as a kind of router to link websocket client connections to worker_thread instances, using MessagePort as the communication medium between websocket connection and worker_thread.
Each worker_thread instance acts as a sort of mini “game loop” which encapsulates all the logic required to manage a collection of spatially-related websocket clients. For this part I use a fork of “ecsy”, an Entity-Component-System engine, to manage interactions between game objects and Babylon.js NullEngine for game object physics.
Each individual Entity which is used for its player counterpart has a custom Machina.js-inspired “server-side” client FSM, whose transitions link directly to websocket clients, controlling their state.
And finally, on the client side, websocket clients have their own copy of the client FSM which contains more client-specific things like managing actual Babylon.js visuals, player input, media asset management, and the client game loop.
This setup is done for a number of reasons, mainly a) reuse of containerized resources during deployment, b) spatially segregating websocket connections, and c) separation-of-concerns for what actually constitutes the portion of the game engine referred to as the “game loop” from the rest of the web-app-based engine (database connectivity, logging, authN and authZ, hot-reloading the UI during development, etc).
Regarding Websockets performance, I don’t think this would be an issue once connection is established, as message frames lack HTTP overhead. However, they are typically JSON or plaintext, so it would behoove the developer to eventually commit to byte-packing message-frames sent over the wire, similar to gRPC or old-school wsock32 if you coded in the previous century , and sending them as binary websockets.