-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
The problem
As far as I can tell, it is not possible to create dynamic observers in user space that observe lifecycle events with the same behavior as Bevy's built in observers. While you can get 99% of the way there, it isn't possible to run arbitrary systems immediately.
Systems need &mut World or UnsafeWorldCell to run. An observer runner function pointer receives a DeferredWorld. The default runner simply unsafely turns the DeferredWorld into an UnsafeWorldCell. So what's the problem? This method is not externally accessible..
Queuing the system execution is not a viable solution. By the time the system runs for Replace or Remove events, the data will be incorrect or simply gone.
Possible solution
Adjusting the visibility of as_unsafe_world_cell resolves this particular problem. I can't evaluate whether there are any real drawbacks here. I'm not sure misuse is a great counterargument; any misuse would fall under unsafe, which inherently carries a high bar of scrutiny.
Additional context
I'm working on an implementation for #20817 in user space (I don't want to fork Bevy since this may not land any time soon, and it's immediately useful to me). I have a type-level implementation, but it's more wasteful than it needs to be. As an example:
fn query_observer(data: DataAdded<(Entity, &A, &B)>) {}My implementation currently creates two observers: On<Insert, A> and On<Insert, B>. Each runs query_observer if the overall shape is satisfied. It would be more efficient to create a single observer that watches both components, but this is difficult to do.
Ideally, (Entity, &A, &B) would implement a trait that describes its Access, and then I could use this to dynamically create the minimal set of observers to handle all cases. We have the former (WorldQuery), but not so much the latter.