SystemId scene templating - #24087
Conversation
|
Glad to see my idea from #24072 (comment) had some merit! I won't approve yet, since i've only looked through it on my phone, but it already seems far simpler. |
|
This was proposed basically 1:1 in #24026 (this was later changed though). The issue is that it's unclear who owns these systems, that is who is responsible for unregistering them once they are no longer needed. Given that recreating the template will spawn the system again this basically becomes a memory leak. |
Hm, are you sure this is the case even tho in build_template it only registers the system the first time its called, switching over to storing the SystemId after the first call? |
If you recreate the template (e.g. you call |
Would it work to use Then it's not a leak because we could always use the If the systems need to capture values, then maybe those values could be stored as components on the parent entity instead of in the system? The system could take |
You cannot use |
Right, you'd have to wrap the |
I've come up with a |
# Objective bevyengine#24087 introduces scene templating for `SystemId`s, however it can result in a memory leak if a scene is re-constructed multiple times: bevyengine#24087 (comment) > This was proposed basically 1:1 in bevyengine#24026 (this was later changed though). The issue is that it's unclear who owns these systems, that is who is responsible for unregistering them once they are no longer needed. Given that recreating the template will spawn the system again this basically becomes a memory leak. bevyengine#24087 (comment) > > Hm, are you sure this is the case even tho in `build_template` it only registers the system the first time its called, switching over to storing the SystemId after the first call? > > If you recreate the template (e.g. you call `my_scene()` again) then you will create a new instance of the system. And since the system is not scoped to the scene once the scene is despawned the system entity will be leaked. Essentially, we need a way to connect the lifetime of the registered system to the lifetime of the scene. ## Solution This is a purely additive / opt-in / backwards-compatible version of bevyengine#24114 Introducing: `SystemHandle`s ```rust pub enum SystemHandle<I: SystemInput = (), O = ()> { /// A strong handle keeps the system entity alive as long as the handle /// (and any clones of it) exist. Strong(Arc<StrongSystemHandle>), /// A weak handle does not keep the system entity alive. Weak(SystemId<I, O>), } pub struct StrongSystemHandle { entity: Entity, drop_queue: Arc<ConcurrentQueue<<Entity>>, } ``` Similar to `bevy_asset::Handle`s,`SystemHandle`'s custom `Drop` implementation enqueues the registered system entity into a concurrent queue. The system `despawn_unused_registered_systems` pulls from the other end of this queue and despawns the registered system entities. `World::register_tracked_system` and `World::register_tracked_boxed_system` are the only functions that return `SystemHandle`s. ## Testing - Added a test to ensure that `despawn_unused_registered_systems` does its job - Added a test to ensure that the default app will automatically call `despawn_unused_registered_systems` ## Future work - bevyengine#24087 will use this PR as a base --------- Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> Co-authored-by: François Mockers <francois.mockers@vleue.com>
a26da4f to
9993182
Compare
|
Was going to just do a git merge but it turned into a headache so I went with a clean rebase on top of current main. |
# Objective #24087 introduces scene templating for `SystemId`s, however it can result in a memory leak if a scene is re-constructed multiple times: #24087 (comment) > This was proposed basically 1:1 in #24026 (this was later changed though). The issue is that it's unclear who owns these systems, that is who is responsible for unregistering them once they are no longer needed. Given that recreating the template will spawn the system again this basically becomes a memory leak. #24087 (comment) > > Hm, are you sure this is the case even tho in `build_template` it only registers the system the first time its called, switching over to storing the SystemId after the first call? > > If you recreate the template (e.g. you call `my_scene()` again) then you will create a new instance of the system. And since the system is not scoped to the scene once the scene is despawned the system entity will be leaked. Essentially, we need a way to connect the lifetime of the registered system to the lifetime of the scene. ## Solution This is a purely additive / opt-in / backwards-compatible version of #24114 Introducing: `SystemHandle`s ```rust pub enum SystemHandle<I: SystemInput = (), O = ()> { /// A strong handle keeps the system entity alive as long as the handle /// (and any clones of it) exist. Strong(Arc<StrongSystemHandle>), /// A weak handle does not keep the system entity alive. Weak(SystemId<I, O>), } pub struct StrongSystemHandle { entity: Entity, drop_queue: Arc<ConcurrentQueue<<Entity>>, } ``` Similar to `bevy_asset::Handle`s,`SystemHandle`'s custom `Drop` implementation enqueues the registered system entity into a concurrent queue. The system `despawn_unused_registered_systems` pulls from the other end of this queue and despawns the registered system entities. `World::register_tracked_system` and `World::register_tracked_boxed_system` are the only functions that return `SystemHandle`s. ## Testing - Added a test to ensure that `despawn_unused_registered_systems` does its job - Added a test to ensure that the default app will automatically call `despawn_unused_registered_systems` ## Future work - #24087 will use this PR as a base --------- Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> Co-authored-by: François Mockers <francois.mockers@vleue.com>
# Objective - Simplified alternative to #24072 I have a bunch of `Bundle`-style code that I want to replace with the new `bsn!` Scene-style macro: ```rust pub fn rest_ui(/* system params */) { // my ui system... } // From pub fn rest(mut commands: Commands) -> impl Bundle { ( Rest, Name::new("Rest"), Activity { render: commands.register_system(rest_ui), }, ) } // To (ideally) pub fn rest() -> impl Scene { bsn! { Rest Name("Rest") Activity { render: rest_ui } } } ``` ## Solution This solution is more inspired by how `HandleTemplate` works. 1. Added `SystemIdTemplate`; it stores either a `SystemId` or a `Arc<Mutex<Either<SystemId, Box<dyn System>>>>` 2. Added a `system_value` function for wrapping system functions (see Future Work for potentially removing the need) ## Testing - Added a unit test for `SystemIdTemplate`. - Added to the `callbacks` example demonstrating how to spawn `SystemId`s via BSN scenes. ## Future work - I believe we can remove the need for wrapping with `system_value` by introducing [`SuperFrom`/`SuperInto` traits a la Dioxus](https://docs.rs/dioxus-core/0.7.6/dioxus_core/trait.SuperFrom.html) and using it in the `bsn!` macros in-place of the implicit `.into()`s. --- ## Showcase You can now spawn components containing `SystemId`s via `bsn!` macros: ```rust #[derive(Component, FromTemplate)] struct Callback { system_id: SystemId<(), ()>, } fn my_scene() -> impl Scene { bsn! { Callback { system_id: system_value(|| { println!("This is a callback spawned via a scene."); }) } } } ```
Objective
Arcify one-shot systems to enableSystemIdtemplating #24072I have a bunch of
Bundle-style code that I want to replace with the newbsn!Scene-style macro:Solution
This solution is more inspired by how
HandleTemplateworks.SystemIdTemplate; it stores either aSystemIdor aArc<Mutex<Either<SystemId, Box<dyn System>>>>system_valuefunction for wrapping system functions (see Future Work for potentially removing the need)Testing
SystemIdTemplate.callbacksexample demonstrating how to spawnSystemIds via BSN scenes.Future work
system_valueby introducingSuperFrom/SuperIntotraits a la Dioxus and using it in thebsn!macros in-place of the implicit.into()s.Showcase
You can now spawn components containing
SystemIds viabsn!macros: