Svelto ECS is now production ready

Note: this is an on-going article and is updated with the new features introduced over the time. [last edit: 09/11/2017, last note added: 22/10/2017]

Six months passed since my last article about Inversion of Control and Entity Component System and meanwhile I had enough time to test and refine my theories, which have been proven to work well in a production environment.  As result Svelto.ECS framework is now ready to use in a professional environment.

Let’s start from the basics, explaining why I use the word framework. My previous work, Svelto.IoC, cannot be defined as a framework; it’s instead a tool. As a tool, it helps to perform Dependency Injection and design your code following the Inversion of Control principle. Svelto.IoC is nothing else, therefore whatever you do with it, you can do without it in a very similar way, without changing much the design of your code. As such, svelto IoC doesn’t dictate a way of coding, doesn’t give a paradigm to follow, leaving to the coder the burden to decide how to design their game infrastructure.

Svelto.ECS won’t take the place of Svelto.IoC, however it can be surely used without it. My intention is to add some more features to my IoC container in future and also write another article about how to use it properly, but as of now, I would not suggest to use it, unless you are aware of what Inversion of Control actually means and that you are actually looking for an IoC container that well integrates with your existing design (like, for example, a MVP based GUI framework). Without understanding the principles behind IoC, using an IoC container can result in very dangerous practices. In fact I have seen smell code and bad design implementations that actually have been assisted by the use of an IoC container. These ugly results would have been much harder to achieve without it.

Svelto.ECS is a framework and with it you are forced to design your code following its specifications. It’s the framework to dictate the way you have to write your game infrastructure. The framework is also relatively rigid, forcing the coder to follow a well defined pattern. Using this kind of framework is important for a medium-large team, because coders have different levels of experience and it is silly to expect that everyone is able to design well structured code. Using a framework, which implicitly solves the problem to design the code, will let the team to focus on the implementation of algorithms, knowing, with a given degree of certainty, that the amount of refactoring needed in future will be less than it would have been if left on their own.

The first error I made with the previous implementation of this framework, was to not define clearly the concept of Entity. I noticed that it is very important to know what the elements of the project are before to write the relative code. Entities, Components, Engines and Nodes must be properly identified in order to write code faster. An Entity is actually a fundamental and well defined element in the game. The Engines must be designed to manage Entities and not Components. Characters, enemies, bullets, weapons, obstacles, bonuses, GUI elements, they are all Entities. It’s easier, and surely common, to identify an Entity with a view, but a view is not necessary for an Entity. As long as the entity is well defined and makes sense for the logic of the game, it can be managed by engines regardless its representation.

In order to give the right importance to the concept of Entity, Nodes cannot be injected anymore into Engines until an Entity is explicitly built. An Entity is now built through the new BuildEntity function and the same function is used to give an ID to the Entity. The ID doesn’t need to be globally unique, this is a decision left to the coder. For example as long as the ID of all the bullets are unique, it doesn’t matter if the same IDs are used for other entities managed by other engines.

The Setup

Following the example, we start from analyzing the MainContext class. The concept of Composition Root, inherited from the Svelto IoC container, is still of fundamental importance for this framework to work. It’s needed to give back to the coder the responsibility of creating objects (which is currently the greatest problem of the original Unity framework, as explained in my other articles) and that’s what the Composition Root is there for. By the way, before to proceed, just a note about the terminology: while I call it Svelto Entity Component System framework, I don’t use the term System, I prefer the term Engine, but they are the same thing.

In the composition root we can create our ECS framework main Engines, Entities, Factories and Observers instances needed by the entire context and inject them around (a project can have multiple contexts and this is an important concept).

In our example, it’s clear that the amount of objects needed to run the demo is actually quite limited:

The EngineRoot is the main Svelto ECS class and it’s used to manage the Engines. If it’s seen as IEntityFactory, it will be used to build Entities. IEntityFactory can be injected inside Engines or other Factories in order to create Entities dynamically in run-time.

It’s easy to identify the Engines that manage the Player Entity, the Player Gun Entity and the Enemies Entities. The HealthEngine is an example of generic Engine logic shared between specialized Entities. Both Enemies and Player have Health, so their health can be managed by the same engine. DamageSoundEngine is also a generic Engine and deals with the sounds due to damage inflicted or death. HudEngine and ScoreEngine manage instead the on screen FX and GUI elements.

I guess so far the code is quite clear and invites you to know more about it. Indeed, while I explained what an Entity is, I haven’t given a clear definition of Engine, Node and Component yet.

An Entity is defined through its Components interfaces and the Implementors of those components. A Svelto ECS Component defines a shareable contextualized collection of data. A Component can exclusively hold data (through properties) and nothing else. How the data is contextualized (and then grouped in components) depends by how the entity can been seen externally. While it’s important to define conceptually an Entity, an Entity does not exist as object in the framework and it’s seen externally only through its components. More generic the components are, more reusable they will be. If two Entities need health, you can create one IHealthComponent, which will be implemented by two different Implementers. Other examples of generic Components are IPositionComponent, ISpeedComponent, IPhysicAttributeComponent and so on. Component interfaces can also help to reinterpret or to access the same data in different ways. For example a Component could only define a getter, while another Component only a setter, for the same data on the same Implementor. All this helps to keep your code more focused and encapsulated. Once the shareable data options are exhausted, you can start to declare more entity specific components. Bigger the project becomes, more shareable components will be found and after a while this process will start to become quite intuitive. Don’t be afraid to create components even just for one property only as long as they make sense and help to share components between entities. More shareable components there are, less of them you will need to create in future. However it’s wise to make this process iterative, so if initially you don’t know how to create components, start with specialized ones and the refactor them later.
The data hold by the components can be used by engine through polling and pushing. This something I have never seen before in other ECS framework and it’s a very powerful feature, I will explain why later, but for the time being, just know that Components can also dispatch events through the DispatcherOnChange and DispatcherOnSet classes (you must see them as a sort of Data Binding feature).

The Implementer concept was existing already in the original framework, but I didn’t formalize it at that time. One main difference between Svelto ECS and other ECS frameworks is that Components are not mapped 1:1 to c# objects. Components are always known through Interfaces. This is quite a powerful concept, because allows Components to be defined by objects that I call Implementers. This doesn’t just allow to create less objects, but most importantly, helps to share data between Components. Several Components can been actually defined by the same Implementer. Implementers can be Monobehaviour, as Entities can be related to GameObjects, but this link is not necessary. However, in our example, you will always find that Implementers are Monobehaviour. This fact actually solves elegantly another important problem, the communication between Unity framework and Svelto ECS.
Most of the Unity framework feedback comes from predefined Monobehaviour functions like OnTriggerEnter and OnTriggerExit. One of the benefit to let Components dispatch events, is that the communication between the Implementer and the Engines become seamless. The Implementer as Monobehaviour dispatches an IComponent event when OnTriggerEnter is called. The Engines will then be notified and act on it.

