Learning Svelto.ECS by Example: The Unity Survival Example

note: while most of the reasonings are still valid, the code listed in this article is obsolete and the example has now radically different code.

Introduction

Lately I have been discussing Svelto.ECS extensively with several, more or less experienced, programmers. I gathered a lot of feedback and took a lot of notes that I will be using as starting point for my next articles where I will talk more about the theory and good practices. Just to give a little spoiler, I realized that the biggest obstacle that new coders face when starting using Svelto.ECS is the shift of programming paradigm. It’s astonishing how much I have to write to explain the novel concepts introduced by Svelto.ECS compared to the small amount of code written to develop the framework. In fact, while the framework itself is very simple (and lightweight), learning how to move from the class inheritance heavy object oriented design or even the naive Unity components based design, to the “new” modular and decoupled design that Svelto.ECS forces to use, is what usually discourages people from adopting the framework.

Being the framework extensively used at Freejam, I also noticed that it’s thanks to my continuous availability to explain the fundamental concepts that my colleagues have less of a hard time to get in to the flow. Although Svelto.ECS tries to be as rigid as possible, bad habits are hard to die, so users tend to abuse the little flexibility left to adapt the framework to the “old” paradigms they are comfortable with. This can result in a catastrophe due to misunderstandings or reinterpretation of the concepts that are behind the logic of the framework. That’s why I am committed to write as many articles as possible, especially because I know that the ECS paradigm is the best solution I found so far to write efficient and maintainable code for large projects that are refactored and reshaped multiple times over the span of years and Robocraft as well as Cardlife are the existing proof of what I try to demonstrate.

I am not going to talk much about the theories behind the framework in this article, but I want to remind what took me to the path of ditching the use of an IoC Container and starting using exclusively the ECS framework: an IoC container is a very dangerous tool if used without Inversion of Control in mind. As you should have read from my previous articles, I differentiate between Inversion of Creation Control and Inversion of Flow Control. Inversion of Flow Control is basically the Hollywood principle “Don’t call us, we will call you”. This means that dependencies injected should never been used directly through public methods, in doing so you would just use an IoC container as a substitute of any other form of global injection like singletons. However, once an IoC container is used following the IoC principle, it mainly ends up in using repeatedly the Template Pattern to inject managers used only to register the entities to manage. In a real Inversion of Flow Control context, managers are always in charge of handle entities. Does it sounds like what the ECS pattern is about? It indeed does. From this reasoning I took the ECS pattern and evolved it into a rigid framework to the point it can be considered using it like adopting a new coding paradigm.

The Survival Example

let’s start downloading the project from https://github.com/sebas77/Svelto.ECS.Examples.Survival

Remember this? I rewrote it for you!

open the scene Level01 and open the project in your IDE. Everything starts from maincontext.cs file.

The Composition Root and the EnginesRoot

The class Main is the Application Composition Root. A Composition Root is where dependencies are created and injected (I talk a lot about this in my articles). A composition root belongs to the context, but a context can have more than one composition root. For example a factory is a composition root. Furthermore an application can have more than one context but this is an advanced scenario and not part of this example.

Before to start digging in the code, let’s introduce the first terms of the Svelto.ECS domain language. ECS is an acronym for Entity Component System. The ECS infrastructure has been analyzed abundantly with several articles written by many authors, but while the basic concepts are in common, the implementations differ a lot. Above all, there isn’t a standard way to solve the few problems rising from using ECS oriented code. That’s where I put most of my effort on, but this is something I will talk about later or in the next articles. At the heart of the theory there are the concepts of Entity, Components (of the entities) and Systems. While I understand why the word System has been used historically, I initially found it not intuitive for the purpose, so Engine is synonym of System and you may use it interchangeably according your preferences.

The EnginesRoot class is the core of Svelto.ECS. With it is possible to register the engines and build all the entities of the game. It doesn’t make much sense to create engines dynamically, so all the engines should be added in the EnginesRoot instance from the same composition root where it has been created. For similar reasons, an EnginesRoot instance must never been injected and engines can’t be removed once added.

