Search Unity

[Open Source] Svelto.ECS - Lightweight Entity Component System for C# and Unity

Discussion in 'Works In Progress - Archive' started by sebas77, Oct 28, 2017.

  1. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Svelto.ECS

    It's a real Entity-Component-System fully-fledged framework with minimal boilerplate and painless integration with whatever platform, including Unity.

    FAQ:

    What is Svelto.ECS?


    Real Entity-Component-System for c# and Unity (it can be adapted to other c# platforms too). Enables to write encapsulated, uncoupled, highly efficient, data-oriented, cache-friendly, code without pain.

    Who is using Svelto?

    Svelto is the official framework of Freejam, so it's extensively used to run Robocraft, Robocraft Infinity, Cardlife and Gamecraft. Please let me know if you use it, as I'd like to be in contact.

    Do you offer support for Svelto?

    Svelto is open source and offered as it is. Unluckily I don't have much time to invest on it beside what I need for our projects, but I will be glad to answer your questions as they come. Of course, being open source and hosted on GitHub, feel free to send me pull requests.

    OK, but what can I do with it?

    I wrote a lot of articles about why ECS is currently the best pattern to use in game development (links at the bottom). Of course, I am not the only one to agree with it, but it's true that there is great confusion around what ECS is, but mostly about how to implement it, as some inherent problems never had a standard solution. Svelto.ECS works very well within other systems, including Unity. It allows writing clean and efficient code. Svelto.ECS is the perfect solution to write complex projects where the maintainability of the code is essential.

    What can I do that I can't do with Unity already?

    • Enables writing uncoupled and encapsulated logic for the entities of your game.
    • The rigid infrastructure allows focusing more on the problem than on the code design.
    • An intuitive framework, or at least I hope it is once understood the fundamental concepts.
    • Designed to be fast and light.

    Further Resources:


    My blog: http://www.sebaslab.com/

    Long dissertation on why ECS is a great pattern:

    http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-i-dependency-injection/

    http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-ii-inversion-of-control/

    http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iii-entity-component-systems/

    http://www.sebaslab.com/the-truth-b...ntrol-part-iv-dependency-inversion-principle/

    http://www.sebaslab.com/the-truth-b...rol-part-v-drifting-away-from-ioc-containers/

    Github: https://github.com/sebas77/Svelto.ECS

    [27/12/20]

    Svelto.ECS 3.0 is out: please read more about it here:

    https://www.sebaslab.com/whats-new-in-svelto-ecs-3-0/
     
    Last edited: Dec 27, 2020
  2. Kuptsevych-Yuriy

    Kuptsevych-Yuriy

    Joined:
    Oct 1, 2008
    Posts:
    20
    Hi. Thank you. Do you have any plans to offer a series of simple examples or article on how to use your framework step by step ? This would greatly simplify start.
     
    dannyalgorithmic likes this.
  3. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Hello

    The bundled example is a good starting point, but I definitely need to write more. I hope in some help from the community too.
     
  4. sanssariph

    sanssariph

    Joined:
    Dec 1, 2017
    Posts:
    1
    I am new to game development with a long background in OOPy software engineering - I have been intent on not developing bad habits from the get-go with Unity and have been trying to figure out the best way to decouple logic details from my Monobehaviors. I came across your article series this afternoon while reading up how folks approach DI and IoC in Unity. Read all of them back to back.

    Your posts really resonated with me. Original plan was to experiment on my own with Zenject but I think I will try learning from your own lessons and experiments and see how I find applying Svelto to the project I'm getting off the ground. Thank you for sharing.
     
  5. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Thanks, finally I have the chance to focus more on my framework. In the next weeks I will release new versions of Svelto.ECS and Svelto.Tasks, but more importantly new articles. I need to write more about:

    - explain in a simpler way how to use the framework
    - explain with practical examples why is it bad to use IoC containers without inversion of control in mind and why using an ECS framework remove the need to use any IoC containers
    - write a presentation to explain why even the Unity devs are now developing an official ECS system, in terms of code design, not optimizations.
     
    AdmiralThrawn likes this.
  6. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    -- message moved from another topic --
    Considering the amount of work you put on code architecture in Unity, I think we are on the same route of mindset, the only difference being you are already a couple of miles ahead of me.

    So I committed the holy sin of starting with a huge project, which wasn't horrible actually. But as the project got bigger, I started to feel like my code was getting messier and messier. In order to implement a new feature or fix a bug, I had to travel between at least 3 classes, work with about the same amount of methods at the same time... etc. It became exhausting to maintain at some point and I had to give up on that huge project.

    Do you think adopting a framework like your SveltoECS will automagically fix these problems, or, at least make the majority of them disappear?
     
  7. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    Really? May you give a citation? I'm actually very excited to hear that. I hope they finally implement proper C# practices instead of magical methods and a serialization system that only the god knows what it is doing.
     
  8. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Let me start saying that there isn't a standard way to implement an ECS framework. There are several problems that arise from this architecture that haven't been solved in a standard way. That's why Svelto.ECS has some unique features that I never seen in other frameworks. All that said I can't say which one is the best, but I can tell you that Svelto.ECS has been designed with two things in mind: simplicity and rigidity. Simplicity is still something I am improving, I want to the coder to write as less as possible boiler plate code and find what they is writing intuitive. Rigidity is all about revealing the coders from the burden to design their code and focus only on their problem to solve. If coders can write their code in one way only, he wouldn't need to wonder if their solution is correct in terms of code design (And its repercussion in maintainability). I am still improving the rigidity of the framework either at compiling time and a runtime, trying to warn the coders when their are doing something wrong.

    So if what I am doing is successful, after the due time to adjust to the new way of thinking, you should be able to write productive code without thinking about how messy your code is.

    Now I understand I have a global vision of what's going on and Svelto.ECS is still not super simple to grasp at first, but I am committed to write new articles to try to overcome this issue.
     
    Last edited: Dec 3, 2017
  9. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    linked the video in the original post.
     
    s-ta-c likes this.
  10. leopripos

    leopripos

    Joined:
    May 25, 2014
    Posts:
    18
    Hi @sebas77

    I am trying to make game using Svelto. But, just for starting my code look like this.
    I am curious "am I doing it right?" or "Am I using svelto in the right way?"

    Can you give some opinions?
    Code (CSharp):
    1. connectionSequencer.SetSequence(
    2.     new Dictionary<IEngine, Dictionary<Enum, IStep[]>>()
    3.     {
    4.         {
    5.             connectionEngine,
    6.             new Dictionary<Enum, IStep[]>()
    7.             {
    8.                 {
    9.                     ServerStatusEventType.ServerStarted,
    10.                     new IStep[]
    11.                     {
    12.                         battleDataSpawnerEngine,
    13.                         playerDataSpawnerEngine,
    14.                         playerTroopDataSpawnerEngine,
    15.                         authenticationEngine,
    16.                     }
    17.                 },
    18.                 {
    19.                     ServerStatusEventType.ServerReady,
    20.                     new IStep[]
    21.                     {
    22.                         battleStateEngine,
    23.                     }
    24.                 },
    25.                 {
    26.                     ServerStatusEventType.ServerFail,
    27.                     new IStep[]
    28.                     {
    29.  
    30.                     }
    31.                 },
    32.                 {
    33.                     ServerStatusEventType.ServerStopped,
    34.                     new IStep[]
    35.                     {
    36.                         battleDataSpawnerEngine,
    37.                         playerDataSpawnerEngine,
    38.                         playerTroopDataSpawnerEngine,
    39.                         authenticationEngine,
    40.                         battleStateEngine,
    41.                     }
    42.                 },
    43.                 {
    44.                     ClientConnectionEventType.ClientConnect,
    45.                     new IStep[]
    46.                     {
    47.                         authenticationEngine
    48.                     }
    49.                 },
    50.                 {
    51.                     ClientConnectionEventType.ClientReady,
    52.                     new IStep[]
    53.                     {
    54.                         authenticationEngine
    55.                     }
    56.                 },
    57.                 {
    58.                     ClientConnectionEventType.ClientDisconnect,
    59.                     new IStep[]
    60.                     {
    61.                         authenticationEngine
    62.                     }
    63.                 },
    64.             }
    65.         },
    66.         {
    67.             authenticationEngine,
    68.             new Dictionary<Enum, IStep[]>()
    69.             {
    70.                 {
    71.                     AuthenticationEventType.AccountAccepted,
    72.                     new IStep[]
    73.                     {
    74.                         playerSlotEngine,
    75.                     }
    76.                 },
    77.                 {
    78.                     AuthenticationEventType.AccountRejected,
    79.                     new IStep[]
    80.                     {
    81.                         connectionEngine,
    82.                     }
    83.                 },
    84.                 {
    85.                     AuthenticationEventType.AccountDisconnected,
    86.                     new IStep[]
    87.                     {
    88.                         playerSlotEngine,
    89.                     }
    90.                 },
    91.             }
    92.         },
    93.         {
    94.             playerSlotEngine,
    95.             new Dictionary<Enum, IStep[]>()
    96.             {
    97.                 {
    98.                     PlayerSlotEventType.PlayerJoin,
    99.                     new IStep[]
    100.                     {
    101.                         waitingTimeEngine,
    102.                     }
    103.                 },
    104.                 {
    105.                     PlayerSlotEventType.PlayerRejoin,
    106.                     new IStep[]
    107.                     {
    108.                         waitingTimeEngine,
    109.                     }
    110.                 },
    111.                 {
    112.                     PlayerSlotEventType.PlayerSlotNotAvailable,
    113.                     new IStep[]
    114.                     {
    115.                         authenticationEngine,
    116.                     }
    117.                 },
    118.                 {
    119.                     PlayerSlotEventType.PlayerOut,
    120.                     new IStep[]
    121.                     {
    122.                         waitingTimeEngine,
    123.                     }
    124.                 },
    125.             }
    126.         }
    127.     }
    128. );
    129.  
    130. battleStateSequencer.SetSequence(
    131.     new Dictionary<IEngine, Dictionary<Enum, IStep[]>>()
    132.     {
    133.         {
    134.             battleStateEngine,
    135.             new Dictionary<Enum, IStep[]>()
    136.             {
    137.                 {
    138.                     BattleStateEventType.EnterWatingTime,
    139.                     new IStep[]
    140.                     {
    141.                         waitingTimeEngine,
    142.                     }
    143.                 },
    144.                 {
    145.                     BattleStateEventType.ExitWaitingTime,
    146.                     new IStep[]
    147.                     {
    148.                         waitingTimeEngine,
    149.                     }
    150.                 },
    151.                 {
    152.                     BattleStateEventType.EnterSelectionTime,
    153.                     new IStep[]
    154.                     {
    155.                         new DebugStepEngine<BattleStateEventData>()
    156.                     }
    157.                 },
    158.                 {
    159.                     BattleStateEventType.ExitSelectionTime,
    160.                     new IStep[]
    161.                     {
    162.                         new DebugStepEngine<BattleStateEventData>()
    163.                     }
    164.                 },
    165.                 {
    166.                     BattleStateEventType.EnterStartegyTime,
    167.                     new IStep[]
    168.                     {
    169.                         new DebugStepEngine<BattleStateEventData>()
    170.                     }
    171.                 },
    172.                 {
    173.                     BattleStateEventType.ExitStrategyTime,
    174.                     new IStep[]
    175.                     {
    176.                         new DebugStepEngine<BattleStateEventData>()
    177.                     }
    178.                 },
    179.                 {
    180.                     BattleStateEventType.EnterPlayingTime,
    181.                     new IStep[]
    182.                     {
    183.                         new DebugStepEngine<BattleStateEventData>()
    184.                     }
    185.                 },
    186.                 {
    187.                     BattleStateEventType.ExitPlayingTime,
    188.                     new IStep[]
    189.                     {
    190.                         new DebugStepEngine<BattleStateEventData>()
    191.                     }
    192.                 },
    193.                 {
    194.                     BattleStateEventType.EnterResultingTime,
    195.                     new IStep[]
    196.                     {
    197.                         new DebugStepEngine<BattleStateEventData>()
    198.                     }
    199.                 },
    200.                 {
    201.                     BattleStateEventType.ExitResultingTime,
    202.                     new IStep[]
    203.                     {
    204.                         new DebugStepEngine<BattleStateEventData>()
    205.                     }
    206.                 },
    207.             }
    208.         },
    209.     }
    210. );
     
  11. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    ah cool! this must be the longest sequence I have seen so far! I don't use sequences that much, but I don't see anything wrong in what you wrote, it seems almost a state machine. If you have any feedback, please let me know.

    About how to use Svelto:

    I am going to write this in the new article, but it's essential that the first step in the design is to identify your entities and components. Then your engines and nodes. Engines and Nodes must come together and fit purposely to the entities that must be managed.
     
  12. leopripos

    leopripos

    Joined:
    May 25, 2014
    Posts:
    18
    It just a starting code, not the gameplay yet, so It will be longer :)
    And yes it adopts state machine, because the state machine pattern is easier for me.

    I have a case: Team and Player.
    1. There are 2 teams.
    2. Each team consist of many players
    How to implement this in my code?
    Should I make component ITeamMemberComponent for Team Entity?
    Code (CSharp):
    1. interface ITeamMemberComponent : IList<PlayerID> { }
    Or group players when players built
    Code (CSharp):
    1. entityFactory.Build(PlayerId, TeamId, new PlayerDescriptor())
     
  13. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Thanks for the feedback, I will write more about the sequencer in my new article I am preparing.

    You need to be careful about not overusing any of the tools available in Svelto.ECS. Everything must make logically and semantically sense. When I say semantically, I mean that ever just reading the code must make sense.

    The power of Systems (engines) is the encapsulation. With ECS is possible to achieve the holy grail of the perfect code encapsulation, something that is very hard to achieve otherwise. Encapsulated systems need to handle the logic of one or several entities in their entirety. The difference between any ECS implementation and Svelto.ECS is that engines can know entities through different nodes, so the same entities can be handled in different way by different engines, keeping the encapsulation intact.

    For example, in Robocraft, we have an engine that handles the physic of all the wings, another engine that handles the graphic of the wings and another engine that handle the audio of all the wings.

    These engines do not know each other and they don't need to know each other, the three engines handle logic that are not coupled in any way (except for the entity data, I obviously assume that data is not a dependency).

    Another engine, that knows the entity through the HealthNode will instead manage the health when healing or destruction happens, enabling or disabling the wing entities according the health.

    This is the best way to have Single Responsibility designed engines. However Sequence born for those cases where engines need to communicate with each other. They create a sort of chain of responsibility (something I am going to explain better) that creates a flow of logic through separate modular engines. Although this sounds cool, must not be overused, otherwise the risk is that the code wouldn't readable unless knowing the sequence. More complex is the sequence, more difficult will be to understand the code flow.
     
    sanssariph likes this.
  14. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Sorry I forgot to reply about this. I need to understand better what you are trying to achieve, but the only thing that doens't look right here is the ITeamMemberComponent implementing an IList interface.
     
  15. leopripos

    leopripos

    Joined:
    May 25, 2014
    Posts:
    18
    What I mean is how to implement ownership when design entity and component. For the example how to implement "Entity A has many Entity B" and "Entity B has many Entity C".
     
  16. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    oh OK, ECS and OO design are quite different, almost diametrically opposed. It's not responsibility of an entity to know other entities. Engines (Systems) must handle entities. What you probably need is the concept of grouping entities, which I am adding in the new version of Svelto. You will be able to query entities by group, so that you will be able to say, inside an engine, return all the nodes of the entities that belong to this other entity.

    In Robocraft, for example, you can say: return all the wings that belong to this specific machine. You can already do it without the new feature, but you will need a custom structure to be used inside your engine.
     
  17. leopripos

    leopripos

    Joined:
    May 25, 2014
    Posts:
    18
    And sorry for my slow response. I was trying to search more resources about ECS and Svelto.

    It is clear enough about ownership for now.
     
  18. leopripos

    leopripos

    Joined:
    May 25, 2014
    Posts:
    18
    I believe i miss some concepts about ECS or Svelto before. I tried to google about ECS and Svelto.ECS, but it is quite hard to find good references about ECS implementation manage game state and logic like networking(ex: connect, disconnect) and state transition (ex: waiting player to character selection). All examples just explain about gameplay character (or unit or troop) behaviour like moving. If you have, please let me know :)

    And about my code before, I tried to change it. This is how it look like now. What do you think?
    Code (CSharp):
    1.  
    2. var connectSequence = new Sequencer();
    3. var loginSequence = new Sequencer();
    4. var disconnectSequence = new Sequencer();
    5.  
    6. var connectionRejection = new ConnectionRejectionObservable();
    7. var connectionRejectionObserver = new ConnectionRejectionObserver(connectionRejection);
    8.  
    9. var authenticationEngine = new AuthenticationEngine();
    10.  
    11. var socketEngine = new SocketEngine(connectSequence, loginSequence, disconnectSequence, connectionRejectionObserver);
    12. var connectionEngine = new ConnectionEngine(connectionRejection);
    13. var networkLoggerEngine = new NetworkingLoggerEngine();
    14.  
    15. connectSequence.SetSequence(new Steps()
    16.     {
    17.         {
    18.             socketEngine,
    19.             new Dictionary<Enum, IStep[]>()
    20.             {
    21.                 { ConnectCondition.Default,  new IStep [] { connectionEngine, networkLoggerEngine } }
    22.             }
    23.         },
    24.     }
    25. );
    26.  
    27. loginSequence.SetSequence(new Steps()
    28.     {
    29.         {
    30.             socketEngine,
    31.             new Dictionary<Enum, IStep[]>()
    32.             {
    33.                 { LoginCondition.Request,  new IStep [] { authenticationEngine, connectionEngine, networkLoggerEngine } }
    34.             }
    35.         },
    36.     }
    37. );
    38.  
    39. disconnectSequence.SetSequence(new Steps()
    40.     {
    41.         {
    42.             socketEngine,
    43.             new Dictionary<Enum, IStep[]>()
    44.             {
    45.                 { DisconnectCondition.Default,  new IStep [] { connectionEngine, networkLoggerEngine } }
    46.             }
    47.         },
    48.     }
    49. );
    50.  
     
  19. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    I am currently working on Svelto.ECS 2.0. I am writing as much documentation as possible and I will need to write several articles since I cannot focus just on the theory or just on examples, people need both. The theory is necessary to understand why the ECS paradigm is superior, the examples are needed to figure out the details.

    The problem about designing engines is that they must be designed with the entities in mind through their nodes.
    In svelto.ECS 2.0 Nodes are now called EntityView in order to try to clarify their goal. I understand that the EntityView concept, while very simple (is just a mapping of Entity components) is hard to get at first.

    this means that if you write a SocketEngine you would likely need a SocketNode. This SocketNode is a set of references to the Entity components needed by the SocketEngine. I am not sure what components your SocketEngine needs, but if you made this reasoning, then you are on the right path!

    I also understand that using sequencers can be confusing because you may see Engines just as a mere collection of code. It must not be like this, engines must still handle entities and the sequencer is just a way to step through several engines that handle logic of the same or different entities.

    Check also this image, it will be part of the next article I am going to write, maybe it can help to see better how all the framework objects interact with each other:
    Svelto-ECS.jpg
     
    AdmiralThrawn likes this.
  20. leopripos

    leopripos

    Joined:
    May 25, 2014
    Posts:
    18
    Nice illustration, thanks
    Please notify forum when you have finished your article :)

    I made SystemConfigurationDescriptor which has SocketComponent, and the SocketComponent has port and max connection properties.
     
  21. AdmiralThrawn

    AdmiralThrawn

    Joined:
    Dec 1, 2013
    Posts:
    18
    Wow this sounds super exciting! My project is in a stadium where everything has been refactored to small, highly modular components following the SOLID principles, except for the fact that I haven't decided on what IoC framework to use. According to your last comment you are no more recommending any IoC framework and instead suggest to integrate an ECS. I havent dealt with any of that before, but if your work is as good as you say, then I'm seriously willing to put a ton of time to integrate this system in my project. Sigh, .. already 2:30 am. Well then, I'm going to check out your github project tomorrow and dive into what you have created for us! :)
     
  22. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Hello,

    thanks for the message. Yes absolutely Svelto.ECS supersedes either Svelto.IOC and all the available IoC container for Unity. I will explain in an article why using an IoC container is not a good idea anymore, at least under my point of view obviously. The principles are that once an IoC container is used with real Inversion of Control in mind, will lead to something close, but less effective, than the ECS paradigm anyway.

    If you start to play with Svelto.ECS now, be sure to use the alpha branch as soon I will merge it down to the master one and it contains the new version of the framework.
     
    Last edited: Jan 9, 2018
    AdmiralThrawn likes this.
  23. AdmiralThrawn

    AdmiralThrawn

    Joined:
    Dec 1, 2013
    Posts:
    18
    Alright, will do. Thanks Sebas! Looking forward to this article!
     
  24. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
  25. AdmiralThrawn

    AdmiralThrawn

    Joined:
    Dec 1, 2013
    Posts:
    18
    Sadly, the project can't compile svelto.dll. I also installing .NET standard 2.0, but VS (I use VS Community 2017) is not able to compile the project as it finds members being already defined or that inherited members have the same signatur as some other types and thus cannot be overridden.
     
  26. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Bear with me I will write some instructions about how clone the repository. Unluckily git sub modules are useful but not intuitive to use.
     
  27. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    AdmiralThrawn likes this.
  28. AdmiralThrawn

    AdmiralThrawn

    Joined:
    Dec 1, 2013
    Posts:
    18
    Yes, that's true. I was not expecting problems when pulling the submodules in a recursive manner, but it was a silly assumption.
    Thanks for your quick update on the wiki pages! Now Unity no more complains and your Example is running perfectly.
    Time for learning on your examples. :)

    After having a solid understanding I'll try to integrate your ECS in the cube collision demo on Adam Martin's blog here.
    I found this little setup very useful in the sense of being able to quickly scale up numbers while observing performance and having visual debugging about what's going on.
     
  29. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Cool, please share your results, more resources the better for everyone :)
     
  30. AdmiralThrawn

    AdmiralThrawn

    Joined:
    Dec 1, 2013
    Posts:
    18
    Check, I'll do, mate! Will spend my next evenings after work on this. Give me a week or two.
     
    sebas77 likes this.
  31. leopripos

    leopripos

    Joined:
    May 25, 2014
    Posts:
    18
    @sebas77, could you please explain about meta entity and how to use it by an example?
     
  32. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    I knew that someone soon or later would have asked this :).

    MetaEntity is a slightly confusing concept that has rare applications. I introduced it because actually Robocraft had few good use cases. It's slightly confusing because a MetaEntity is not defined in terms of Game Design entity, so it's abstracted and thus harder to conceive. A MetaEntity is a way to control a group of other entities that share the same implementation of an entity component.

    You can build N normal entities using a specific entity component and then just one Meta Entity with one EntityView using the same component, in this way the engine that uses the MetaEntity can control N entities with just one EntityView.

    Basically you don't need to iterate N entities and set a value if this value must be the same for all N entities. In robocraft we use it to control weapons of the same type grouped by size.

    This "trick" is possible thanks to the use of shared implementors. While the Meta Entity and the N Entities will build different EntityViews (different instances in memory), they will point to the same implementation for that specific component.

    //shared implementor for the component shared between the weapons of the same size
    chaingunGroupImplementor = new ChaingunGroupImplementor();

    //build the meta entity, a specific engine will be able to control a group of entities sharing the same implementor
    enginesRoot.BuildMetaEntity<MetaChaingunDescriptor>(id, new object {chaingunGroupImplementor});

    //build N entity using the shared implementor.
    enginesRoot.BuildEntity<ChaingunDescriptor>(id, new object[] { chaingunGroupImplementor, specificweaponImplementor});


    if you understood how the entity components are implemented through the implementors, you can understand why you are enable to exploit this kind of tricks.

    If you don't get it at glance, don't worry. This is an advanced scenario that you will understand once get used of the framework logic.
     
  33. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
  34. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    I am super glad to announce that finally another Svelto user started to write articles! Dario uses it for a while now and the articles are pretty insightful! 3 articles so far, but he is planning to write the fourth and improve the examples!

    https://eagergames.wordpress.com/2018/02/10/learning-the-svelto-ecs-framework-part-i/
    https://eagergames.wordpress.com/2018/02/17/learning-Svelto-ecs-framework-part-ii/
    https://eagergames.wordpress.com/2018/02/24/svelto-ecs-framework-part-iii-testing-engines/
     
  35. DaniParra

    DaniParra

    Joined:
    Apr 27, 2014
    Posts:
    29
    Hello!

    Some colleagues and I are doing a simple game of "rock, paper, scissors" using Svelto.ECS and we have some doubts about the development.

    On the one hand I've a component "IUserMovementButton" and its implementor is a monobehavior, this implementor with the component is responsible for fire an action with the movement selected by the player.

    Code (CSharp):
    1. public interface IUserMovementButtonComponent : IComponent
    2.     {
    3.         Action<UserMovementInfo> OnPressed { get; set; }
    4.         bool IsInteractable { get; set; }
    5.     }
    Code (CSharp):
    1. public class UserMovementButtonImplementor : MonoBehaviour, IUserMovementButtonComponent, IImplementor
    2.     {
    3.         [SerializeField] private Button _button;
    4.         [SerializeField] private UserMovement _movement;
    5.         public Action<UserMovementInfo> OnPressed { get; set; }
    6.  
    7.         public bool IsInteractable
    8.         {
    9.             get { return _button.interactable;}
    10.             set { _button.interactable = value; }
    11.         }
    12.  
    13.         private void Start()
    14.         {
    15.             IsInteractable = _button.interactable;
    16.             _button.onClick.AddListener(OnButtonClick);
    17.         }
    18.  
    19.         private void OnButtonClick()
    20.         {
    21.             if (OnPressed != null)
    22.             {
    23.                 OnPressed(new UserMovementInfo(_movement));
    24.             }
    25.         }
    26.     }
    I also have an "IHandComponent" with its monobehavior implementor that will animate the hand.

    Code (CSharp):
    1. public interface IHandComponent
    2.     {
    3.         Animations SetAnimationTrigger { set; }
    4.     }
    5.  
    6. public class HandImplementor : MonoBehaviour, IHandComponent, IImplementor
    7.     {
    8.         [SerializeField] private Animator _animator;
    9.  
    10.         public Animations SetAnimationTrigger
    11.         {
    12.             set { _animator.SetTrigger(value.ToString()); }
    13.         }
    14.  
    15.     }
    On the other hand I have an "IUserComponent" and its implementer that isn't a monobehaviour.


    Code (CSharp):
    1. public class LocalUserImplementor : IUserComponent, IImplementor
    2.     {
    3.         public int userID { get; private set; }
    4.     }
    5.  
    6. public interface IUserComponent : IComponent
    7.     {
    8.         int userID { get; }
    9.     }

    The question I have is how to relate everything in an entity. In the MainContext I'm creating an entity that contains the IUserComponent implementor and I'm creating an entity for each monobehavior implementor (I copied it from your example, BuildEntitiesFromScene), but I don't know how to relate them.


    Code (CSharp):
    1. List<IImplementor> implementors = new List<IImplementor>();
    2.             implementors.Add(new LocalUserImplementor());
    3.             //Add HandImplementor
    4.             _entityFactory.BuildEntity<LocalUserEntityDescriptor>(0, implementors.ToArray());
    5.  
    6. IEntityDescriptorHolder[] entities = contextHolder.GetComponentsInChildren<IEntityDescriptorHolder>();
    7.  
    8.             for (int i = 0; i < entities.Length; i++)
    9.             {
    10.                 var entityDescriptorHolder = entities[i];
    11.                 var entityDescriptor = entityDescriptorHolder.RetrieveDescriptor();
    12.                 _entityFactory.BuildEntity
    13.                 (((MonoBehaviour) entityDescriptorHolder).gameObject.GetInstanceID(),
    14.                     entityDescriptor,
    15.                     (entityDescriptorHolder as MonoBehaviour).GetComponentsInChildren<IImplementor>());
    16.             }


    What I want is that when player 1 presses on a button his hand is animated and when player 2 (AI) makes his move, the other hand is animated.


    A solution that I have applied has been to modify "UnityContext" to access to "_applicationRoot" and call an method "BuildEntities" that receives a container with all the implementers of the scene. But I don't like this solution.


    Code (CSharp):
    1. public class MainContext : UnityContext<Main>
    2.     {
    3.         [SerializeField] private GameObjectImplementors _gameObjectImplementors;
    4.  
    5.         protected override void OnAwake()
    6.         {
    7.             base.OnAwake();
    8.             _applicationRoot.BuildEntities(_gameObjectImplementors);
    9.         }
    10.  
    11.         [Serializable]
    12.         public class GameObjectImplementors
    13.         {
    14.             [SerializeField] private HandImplementor _handImplementor;
    15.             public HandImplementor HandImplementor
    16.             {
    17.                 get { return _handImplementor; }
    18.             }
    19. //...
    20.         }
    21.     }
    22.  
    23. public class Main : ICompositionRoot
    24.     {
    25. //...
    26. public void BuildEntities(MainContext.GameObjectImplementors gameObjectImplementors)
    27.         {
    28.             List<IImplementor> implementors = new List<IImplementor>();
    29.             implementors.Add(new LocalUserImplementor());
    30.             implementors.Add(gameObjectImplementors.HandImplementor);
    31.             _entityFactory.BuildEntity<LocalUserEntityDescriptor>(0, implementors.ToArray());
    32.         }
    33. //...
    34. }
    How could I solve this problem?
    Thank you in advance.

    Daniel P.
     
  36. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Hello Daniel,

    first of all I suggest you to update to Svelto.ECS 2.0. From the code I see, it seems you have studied Svelto from the previous version. The Survival example has been re-factored heavily and commented a lot to help more.

    Second it's awesome crazy that you are using Svelto for a Rock Paper Scissors game :). It's super cool to learn how to use it though! If you can share something in future it would be great for the whole svelto community.

    Let's dig our code now:

    first, don't use this pattern:

    1. List<IImplementor> implementors = new List<IImplementor>();
    2. implementors.Add(new LocalUserImplementor());
    3. //Add HandImplementor
    4. _entityFactory.BuildEntity<LocalUserEntityDescriptor>(0, implementors.ToArray());

    5. IEntityDescriptorHolder[] entities = contextHolder.GetComponentsInChildren<IEntityDescriptorHolder>();

    6. for (int i = 0; i < entities.Length; i++)
    7. {
    8. var entityDescriptorHolder = entities;
      [*] var entityDescriptor = entityDescriptorHolder.RetrieveDescriptor();
      [*] _entityFactory.BuildEntity
      [*] (((MonoBehaviour) entityDescriptorHolder).gameObject.GetInstanceID(),
      [*] entityDescriptor,
      [*] (entityDescriptorHolder as MonoBehaviour).GetComponentsInChildren<IImplementor>());
      [*] }

    I added in the example just to show what it's possible to do, but really I should remove it. Actually I will, it's not needed and confusing as you are showing me.

    The right pattern is to use BuildEntity<T> directly, but not like you showed in the code, you don't need to do that.

    Have a look a the survival demo:

    https://github.com/sebas77/Svelto.ECS.Examples.Survival

    MainContext:

    https://github.com/sebas77/Svelto.E...ets/Svelto-ECS-Example/Scripts/MainContext.cs

    Code (CSharp):
    1. void BuildPlayerEntities(PrefabsDictionary prefabsDictionary)
    2. {
    3. //Building entities dynamically should be always preferred
    4. //and MUST be used if an implementor doesn't need to be
    5. //a Monobehaviour. You should strive to create implementors
    6. //not as monobehaviours. Implementors as monobehaviours
    7. //are meant only to function as bridge between Svelto.ECS
    8. //and Unity3D. Using implementor as monobehaviour
    9. //just to read serialized data from the editor, is also
    10. //a bad practice, use a Json file instead.
    11. var player = prefabsDictionary.Istantiate("Player");
    12. List<IImplementor> implementors = new List<IImplementor>();
    13. //fetching implementors as monobehaviours, used as bridge between
    14. //Svelto.ECS and Unity3D
    15. player.GetComponents(implementors);
    16. //Add not monobehaviour implementors
    17. implementors.Add(new PlayerInputImplementor());
    18. implementors.Add(new PlayerHealthImplementor(100));
    19. _entityFactory.BuildEntity<PlayerEntityDescriptor>(player.GetInstanceID(), implementors.ToArray());
    20. //unluckily the gun is parented in the original prefab, so there is no easy way to create it
    21. //explicitly, I have to create if from the existing gameobject.
    22. var gun = player.GetComponentInChildren<PlayerShootingImplementor>();
    23. _entityFactory.BuildEntity<PlayerGunEntityDescriptor>(gun.gameObject.GetInstanceID(), new object[] {gun});
    24. }
    Note while I don't limit using references and events inside components I don't suggest you to do so. I rewrote the whole exampled to avoid this case.
     
  37. DaniParra

    DaniParra

    Joined:
    Apr 27, 2014
    Posts:
    29
    Hello, thanks for your quick response.

    I think that I have Svelto.ECS 2.0, but I will check it.

    When we finish it game we want to publish the code, I will share the link here.

    I have pending change the action to DispatchOnSet or Observer, I'm not sure which one is better.

    I had seen the solution of the json but I hadn't understood all the potential that this has, I'm going to change the creation of entities with the json file :D.
     
  38. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    Sorry I misunderstood your initial question. You actually asked me how to mix implementors as Monobehaviours and standard implementors. Coincidentally I copy and pasted the code that shows you how to do it anyway, just fetch the implementors as monobehaviours from the gameobject that holds them ;)
     
  39. DaniParra

    DaniParra

    Joined:
    Apr 27, 2014
    Posts:
    29
    Hello!

    I have corrected these errors but I have another doubt. I have an engine that reacts to the button and asks the entityViewsDB for a view, through the sequence another engine has to consult another view of the same entity. Should the first engine send the view the second one needs or send it the ID? Or is there another way to solve this?
    Another way that occurs to me is to mark a property in the component and the other engine to react to it, but I would like to know if the first one would be correct.
     
  40. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    you send the ID absolutely. You can use the QueryEntityView as many times as you wish, it has been designed to be fast!
     
  41. DaniParra

    DaniParra

    Joined:
    Apr 27, 2014
    Posts:
    29
    ok, thank you!
     
  42. DaniParra

    DaniParra

    Joined:
    Apr 27, 2014
    Posts:
    29
    Hello! I'll be back with another question xD

    I have 3 buttons with the same implementor that communicate with the engine through DispatchOnSet. This implementor has a button component that launches the event and another component where it saves the user's movement before launching the event.

    Code (CSharp):
    1. public class UserMovementButtonImplementor : MonoBehaviour, IButtonComponent, IUserMovementButtonComponent, IImplementor
    2.     {
    3.         [SerializeField] private Button _button;
    4.         [SerializeField] private UserMovement _movement;
    5.         public DispatchOnSet<bool> OnPressed { get; private set; }
    6.         public UserMovementInfo UserMovementInfo { get; private set; }
    7.  
    8.         public bool IsInteractable
    9.         {
    10.             get { return _button.interactable; }
    11.             set { _button.interactable = value; }
    12.         }
    13.  
    14.         private void Awake()
    15.         {
    16.             OnPressed = new DispatchOnSet<bool>();
    17.             IsInteractable = _button.interactable;
    18.             _button.onClick.AddListener(OnButtonClick);
    19.         }
    20.  
    21.         private void OnButtonClick()
    22.         {
    23.             UserMovementInfo = new UserMovementInfo(_movement);
    24.             OnPressed.value = true;
    25.         }
    26.     }
    Code (CSharp):
    1.  public interface IButtonComponent : IComponent
    2.     {
    3.         DispatchOnSet<bool> OnPressed     { get; }
    4.         bool IsInteractable { get; set; }
    5.     }
    6.  
    7.     public interface IUserMovementButtonComponent : IComponent
    8.     {
    9.         UserMovementInfo UserMovementInfo { get; }
    10.     }

    In the engine I need to consult the component that stores the movement, but for this I need the ID of the entity, how can I know its ID? or how do I send the ID of the entity with DispatchOnSet?


    Code (CSharp):
    1. public class LocalUserMovementEngine : SingleEntityViewEngine<UserMovementButtonEntityView>, IQueryingEntityViewEngine
    2.     {
    3.         private ISequencer _localUserMovementSequence;
    4.  
    5.         public IEntityViewsDB entityViewsDB { get; set; }
    6.  
    7.         public LocalUserMovementEngine(ISequencer localUserMovementSequence)
    8.         {
    9.             _localUserMovementSequence = localUserMovementSequence;
    10.         }
    11.  
    12.         public void Ready() {}
    13.  
    14.         protected override void Add(UserMovementButtonEntityView entityView)
    15.         {
    16.             entityView.ButtonComponent.OnPressed.NotifyOnValueSet(OnPressed);
    17.         }
    18.  
    19.         protected override void Remove(UserMovementButtonEntityView entityView)
    20.         {
    21.             entityView.ButtonComponent.OnPressed.StopNotify(OnPressed);
    22.         }
    23.  
    24.         private void OnPressed(int entity, bool pressed)
    25.         {
    26.             FasterReadOnlyList<UserMovementButtonEntityView> buttonEntityViews = entityViewsDB.QueryEntityViews<UserMovementButtonEntityView>();
    27.             int ID = 0;
    28.             UserMovementInfo userMovementInfo = buttonEntityViews[ID].UserMovementButtonComponent.UserMovementInfo;
    29.             userMovementInfo.entityID = ID;
    30.  
    31.             for (int i = 0; i < buttonEntityViews.Count; ++i)
    32.             {
    33.                 buttonEntityViews[i].ButtonComponent.IsInteractable = false;
    34.             }
    35.  
    36.             _localUserMovementSequence.Next(this, ref userMovementInfo);
    37.         }
    38.     }
    I know that with DispatchOnSet you can send the ID, but I don't know how to know the ID from the component.


    Thank you.
     
  43. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    if the ID never changes, then it's easy, as the DispatchOnSet has a constructor where you can set the id sender. The id of the sender can be retrieved then afterward from the dispatch on set itself (it comes as parameter in the listener).

    if the ID changes, then just use a DispatchOnSet<int> ;)

    remember that you could potentially use a DispatchOnSet<struct> for complex cases.
     
  44. DaniParra

    DaniParra

    Joined:
    Apr 27, 2014
    Posts:
    29
    I was creating the DispatchOnSet on implementor, but to use the constructor with entityID y should create it on the engine, isn't it?
     
  45. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    usually I use the gameobject ID as id, so in the implementor you could use this.GetInstanceID(), otherwise you should pass the ID as implementor constructor parameter (not a MB though :( ). You can create it in the Engine otherwise.
     
  46. emreCanbazoglu

    emreCanbazoglu

    Joined:
    Jan 31, 2012
    Posts:
    15
    Hey @sebas77 !

    I've read most of the articles you wrote about IoC, ECS and stuff and still reading the rest. I try to read many articles from other people as well since I've no experience with the approach and it's almost totally opposite of what we'd been doing since we started using Unity (other than using components for behaviours, gameobjects for entities etc). I still have couple of questions about the approach which are not clear enough for me to start using ECS. I'd be grateful if you could give some insight about these questions really briefly.

    P.S: I haven't dove deep into the example project you provided and if I will find the answers in the project, you can just direct me to the example. These questions are in my mind since I started digging ECS. None of the examples, use cases I have seen answer those questions.

    1) In ECS, do we need to implement Systems (or Engines in Svelto.ECS) for every behaviour we want to add or the Systems are just needed for the behaviours that are shared/used for more than one entity. For example, for Camera Movement/Follow Behaviour, do we implement a System that will only responsible this specific behaviour on only one specific camera entity?

    2) How do we handle situations like Movement systems with different movement behaviours like run, fly etc. All of these behaviours are "Movement" however, one movement system can't handle all kinds of movements. What is the common approach for this kind of situation? Should I have an master movement system that controls different kind of movement sub-systems and handle common things like updating the position etc and the sub-systems implement the movement behaviour itself? Or is this approach totally wrong?

    Sorry if these questions sounds wierd or dumb, but I am trying to understand the underlyning concept of ECS and a lot confused since this methodology changes a lot from what I am used to.

    Thanks.
     
  47. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    The articles are mostly about the theory. In the latest versions of the Survival example I tried to add as many as possible comments so that you can have a better idea of what's going on in practice.

    1) Every system should be designed without assuming how many entities will be handled in it. Zero, one or many, it doesn't matter. It doesn't matter how small or focused it is either. After all cannot be otherwise because all the logic must be inside systems only

    2) Actually this is an important point that I try to highlight multiple times in my articles, your systems must follow the Single Responsibility Principle, so you are going to have a walk engine a fly engine and so on. You have to think it as modular composition. In reality you should do the same with monobehaviours too, as you shouldn't have a single movement monobehaviours, but a flymb, walkmb and so on so you can compose attributes to have different kind of abilities per character. The engines rarely need to know each other, you should be able to write code that can act only on the data found in the components. However if for some reason an order of execution is needed, you can use the sequencers, as this is what they are for.
     
    Last edited: Mar 15, 2018
  48. emreCanbazoglu

    emreCanbazoglu

    Joined:
    Jan 31, 2012
    Posts:
    15
    Thanks for the quick response. Here is an example of how I am handling different kind of movement types:

    Code (CSharp):
    1.  
    2. public abstract class MoveBehaviourBase : MonoBehaviour
    3. {
    4.     bool _canRun;
    5.  
    6.     IEnumerator _moveRoutine;
    7.  
    8.     public void StartMovement()
    9.     {
    10.         StopMovement();
    11.  
    12.         _moveRoutine = MoveProgress();
    13.         StartCoroutine(_moveRoutine);
    14.     }
    15.  
    16.     public void Stop()
    17.     {
    18.         StopMovement();
    19.     }
    20.  
    21.     void StopMovement()
    22.     {
    23.         if (_moveRoutine != null)
    24.             StopCoroutine(_moveRoutine);
    25.     }
    26.  
    27.     protected virtual IEnumerator MoveProgress()
    28.     {
    29.         while (true)
    30.         {
    31.             MoveStep();
    32.  
    33.             yield return Utilities.WaitForFixedUpdate;
    34.         }
    35.     }
    36.  
    37.     protected abstract void MoveStep();
    38. }
    39.  
    40. public class RunBehaviour : MoveBehaviourBase
    41. {
    42.     public Transform TargetTransform;
    43.     public AxisVector3 HeadDirection { get; set;}
    44.     public float GridPerSecond { get; set; }
    45.  
    46.     protected override void MoveStep()
    47.     {
    48.         if (TargetTransform == null)
    49.             return;
    50.  
    51.         Vector3 newPos = TargetTransform.position;
    52.         newPos += GameSettingsController.GameSettingsInstance.GameSpeedScale
    53.             * GridPerSecond
    54.             * HeadDirection.GetNormilzedVector()
    55.             * Time.fixedDeltaTime * Time.timeScale;
    56.  
    57.         TargetTransform.position = newPos;
    58.     }
    59. }


    Using inheritance saves here saves a bit of code re-writing (copy&paste) and also defines it as a Movebehaviour. It can also be defined by using interfaces however the MoveProgress coroutine should be imlplemented in both behaviour in that case. I can't imagine how can I avoid copy&paste code in these kind of situations without inheritance and that's where I get lost. Of course the problem for this specific example is easy to solve but I guess you got the general idea.
     
  49. sebas77

    sebas77

    Joined:
    Nov 4, 2011
    Posts:
    1,642
    With ECS you don't use inheritance, not even to save code. What you usually do is to write different levels of abstracted engines, like I explain in the articles, from the more generic (handling different type of entities through a unique generic entity view), to the more specifics (let me know if this is not clear I will explain it more).

    However from the code I can see, you are not saving code that runs logic, you are saving code to setup stuff. In this case you can easily use static stateless classes or extension methods if you really need to save code.

    it's ok to call external static functions that are pure code. Btw if you want to use Svelto.ECS you need to use Svelto.Tasks to run coroutines.
     
    Last edited: Mar 15, 2018
  50. emreCanbazoglu

    emreCanbazoglu

    Joined:
    Jan 31, 2012
    Posts:
    15
    Ok thanks for the answer.
    -Yes that would be great!

    In the meantime, I will start digging the example project. I hope it will become more clear once I'm done with exploring a bit more.

    Btw, also thanks for the articles and the framework. Last year I started to think that the methodology I was using (and most of the Unity users) was not the best way to write code for big game projects and started to explore a new way and came across ECS. However, at that time it seemed like it was only a theory and impossible to implement at some point. With the Unite 2017 talk (and upcoming talk in GDC), it became obvious that the future is ECS for game development for both performance and maintability. Your guideance with the article series help a lot to understand the underlying theory.(it still seems like I have a long way to go but I'll get there).