It’s fundamental to keep in mind that Implementers MUST NOT DEFINE ANY LOGIC. They must ONLY implement component interfaces, hold the relative states and act as a bridge between Unity and the ECS framework in case they happen to be also Monobehaviours.

So far we understood that: Components are a collection of Data, Implementers define the Components and don’t have any logic. Hence where all the game logic will be? All the game logic will be written in Engines and Engines only. That’s what they are there for. As a coder, you won’t need to create any other class. You may need to create new Abstract Data Types to be used through components, but you won’t need to use any other pattern to handle logic. In fact, while I still prefer MVP patterns to handle GUIs, without a proper MVP framework, it won’t make much difference to use Engines instead. Engines are basically presenters and Nodes hold views and models.

OK, before to proceed further with the theory, let’s go back to the code. We have easily defined Engines, it’s now time to see how to build Entities. Entities are injected into the framework through Entity Descriptors. EntityDescriptor describes entities through their Implementers and present them to the framework through Nodes. For example, every single Enemy Entity, is defined by the following EnemyEntityDescriptor:

This means that an Enemy Entity and its components will be introduced to the framework through the nodes EnemyNode, PlayerTargetNode and HealthNode and the method BuildEntity used in this way:

While EnemyImplementor implements the EnemyComponents, the EnemyEntityDescriptor introduces them to the Engines through the relative Enemy Nodes. Wow, it seems that it’s getting complicated, but it’s really not. Once you wrap your head around these concepts, you will see how easy is using them. You understood that Entities exist conceptually, but they are defined only through components which are implemented though Implementers. However you may wonder why nodes are needed as well. While Components are directly linked to Entities, Nodes are directly linked to Engines. Nodes are a way to map Components inside Engines. In this way the design of the Components is decoupled from the Engines. If we had to use Components only, without Nodes, often it could get awkward to design them.
Should you design Components to be shared between Entities or design Components to be used in specific Engines? Nodes rearrange group of components to be easily used inside Engines. Nodes are mapped directly to Engines. specialized Nodes are used inside specialized Engines, generic Nodes are used inside generic Engines. For example: an Enemy Entity is an EnemyNode for Enemy Engines, but it’s also a PlayerTargetNode for the PlayerShootingEngine. The Entity can also be damaged and the HealthNode will be used to let the HealthEngine handle this logic. Being the HealthEngine a generic engine, it doesn’t need to know that the Entity is actually an Enemy. different and totally unrelated engines can see the same entity in totally different ways thanks to the nodes. The Enemy Entity will be injected to all the Engines that handle the EnemyNode, the PlayerTargetNode and the HealthNode Nodes. It’s actually quite important to find Node names that match significantly the name of the relative Engines.

As we have seen, entities can easily be created explicitly in code passing a specific EntityDescriptor to the BuildEntity function. However, some times, it’s more convenient to exploit the polymorphism-like feature given by the IEntityDescriptorHolder interface. Using it, you can attach the information about which EntityDescriptor to use directly on the prefab, so that the EntityDescriptor to use will be directly taken from the prefab, instead to be explicitly specified in code. This can save a ton of code, when you need to build Entities from prefabs.

So we can define multiple IEntityDescriptorHolder implementations like:

and use them to create Entities implicitly, without needing to specify the descriptor to use, since it can be fetched directly from the prefab (i.e.: playerPrefab, enemyPrefab):

Once Engines and Entities are built, the setup of the game is complete and the game is ready to run. The coder  can now focus simply on coding the Engines, knowing that the Entities will be injected automatically through the defined nodes. If new engines are created, is possible that new Nodes must be added, which on their turn will be added in the necessary Entity Descriptors.

Note though: with experience I realized that it’s better to use Monobehaviour and Gameobject to build entity ONLY when strictly needed. This means only when you actually need engines to communicate with the Unity engine through the implementors. Avoid this otherwise, for example, do not use Monobehaviours as implementers because your monobehaviours act as data source, just read the data in a different way. Keep your code decoupled from unity as much as you can.

The Logic in the Engines

the necessity to write most of the original boilerplate code has been successfully removed from this version of the Framework. An Important feature that has been added is the concept of IQueryableNodeEngine. The engines that implement this interface, will find injected the property

This new object remove the need to create custom data structures to hold the nodes. Now nodes can be efficiently queried like in this example:

This example also shows how Engines can periodically poll or iterate data from Entities. However often is very useful to manage logic through events. Other ECS frameworks use observers or very awkward “EventBus” objects to solve communication between systems. Event bus is an anti-pattern and the extensive use of observers could lead to messy and redundant code. I actually use observers only to setup communication between Svelto.ECS and legacy frameworks still present in the project. DispatchOnSet and DispatchOnChange are an excellent solution to this awkward problem as they allow Engines to communicate through components, although the event is still seen as data. Alternatively, In order to setup a communication between engines, the new concept of Sequencer has been introduced (I will explain about it later in the notes).

IRemoveEntityComponent

IRemoveEntityComponent needs a dedicated paragraph. This Component is a special Component partially managed by the framework. The user must still need to implement it through an implementer, but all is needed to implement is:

the removeEntity action is automatically set by the framework and once called, it will remove, from all the engines, all the nodes related to the entity created by the BuildEntity function. This is the only way to execute the Remove engine method. A standard example is to use in a Monobehaviour implementer like this:

but can be used also in this way:

the removeEntity delegate will be injected automatically by the framework, so you will be find it ready to use.

Conclusions

I understand that some time is needed to absorb all of this and I understand that it’s a radically different way to think of entities than what Unity taught to us so far. However there are numerous relevant benefits in using this framework beside the ones listed so far. Engines achieve a very important goal, perfect encapsulation. The encapsulation is not even possibly broken by events, because the event communication happens through injected components, not through engine events. Engines must never have public members and must never been injected anywhere, they don’t need to! This allows a very modular and elegant design. Engines can be plugged in and out, refactoring can happen without affecting other code, since the coupling between objects is minimal.

Engines follow the Single Responsibility Principle. Either they are generic or specialized, they must focus on one responsibility only. The smaller they are, the better it is. Don’t be afraid of create Engines, even if they must handle one node only. Engine is the only way to write logic, so they must be used for everything. Engines can contain states, but they never ever can be entity related states, only engine related states.

Once the concepts explained above are well understood, the problem about designing code will become secondary. The coder can focus on implementing the code needed to run the game, more than caring about creating a maintainable infrastructure. The framework will force to create maintainable and modular code. Concept like “Managers”, “Containers”, Singletons will just be a memory of the past.