We need at least one composition root to be able to create and inject dependencies wherever needed. Yes, it’s possible to have even more than one EnginesRoot per application, but this is too not part of this article, which I will try to keep as simple as possible. This is how a composition root with engines creation and dependencies injection looks like:

This code is part of the survival example which is now well commented and follows almost all the good practice rules that I suggest to use, including keeping the engines logic platform independent and testable. The comments will help you to understand most of it, but a project of this size may be already too much to swallow if you are new to Svelto. For this reason, let’s proceed as we would have done if started from scratch:

Entities

the first step after creating the empty Composition Root and an instance of the EnginesRoot class, would be to identify the entities you want to work with first. Let’s logically start from the Player Entity. The Svelto.ECS entity must not be confused with the Unity GameObject. If you had the chance to read other ECS related articles, you will see that in many of those, entities are often described as indices. This is probably the worst way possible to introduce the concept. While this is true for Svelto.ECS too, it’s well hidden. As matter of fact I want the Svelto.ECS user to visualize, describe and identify every single entity in terms of Game Design Domain language. An entity in code must be an entity described in the game design document. Any other form of entity definition will result in a contrived way to adapt your old paradigms to the Svelto.ECS needs. Follow this fundamental rule and you won’t be wrong in most of the cases. An entity class doesn’t exist per se in code, but you still must define it in a not abstract way.

Engines

Next step is to think about what behaviours to give to this Entity. Every behaviour is always modeled inside an Engine, there is no way to add logic in any other classes inside a Svelto.ECS application. For this purpose we can start from the player character movement and define the PlayerMovementEngine class. The name of the engine must be very specific, as the more specific it is, the higher is the chance the Engine will follow the Single Responsibility Rule. Naming classes properly in Svelto.ECS is of fundamental importance. It’s not just to comunicate clearly your intentions, but it’s actually more about letting you think about your intentions.

For the same reason is as important to put your engine inside a very specific namespace. If you use namespaces according the folder structure, adapt to the Svelto.ECS convention. Using very specialized namespaces helps a lot to identify code design errors when entities are used inside not compatible namespaces. For example, you wouldn’t expect any enemy entity to be used inside a player namespace, unless you want to break the good rules related to modularity and decoupling of objects. The idea is that objects of a specific namespace can be used only inside that namespace or a parent namespace. While with Svelto.ECS is much harder to turn your code in to a fully fledged spaghetti bowl, where dependencies are injected everywhere and randomly, this rule will help you to take your code to an even better level where dependencies are correctly abstracted between classes.

In Svelto.ECS abstraction is pushed on several fronts, but ECS intrinsically promote abstraction of the data from the logic that must handle the data. Entities are defined by their data, not their behaviours. Engines instead are the place where to put the shared behaviours of the same entities, so that engines can always operate on a set of entities.

Svelto.ECS and the ECS paradigm in general allow the coders to achieve one of the holy grails of clean programming, that is the perfect encapsulation of logic. Engines must not have public functions. The only public functions that need to exist are the ones needed to implement framework interfaces. This naturally leads to forget about dependency injection and avoid the awkward code deriving by the use of dependency injection without inversion of control. Engines must NEVER be injected in any other engine or whatever other type of class. If you think to inject an engine, you would just make a fundamental code design mistake.

Compared to Unity monobehaviours, engines already show the first huge benefit, which is the possibility to access to all the entity states of a given type from the same code scope. This means that the code can easily use the state of all the entities directly from the same place where the shared entity logic is going to run. Furthermore separate engines can handle the same entities so that an engine can change an entity state while another engine can read it, effectively putting the two engines in communication through the same entity data. An example of this can be seen with the engines PlayerGunShootingEngine and PlayerGunShootingFxsEngine. In this case the two engines are in the same namespace, so they can share the same entity data. PlayerGunShootingEngine determines if a player target (an enemy) has been damaged and writes the lastTargetPosition of the IGunAttributesComponent (which is a component of the PlayerGunEntity). the PlayerGunShootFxsEngine handles the graphic effects of the gun and reads the position of the currently targeted player target. This is an example of communication between engines through data polling. Later in this article I will show how to let engine communicate between them through data pushing (or data binding). It’s quite logical that engines should (and must) never hold states.

