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

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:

Github: GitHub - sebas77/Svelto.ECS: Svelto ECS C# Entity Component System

[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/

5 Likes

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.

1 Like

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.

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.

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.
1 Like

– 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?

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.

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.

2 Likes

linked the video in the original post.

1 Like

Hi @sebas771

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?

connectionSequencer.SetSequence(
    new Dictionary<IEngine, Dictionary<Enum, IStep[]>>()
    {
        {
            connectionEngine,
            new Dictionary<Enum, IStep[]>()
            {
                {
                    ServerStatusEventType.ServerStarted,
                    new IStep[]
                    {
                        battleDataSpawnerEngine,
                        playerDataSpawnerEngine,
                        playerTroopDataSpawnerEngine,
                        authenticationEngine,
                    }
                },
                {
                    ServerStatusEventType.ServerReady,
                    new IStep[]
                    {
                        battleStateEngine,
                    }
                },
                {
                    ServerStatusEventType.ServerFail,
                    new IStep[]
                    {

                    }
                },
                {
                    ServerStatusEventType.ServerStopped,
                    new IStep[]
                    {
                        battleDataSpawnerEngine,
                        playerDataSpawnerEngine,
                        playerTroopDataSpawnerEngine,
                        authenticationEngine,
                        battleStateEngine,
                    }
                },
                {
                    ClientConnectionEventType.ClientConnect,
                    new IStep[]
                    {
                        authenticationEngine
                    }
                },
                {
                    ClientConnectionEventType.ClientReady,
                    new IStep[]
                    {
                        authenticationEngine
                    }
                },
                {
                    ClientConnectionEventType.ClientDisconnect,
                    new IStep[]
                    {
                        authenticationEngine
                    }
                },
            }
        },
        {
            authenticationEngine,
            new Dictionary<Enum, IStep[]>()
            {
                {
                    AuthenticationEventType.AccountAccepted,
                    new IStep[]
                    {
                        playerSlotEngine,
                    }
                },
                {
                    AuthenticationEventType.AccountRejected,
                    new IStep[]
                    {
                        connectionEngine,
                    }
                },
                {
                    AuthenticationEventType.AccountDisconnected,
                    new IStep[]
                    {
                        playerSlotEngine,
                    }
                },
            }
        },
        {
            playerSlotEngine,
            new Dictionary<Enum, IStep[]>()
            {
                {
                    PlayerSlotEventType.PlayerJoin,
                    new IStep[]
                    {
                        waitingTimeEngine,
                    }
                },
                {
                    PlayerSlotEventType.PlayerRejoin,
                    new IStep[]
                    {
                        waitingTimeEngine,
                    }
                },
                {
                    PlayerSlotEventType.PlayerSlotNotAvailable,
                    new IStep[]
                    {
                        authenticationEngine,
                    }
                },
                {
                    PlayerSlotEventType.PlayerOut,
                    new IStep[]
                    {
                        waitingTimeEngine,
                    }
                },
            }
        }
    }
);

battleStateSequencer.SetSequence(
    new Dictionary<IEngine, Dictionary<Enum, IStep[]>>()
    {
        {
            battleStateEngine,
            new Dictionary<Enum, IStep[]>()
            {
                {
                    BattleStateEventType.EnterWatingTime,
                    new IStep[]
                    {
                        waitingTimeEngine,
                    }
                },
                {
                    BattleStateEventType.ExitWaitingTime,
                    new IStep[]
                    {
                        waitingTimeEngine,
                    }
                },
                {
                    BattleStateEventType.EnterSelectionTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
                {
                    BattleStateEventType.ExitSelectionTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
                {
                    BattleStateEventType.EnterStartegyTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
                {
                    BattleStateEventType.ExitStrategyTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
                {
                    BattleStateEventType.EnterPlayingTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
                {
                    BattleStateEventType.ExitPlayingTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
                {
                    BattleStateEventType.EnterResultingTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
                {
                    BattleStateEventType.ExitResultingTime,
                    new IStep[]
                    {
                        new DebugStepEngine<BattleStateEventData>()
                    }
                },
            }
        },
    }
);

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.

It just a starting code, not the gameplay yet, so It will be longer :slight_smile:
And yes it adopts state machine, because the state machine pattern is easier for me.

I have a case: Team and Player.

  • There are 2 teams.
  • Each team consist of many players

How to implement this in my code?
Should I make component ITeamMemberComponent for Team Entity?

interface ITeamMemberComponent : IList<PlayerID> { }

Or group players when players built

entityFactory.Build(PlayerId, TeamId, new PlayerDescriptor())

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.

1 Like

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.

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”.

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.

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.

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 :slight_smile:

And about my code before, I tried to change it. This is how it look like now. What do you think?

var connectSequence = new Sequencer();
var loginSequence = new Sequencer();
var disconnectSequence = new Sequencer();

var connectionRejection = new ConnectionRejectionObservable();
var connectionRejectionObserver = new ConnectionRejectionObserver(connectionRejection);

var authenticationEngine = new AuthenticationEngine();

var socketEngine = new SocketEngine(connectSequence, loginSequence, disconnectSequence, connectionRejectionObserver);
var connectionEngine = new ConnectionEngine(connectionRejection);
var networkLoggerEngine = new NetworkingLoggerEngine();

connectSequence.SetSequence(new Steps()
    {
        {
            socketEngine,
            new Dictionary<Enum, IStep[]>()
            {
                { ConnectCondition.Default,  new IStep [] { connectionEngine, networkLoggerEngine } }
            }
        },
    }
);

loginSequence.SetSequence(new Steps()
    {
        {
            socketEngine,
            new Dictionary<Enum, IStep[]>()
            {
                { LoginCondition.Request,  new IStep [] { authenticationEngine, connectionEngine, networkLoggerEngine } }
            }
        },
    }
);

disconnectSequence.SetSequence(new Steps()
    {
        {
            socketEngine,
            new Dictionary<Enum, IStep[]>()
            {
                { DisconnectCondition.Default,  new IStep [] { connectionEngine, networkLoggerEngine } }
            }
        },
    }
);

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:

1 Like

Nice illustration, thanks
Please notify forum when you have finished your article :slight_smile:

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