Some rules to remember:

  • Nodes must be defined by the Engine themselves, this means that every engine comes with a set of node(s). Reusing nodes often smells
  • Engines must never use nodes defined by other engines, unless they are defined in the same specialized namespace. Therefore an EnemyEngine will never use a HudNode.
  • Generic Engines define Generic Nodes, Generic Engines NEVER use specialized nodes. Using specialized nodes inside generic engines is as wrong as down-casting pointers in c++

Please feel free to experiment with it and give me some feedback.

Notes from 21/01/2017 Update:

Svelto ECS has been successfully used in Freejam for some while now on multiple projects.  Hence, I had the possibility to analyse some weakness that were leading to some bad practices and fix them. Let’s talk about them:

  1. First of all, my apologies for the mass renames you will be forced to undergo, but I renamed several classes and namespaces.
  2. The Dispatcher class was being severely abused, therefore I decided to take a step back and deprecate it. This class is not part of the framework anymore, however it has been completely replaced by DispatcherOnSet and DispatcherOnChange. The rationale behind it is quite simple, Components must hold ONLY data. The difference is now how this data is retrieved. It can be retrieved through polling (using the get property) or trough pushing (using events). The event based functionality is still there, but it must be approached differently. Events can’t be triggered for the exclusive sake to communicate with another Engine, but always as consequence of data set or changed. In this way the abuse has been noticeably reduced, forcing the user to think about what the events are meant to be about. The class now exposes a get property for the value as well.
  3. The new Sequencer class has been introduced to take care of the cases where pushing data doesn’t fit well for communication between Engines. It’s also needed to easily create sequence of steps between Engines. You will notice that as consequence of a specific event, a chain of engines calls can be triggered. This chain is not simple to follow through the use of simple events. It’s also not simple to branch it or create loops. This is what the Sequencer is for:

This example covers all the basics, except the loop one. To create a loop would be enough to create a new step that has as target an engine already present as starting point of an existing step.
A Sequence should be created only inside the context or inside factories. In this way it will be simple to understand the logic of a sequence of events without investigating inside Engines. The sequence is then passed into the engines like it happens already with observables and used like this:

  1. The Concept of MetaEntity has been added. This is a bit of an abstract concept, but useful in some specific cases. It allows to define an Entity as a Group of Entities. A meta entity is a way to manage a set of entities that are not easily “queriable” otherwise. For example you may want to group existing entities by size and type and then use the meta entity node to manage the data shared among the single entities of the same type and size. This will prevent the scenario where the coder is forced to parse all the entities to find the ones of the same size and type. Since the entities are managed through the shared node, the same shared node must be found on the single entities of the same type and size. The shared node of the meta entity is then used by engines that are meant to manage a group of entities through a single node. The same engine can manage several meta entities nodes too. The Engine manages the logic of the Meta Node data and other engines can read back this data through the normal entity as the shared node will be present in their descriptor too. The use cases for this feature are limited, but you will understand the benefits in the rare case you will need it.
  2. The IComponent interface was never used by the framework so I removed it, you can keep on using it for the sake to optimize the GetComponents call in a BuildEntity.
  3. Added a new GenericEntityDescriptor class, now it’s possible to create Descriptors without declaring a new class every time (it depends by the number of nodes used).
  4. Since the Tickable system has been deprecated, the TaskRunner library should be used to run loops. TaskRunner comes with a profiler window that can be used to check how long tasks take to run. This has been copied and adapted from the Entitas source code. You can Enable it from the menu Tasks -> Enable Profiler. Similarly  you can enable an Engine Profiler from Engine -> Enable profiler to profile adding and remove nodes into Engines. It will look more or less like this:

In future, I may implement some of the following features (feedback is appreciated):

  • Entity pooling, so that a removed entity can be reused
  • Some standard solution to handle input event in engines
  • Using GC handles to keep hold of the Node references, all these references around can make the garbage collection procedure slower

Notes from 22/10/2017 Update:

Biggest feature introduced is the support of Data Oriented, Cache Friendly engines. ECS design is by its nature data oriented; however without the support of nodes as structs, it can’t be cache friendly.

The new IStructNodeEngine<> and IStructNodeWithID allow to iterate over array of structs so that you are enabled to write cache friendly, data oriented code.

In the example that comes with the new article I wroteBoidEngine becomes an IStructNodeEngine<BoidNode> and BoidNode moves from the classic class made out of components, to a struct made by of simple value fields only.

Classic Svelto.ECS BoidNode:

New Svelto.ECS BoidNode as struct:

the IStructNodeEngine will force you to implement the following method in your cache friendly engine:

where _structNodes is

this pretty much becomes a pattern that will be copy and pasted for every IStructNodeEnginestructNodes contains an up to date array of BoidNode structs.

IStructNodeEngines are managed differently than other engines in Svelto.ECS and it’s the coder responsibility to add and remove the nodes explicitly. The system is still in its early stages and may change a bit in future, but it’s important to know that the nodes are shared between engines so that the list of nodes returned from SharedStructNodeList<Node> is the same between all the IStructNodeEngines<Node>. That’s why only one engine should take the responsibility to add and remove nodes through the MultiNodesEngine (or SingleNodeEngine) AddNode and RemoveNode methods. Other engines can instead read and update back values inside the array.

StructNodes are designed to be different than the normal nodes. They won’t need implementors, although they still need to be built through a BuildEntity call and an EntityDescriptor. The EntityDescriptor itself will use a FastStructNodeBuilder during the declaration of nodes to use. FastStructNodeBuilder assumes that the node doesn’t need reflection to fill its fields, which should be considered the normal case. Although I added the option through the StructNodeBuilder class, accessing a field of a struct, that is not a value type, would defeat the purpose of using an array of structs, which is the exploiting of the spatial memory locality and CPU cache.

Other features introduced:

  • INodesEngine interface is now internal (so deprecated for normal use), MultiNodesEngine must be used instead.
  • Scheduling of node submission is not responsibility of the EnginesRoot anymore. You can now inject your own node submission scheduler
  • Entities can now be disabled and enabled, instead of just added and removed. This introduce new engines callbacks that can make simpler to manage entities life.
  • GroupEntity has been renamed in to MetaEntity, more comments have been added to explain when to use them. The previous name was misleading.

 

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