Engines are not supposed to know how to interact with other engines. External communication happens through abstraction and Svelto.ECS solves communication between engines in three different official ways, but I will talk about this later. The best engines are the ones that do not even need to trigger any form of external communication. These engines reflect a well encapsulated behaviour and usually work through a logic loop. Loops are always modeled with a Svelto.Task task inside Svelto.ECS applications. Since the player movement must be updated every physic tick, it would be natural to create a task that is executed every physic update. Svelto.Tasks allows to run every kind of IEnumerator on several types of schedulers. In this case we decide to create a task on the PhysicScheduler that allows to update the player position:

Svelto.Tasks tasks can be executed directly or through ITaskRoutine objects. I won’t talk much about Svelto.Tasks here as I wrote other articles for it. The reason I decided to use a task routine instead to run the IEnumerator implementation directly is quite discretional. I wanted to show that is possible to start the loop when the player entity is added in the engine and stop it when it is removed. However in order to do so, I need to know when the entity is added and removed.

Svelto.ECS introduces the Add and Remove callbacks to know when specific entities are added or removed. This is something unique in Svelto.ECS, but it should be used wisely. I have often seen these callbacks being abused, as in many cases is just enough to query entities. Even holding an entity reference as engine field must be seen as an exception more than a rule.

Only when these callbacks need to be exploited the engine must inherit either from SingleEntityViewEngine<EntityView> or a MultiEntitiesViewEngine<EntityView1,…,EntityViewN>. Again the use of those should be rare and by no means they intend to communicate what entities the engine is going to handle.

Engines more commonly implement the IQueryingEntityViewEngine interface instead. This allows to access the entity database and retrieve data from it. Remember you can always query any entity from inside an engine, but in the moment you are querying an entity that is not compatible with the namespace where the engine lies, then you know you are already doing something wrong. Engines should never assume that the entities are available and they must work on a set of entities. The fact that the game will always have only one player shouldn’t be assumed like I am doing in the code example. A very common approach to how to query entities is found in the EnemyMovementEngine:

In this case the engine main loop is running directly immediately on the predefined scheduler. Tick().Run() shows the shortest way to run an IEnumerator with Svelto.Tasks. The IEnumerator will keep on yielding to the next frame until at least one Enemy Target is found. Since we know that there will be always just one target (another not nice assumption), I pickup the first available. While the Enemy Target can be just one (although could have been more!), the enemies are many and the engine takes care of the movement logic for all of them. In this case I am cheating, as I am actually using the Unity Nav Mesh System, so all I have to do is just to set the NavMesh destination. To be honest with you, I never used the Unity NavMesh code, so I am not even sure of how it works exactly, this code has been just inherited from the original Survival demo.

Note that the component never exposes the Unity navmesh dependency directly. Entity Component, as I will say later, should always expose value types. In this case this rule also allows to keep the code testable, as the field value type navMeshDestination could be later implemented without using a Unity Nav Mesh stub.

To conclude the paragraph related to engines, note that there isn’t such a thing as a too small engine. Hence, don’t be afraid to write an engine even for just few lines of code, after all you can’t put logic anywhere else and you want your engines to follow the Single Responsibility Rule.

EntityViews

So far we introduced the concept of Engine and an abstracted definition of Entity, let’s now define what an EntityView is. I have to admit, of the 5 concepts of which Svelto.ECS is built upon, the EntityViews is probably the most confusing. Previously called Node, name taken from the Ash ECS framework, I realized that node meant nothing. EntityView may be confusing as well, since programmers usually associate views with the concept coming from Model View Controller pattern, however in Svelto.ECS is called View because an EntityView is how the Engine views an Entity. I like to describe it in this way, as it seems the most natural, but I could have also called it EntityMap, as the EntityView maps the entity components that the engine must access to. This scheme of the Svelto.ECS concepts should help a bit:

