Skip to content

Server side

SFilinsky edited this page Oct 28, 2021 · 29 revisions

World layer

World layer contains game world state as single data structure. Its responsibility is to process and update it according to events that come from Event layer.

After World layer processed all events, world state snapshot goes to Presentational layer.

Except event processing, World layer can have recurring tasks to update world state with time. Those tasks can run and stop depending on world state.

This layer can be local for single-player games and remote for multiplayer.

World layer passes only updates to presentation layer, so it compares new state value after applying all changes with old state.

Domains

  • Scene entities organization World layer organized game entities by Entity-Component-System pattern

  • World persistence We can save game state server-side

  • Server-side aspects

    • AI
    • Physics
    • User Input reactions

Architecture

World layer keeps world state in a single structure. Structure is Record with Entities, each Entity is a set of Components handled by Systems.

Systems perform changes of world state. They are pure functions, that take world state and any needed extra data, and returns updated copy of it. Those functions can be closured by container functions to provide additional cache.

Since systems are pure functions, they can be easily covered with tests.

Idea:

Systems usually describe rules of how game world changes. Those rules are done as javascript logic.

The idea is to replace systems with separate rules that can be applied to the game world. It will make game logic composable and easily extencible since game development will mean just describing those world rules.

It can lead to performance problems if each rule is handled separately, since they can use same components but iterate over them separately. Also, if there are too many rules, pipeline can become crowded.

To solve this, we can group those rules as RuleSets, which are basically array of rules that handle same data and on it called as small pipeline. It will allow to handle grouped rules in one iteration and organize them semantically.

The main issue here is that rules can be interrelated and need each other's results.

Example:

  • Some rule needs to be applied only after collision rule detected collision.
  • Characted is killed so it's not needed to calculate physics for it this frame, but physics system runs after health calculation.

This can be solved several ways:

  • Allow rules to pass events downstream.
    This can lead to problem when two rules need to pass some state to each other, but it should be solved another way around since it's most likely the design issue. More to it, it's general for any pipeline since it's improssible to determine execution order (which is bad by default). But how to persist events if they are not part of game world state?

  • Keep needed data inside of Components, so they can be used by other rule conditions. But can all cases be solved like this? It can make connections between rules not so clear. Also, some events cannot be easily related to existing components, so they will need to create extra Entities with components that just keep those events as world state. It might complicate game world structure and make it harder to understand > which rules Components are related to.

Server Engine Example

const engine = new ServerEngine({

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

  // List of systems for pipeline, in order of execution
  systemsPipeline: [
    buildDamageSystem({ enableLogging: true }),
  ],

  // 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
    }
  }
});

Framing

Since in multiplayer users can see different parts of the world as well as have their own view of it, we need to frame (filter) world state before passing it to client. This is where framing logic comes into play.

It passes world state with client-related information to framing logic, so it can remove all private, restricted or excessive information before it goes to client-side.