This an introductory post to Svelto.ECS 2.0. Other two posts will follow, one explaining the examples line by line and another explaining all the Svelto.ECS concepts in a simpler fashion than before. Therefore this article is written for who already knows Svelto.ECS.
Svelto.ECS 2.0 is (at the time of writing this) available in alpha stage (pick the alpha branch to have a look now) and, compared to Svelto.ECS, introduce new features, new optimizations and unluckily some breaking changes (main reason why I decided to bump the major version).
I have also updated the Svelto.ECS.Examples code (available on the alpha branch at the time of writing) and added a new “vanilla” example which I can consider the minimum amount of code to write to show almost all the framework features available without using Unity. The new example is, in fact, targeting Net.Core and Net.Standard.
I will quickly go through the list of new features:
- New terminologies have been introduced. If you read the previous articles or used Svelto.ECS 1.0, you must know that the term “Node” has been abolished as it was meaningless in the framework context and has been renamed to “EntityView“. I will explain why it makes more sense with the next articles.
- The GenericEntityDescriptor has been extensively used in all the examples to show how to reduce the boilerplate code with minimum effort.
- EntityDescriptors are now pure static classes and must not be instantiated. The EntityDescriptor must conceptually identify your entity. The future articles will explain why it’s a big design mistake to identify Entities as EntityViews instead. A Entity is now built with the new signature entityFactory.BuildEntity<SimpleEntityDescriptor>(entityID, implementors). In any case you should rarely need to inherit from EntityDescriptor as most of the time you must inherit from GenericEntityDescriptor and MixedEntityDescriptor.
- EntityDescriptors can now refers, using the same signature, either to class based EntityViews and struct based Entities, so that entityFactory.BuildEntity<SimpleStructEntityDescriptor>(entityID); will actually build struct based entities. Previously the struct based entities were build in a more awkward fashion. While I deprecated the previous method to build struct entities, changes may be introduced later to support thread safe code as currently Svelto.ECS data structures are not thread safe (custom structures or strategies must be adopted to support thread safe engines). With the next articles I will explain when and how to use EntityViews and Entities as struct. EntityViews should be used for flexibility and Entities as structs for speed. An EntityDescriptor must inherit from the new MixedEntityDescriptor generic class to build entities made out of Entity structs and/or EntityViews.
- Another very important feature is the possibility to create entities inside buckets (groups). In this way is possible to query a set of entities from a specific group. This is a feature that was absolutely needed in our products (and very likely in yours too). For example we can now easily query all the wings for each machine in Robocraft. This means that we could potentially create a Svelto.Tasks taskroutine for each machine inside the WingsPhysicEngine and stop a specific task if all the wings of a specific machine are shot off. Previously there wasn’t a way to be able to split wings per machine unless custom datastructures were used inside the engines. The new function has the signature: entityFactory.BuildEntityInGroup<SimpleStructEntityDescriptor>(entityID, groupID) and can be used both for struct Entities and class EntityViews.
- Entities can also be moved between groups. This was a last minute idea and I need to experiment more with it.
- EnginesRoots must never be hard referenced. Previously I made two mistakes: first hard referencing it inside the SubmissionNodeScheduler (now called SubmisisonEntityViewScheduler) and second letting it be passed around through the IEntityFactory interface. Now an IEntityFactory wrapper must be generated from the EngineRoot.
- A IRemoveComponent cannot be implemented with a custom implementor any more. Just use it and don’t worry to pass a RemoveImplementor among the other implementors (it will be ignored as it is created by the framework now).
- The framework is now more rigid and output more warnings in case of misuse.
- All the previous framework engines, except for the SingleEntityViewEngine and MultiEntityViewsEngine, have been deprecated. An engine can inherit either from one or the another and/or implement IQueryingEntityViewEngine (which is an IEngine too now)
- I am adding comments all over the code, but I will add more while I write the new articles to come.
- Building an entity can be up to 3.3 times faster than in svelto.ECS. However remember that entities should be sporadically or never built during the execution of the gameplay. Entities should always be prebuilt and enabled/disabled when needed. This is even more effective than pooling them, so design them properly. Below a table to show how long takes to build 256k entities as class with Unity 2017.3 (building entities as struct is an order of magnitude faster).
Version Platform Time(MS) 1.0 .net 2.0 903 2.0 .net 2.0 344 2.0 .net 4.6 260(!) 2.0 .net Core UWP 203 2.0 .net Core IL2CPP 288
if you wonder while IL2CPP is a bit slower, is because I cannot dynamically create code that allows avoid Reflection functions, however the c++ implementation of the reflection is much faster so no much trouble there.
- Thanks to the new feature to move entities between groups, you can create a group to store the disabled entities, so that you can retrieve a disabled entity yourself to be reused later.
- Several optimizations here and there, especially to reduce allocations.
and breaking changes (at least the ones I took note of):
- The BuildEntity signature is totally different and will need to be explained in an another article.
- IRemoveComponent was breaking a not enforced (yet) rule of Svelto.ECS which is that a component cannot hold references to other classes (except Unity ones at the moment). This is because a component must be seen a data container and cannot be used to call external functions. Thus, the method to remove an entity has changed to _entityFunctions.RemoveEntity<SimpleEntityDescriptor>(entityView.ID);
- Mass Renames (hope I didn’t get any wrong, I will fix later in case):
- rename IQueryableNodeEngine into IQueryingEntityViewEngine
rename IEngineNodeDB into IEngineEntityViewDB
rename NodeWithID into EntityView
.QueryNode< to .QueryEntityView<
.QueryNodes< to .QueryEntityViews<
.TryQueryNode( to .TryQueryEntityView(
all the nodesDB must be renamed to entityViewsDB
MultiNodesEngine to MultiEntityViewsEngine
SingleNodeEngine to SingleEntityViewEngine
- INodeBuilder to IEntityViewBuilder (you should use the GenericEntityDescriptor/MixedEntityDescriptor though)
- NodeBuilder to EntityViewBuilder (you should use the GenericEntityDescriptor/MixedEntityDescriptor though)
ICallBackOnAddEngine doesn’t exist anymore and it has been merged into the IQueryingEntityViewEngine interface. This means that the Ready function must be always implemented in these cases.
N.B.: As long as Svelto.ECS 2.0 stays in to alpha state, do not use in a production environment. It still needs to be heavily tested and I will need to write some unit tests too. However you are invited to start experimenting with it and leave some feedback.
(*) The evolution of my reasoning can be found in these articles that I strongly suggest to read:
- http://www.sebaslab.com/svelto-2-7-whats-new-and-best-practices/ (shows what’s changed since 2.5)
- http://www.sebaslab.com/svelto-ecs-2-5-and-allocation-0-code/ (shows what’s changed since 2.0)
- http://www.sebaslab.com/svelto-ecs-2-0-almost-production-ready/ (shows what’s changed since 1.0)