I suggested to start working on the Engine first, thus we are on the right side of this scheme. Every Engine comes with its own set of EntityViews. An Engine can reuse namespace compatible EntityViews, but it’s more common for an Engine to define its entity views. The Engine doesn’t care if a Player Entity definition actually exists, it dictates the fact that it needs a PlayerEntityView to work. The writing of the code is driven by the Engine needs, you shouldn’t create the entity and its field before to know how to use those fields. In a more complex scenario, the name of the EntityView could have been even more specific. For example, if we had to write complex engines to handle the logic of the player and the rendering of the graphic of the player (or the animation and so on) we could have had a PlayerPhysicEngine with a PlayerPhysicEntityView, then a PlayerGraphicEngine with a PlayerGraphicEntityView or even a PlayerAnimationEngine with a PlayerAnimationEntityView. Even more specific names could be used, like PlayerPhysicMovementEngine or PlayerPhysicJumpEngine (and so on).

EntityViews are classes that hold only Entity Components. Entity Components in Svelto.ECS are always interfaces that must be implemented, but the Engine and EntityView doesn’t need to know the implementation. For this reason, without even implementing the component yet, I can just start to type the logic in the engine like if it was in place:

Even if PlayerEntityView is still an empty class, I start to use the fields I need. Since an EntityView can hold only components, the field must be a component interface.

Hoping you use an IDE that supports refactoring, the IDE will immediately warn you that the field you are trying to access actually doesn’t exist. This is where the refactoring tools can help you speeding up the writing of the code. For example, using Jetbrains rider (but it’s the same with Visual Studio) you can create the field automatically like this:

this would add the component field in the EntityView like:

since the IPlayerInputComponent interface doesn’t exist yet, I name it and use on the spot. Then I use again the refactoring tool:

this will create an empty interface, so that now the code would look like:

the inputComponent field now exists, but it’s empty so the input field is not defined yet, but I know I need it.

Yes that’s right, I would use again the refactoring tool:

so that the IPlayerInputComponent interface will be filled with the right properties. As long as I don’t run the code, I can build it without needing to implement the Entity Component IPlayerInputComponent interface yet. Honestly, once you get in this flow, you will notice how fast can be coding with Svelto.ECS using the IDE refactoring tools.

EntityViews also help to keep the code modular. If engines would be able to access to all the entity components, you wouldn’t be pushed to think what the engine actually really needs to do. You could fall into the trap to add several responsibilities inside the same engine as you can easily access all the properties, even if they are not closely related to that engine behaviour.

Components

We understood that engines model behaviours for a set of entities data and we understood that engines do not use entities directly, but use the entity components through entity views. We understood that an EntityView is a class that can hold ONLY public entity components. I also hinted that entity components are always interfaces, so let’s give a better definition now:

Entities are a set of data and entity components are the way to access that data. In case you haven’t notice it yet, defining entity components as interfaces is another pretty unique feature of Svelto.ECS. Commonly components in other frameworks are objects. Using interfaces instead helps dramatically to keep the code abstracted. If you follow the Interface Segregation Principle writing small component interfaces, even with just one property each, you will notice that a given point you will start to reuse component interfaces inside different entities. In our example ITransformComponent is reused inside many entity views. Using components as interfaces also allow them to be implemented with the same objects, which in many case allows to simplify the communication between entities that see the same entity through different entity views (or the same one if possible).

Therefore, in Svelto.ECS, an entity component is always an interface and this interface is only used through the field of an EntityView inside an Engine. The Entity Component interface is then implemented with a so called Implementor. We are now starting to define the Entity itself and we are on the left side of the scheme above.

Components should always hold value types and the fields are always getter and setter properties. Exceptions may be made only to write setters ad getters as methods to exploit the ref keyword when this optimization is needed. This doesn’t really mean that the code is data oriented, but it would allow to create testable code as the Engine Logic shouldn’t deal with references to external dependancies. Moreover it prevents people from cheating and use public functions (which would include logic!) of random objects. The only reason one could feel the need to use references inside Entity Components interfaces is to deal with third party dependencies, like unity objects. However, the Survival example, shows how to deal with this case too, leaving the engines code testable without need to be concerned with Unity dependencies.

