An Entity-Component-System library
Sometimes we may want to remove bidirectional dependencies between our code, and ECS is a good way to do this.
This design pattern offers a clean way to separate the data from the logic, and it's also a good way to improve the performance.
E - Entity
C - Component
S - System
But what really is it?
Well, as a human, we (entities) live in the world (context), and we have some properties (components). Moreover, we have things to do based on our properties (systems).
- A
Contextholds severalEntityinstances, someSysteminstances and someSingletonComponentinstances. - Each
Entityhas someComponentinstances. - Each
Component(orSingletonComponent) has only data properties. - Each
Systemcan filter lots ofEntityinstances in the sameContextby their components and operate logics on them.
- Only
Systemcontains logics and none of them should reference on each other. But we allow one system depends on another by specifyingPriority. (They should only depend on the filtered entities/components) Componentonly contains data properties and no logics. (Again, no way to have dependency)Entityonly contains components. (It is really just a container)
- As a
Systemcan operate on a batch of entities with components, we can well utilize the cache of the MMU. - We can also easily parallelize the systems to improve the performance, in a multi-core CPU environment.
- We have priority for
System, so you can control the order of systems. - We have frequency for
System, so you can control the frequency of systems being executed. - We only allow asynchronous interfaces for
SystemandContext, so our ECS should not block the thread (unless you screw up). - We introduce built-in parallelism and/or concurrency for
System, so you can easily parallelize your systems (for those who are in the same priority) and well utilize the multi-core CPU. - We have a cool guy who is maintaining this library. (Just kidding)
CountandIsEmptyonGroupResultEnumerator— check query result count without iterating:var group = ctx.GroupOf<Health, Position>(); if (!group.IsEmpty) { /* ... */ } int n = group.Count;
CountOf<T>()andAnyOf<T>()onContext— lightweight alternatives that skip enumerator creation:if (ctx.AnyOf<Health>()) { /* ... */ } int count = ctx.CountOf<Health, Position>();
Tag.SetBits/Tag.ClearBits— bulk bit operations withparams ReadOnlySpan<int>:tag.SetBits(bitIdx1, bitIdx2, bitIdx3);
Tag.ContainsAll— SIMD-optimized archetype matching viaVector256.AndNot, replacing the previous(mask & query) == querypattern (eliminates temporaryTagallocation and reduces from 2 SIMD ops to 1).
GetFragmentationStats()is now lock-free (previously locked + iterated all archetypes every call).Update()reuses a cached task buffer instead of per-bucketArrayPool.Rent/Return.ExecuteFrequencyis cached as a local variable perUpdatecall to avoid repeated interface property access.AggressiveInliningadded toGroupOf,CountOf, andAnyOfmethods.- All archetype dictionary iterations replaced with index-based
Listtraversal for better cache locality.
Just check out the EasyEcs.UnitTest project. I have comments there.
Believe me, one day I will make a website for this and document everything.