Replication System - vux0303/typescript-ECS-framework GitHub Wiki

Usage

web-based games, nodejs server, simulation run on both client and server

Idea

ECS allow our game code break into to separate and clean chunk of two chunk of code: data and systems. The idea behind this replication system is synchronize data between clients and server.

A typical multiplayer is where all clients and server share a level, so level's data should be identical on all sides. Major different in data on character states and control on each clients which is not implemented to sync them. Overall, this system is just an experiment on the idea.

Workflow

This system provide:

Decorator

Example using a decorator to have position interpolated when receiving message from server.

    class Position extends ecs.Component {
        @serverToClient({isBroadcast, processors:[]}, {processors:[Interpolator]})
        x: number = 0;
        @serverToClient({isBroadcast, processors:[]}, {processors:[Interpolator ]})
        y: number = 0;
    }

There are two decorator for this system:

  • @clientToServer: sent data on change, on client env, with client deliver options and server receive options
  • @serverToclient: sent data on change, on server env, with server deliver options and client receive options

For now, deliver options and receive option are just processors. Except for deliverOption of @serverToclient allow you to chose to broadcast the message or not.

Some important notes that these two decorator can not use on a same properties, which will cause an infinite feedback loop. The system will throw error to prevent that happen.

In real cases, client sent input and server return position, so there no reason to use them together. Also you can work around by using additional data.

Decorator work on ecs.Singleton.
Decorator don't work on reference types or getter/setter. There also no point in using getter and setter in ECS, all properties should be public.

MessageProcessSystem

MessageProcessSystem allow you to define a processor that you can use in your properties decorator.

At the end of each frame, all changing data will stored into an object called content. This object then passing through all processors that was assigned.

Each processor receive the content object by overriding process method. content actually contains data of all others processors, so to know which entries of content will be process by an particular processor, a second parameter keys provide indexes to data entries that belong to that processor.

    class Interpolator extends MessageProcessSystem {
        process(content: any, keys: string[]) {
            //keys are indexes to data entries that belong to this processor
            //you can iterate over all data entries for this processor by this
            //content are collect at runtime so Typescript type system can not reach here.
            keys.forEach((key) => {
                content[key]
            })
            
            //if this system is used to processing received data, you can access current value of a property by following method to make comparison and resolve conflict
            this.getKeyCurrentValue(key);
        }
    }

This system are supposed to collect what to be sent, there are no networking implementation provided so you have to do it yourself. After passing through all processors, final payload will be given to a processor to sent over network.
Define this processor just as same as others. But in this one, you will pay attention to other override method.

    class MessageShipSystem extends MessageProcessSystem {

        shipMessageAtClient(content: any) {
            //content are payload to send to server
        };

        shipMessageAtServer(personalMsg: any, broadcastContent: any) {
            //to get payloads for a particular client
            personMsg.forEach((clientID) => {
                let personalContent = personMsg[clientID];
            })

            //broacastContent are payload to send to all client
        }
    }

shipMessageAtClient run at environment where repConfig.repEnv are set to repEnv.Client.
shipMessageAtServer run at environment where repConfig.repEnv are set to repEnv.Server.

You also have to add this processor to the repConfig object before initiate an admin:

repConfig.messageShipSystem = MessageShipSystem;

RepAttachmentComponent

Decorators only mark properties of a component class. To synchronize a component instance, add a RepAttachmentComponent to entities that you wish to synchronize their components.
ReplicationSystem will execute logic on this component so don't forget to register this system as your last systems.