66 thoughts on “Svelto ECS is now production ready

  1. Hi Sebastiano,

    After hitting a wall with a project after about a year’s worth of bolting on new features at the request of a client, I’ve entered dependency hell. I began weighing the pros and cons of a different framework other than what Unity3D’s engine tries to enforce, which led me to your blog. I noticed various other ECS implementations have been mentioned in the comment sections of your articles.

    What would you say is the primary shortcomings/drawbacks for using other ECS frameworks such as EgoECS and Entitas relative to yours from what you can tell? Do they enforce a strong SOLID design ethic? I am working with a new team (knowledgable to code and OOP, web framework like MVC, etc but not Unity) and want to enforce good design practices without becoming a Computer Science lecturer while also not making the project convoluted and heightening the learning curve. One of the best parts of Unity is its low bar to entry, and I don’t want to lose that.

    Thank you for your investigation and dedications to this topic. I am glad I stumbled across your blog.

    1. I assume you read all my articles on the argument, starting from the IoC ones. I haven’t used EgoECS or Entitas, I assume both are good. My ECS implementation has got 2 unique features, one is the fact that components are not objects and the other one that components can use dispatcher. I also like the Nodes design a lot.
      I think that communication between systems is the biggest problem to solve in an ECS framework and I usually discard frameworks that solve this problem with an EventBus (a singleton to dispatch messages).
      We have been using my ECS in robocraft for several months now and we wrote more than 80 engines so far. I have already found a couple of new design challenges to solve: the first one is about the code re-usability and how to address it. I will write new articles when I have the time, still a lot of stuff to talk about.

  2. Hello, I really admire your Svelto ECS. It’s hard to get your head wrapped around it at first but it is probably the best ECS framework for Unity at the moment with creator who knows what he’s doing exactly.

    I’m creating a roguelike in Unity and I want to use your system (ECS seems to be perfect fit for such project as everything is modular). However there is much dispute online about handling the game world itself using the ECS.

    You are an expert/guru in my eyes when it comes to ECS so I wanted to ask you about how would you recommend handling it? The world is made from chunks, those chunks are made from tiles. Creating an entity for each tile would have really cool advantages allowing nice composition of components that would define that tile. However with HUGE amount of tiles this solution ends up rather unefficient (100×100 map, which isn’t that big when you consider an open world, is already at 10k entities).

    Also, nearly all engines and entities are more or less connected to the map (visual representation, coordinates, pathfinding, collision checks, terrain destruction, and much much more) and that is why people suggest using a singleton approach, keeping the map functionality separate from ECS and just using the singleton object from any place where needed (methods like IsObstacle(), GetPath(), DestroyTile(), PlaceTile() etc). This seems to be somewhat dirty solution in my eyes and I was wondering what would be the “proper way” of doing it.

    Should the whole map be a single entity used by most of the engines? Only the engines can have the logic so this could lead to lots of repeated code (for example simply checking “what is on that tile” which would most probably end up in a lot of systems). Should I stick to the singleton approach like others suggested? What would you do?

    Also sorry if my message is confusing. I spent some time reading into it and it left a mess in my brain.

    1. Hello and thanks for your message. Let’s be clear, there is a lot of hostility around frameworks that step away from the Unity logic. I don’t want to be too blunt, but the truth is that whatever force you to use stateful global objects must be avoided like the pest. Obviously many of the unity users never really have created something truly complex, so for them tools like IoC containers or frameworks like ECS ones are seen as “over-engineering” since you can get things done also using singletons.
      While it’s true that you can get things done using whatever you want, it’s not true at all that solutions like the ones I advocate must be over-engineered. Sure, many frameworks out there actually implement way too much and that’s why my libraries are kept as simple as possible. Now an update to Svelto.ECS is long due and I hope I will work on it very soon now, so keep an eye here. Svelto.ECS is currently used on several projects and, so far, I haven’t met a situation where the framework makes things more complicated than should be.
      About your specific case, well Entity Component Systems are or can be data driven, so actually you can see the whole map as an entity, but it really depends by what you want to achieve and that’s why I need more information about it. For the number of tiles, it depends how much memory a tile takes in memory. if I tile takes, for example, 64 bytes, 640k for 10k tiles is not that much amount of memory to store :).
      About the map functionality, also that depends. If I understood correctly, you need to be aware of which tile you stepped on. Let’s take a step back. Using ECS doesn’t mean that you can’t inject “services” inside engines (by constructor). Therefore, if you wish, you can create a MapManager and inject it in the engines that are going to need the Map Manager Services (without the necessity to use a singleton), however I am sure there are many way you can solve the same problem just using Engines.
      For example you could have the MainCharacterOnMapEngine that knows the Map node and the MainCharacter node , inside the update you can use the main character node, to know the main character position and see on which tile it actually is. If, for example, you are on a tile that slows the character down, you can set in the MainCharacter node a multiplier value that will be used by the CharacterMovementEngine (or something like that).
      I hope this example makes sense, it could be better, but I would need to see what you have in mind to understand what direction is better to take. Using ECS is a paradigm shift, you need to think in a different way, totally inverted, so at first, solutions are not obvious to see and that’s why people may think it’s not worth it. However on the long run it will make things easier.

      1. I was a lead programmer in a rather big game project (full time job, not a hobbyist after-hours thing) and I know for sure your system is not “over-engineering”. The project ended up in a total mess of a code, full of managers and singletons (multi-responsibility, thousands of lines of code per class). It wasn’t a problem at the beginning but further in the project it became a nightmare to extend and maintain. I guess that I lost at least 3-6 months fixing/patching/extending/refactoring (then fixing again) because of poor design choices. I would really love to go back in time and slap myself in the face for not doing it more properly.

        That’s why you won’t get a single drop of hostility from me. I know that I need solutions like yours to make my work easier in the long run 🙂

        But I digress…

        Injecting a map service into engines seems to be the easiest solution, however it does seem similar to regular singleton/manager usage, taking away the logic from the engine into it’s own class. If this is still an elegant solution then I would probably lean towards this as it “feels” right. However, if it’s also possible to get the same effect using engines without huge drawbacks then I’m all for that.

        However each solution I think about has some problems

        1. Each tile as entity: Entities just “float around in a big bag”. They are not ordered in any way (or at least not in the way they naturally should in case of a tilemap) and they lose the advantages of grid layout. You need to query each entity about it’s coordinates etc. Each engine wanting to know anything like “is this tile passable?” “are there items on this tile?” would either have to go through all the tiles to find the one in question (instead of simply accessing tilemap[x,y]) or build some sort of array for quick access and modify it every time a chunk is loaded/released. Both ways seem wasteful.

        2. Chunks as entity: Not as wasteful as option 1, fits in nicely with loading/releasing chunks, but still suffers from similar problems.

        3. Map as entity: no need to iterate in search for right chunk or tile – simple array access. However, this seems to be only one step away from the service solution you mentioned which makes more sense than this option

        All options additionally suffer from
        a) repeated code (both MoveEngine and PathfindingEngine check if tiles are passable. I know it’s a poor example as code for such check is very short, but I bet you can imagine situations with whole big functions copy-pasted between engines)
        b) somewhat breaking the “single responsibility” rule. Engines apart from doing their job keep implementing functions that manage the map. Such functions end up spread and repeated across many engines. It feels more natural to have all tilemap related operations in a single place instead.

        I will edit this post if I recall anything more (I got back to my roguelike project after quite long break and I’m sure I’m still forgetting something about the struggle of implementing the map in ECS)

        1. I don’t see each tile as entity as a proper solution. The map itself is just data, so the data structure of the map can be stored by just one mapComponent. If you go for the Service route, the mapService won’t need any logic. A service is usually a way to query a datastructure, nothing else. The data source of the data structure can be everything (a DB, a file, a datastructure in memory), and the service has only the responsibility to give the functions to query this data. This means that all the logic is still in the engines, that use the mapService only to be able to query the data.
          MoveEngine and PathFindingEngine, assuming they do different things, will have different code to execute in case a tile is not passable.

  3. I have just ony 1 complaint about SveltoECS is the amount of boiler plate code. Svelto is aimed at really big projects, so as long as you need something that address all the problems that it addresses, I can’t came up with a simpler syntax: every single line of code is needed :/.
    But let’s assume for a while we are not working on Robocraft(assume I’m in a small team with 6 people working on something that should last for 1/2 years at most): Isn’t there anything we can drop from Svelto in order to allow it become more intuitive and with less boilerplate?

    Svelto enforce everything upfront, good for big projects, but what for smaller projects?

    Compare the following with Svelto.ECS:

    void SpawnSomeEntity(){
    Entity entity = factory.CreateEntity();
    engine1.RegisterEntity( entity, defaultValues ); //automatically add components for engine1
    engine2.RegisterEntity( entity, defaultValues2); //automatically add components for engine2 (If not already added by engine1)
    }

    You 1) write the engines 2) the components 3) the above function and you are done. Yes you have a lot of downsides compared to Svelto, but it is much more intuitive and fast to write.

    Coming back to map problem:

    I agree the map should be a DataStructure that can be queried (read only) by each engine.

    Now assume that we want the chance to add more and more terrain types to the map, in example the Lava Terrain type.In that case we need a LavaDamageEngine. Then we just need a component on each Character to keep track of terrain type, we use that to trigger events on Characters.

    public class TerrainEngine{

    override protected void Add(CharaterTerrainNode node){
    node.terrainTypeTracker.type = GetTerrainTypeForCurrentPosition( node.position);
    node.movementTriggerComponent.subscribers += OnCharacterMoved;
    }

    void OnCharacterMoved(int ID){
    var node = nodesDB.QueryNode(ID);

    if( node.terrainTypeTracker.type != GetTerrainTypeForCurrentPosition( node .position))
    {
    var newTerrainType = GetTerrainTypeForCurrentPosition( node .position);
    ResolveTerrainType( node .terrainTypeTracker.type).OnExitTerrainType( node );
    e.terrainTypeTracker.type = newTerrainType;
    ResolveTerrainType( newTerrainType).OnEnterTerrainType( node );
    }

    ITerrainTypeEngine ResolveTerrainType ( TerrainTypeEnum type){
    switch(type){
    case TerrainTypeEnum.Lava: return lavaEngine;
    //…
    }

    // … more methods hided
    }

    Ok now we reached the culprit: we want the Lava Terrain to do 1 damage every second to the character. while In my pseudo-ECS it is easy to figure out the solution:

    public class LavaTerrainTypeEngine: ITerrainTypeEngine{

    public void OnExitTerrainType( Entity e){ // I use entities: not nodes
    DeregisterEntity(e); // removes the LavaDamage Component
    }

    public void OnEnterTerrainType( Entity e){
    RegisterEntity(e); // adds the LavaDamageComponent and check if we have DamageEventComponent
    }

    void Tick(){
    foreach( Entity e in entitiesWithLavaDamageComponent){
    var comp = e.GetComponent();
    comp.time -= deltaTime;
    if(comp.time <= 0){
    comp.time += 1;
    comp.GetComponent().DoDamage(1);
    }
    }

    }

    }

    However in Svelto this is not possible. We cannot add more components at Runtime since implementors are fixed, I’m a bit unsure how to handle it, however in Svelto I think lava damage is nothing more than a periodic damage, which could be viewed as adding a “pending bullet”, so in Svelto we could implement that periodic damage just by adding another Entity that will do periodic damage to the Character, however to do that we have to create all the boiler plate needed for a new entity (Nodes, Interfaces, Components, another engine).

    And I really hope that code identing don’t get messed up in comments ^^ In that case sorry but you should go for a code beautifier XD (hoping I didn’t forget any curly braces).

    1. Aehm. “>” and “<" template arguments were stripped from my comment :/. And also identation was wrong. However the point was that Implementors are good, I can see and appreciate their advantage, however there are certain behaviours that can be modelled more easily by adding dynamically components at Runtime, and this thing is not allowed with implementors (unless there's somewhere a undocumented feature of Svelto I missed). So while I believe in really big projects it is preferrable to have a Whole different entity for that, I think medium-small projects will benefit more from having separate components that can be changed at Runtime. I don't know if Svelto would allow in future the best of both worlds.

      1. Forgive me Dario, it’s a while I don’t play with Svelto.ECS. I changed it quite a lot since the last commit on github and I lost the track of it. I am working on the new github version as we speak. Once I am done, I will re-read your comments and answer them, OK? Please also be aware that, as usual, there will be some (hope minor) breaking changes 🙁

  4. Hi Sebastiano,

    I have a question, primarily about one the actual code examples above you used and the thinking behind it.
    Your TriggerDamage method seems to be within the PlayerGunShootingEngine.
    The method updates the healthcomponent.current health node to indicate it’s been damaged.
    It then fires the “isDead” or “isDamaged” dispatch to let the other engines (and systems listening) that the entity the component is attached to is either dead or has been damaged.

    I’m not a professional programmer, so I’m here to learn, but I feel that’s a violation of the S in SOLID.

    In this scenario, wouldn’t the PlayerGunShootingEngine only be responsible for determining if the enemy was hit, and then firing a “isDamaged” dispatch (along with the damage object).
    It would be up to the HealthEngine to apply damage/healing, thus it’d receive the “IsDamaged” dispatch, apply the damage and then it would contain the logic of “is this thing dead?”.
    At the moment it feels that the logic of “is this dead” is two places, PlayerGunShootingEngine and HealthEngine, which isn’t DRY either.

    Please note, I’m nod asking this to nit-pick; I’m actually concerned I’ve missed part of the concept of this approach. I don’t have a lot of experience in designing games, especially from this angle.

    Thanks.

    1. Hi again Sebastiano,

      I was just reading through the code in the github project, and it turns out the TriggerDamage method is within the HealthEngine. This is exactly where I would expect it to be and invalidates any of my previous concerns.

      Reading through the article left me with the feeling that the example code was in PlayerGunShootingEngine, which really didn’t feel right. I’m incredibly glad I got that feeling now, as it means my thinking is in the correct zone. 🙂

      Your IOC+ECP framework is exactly the direction I was heading in my own code and I’m going to rework my current project to implement it in this manner. The two genius things you’ve done here that I would never have thought about was:
      Events fired through components. Seriously, this is awesome.
      Unity integration – this method of integration just feels right and it’s so clean!

      I’ll try to ensure I respond once I’m familiar with the framework, really looking forward to this!

      Rob

      1. thanks Robert!

        I am updating the github version since it’s quite old. The difficult part will be to remember the changes I made and explain why I made them :). Also I still need to add a fundamental feature, that I will explain later. At last, I have to update the article, since it’s a bit outdated…arggh! Too much work for too few weekends in a year 😀

  5. Hi Sebastiano,

    I’ve been using it for a few days now and have to say I really like it, it feels neat that once setup I’m just writing engines (whilst only thinking about nodes), AND i get compile errors when I’ve missed a component/node/Implementor etc.
    One issue i’ve had (which I just created a hack to get around to start with) is this situation:

    I have a component, it’s job is to represent a producer of items in my game (think Farm creating wheat). It has an event that takes a data object called “ProductData” which just contains a string and an int, string = ProductName, int = Quantity.
    My producerCreateEngine creates items, then dispatches a ProductCreated event, passing in the ProductData object.

    I have an inventoryAddEngine which subscribes to events from anything that can produce things and adds them to the inventory of the entity that produced it (with all the associated checks etc).

    Problem is that the InventoryAddEngine is given the id of the ProducerNode in the event, which can be retrieved from the nodeDB. I’m then a little stuck because can’t easily get reference to the InventoryNode from the same entity. I’m loath to put the target inventory in the ProductData object, because then the producerCreateEngine will need to know information it really shouldn’t (where the product will go). For now, that’s the path I went down.

    Being able to say “.EntityOf(ProducerNode) as AnotherNodeOnSameEntityDescriptor” to retrieve other nodes from an entity would be really nice.

    As I said i haven’t put a lot of brainpower into this and I could be just looking at a design failure on my behalf here instead of a missing framework feature.

    Thanks very much for releasing this framework Sebastiano, it’s really exactly what I was looking for.

    If it helps, I can send you a list of definitions for all of the parts of the ECS system (when I get home tonight). It’s just a list of all of the main objects in your ECS (Component, Implementor etc), a quick description of what it is and a brief “this is how you use it”. For example, Implementor i’ve said something like:
    Implementor:
    This is the concrete implementation of your IComponents to an entity that is going to be visible in the game world, It’s also where the components are linked to the data. Implementors are all (currently) monobehaviours and become Components on GameObjects in the Unity engine.
    Implementors contain references to IComponents. They can also contain public fields and properties to allow users to assign values either directly in the inspector, or via code.
    Implementors should NEVER be directly referenced by Engines (that’s what Nodes are for) and do not represent the ‘Entity’ objects to engines either, that is what EntityDescriptor is for.
    Implementors are unique in that they contain the games data.

    Having a list of all the components of the engine to reference in this format was the thing that actually got me past that initial pain of getting into the framework.

    I have some very basic relationship flow diagrams too (not UML, just basic things to show me dependencies and how things are composed):
    IComponent -(many)-> Implementor -(single)-> Unity Component (Monobehaviour).
    IComponent -(Many)-> Node -(Many)-> EntityDescriptor -(Many)-> Engine
    Those two are the big ones that come to mind.

    Robert

  6. Hey again,

    So after another couple of days learning it turns out that the correct nodes were returned by the nodeDB, I caused the incorrect behaviour when I passed in the ID of the unity component instead of the GameObject when setting up the dispatcher.
    I had:
    productCreated = new Dispatcher(this.GetInstanceID());

    instead of

    productCreated = new Dispatcher(gameObject.GetInstanceID());

    So I’m happily retrieving nodes from entities now, all good!

    Rob.

  7. I tried to think if there are ways to avoid using the DB, there can even be shortcuts (if a class manages always events from EntityA to EntityB, why not inheriting something like EventsDispatcher ?), but for some cases we still need the DB. Overall I think this is actually the most beautifull ECS out there (and it has still some improvements to be done? Seriously?).

  8. Hi Sebastiano,

    First of all, congratulations for this framework. As a programmer who participated to two different ECS development in the past, I got to admit that this one provides a lots of interesting features.
    Especially the notion of events. We were using tags on entity as a form of boolean, which was generating a lot of spaghetti code in the update function of our systems. Your event callbacks here is much more elegant.
    Good job!

    I’m trying out your framework on a personal project, to test out the performances of your system when querying nodes (with a lots of entities running), by going through your example, and I have just one question:

    EnemyAnimationEngine implements IQueryableNodeEngine to have access to nodesDB, and query the required nodes EnemyNodes at every Tick. Good!
    Now EnemyMovementEngine, or EnemySpawnerEngine, implements INodesEngine which provides the methods Add and Remove. These methods would then be called on the presence of a new Node that fits the requirements of
    this engine. Within these methods, the nodes are then stored in variables within the engine. (e.g _targetNode, _enemiestoSpawn)

    In my previous developments of ECS, we lived by the following rule: a system never stores any entities (or in your case Nodes) inside itself. It will always query elsewhere these entity/components.
    I am not saying this is the best rule, but this is what we decided at the time, to enforce a certain rigor in the dev team.

    In your documentation, you say: “This functionalities is still of fundamental importance both to use custom data structures to hold nodes and to add/remove listeners from component events.”
    I do understand the logic behind that, you will listen to events when they are added. It makes a lot of sense.

    But in the example provided, unless I missed it somewhere it doesn’t seem to be intended that way. Maybe I misunderstood the intention in the example, but Add() in most of the engines seems to be here only to store
    specific nodes. So from what I can gather, EnemyAnimationEngine could have been written in a way that nodesDB is never used, and instead EnemyNodes are directly stored within the engine through Add.

    My questions would be this: what is the actual best practice in your framework to use these Add() / Remove() methods?

    Again, thanks for your great work!

    1. Thank you for the comment and the question. First, let me clarify that the framework is in continuous evolution and the version we use currently for Robocraft is different. I do update the github version every now and then, and I plan to update it soon (but I don’t know how soon).

      I hate when frameworks provide more than one way to do the same thing, so I do agree that what you are saying causes confusion. Fact is that I added the (very much needed) IQueryableNodeEngine much after the first release. So IQueryableNodeEngine is absolutely the way to prefer when the standard datastructure fits your need. It’s also super fast as it news only structs and returns wrapped references (no copies involved).

      My example is a bit dumb, I left some code as it is just for illustration purpose, so don’t take it as guideline, but yes Add and Remove is still useful in several cases.

      For example the standard datastructure couldn’t be fast enough to query in some cases, so you may want to have your own datastructure. Events is another case like you said. However yes, the case you specifically report is controversial. We don’t have the rule to not store entities and we found out that, some times, holding the node is more convenient than querying by index. After all if you don’t hold the node, you must hold the index to the node.

      Edit: careful about using DispatchOnChange and DispatchOnSet, use them only if you see as changing data. In complex cases, where communication between several engines is necessary, the Sequencer is a much more elegant and powerful tool.

      1. Excellent, thank you very much for the clarification. It’s tough to jungle full time job + a public framework on the side. Just curious now, what would be the upcoming critical changes in the eventual next update? I will make sure I keep an eye on this, and will use the current version of your framework, which I believe is already strong enough for my need.

  9. Hi Sebastiano,
    It’s been now a few days I’m playing around with your framework, and it’s indeed very interesting.

    However, I have a bit of a concern when it comes to the moment an entity and its components are defined:
    One of the main feature of previous ECS I’ve worked on was the possibility to add and remove components from an entity at run-time. This would result in triggering a system that was waiting for the presence of a certain component.

    Because an engine looks at nodes only, and doesn’t really access entities, this doesn’t seem the case here.
    In the example provided it feels like an entity is purely defined at compilation, and adding/removing components to it doesn’t seem possible. Is that the intention here?

    1. The framework is designed to remove and build entities in runtime but not change them. However we actually never used this feature as we prefer to enable and disable features instead just using simple flags inside nodes themselves

      1. Thanks Sebastiano, this is quite fascinating actually. It forces devs to make better decisions when defining entities beforehand, as they will remain the same in their structure. I will definitely keep that in mind.

  10. Hi Sebastiano,

    Thank you for all of your shared work and thoughts.

    What would be recommended way of communicating 2 engines only? One-step sequence feels like a great deal of work in this case.

      1. Let me paraphrase that:

        Engine is interested to know when when a value of node (which is not managed by that engine) is changed.

        Is that what you mean?

          1. So we have Dispatcher for data updates and Sequences for more complicated scenarios involving many engines. I have also noticed you make use of Observer-Observable couples; how would they fit into grand scheme? Do you have any specific recommendation for O-Os vs Dispatcher&Sequences?

  11. Hi Sebastiano,

    I have a quick question regarding the initialization of entities. When a entity is created, it is currently not possible to set any of its component data.

    A good example of it is EnemySpawnerEngine: once the enemy has spawned it’s necessary to position it according to the data available within this engine.

    var go = _factory.Build(spawnData.enemy);
    _entityFactory.BuildEntity(go.GetInstanceID(), go.GetComponent().BuildDescriptorType());
    var transform = go.transform;
    var spawnInfo = spawnData.spawnPoints[spawnPointIndex];

    transform.position = spawnInfo.position;
    transform.rotation = spawnInfo.rotation;

    Another way to do so I suppose would be to go through another engine, but that engine might not have access to the data necessary to position the enemy.
    Is this good practice? or have you find a better to handle this since then?

      1. My point here was that in the example we were directly accessing from the engine to a data inside a GameObject itself, instead of even going through the implementor. And still I was wondering if even accessing the implementor itself is good practice. In my mind, it feels like an engine should not even be aware of any implementor at all, only Nodes.

        Looking forward to your update 🙂

          1. So knowing all that, the question is: how can an engine calling a BuildEntity be able to pass any information to that entity? (Such as positioning it for instance)

          2. Are you asking how to pass data inside implementors to be used with the entity descriptors? If so you can pass data to the implementors constructor. If you are asking if an engine can create implementors I’d say yes.

          3. I’m very confused, and all my apology if I don’t understand this correctly. I really want to get to the bottom of this if possible. 😀

            You are suggesting “to pass data to the implementors constructor”… but implementors are Monobehaviors here, there’s not supposed to be a notion of constructor…

            I will try to re-explain the issue:

            We have a EnemySpawnerEngine which holds all the data necessary on where and when to spawn new enemy.

            At every n seconds this is called:
            var go = _factory.Build(spawnData.enemy);
            _entityFactory.BuildEntity(go.GetInstanceID(), go.GetComponent().BuildDescriptorType());

            Now instantiating the GameObject, and building the entity is not enough, a few more information needs to be passed to that newly created enemy. Let’s say we have to position it depending on the spawn point right after calling BuildEntity.

            Option 1: Accessing transform directly through the GameObject.
            go.transform.position = spawnData.position;

            Option 2: going through the Implementor (which is here public class EnemyMovement : MonoBehaviour)
            and so calling go.GetComponent().transform.position = spawnData.position;

            Option 3?

          4. Hehe nvm the example I have to revisit it. However I had the suspect that the mono behavior thing could have confused you. Implementors don’t have to be monobehaviours and actually is better to use them only when essential. You can also pass several implementors to an entity descriptor so it’s better to have implementors as modular as possible so you can reuse them. For the specific case of the example I have to see it again to take a decision. I’ll try to check this weekend.

          5. @apartment609
            Isn’t what you need is to get node associated with newly created entity and then fetch ITransform component via node?
            Then you could do whatever you need on the transform’s reference

          6. @Sebastiano Mandalà haha yes, there might be a whole realm of use I am not seeing yet, because I got too focused on Implementors being MonoBehaviors, like in the example. I’m going to read the article again, get deep in the code and all the comments all over again. Different use cases as example would be very interesting to see. Thank you so much for your patience. I am going to use this framework, it has so much potential! (Hell I’m already using it)

            @Szymon You are absolutely right, engines usually check the presence of certain types of Nodes before processing them. But in this specific case, we want to initialize it right at the moment it gets created, as the necessary data are available right at this point, not anywhere else. Calling BuildEntity doesn’t expose or return any form of Node, so there must be a way to pass some form of parameters at the moment of the creation of the entity… that’s what I’m trying to analyse here.

          7. (Note: “>” and “<" template arguments are stripped from comments so I will use quotes)

            Option 3: querying immediately a Node right after calling BuildEntity so that means:

            var go = _factory.Build(spawnData.enemy);
            _entityFactory.BuildEntity(go.GetInstanceID(), go.GetComponent "” ().BuildDescriptorType());
            var node = nodesDB.QueryNode “” (go.GetInstanceID());
            node.transformComponent.transform.position = spawnData.position;

          8. …and the quotes didn’t work!… so again:

            var go = _factory.Build(spawnData.enemy);
            _entityFactory.BuildEntity(go.GetInstanceID(), go.GetComponent““().BuildDescriptorType());
            var node = nodesDB.QueryNode““(go.GetInstanceID());
            node.transformComponent.transform.position = spawnData.position;

          9. OK Finally checked the EnemySpawnerEngine code. That code is fine. If I extend the GameObject factory to accept the initial position, that code would have been inside the factory. So tbh it’s a factory responsibility to set the initial transform, but all that said, I will leave the code like that.

  12. Hi Sebastiano.
    Thanks for the great blog and framework.

    I’m interested, why do you think that interface components are better than components implemented as objects?
    With components as objects I can add behavior to entities or remove it in realtime, which adds flexibility. Same entities can behave differently depending on what components are added to them in runtime.

    And I cannot do that with interfaces. Is there any advantage that outweighs this drawback?
    Thanks

    1. Allocating objects especially in run time has a cost. Having hundreds of object references hanging around has a cost on the garbage collection as well. All that said Svelto.Ecs can do everything the other frameworks do but in a different way. Usually we enable and disable behaviours in runtime with simple booleans in the implementors themselves.

      1. Thanks for quick reply. Looks interesting. We’re using Entitas now at our team. In Entitas the problems with garbage collection are handled by using pools for components. However this adds some complexity, which is hidden under code-generated APIs.
        Code generation adds level of indirection to the code, it’s difficult to research it, especially for junior-level developers.
        That’s why I’m looking for an ECS implementation without code generation. I was thinking of making it myself, but then found your implementation.
        Thanks for sharing it!

        1. Hi Sebastiano!

          I’ve looked through the example code. First it seemed complex, but after a day of investigating now everything is clear and logical. I really liked the concept of Sequencer. It solves one of biggest problems we had in Entitas – need to look through all the code to find what happens next.

          However I haven’t found DispatcherOnSet and DispatcherOnChange in the project. Have you removed them? Or is there still a way to dispatch events in components?

  13. Hi Sebastiano.
    Thank for great framework. Pooling, GC, and Input system are parts of the framework, and any more…. so how do i implement them, using engine? or … something to extend your framework, thank for your feedback.

    Regards,
    kkmct.

  14. Hi Sebastiano.

    I’ve looked through the framework source code and here are some questions I have:
    1. What platforms is Svelto.ECS tested on? Does it work in plain C#, without Unity? There’s #if NETFX_CORE statement in some places in source code. Is it supposed to be working also in .NET Core?
    2. I’ve looked through FasterList implementation and it looks the same as System.Collections.Generic.List one: a dynamically allocated array. Is FasterList really faster than System.Collections.Generic.List? Is it tested? How is it achieved?
    3. I noticed you use reflection in EntityDescriptor.BuildNodes method. It’s being called every time a new entity is created. Is performance good enough when creating a lot of entities?

    One more thing: it would be great to have a more convenient place for questions than comments in the blog. Having Gitter or forum specifically for the framework would be cool and would help to build the community around Svelto.ECS.

    1. Once I am back on my blog I’ll open a thread in the unity forum. Answering your questions:

      Everything is tested in production on several project at freejam

      Faster list is not faster because of the code is faster because of some different choices that work in most cases, like the unordered remove and the clear that doesn’t actually clear the buffer.

      Entity creation isn’t a problem for us at the moment

      1. I was checking the code of the Entity Creation. To be honest in Robocraft we never destroy entity, so this feature is not well tested yet, however we do use for a not announced new product. in RC, the creation of entities is quite rare too as everything is created at the startup phase. All that said I can see how it could potentially be needed to create an entities pool to recycle nodes instead to create them everytime. Can’t think about it now, but if you have ideas let me know.

  15. Hello Sebastiano!

    Great framework! Thanks for making this!

    I’m still figuring out stuff and it would be great if you could help me with some questions.

    – What’s the best course of action for querying StructNodes to get an item with ID x?
    – Should I update data in StructNodes? I added Count, SetAt, GetAt, Remove and Clear to it. This is the wrong way I guess.
    – Building structs and setting init data is really weird as I didn’t find a way to set the data directly. Is the descriptor and constructor arguments that are saved for the BuildNodes event the best?

    Thanks!

    1. Hello and thanks for the comment.

      Nodes as struct are an advanced feature of Svelto.ECS. Since you want to take advantage of the cache when using them, it doesn’t make sense to pick-up a specific index. The array of nodes is meant always to be iterated, from the begin to the end. In the other cases you can use the standard approach. Alternatively you can manage the array of struct removing/add nodes if you know what you are doing.

      Nodes MUST not have any functions inside. I am actually going to add soon a check that will throw an exception if anything else than interfaces are found inside class components and anything else than interfaces or value type are found inside struct nodes. It will throw an exception also if anything else than getter and setter are found inside a component interface.

      When struct nodes made out of value type are used (therefore they don’t need implementors) is responsibility of the engines to initialize/use the data inside them. I wouldn’t probably mind (at the moment) if you would initialize the values also when they are built, but I haven’t thought through this yet.

      1. You can put a comment in StructNodes, if you want to change this you are not thinking data-oriented enough.

        I think I know now where I’m going wrong. The engine does too much and I need to get away from thinking in sequential code. Learning new paradigms isn’t easy, thanks for helping out!

        1. “You can put a comment in StructNodes, if you want to change this you are not thinking data-oriented enough.”

          didn’t understand this. I have to put the exceptions to avoid inserting functions inside nodes just because it is data oriented. If instead you mean why I want to keep coders from adding anything else than interfaces inside class nodes is because the framework MUST be rigid. I noticed few people doing otherwise, and this would eventually take to bad design. The nodes have a specific purpose, they don’t have to do anything else than just mapping components. Components have specific purposes, they must contain just data (Through getter and setters). Nodes as struct have a very specific purpose, they are meant to be used only when optimizations matter.

          1. Nodes only contain struct data in my case and I extended your StructNodes class to have more helper functions, but instead of manipulating the data directly in StructNodes it’s better to just remove/add them, right? These are long-living entities so would this really be the best course?

            Have you had any algorithms that iterated over nodes and prev/next nodes data had to be changed? I’m trying to get this behaviour out but the only thing I know how to do this is to build new data that are processed later on. Like your addNodesToBuild which work deferred. I’m not sure how reasonable it is to build new structs vs. setting a varible(s) in an array of structs. You wrote about cache invalidation which I’m triggering when I do stuff like this, correct?

            I’m also not sure how I should handle relations in structNodes. For a simulation game I’ve many entities, lets say you can build many road segments of 10mx10m and you can put cars on it. These cars have to know from the init which road they are on (which my octree handles) and then get the data to move in the right direction and don’t crash in other cars when one in the front stops. If the car leaves the road segment it has to be moved to the next one.

            I’ve 2 StructNodes and StructNodes. What I’m doing now is quite hacky as I use the direct index to the StructNode in iterations.
            I loop through RoadSegment and calculate if cars can sleep/move but I have to touch some Car nodes when it’s time to move a car to the next segment (I could defer this). After that I loop over the cars and process their movement which is blazing fast and I really like.

            Do you have any suggestions for improvement for this? I feel like the data structure is too much like OOP.

          2. I should see the code to understand what you are trying to achieve, however if you are new to Svelto.ECS I still suggest you to use the normal nodes and not the struct nodes. The survivor little game bundled do not use struct nodes at all. Have you seen that code before to start experimenting?

Leave a Reply