EntityDescriptors

This is where the Entity Descriptors actually come to help to put everything together and hopefully let everything click in place. We know that Engines can access to Entity data through the Entity Components held by the Entity Views. We know that Engines are classes, EntityViews are classes that hold only Entity Components and that Entity Components are interfaces. While I have given an abstracted definition of entity, we haven’t seen any class that actually represent an entity. This is in line with the concept of entities being just IDs inside a modern ECS framework. However without a proper definition of Entity, this would lead coders to identify Entities with EntityViews, which would be catastrophically wrong. EntityViews is the way several Engines can view the same Entity but they are not the entities. The Entity itself should be always be seen as a set of data defined through the entity components, but even this representation is weak. An EntityDescriptor instance gives the chance to the coder to name properly their entities independently by the engines that are going to handle them. Therefore in the case of the Player Entity, we would need an PlayerEntityDescriptor. This class will then be using to build the entity, and while what it really does is something totally different, the fact that the user is able to write BuildEntity<PlayerEntityDescriptor>() helps immensely to visualize the entities to build and to communicate the intentions to other coders.

However what an EntityDescriptor really does is to build a list of EntityViews!!! In the very early stages of the framework development I was letting the coders building this list of EntityViews manually, which was leading to very ugly code as the couldn’t visualize anymore what an entity actually was.

This is how the PlayerEntityDescriptor looks like:

the EntityDescriptors (and the Implementors) are the only classes that can use identifiers from multiple namespaces. In this case the PlayerEntityDescriptor defines the list of EntityViews to instantiate and inject in the engine when the PlayerEntity is built.

EntityDescriptorHolder

The EntityDescriptorHolder is an extension for Unity and should be used only in specific cases. The most common one is to create a sort of polymorphism storing the information of the entity to build on the Unity GameObject. In this way the same code can be used to build multiple type of entities. For example, in Robocraft, we use a single cube factory that builds all the cubes of the machine. The kind of cube to build is stored in the prefab of the cube itself. However this is fine as long as implementors are the same between the cubes or found on the gameobject as monobehaviours. Building entities explicitly is always preferred, so use EntityDescriptorHolders only when you have understood Svelto.ECS properly otherwise there is the risk to abuse it. This function from the example shows how to use the class:

Note that with this example I am already using the less preferred, not generic, function BuildEntity. I will talk about it in a bit. The Implementors in this case are always monobehaviours in the gameobject. Also this is not a good practice. I actually should remove this code from the example, but left to show you this other case. Implementors, as we will see next, should be Monobehaviours only when strictly needed!

Implementors

Before to build our entity, let’s define the last concept in Svelto.ECS that is the Implementor. As we know now, Entity Components are always interfaces and in c# interfaces must be implemented. The object that implements those interfaces are called Implementors. Implementors have several important characteristics:

  • Allow to uncouple the number of objects to build from the number of entity components needed to define the entity data.
  • Allow to share data between different components, as components expose data through properties, different component properties could return the same implementor field.
  • Allow to create stub of the entity component interface very easily. This is crucial for leaving the engine code testable.
  • Act as bridge between Svelto.ECS Engines and third party platforms. This characteristic is of fundamental importance. If you need unity to communicate with the engines you don’t need to use awkward workarounds, simply create an implementor as Monobehaviour. In this way you could use, inside the implementor, Unity callbacks, like OnTriggerEnter/OnTriggerExit and change data according the Unity callback. Logic should not be used inside these callback, except setting entity components data. Here an example:

Remember the granularity of your EntityViews, entity components and implementors is completely discretional and up to you. More granular they are, more the chance are to be reusable.

Build Entities

Let’s say we have created our Engines, added them in the EnginesRoot, created its EntityViews that use Entity Components as interfaces to be implemented by one or more Implementors. It is now time to build our first entity. An Entity is always built through the Entity Factory instance generated by the EnginesRoot through the function GenerateEntityFactory. Differently than the EnginesRoot instance, an IEntityFactory instance can be injected and passed around. Entities can be built inside the Composition Root or dynamically inside game factories, so for the latter case passing the IEntityFactory by parameter is necessary.

