bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
36.05k stars 3.56k forks source link

ECS Feature: Add ReactiveSystem like Entitas library (Active Triger Event Data, Like Subscribe-Pulish Mode) #2341

Closed ghost closed 3 years ago

ghost commented 3 years ago

Low performance like move a little obejcts in many static object scene

Let's Think such scene like GUI, a millision of static item, but per frame only low some is moving, now is listener by update stage's system running per frame, event if add query filter Changed.

Add the ReactiveSystem like Entita, more see Entita

reference by the aticle:

"Let’s base it on an example. Imagine we have a tower defence game and we need to implement find target system. In a naive implementation, we could just go other all enemies and figure out if an enemy is close enough. However we could have a small optimisation, we could introduce a Moved component, which indicates that an enemy just moved, so in the find target system, we could iterate only through the enemies which just moved. This optimisation reduces the set of enemies we have to iterate through. Having tag components like Moved can be tricky in regard to it’s life cycle. Moved component has to be added in every system which changes/adds Position. So we might end-up with code duplication. Moved component might be consumed by multiple systems, this means that the removal of the tag component becomes non trivial as well. All this hassle can be simplified, when we introduce a concept of reactive system. A reactive system is executed only on entities which got certain component added/removed or replaced. In our small example, the target system is a reactive system, which works only on entities, which moved since last time this system was executed. The semantics of the system stay the same, but reactive system makes the Moved tag component redundant and remove the cognitive load of managing this tag components life cycle. Introducing the concept of reactive system, profoundly changed the way, we implemented UI and game play systems."

ghost commented 3 years ago

Or Like Monitors && OnSet && Triggers in flecs library

flecs: https://github.com/SanderMertens/flecs/blob/master/docs/Manual.md

Weibye commented 3 years ago

To my untrained eyes, this sounds like what the change detection system accomplishes. Could you elaborate what you would like to achieve with this that the current change detection system does not solve?

ghost commented 3 years ago

To my untrained eyes, this sounds like what the change detection system accomplishes. Could you elaborate what you would like to achieve with this that the current change detection system does not solve?

Thanks for reply me.

Now, I explain by a example, such as the following code, I modify 3 entities in 10003 entities, and Query<, Changed> iter 10003 count;

And the scene is many object, but I changed a little obejct per frame

image

cart commented 3 years ago

Just for clarity, the example above does actually run without failing the assert. Count is 3 at the end of execution. This isn't a bug. @moyy is pointing out that Changed is a per-entity filter that is evaluated during iteration. So if there are 10,000 entities with the i32 component, we are checking 10,000 i32 component change states during iteration (this is hidden from the user ... the iterator only returns changed entities).

This was an intentional tradeoff I made to make "automatic change tracking" a performant thing to do globally by default. Any method that involves pushing changes to a dense list (without a full entity scan) would have a significant performance impact when derefing Mut pointers (ex: check to see if we've already pushed a change (to avoid dupes), grab unique access to a shared list (either a RefCell if Mut doesn't need to be Send/Sync, or a Mutex or crossbeam channel for Send/Sync), then push the changed Entity (and potentially allocate)). And then after that, we would need to consolidate changes across queries, which would also involve more allocations and would require de-duplication (so either a sort or a hashmap, neither of which is cheap).

@BoxyUwU had the idea of making "changed status" a part of archetypes, but that would likely also be prohibitively expensive to do by default.

I briefly looked into the Entitas implementation:

  1. There is no multithreaded System execution in Entitas
  2. They don't use tracking pointers (because c# doesn't have a DerefMut interface). Instead they have a centralized Entity type that has the handlers registered there. You must manually call ReplaceComponent for the "change" to get picked up.
  3. Reactions are handled "immediately" using C# events / lambdas. This removes the need to store consolidated "replace" events. Doing that severely limits parallelization potential, because we need to claim all potential resources used by all event handlers a system might call. We can consider doing this, but it won't scale well to large numbers of event handlers. It is also generally incompatible with respecting rust's mutability rules. A "reaction" system that mutates resource X can't be run as a reaction if the host system is already mutating X.

We've been discussing adding "reactive ECS" elements to Bevy ECS, but we can't adopt the Entitas implementation without throwing away the things that make Bevy ECS special (parallel, lockless, resource access-aware system execution).

We could consider building Entitas-style reactions in "exclusive single-threaded" systems (probably with a new set of high level reactive apis). But I'd like to explore ways to integrate reactions into the existing parallel context first.

cart commented 3 years ago

It looks like Unity's ECS (which is parallel) uses an approach very similar to Bevy's. Getting changed components requires checking each component's version against the system's version: https://www.effectiveunity.com/ecs/07-how-to-build-reactive-systems-with-unity-ecs-part-2/