Bot releases are hidden (Show)
Thanks to @thejayman for adding a fix for ComputedGroups
incorrectly raising removal events for entities which were not currently being tracked internally.
Published by grofit over 2 years ago
The high level information here is that we have dropped explicit releases for .Net Framework 4.6 and instead are now just focusing on building netstandard2.0
, in the future we would like to move to net7
but until a lot more frameworks have migrated over to it we are just going to stick with netstandard
.
We have also changed the way ObservableGroup
objects work by separating out the group matching mechanism to a new interface known as an IObservableGroupTracker
which will monitor the groups/entities and then relay onto the ObservableGroup
when there is a change.
This diagram shows the new process, which historically all used to take place within the ObservableGroup
so this makes the object simpler and allows other parts of the system to track group changes.
There are some breaking changes around namespaces for events changing as well as a new wrapper around component interactions on the type called IComponentAccessor
, which reduce the amount of type lookups if you are doing a lot of interactions with component types.
Published by grofit almost 3 years ago
While this isn't a massive release it does have a few small changes in terms of how component removal is handled.
So first of all a HUGE thanks to DoctorDepthum
on the discord who noticed the regression of the IDisposable
handling of components within entities, this has been rectified now so components with IDisposable
are now correctly disposed.
So just to clarify when you remove a component now, the underlying logic that runs in the ComponentPool
will check if the pool contains reference types, and if so null out the index, and check if the component implements IDisposable
and if so will trigger the disposable logic, then finally release the index for future consumption.
Published by grofit over 3 years ago
The high level change for this version is that SystemsRx
is now its own repository within the EcsRx
org, however realistically at a high level you wont notice much difference other than a few package name changes:
EcsRx.Infrastructure.Ninject
-> SystemsRx.Infrastructure.Ninject
EcsRx.ReactiveData
-> SystemsRx.ReactiveData
EcsRx.MicroRx
-> SystemsRx.MicroRx
There is also now a non EcsRx
dependent SystemsRx.Plugins.Computeds
which has base computed types without being dependent upon IGroup
etc. The EcsRx version still exists and builds on top of this allowing you little change in terms of usage.
IEventSystem
ChangesThe IEventSystem
now has 2 publish methods, the default one which is backwards compatible is the default Publish<T>
however there is also now a PublishAsync<T>
which will use IThreadHandler
to Run
a new task for it which by default does a Task.Run(...)
for the publish.
IThreadHandler
ChangesThe IThreadHandler
now has a Run
method which allows you to just do an arbitrary run of an action via the thread handler (defaulted to Task.Run
).
Published by grofit over 3 years ago
Some of these changes actually happened in previous releases but a few things have been moved down into SystemsRx
such as the pooling mechanisms, as they are useful regardless of the ECS aspect of things, so now they can be used without.
Also there have been some changes thanks to @Fijo which makes it easier to include IObservableGroup
objects on the fly in your systems via attributes by using the GroupBinding
plugin.
This is optional but if you do want to include multiple IObservableGroup
objects within your systems (such as IManualSystem
) you can do so like this:
[FromComponents(typeof (LevelComponent))] public IObservableGroup LevelAccessor;
[FromComponents(typeof(EnemyComponent))] public IObservableGroup EnemyAccessor;
Which is directly lifted from the Roguelike2d Repository which has some real life usages of this plugin and how it can simplify the system and reduce dev time/boilerplate, there are some other approaches where you can use FromGroup
to pass in an explicit group or just use FromGroup
without a group on systems which implement IGroupedSystem
, and it will take the default group property as its argument.
This also simplifies those usecases for people who were originally using
IManualSystems
before they got moved toSystemsRx
and were left without a simple way to resolve anIObservableGroup
when needed.
Published by grofit over 3 years ago
Currently a lot of the EcsRx features are not directly tied to the ECS paradigm but as they are part of core they come bundled together.
Part of this change is to decouple some of that so there can be a SystemsRx
library which has no notion of EcsRx
which can be used by people who want to have nice infrastructure and DI abstractions, as well as nice simple systems to run logic in a simple way, but don't want to have to worry about using entities and components.
So this split has now made it so the new library acts as a layer underneath EcsRx
and provides basic System Execution, Infrastructure, DI and common extensions as well as a couple of simple systems such as IManualSystem
and event handling systems, it even supports plugins without needing to worry about the EcsRx
layer.
At the high level EcsRx
hasn't changed much, its just a lot of the core innards have been moved down a layer (and this is mentioned more on front page readme), there are a couple of breaking changes (mentioned in breaking changes document), but for the most part you continue to use the library as you do right now.
Under the hood to use EcsRx
you will be pulling in SystemsRx
but this simplifies things a bit and also lets you run systems outside of the ECS paradigm alongside your ECS related ones, so its best of both worlds.
Published by grofit almost 4 years ago
As the issue with structs and batches still seemed to have no real avenue to being resolved other than only allowing blittable types, the decision has been made to just make this a requirement.
If you do not use the batching plugin then your structs (if you use them) can be of any type you want, but if you want to get the performance boost of them all being batched together you need to use blittable types otherwise they cannot be pinned.
Added a couple of struct methods to the group builder, this should allow you to use the builder to make groups with struct types in too
Published by grofit over 4 years ago
The major thing to talk about is the new persistence plugin which adds the ability to save your entities to and from files (or anywhere else you want), out the box it supports saving and loading from binary files, but there is an example project for how to change that to JSON if you prefer it to be human readable/editable.
As the persistence functionality is built on top of Persistity you can also make use of pipelines for your own needs, like extracting certain data from your game state and persisting it as a save game file, or even sending off metrics to PlayFab etc.
For more info on this look at the example projects and persistity documentation, or feel free to discuss in the discord server.
There are some new helpers for DI around custom functions on activation as well as being able to specify that your binding should only be used when injecting into a certain type or set of types.
Published by grofit over 4 years ago
As part of the previous update we moved the IObservableScheduler
into the core, but on top of this we have streamlined what it does, so its now IUpdateScheduler
and also implements ITimeTracker
which allows you to get the elapse/total time in a cross platform way.
This would be a minor update but as it breaks the surface API it needs to be a major release.
Published by grofit over 4 years ago
This isn't a huge update but there are lots of words explaining whats happened :D
IObservableScheduler
now in coreThis was originally brought in as a way to have a streamlined cross platform "EveryUpdate" style notion, as every engine/framework does their update loop differently, so this exposed a way for those events to be fed through in a sane-ish way. So in Unity it may be from a UniRx Observable.EveryUpdate
in MonoGame it may be from a Game.Update
while in a console app it may just be a timer ticking at 60fps.
The point is that this allows plugins to have systems which update according to the host platform but be agnostic of it, so plugin systems no longer need to worry about having platform dependent observables, as historically if you wanted to have a cross platform system your only option really was an IManualSystem
and make some update loop yourself which is not great.
And off the back of this a new basic system has been introduced which wraps up the underlying update mechanism and exposes a simple processing point.
IBasicSystem
This is basically a cut down IReactToGroupSystem
but the schedule of it is taken care of for you, as in most cases people would just have it default to every update anyway, so in these cases you can just use the normal IBasicSystem
and it will work on any platform and doesnt need the ReactiveSystems
plugin.
This as mentioned above makes plugin development easier as you can at least have a cross platform system without needing other plugins or writing any of your own boilerplate code.
IGroup
and Group
are more in lineThis isn't a massive update, but the gist is that IGroup
and Group
have not been on the same page for a while, as IGroup
just exposed component lists to match on, whereas the Group
implementation had that AND predicates built in.
In most cases predicates are the exception, and while they allow you some great flexibility it needs to be more separated, so there is now a GroupWithPredicate
which is basically the same as the old Group
class, and the new Group
class just contains component lookups.
For the most part this wont effect most people, as the helpers/builders have been updated to automatically create the appropriate class for you, however if you had your own classes inherit off Group
and you used predicates in them, you may need to now inherit from GroupWithPredicate
.
Published by grofit over 4 years ago
Didn't realise but some people are still stuck on 4.6 and netstandard 2.1 wont support .net framework :(.
Anyway happy days a new release++;
Published by grofit over 4 years ago
This update fixes an issue which caused some components to not get registered correctly from plugins depending upon how they were called in the codebase. This fix should hopefully ensure that all plugins can be resolved and loaded before component types are mapped. This being said you are still free to provide your own IComponentTypeLookup (those users of custom ones would have been unaffected by this bug).
There has also been a jump to .net standard 2.1 and .net 4.7.2 as build targets for the libs.
Published by grofit over 5 years ago
As there havent been many descriptions on the updates in the 3.x.x cycle I just wanted to dump a few of them in here:
OnUpdate
One of the problems with supporting multiple platforms and frameworks is being able to depend on native code but without having a hard dependency on it, so as part of this there is now an IObservableScheduler
which exposes an OnUpdate
observable. This is an opt-in thing and you can still keep using your UniRx EveryUpdate
or your GameScheduler.OnUpdate
on monogame, but under the hood the OnUpdate
in this new object will implement the platform specific update loop notifier so plugins can be made cross platform while still depending upon the same update loop as everything else.
ReactToGroupExSystem
This was added to the EcsRx.Plugins.ReactiveSystems
plugin which acts the same as the normal ReactToGroupSystem
but also provides the ability to run code BeforeProcessing
and AfterProcessing
which can help with scenario where you want to setup something before processing everything and cleaning up afterwards.
There have been a few minor improvements to the batched systems so you can now override the behaviour of group change notifications, i.e rather than reacting to each one, throttle the changes for a period. There is also a fix for entities changing while rebuilding where it will break out of the rebuild and attempt to rebuild on the next cycle.
Thanks to @floatW0lf there have been a some great fixes added which caused some pretty rubbish problems around component notifications and group changing, these were in previous releases but still wanted to say thanks to the community for assisting with notifying us of issues and assisting with PRs.
Some of the previous releases in this cycle have moved some namespaces around, but hopefully this is all internal classes and wont effect many people.
Published by grofit almost 6 years ago
Under the hood a lot of the layering and architecture from IEntity
down to IComponentDatabase
has been changed to use less memory, be more efficient and support structs. There is a lot of other changes but lets drill down into them a bit more.
struct
support for componentsHistorically a component always used to be a class, which is fine for most people, and you can continue to use components this way without issue. However this comes with a performance overhead in terms of how its accessed and allocated.
Now that structs can be used it allows them to be allocated far quicker and accessed quicker, this also can provide MASSIVE performance benefits if you combine it with batching where you are trying to make better use of the CPU prefetch and cache to only access the bits you care about and just iterate over them.
public struct PositionComponent : IComponent
{
public Vector3 Position;
}
More docs on this subject will be added shortly which will go into far more depth on the subject.
Without droning on too much, the database used to historically bulk allocate component memory based on the entity size, whereas now it will instead allocate what it needs and try re-using until it needs to expand, this will reduce memory usage and also makes it more efficient.
Building off the back of those changes we also now are able to expose the underlying component pools and arrays easier, which means you can actually grab them and manually iterate through them yourself if you want to get faster performance than querying each entity individually.
ref
If you mainly use class based components this wont be of any interest to you, but to those who use struct based components you can get the components by ref which allows you to update it in place, and ripple those changes down into the underlying component. This can make things slightly quicker and more succinct without having to replace the component every time you change something.
// Additions to IEntity
ref T GetComponent<T>(int componentTypeId) where T : IComponent;
ref T AddComponent<T>(int componentTypeId) where T : IComponent, new();
// Example use case
ref var positionComponent = ref entity.AddComponent<PositionComponent>(PositionComponentTypeId);
Until now when you were dealing with groups and systems you would generally get given a load of entities and you would loop through them getting the components for each one and then doing your calculations on them. While this is fine for simple things, when have lots of entities and more components it can become a chore to get each component, and it is also slower to do as it needs to keep retrieving random bits of memory all over the place.
With the new BatchedSystem
and ReferenceBatchedSystem
types you are able to stipulate ahead of time what components you require for this system to operate and pre-fetch them all in one big chunk of memory. This makes performance FAAAAR quicker and in most cases makes it slightly easier to do your work, as you can just loop through each Batch
(contains entity id and the components you need) and already have the components in memory ready to go, meaning less effort for the computer to resolve all your guff.
// example of typical component access
public void Process(IEntity entity)
{
var basicComponent = entity.GetComponent<SomeComponent1>();
basicComponent.Position += Vector3.One;
basicComponent.Something += 10;
var basicComponent2 = entity.GetComponent<SomeComponent2>();
basicComponent2.Value += 10;
basicComponent2.IsTrue = true;
}
// example of batched component access (showing structs, but reference types same without ref
)
public void Process(int entityId, ref SomeComponent1 basicComponent, ref SomeComponent2 basicComponent2)
{
basicComponent.Position += Vector3.One;
basicComponent.Something += 10;
basicComponent2.Value += 10;
basicComponent2.IsTrue = true;
}
As an example if you were to compare the 2 approaches you would get a drastic difference in time taken to process, there is an example bare bones scenario which does this (abiet simpler form) in the project it makes 200,000 entities with 2 components, and has some logic to mimic the system process, then loops 100 times.
This is on a potato laptop and goes to show that the batched approach is roughly 20x faster, and its very little extra effort, it also provides large performance boosts for class based components, just not as fast as structs.
One other benefit is behind the scenes the batches are managed for you, much like IObservableGroup
instances, so if you have 5 systems sharing the same batches, they will all be using the same underlying batch behind the scenes which means each system isn't having to keep its own copy and maintain it.
So plugins have existed for quite a while in EcsRx but all changes to EcsRx framework have happened within the core part. Going forward we have tried to split out more of the optional parts of the system into plugins. This allows you to decide if you want to use reactive systems, and this has also allowed the new batching process to be developed without impacting the core framework (as it requires unsafe
code).
The first parts to be split into plugins are:
EcsRx.Views
-> EcsRx.Plugins.Views
EcsRx.Systems
-> EcsRx.Plugins.ReactiveSystems
EcsRx
(computeds) -> EcsRx.Plugins.Computeds
EcsRx.Plugins.Batches
(new)This latest version makes use of the latest and greatest C# 7.3 language features, this is going to be a problem for some people, and for those people who are not able to adopt the latest C# version I would suggest sticking with the previous version until you can update.
As part of these changes it paves the way to potentially have more performance increases going forward as well as improve the eco system in a simpler more isolated way using plugins. There is a lot of work that is still proposed for interacting with entities and observable groups (as this is still a slow part of the system), but this hopefully will give people more freedom and more flexibility to do what they want with the system.
These changes will hopefully be rolled into the Unity and Monogame versions shortly, and if anyone wants to help out we could really do with assistance with docs/example maintenance and creation.
Published by grofit about 6 years ago
Historically you had 2 methods that were mainly used for setting up your application:
ApplicationStarting
- Modules are loaded, go prep your appApplicationStarted
- Everything is loaded, go use your appThis was fine to begin with but does not scale well when you have more complex scenarios or plugins that augment the internal parts of the framework.
So as part of trying to improve this workflow the lifecycle has now been changed to have several virtual methods which you can opt in to plug in your own logic, here is a list of the methods and the order they run in.
LoadModules
This is where you should load your own modules, the base.LoadModules()
will load the default framework so if you do not want this and want to load your own optimized framework components just dont call the base version. An example of this is shown in the optimized performance tests where we are manually assigning the component type ids so we do not want the default loader.
LoadPlugins
This is where you should load any plugins you want to use, if you have no plugins to use then dont bother overriding it.
One major change in plugin loading is that it now happens before internal dependencies are resolved, as historically this was run AFTER certain dependencies were resolved such as ISystemExecutor
and IEventSystem
so if you had a plugin which removed base bindings and put its own in, you would be unable to consume them as the application had already resolved the things it was changing, so this now allows plugins to augment the framework and application dependencies before they are resolved, which makes everything more flexible.
ResolveApplicationDependencies
This is where the dependencies of the application are manually resolved from the DI Container, so the ISystemExecutor
and IEventSystem
etc are all resolved at this point, once all plugins and modules are run. The base.ResolveApplicationDependencies()
will setup the core EcsRxApplication dependencies so you should call this then resolve anything specific you need after this point.
BindSystems
This is where all systems are BOUND (which means they are in the DI container but not resolved), by default it will auto bind all systems within application scope (using BindAllSystemsWithinApplicationScope
), however you can override and remove the base call if you do not want this behaviour, or if you want to manually register other systems you can let it auto register systems within application scope and then manually bind any other systems you require.
StartSystems
This is where all systems that are bound should be started (they are added to the ISystemExecutor
), by default this stage will add all bound systems to the active system executor (using StartAllBoundSystems
), however you can override this behaviour to manually control what systems are to be started, but in most cases this default behaviour will satisfy what you want.
ApplicationStarted
Much like the old world, this is where you should start using everything, by this point:
So from here you can just get on with making your entities and starting your game, but as you can see you now have far more flexibility and structure to how you compose your application and in default scenarios you will generally get everything loaded for you ready to go assuming default conventions.
Published by grofit about 6 years ago
TFrom
instead of TTo
in the Ninject wrapper.HasBinding(name?)
method which lets you check if a binding exists in the DI container.HasSystem
method on ISystemExecutor
ISystemExecutor
will now throw an exception if you try to add the same system twicePublished by grofit about 6 years ago
This release includes some more extension methods and has added to the DI part of the framework, there has also been some improvements to DI support adding support for:
There has also been a change in the order that components are added in batches, which now makes it add left to right rather than right to left like it was doing before (due to optimization on iterating towards zero).
Published by grofit about 6 years ago
This release requires little fanfare really, it is just a version bump to align with semver (which I very much like, but wish breaking changes could be represented by 0.THISBIT.0.0
).
That aside the polyfills inside EcsRx have now been moved to a new MicroRx
project, and the Ninject
dependency wrapper has been released as a separate package, finally the EcsRx.ReactiveData
package is now out there which contains ReactiveDictionary
, ReactiveCollection
and ReactiveProperty
incase you are not using UniRx (its all unirx code which has been slightly altered to work with rx.net anyway).
Other than that nothing to report!
Published by grofit about 6 years ago
This release is generally a performance tweak, but it changes surface API of events from entities all the way up to collections. For most users this wont change anything, but really its 0.3.1 but due to the change in surface API I have bumped the version (its not 100% semver as I am using minor as the major in this instance).
Published by grofit about 6 years ago
This contains some breaking changes hence the version bump, but this is mainly around slimming down IEntity
and moving a lot of helper calls to extension methods (this way they can be applied to any implementation).
Big thanks to @JayPavlina for helping with testing and bugs around component removal and duplicate events in Observable Groups have now been fixed and test cases updated accordingly. So now your ITeardownSystem
implementations should only trigger once per entity, and your entity remove calls will verify the components exist and raise events accordingly.
This release exposes the underlying application paradigm further so by default it will install all needed framework components, however if you are a power user you may want to add your own implementations for different things, such as adding your own component type lookups, your own entity implementations, collection factories etc.
Historically it was a pain to unbind everything and re-bind your own stuff, but now there is a GetFrameworkModule
virtual method in the application which lets you inject your own bootstrap module, there is an example of this in the optimized performance test examples.
DONT WORRY you dont need to use any of this stuff, and by default you will be fine, but for those who want to build upon this framework further with their own conventions and implementations this goes a long way to helping them.
So as part of the previous performance improvements there was underlying potential to bypass entity interactions by type and provide the raw component type id. This has now been exposed further so IEntity
implementations how allow you to Get/Has/Remove by both Type
and int (componentTypeId)
.
Now in most common scenarios component types are cached ahead of time and automatically assigned ids, so when you call GetComponent
with a Type
it actually looks up the id of the component and then passes that to the underlying database to resolve it. However now you are able to bypass this type lookup if you need to and use the ids directly but to do this you need to explicitly set your component type ids and manage that yourself within the project.
You can potentially add your own codegen to do some of this for you or hand roll your own approach, but if you look at the example performance tests which are optimized you will see that they are overriding the default framework module and providing a hardcoded version of the component lookups.
There are a few different ways to approach the management of ids, you could set them as an enum, or a static class full of properties, or even extension methods which have hardcoded ids like GetHealthComponent()
which internally knows HealthComponent
is id 54 etc.