The IEntityFactory comes with several look alike functions. For the purposes of this article I will skip explaining the PreallocateEntitySlots<T> and the BuildMetaEntity<T> functions to focus on the most commonly used BuildEntity<T> and BuildEntityInGroup<T>.

BuildEntityInGroup<T> should actually always preferred, but I didn’t need it for the Survival example, so let’s see how the normal BuildEntity<T> is used inside the:

EnemySpawner Code

Don’t forget to read all the comments in the example, they help to clarify even more the Svelto.ECS concepts. Due to the simplicity of the example, I am actually not using the BuildEntityInGroup<T> which is instead commonly used in more sophisticated products. In Robocraft every engine that handles the logic of the functional cubes handles the logic of ALL the functional cubes of that specific type in game. However often is needed to know to which vehicle the cubes belong to, so using a group per machine would help to split the cubes of the same type per machine, where the machine ID is the group ID. This allows us to implement fancy things like running one Svelto.Tasks task per machine inside the same engine, which could even run in parallel using multi-threading.

This piece of code highlight one crucial issue, which I may talk more about in the next articles…from the comment (in case you haven’t read it):

Never create implementors as Monobehaviour just to hold data. Data should always been retrieved through a service layer regardless the data source. The benefit are numerous, including the fact that changing data source would require only changing the service code. In this simple example I am not using a Service Layer but you can see the point. Also note that I am loading the data only once per application run, outside the main loop. You can always exploit this trick when you now that the data you need to use will never change.

Initially I was reading the data directly from the monobehaviour like a good lazy coder would have done. This forced me to create an implementor as monobehaviour just to read serialized data. It could be considered OK as long as we don’t want to abstract the data source, however serializing the information into a json file and reading it from a service request is much better than reading this kind of data from an entity component.

Every entity needs an unique ID. This unique ID must be unique regardless the descriptor type and the group it belongs to. I took this decision recently, so if I say otherwise in other articles, please let me know I will fix it.

Communication in Svelto.ECS

One problem which solution has never been standardized by any ECS implementation is the communication between systems. This is another place where I put a lot of thought on and Svelto.ECS solves it in two novel ways. The third way is the use of the standard observer/observable pattern which is acceptable in very specific and uncommon cases.

DispatchOnSet/DispatchOnChange

We previously saw how to let engines communicate through entity components data polling. DispatchOnSet<T> and DispatchOnChange<T> are the only references (not value type data) that can be returned by entity components properties, but the type of the generic parameter T must be value type. The function names sound like event dispatcher, but instead they must be seen as data pushing methods, as opposite of data polling, a bit like data binding works. That’s it, some times data polling is awkward, we don’t want to poll a variable every frame when we know that the data changes rarely. DispatchOnSet<T> and DispatchOnChange<T> cannot be triggered without a data change, this will force to see them as a data binding mechanism instead of a simple event. Also there is no trigger function to call, instead the value of the data hold by these classes must be set or changed. There aren’t great examples in the Survival code, but you can see how the targetHit boolean of the IGunHitTargetComponent works. The difference between DispatchOnSet<T>and DispatchOnChange<T>is that the latter triggers the event only when the data actually changes, while the former always.

The Sequencer

Perfect engines are totally encapsulated and you can write the logic of that engine as a sequence of instructions using Svelto tasks and IEnumerators. However this is not always possible, as some times engines need to signal events to other engines. This is usually performed through entity data, especially using DispatchOnSet<T>and DispatchOnChange<T>, however like in the case of entities being damaged in the example, a series of independent and uncoupled engines are acting on it. Other times you want the sequence to be authoritative on the order of engines to call, like in the example where I want the death always happening for last. Not only a Sequence is very easy to use in this case, but it’s also very handy! Refactoring the sequence is super simple. So use IEnumerator Svelto Tasks for “vertical” engines and sequence for “horizontal” logic between engines.

Observer/Observable

