Skip to content

Architecture Overview

Lorenzo Rossi edited this page Oct 7, 2020 · 1 revision

How does this machine work?

I've crafted a custom ECS system that helps me to keep the complexity reduced, in this wiki I'll assume you have a basic understanding of what an ECS architecture consists of. Each entity can have one or more components of the same type (depending on storage). Each entity has a randomly generated id and each component of a multi-type has a multiId. To identify a single component all of this parts are needed (entityId, type, multiId?).

This slight change in the ECS component architecture allows me some strange behaviour without complexity cost, for example I can hide an entity, or just a single component. So a DM could have multiple notes for each player, some visible (thus sent to the viewers), some personal.

The other design I still haven't mentioned is the Resource (you may find this called singleton component in other ECS designs), that is a single component that does nothing but hold data. Those are used for data that should not be stored in a single system, but should be shared (or saved/synced).

The Systems then are the working heart of the design, they react to changes in the components, and change the components themselves, they can also spawn/despawn entities, add or remove components and such. The beauty of the design is that each component tackles only a bit of the complexity, when a systems becomes too big it will be split into two, each doing a finer task.

The networking and serializing codes are also quite simple (though not really optimized) thanks to the ECS design. Each type of component and resource can decide wether to be saved and to be synced, the general rule of thumb is that the input components/resources should be saved and synced (ex. position) while the middle/front components should not (ex. visibility). Both saving and syncing are made with the same tools, but syncing is slightly more complex since each component/entity can be hidden from the viewers, this is handled by the ClientNetworkSystem/HostNetworkSystem. The idea is that in storage (and when a client connects) all of the components are saved as json, then when a component changes the changed parts are sent aswell.

This is quite clean but it's not perfect as each change is sent to all of the viewers, not that big of a problem, until you handle big files. Turns out sending 10Mb of data to 10 computers is quite slow, even in a LAN. To help with this I've added an upload indicator, telling the master when packets are still being sent. In the future I would like to add a bittorrent/like protocol in a different channel for big data chunks.

Purity

Of course this is not all, given that I'm working in typescript, for reasons of time and project size I'm using an external library to handle the graphics, PIXI is more like a framework than a library so my ECS system is mixed up with it quite a lot. Outside of PIXI the project uses Phases to bundle everything together, gluing Vue, ECS and Pixi. This is a concept I copied straight from carcassonne, in that project they became quite bloated as they became the main place to put behavioural code. Systems are then an opportunity to divide code and manage its complexity. When you're adding game code do a Stage ask yourself if you can put it in a System, if you can please do.

Performance

All of what I discussed is clean and great, but how does it perform? Having implemented all this in TypeScript in can say a big, meh. I expected far worse but the majority of the time is spent in Vue or the DOM, there is still a lot to optimize, PIXI is really easy to use but it could be faster, the network and storage sizes could be reduced with better serialization (god I miss serde), ecc. But all in all, everything is quite smooth. Every once in a while I still think about rewriting everything from scratch in rust, but I'll need to weight the gains and losses (if you're intrested please contact me).

Clone this wiki locally