Overview - worldscapes/engine GitHub Wiki

Concept

Engine is a framework that consists from two parts: client-side and server-side. They are provided as objects (Engine API + global state for this engine instance) that run pipeline.

There are two ways to run those parts:

  1. Singleplayer: Both parts can together run localy on player computer
  2. Multiplayer: One or more client-sides connect to mutual server-side which is either on one of clients (Host) or on remote server

It allows to separate presentation and world simulation logic and make sure they do not mix.

Main game world simulation runs server-side. Simulation uses ECR Architecture. It holds game-world state, updates it every simulation tick and outputs updated game-world state down the pipeline.

After simulation, Engine passes updated world state from server-side to client-side using Networking.

Then world state update is applied to client world state.

Then client-side runs it's own simulation to process world state before it's displayed. This simulation can include some entities that do not exist on server-side (e.g. cameras, effects).

After the processing, client world state goes to presentation layer. This is logic that displays world to user, collects his input and returns commands to be executed in Engine.

For presentation Engine will use abstraction, built-in implementation will be done Babylon.js (which will use additional abstractions inside). In case of need custom abstraction can be implemented to be used with Engine.

Engine does not depend on presentation and assets, so it can run simulation even if assets are still loading.

For interaction Engine uses Commands, which are source of Engine state changes. Commands are generated by player input or simulated systems.

Pipeline

Important feature of engine is unidirectional data flow. It makes developer focused more on data processing instead of internal organization, allows to avoid unneeded dependencies and simplify engie struture.

Data flow is organized as pipeline. Pipeline is split by layers, each layer targets different concerns.

Layers are not also of each other, they are organized and interconnected by engine itself. Basically, layers take input and produce output by chain each tick. It's needed to improve engine extensibility and make it easier to think about layers.

Engine can do additional processing in between layers and this processing cannot be customized (adding new layers requires engine code update), but layers can be customized by engine user.

Aso, different pipeline parts are not required to have same tickrate, they just give latest processing result to be used in subsequent parts. This data can be updated anytime in future.

Those pipelines can run as separate applications and only connect by networking world state and events. Because of this, pipelines can have different tick rate. Since engine runs layers, it can monitor performance and set tickrate depending on performance of each part.

All layers should only have one direction of data flow. The only exception is Network layer which is bidirectional protocol for data exchange between server and client.

Pipeline flow

  • Collect input from validation layer on server side, put event into Event container
  • Run simulation
  • Frame updated world state
  • Network framed world state to client
  • Merge framed world state on client
  • Run client-side simulation
  • Pass simulated client-side world state to display implementation
  • Receive input from display
  • Pass input to server-side

Structure

  • Server-side

    • World Simulation layer - keeps and updates world state
      • Takes: events, world state
      • Returns: new events, updated world state
    • Framing layer - filters world state and server-side events before passing it to player
      • Takes: events, world state, user info
      • Returns: new events, framed world state
    • Server networking layer - handles client connection, sends messages to client
      • Validation layer - validates events received from connections
      • Translation layer - translates server data into messages that will be sent by network
        • Takes: data to be sent from server
        • Returns: data as messages
      • Networking adapter - transmits messages from client to server
  • Client-side

    • Client networking layer - requests connection to server, sends user input events to server
      • Validation layer
      • Translation layer
      • Networking adapter
    • Merge layer - gets server world state and merges it with client world state (by default client state is replaced by server state)
      • Takes: server world state, client world state
      • Returns: updated client world state
    • Preprocessing (Client World Simulation) layer - keeps client entities, updates client world state
      • Takes: events, current clent world state
      • Returns: new events, updated client world state
    • Display layer - displays world state to player and receives input
      • Takes: current client world state
      • Returns: user input events

Server-side and Client-side

WorldscapesServer and WorldscapesClient are classes that run pipeline. They are needed to organize data flow on server-side and client-side and hold additional engine state.

Both provide API to extend and customize pipeline.

Server-side domains

  • World simulation
    • AI
    • Physics
    • User input reactions
  • World persistence

Client-side domains

  • Render
  • Audio
  • Cameras
  • User interface
  • Resource management
  • Reading user input
  • User input reactions

WorldscapesServer example

const engine = new WorldscapesEngine({

  // Network server implementation that will be used by Engine
  networkServer: new NetworkServer(),

  // Function that will handle errrors if they happen inside of entity
  errorHandlerWrap: callback => {
    try {
        return callback;
    } 
    catch (error) {
        // handling

        throw new HandlerWrapWorkedError(); // this is needed to notify Engine that error was handled, so it keeps old component states
    }
  }
});

engine
  .addRule(buildDamageRule({ enableLogging: true }))
  .run();

Trade-offs

Some architectural decisions can lead to restrictions, which should be described here when figured out.

  • Can be hard to implement game rehost when server disconnects (is not needed in MMO)