I left the option to use this pattern especially for cases when legacy or not Svelto.ECS code must communicate with Svelto.ECS engines. For all the other cases, it must be used with extreme caution, as there is a high chance to abuse the pattern since it looks familiar to the coder new to Svelto.ECS and Sequencers are usually a better choice.

Svelto.ECS and Unity

Svelto.ECS (as well as Svelto.Tasks) is designed to be platform agnostic. However I mainly use it with unity, so Unity extensions are provided. The EntityDescriptorHolder is an example. Using implementors as Monobehaviour let to exploit most of the Unity callbacks, but on other platforms the reasoning could be very similar. All that said, Svelto.ECS gives you the chance to abstract from Unity and you should use Unity classes as less as possible, especially Monobehaviours. It’s also important to keep in mind that the creation of GameObject(s) is uncoupled from the creation of Entities, the only thing they have in common is the fact that gameobject monobehaviours can be implementors. You can see from the example that enemy gameobjects and their ECS entities are built indipendently.

Logic inside utility classes

You can create static utility classes to share code, that is not a problem as long as the static classes do not hold any state (they must be just a set of static functions)

closing notes EntityStruct:

I left the EntityStruct concept out of this article. As you may know, Unity is pushing their ECS framework as a mere optimization tool. As I am trying to explain in these articles, this is a mistake, but not only because the ECS paradigm is actually a great way to write maintainable code, but also because in real life, cases where the extreme optimizations resulting from writing cache friendly code is useful are very limited. However EntityStruct are cool and useful for multithreading code as well, but I will need to write better examples to show their power.

If you this article is not enough, you can also read the old ones: 

Svelto ECS is now production ready

if you are new to the ECS design and you wonder why it could be useful, you should read my previous articles:

That’s all folks! FEEDBACK ME!

25 thoughts on “Learning Svelto.ECS by Example: The Unity Survival Example

  1. Ok Im still not convinced 100% that components Should not have references to other components.

    Take this example:
    https://github.com/Darelbi/svelto-ecs-sandbox/blob/master/SveltoSandbox/Assets/Scripts/ECS/Engines/Lifter/LitferCollectionEngine.cs

    This is a engine that keeps objects parented for the purpose of moving things
    Along with a carrier.

    I could code the hierarchy Just by querying entities and find entity views by querying IDs, but the code would be perfetcly (in this case) equivalent to the one present in the example. Am I missing some important point? Off course I would only maintain references from that engine only.

    1. Put in this way how would have you written the code if the engine would handle a set of lifter entities and a set of liftable entities?

      1. Instead of a List inside the component I would have used a List then the movement engine access the int list, and for each int in it, TryQuery the Liftable so the engine cann add to it the delta movement of the lifter.

    1. this is a super interesting question, but first I need to reply with another question, what are you trying to achieve?

      1. sorry I asked that from phone. Not sure, just curious on the topic :). Actually I started migrating a game to using Svelto ECS, and I’m surprised it is actually possible to have the game partially ECSized without much troubles. I tried to think to many usages but apart writing isolated Unit tests I can’t think to some serious use cases. In theory a sequencer can have engines from different roots. If a game part is isolated enough it may make sense using a different root for it (in example the trophy/profile part for a XBOX game may be a self-contained root that use sequencers to send events for very specific things related to player profiles on XBOX).

        Another use case could be handling for PVP games, 2 players have 2 machines so 2 different roots, but no one prevent using 2 roots in the same machine for a hotseat game. In this case there could be 2 symmetric sequencers that send/receive data packets.

        Also.. I noticed I tend to write engines that have a precise execution order. To keep the order correct I wrote many mini-managers that call the engines in a precise order (using a enum and the engines register themselves in the manager using the enum, making the ordering explicit). reading your posts in unity forums I suspect there is a better way to not rely on update order of engines (actually what I’ve done is a sort of simplified sequencer that do “steps” without events).. any clue? XD

        1. Contexts are actually very useful to isolate the engines like you said by for the same reason communication should not be needed. At most you can exchange data through service requests.

        2. Execution order must be driven only by sequencers, but if you need it a lot there is something wrong in your design.

          1. Well. In example the 2d hyperfast Shadow caster use a order dependent algorithm. Light mesh generation/ Clipping against static intersecting Shadow casters,/ Clipping against moving obstacles.

            Of course every moving thing Should move before the Shadow casting phase and many optimizations to remove invisibile stuff Should be performer After movement update phase and before Shadow casting.

            It seems everything is order dependent. Ho do i Should design that to not be’ order dependent?

          2. This is very rendering specific. ECS is a general purpose design, but of course must be adapted for each single specific case. For example, it’s true that svelto has several solutions that are close to data binding and mvvm but it will never beat a proper GUI framework. All that said, it sounds to me that your problem could be easily solved with a serialtaskcollection. Create it in the context, pass it as parameter of the constructor of each engine, register a new enumerator in the right order. Let it start. Now actually it may not be so straightforward but I can create a special collection for this purpose so you can run engines tasks in sequence

        3. Hey I read your original post somehow it seems I didn’t read it all the first time. The mini managers idea sounds terrible what you do with that ? Remember engines can never be injected anywhere

          1. Actually the managers are injected in engines and each engine implements a ITick interface. I have a mini manager for movement update, One for Shadows, One for culling, and the managers are runned in a determined order(move/ cull/ Shadows) and each manager update 3/4 engines.

            Since usually if you write something There is a reason for that I wanted to understand how to get rid of the order dependence… Well yes i can Just use svelto tasks and achieve the same execution order, but I feel There is a better way. Yes it is all’ rendering related stuff, but svelto helped a lot keeping it manageable. It is very Easy adding Shadows tricks without touching existing engines

          2. Don’t use tick stuff either. We had something like that and ran away from it. Use tasks always. If you tell me what you need exactly I can write a new collection for you if the serialtaskcollection is not enough

  2. Hi, I do appreciate the ECS framework and all the examples. Great job,

    I have however a questions about Group Entities.

    Why there seem to be restriction of moving grouped entity out of group altogether or move un-grouped entity to a group?

    Best regards,
    Dawid

    1. The swap group function is largely untested because I never needed to use it so far. All the other applications are therefore theoretical and may be avoided with different designs, so I need to be sure that there are practical applications before implementing them. What are your use cases?

      1. One of our Ideas was to use grouping for creating a pool of objects, instead of instantiating new just put them in to pool or out of it. This can be achieved with specific use cases, or with making all entities grouped. And have separate groups for active(in use entities) and free(objects in pool). I was just wandering if there is practical need for restricting move of entity out of a group (as opposed to just moving it to another group – making a grouped entity un-grouped and vice versa) – I understand that there may have been no need for that – Just wanted to make sure I didn’t missed any principle/rule that would restrict such actions 🙂

        1. Pooling was one of the reason why I introduced group swapping. Tbh I could make by default entities being built in a group as there isn’t really a reason to not do so. You didn’t miss anything it’s just I don’t want over complicate the framework. Mind some things tho: I am working already on several new features. Some of them will make building entities require less allocations however if you want to go allocation free you should use entity structs although they come with several limitations

          1. Good stuff. Thanks again for sharing your awesome work.

            A little thing I noticed btw MoveEntityView method name seem to be a bit of a missnoma as it does copy/reference but does not remove from the source list – got me confused a little 🙂

            Best Regards,

          2. Thanks there are some things to improve in the example.. I’ll try to fix most of the things this weekend!

    1. I will fix it today but the example currently points to the database refactoring branch of svelto ecs

  3. Hi, I’m trying to use Svelto.ECS in my turn based strategy project. The game has Routes (trade routes) consisting of Points. I’ve decided to group all Points belonging to the same Route as the idea of groups seems to suit the case. Nevertheless, I’ve discovered that group can’t be changed, so it is impossible to add or remove Points of the already created Routes. Is it intended behavior or I have missed any Svelto function? Perhaps, creating temporary group and further swapping between it and edited Route’s group can be a workaround?

    1. Hello, I just noticed your comment. Yes swapping is the way to go in this case. A group of disabled point is OK if you don’t want to remove the entity.

Leave a Reply