Skip to content

Arcify one-shot systems to enable SystemId templating#24072

Closed
ItsDoot wants to merge 4 commits intobevyengine:mainfrom
ItsDoot:ecs/arconeshot
Closed

Arcify one-shot systems to enable SystemId templating#24072
ItsDoot wants to merge 4 commits intobevyengine:mainfrom
ItsDoot:ecs/arconeshot

Conversation

@ItsDoot
Copy link
Copy Markdown
Contributor

@ItsDoot ItsDoot commented May 2, 2026

Objective

I have a bunch of Bundle-style code that I want to replace with the new bsn! Scene-style macro:

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
        }
    }
}

However, there is currently no simple way to implement a SystemIdTemplate because systems are not cloneable.

Solution

  1. Introduced SystemArc<S: System + ?Sized>, a wrapper around an Arc<Mutex<S>>
  2. Changed the one-shot system registry code to store SystemArcs instead of BoxedSystems
  3. Added SystemIdTemplate that makes use of the new SystemArc type
  4. Added a system_value function for wrapping system functions (see Future Work for potentially removing the need)

Note: Until #[derive(CoercePointee)] (or some other rust feature that gives user-land CoerceUnsized) is stabilized, one-shot systems cannot be used on no-std-portable-atomic because portable_atomic_util::Arc can not do Arc<T> to Arc<dyn Trait> conversions like std::sync::Arc can.

Testing

  • Added a unit test for SystemIdTemplate.
  • Added to the callbacks example demonstrating how to spawn SystemIds 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 and using it in the bsn! macros in-place of the implicit .into()s.
  • SystemArc lays a bit of the foundation for Arcifying Schedules.

Showcase

You can now spawn components containing SystemIds via bsn! macros:

#[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.");
            })
        }
    }
}

@ItsDoot ItsDoot added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use A-Scenes Composing and serializing ECS objects D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 2, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in ECS May 2, 2026
@ItsDoot ItsDoot added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 2, 2026
@ItsDoot ItsDoot added S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels May 2, 2026
i.e. they can't use one-shot systems until a rust feature is stabilized
@ItsDoot ItsDoot added the M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label May 2, 2026
Copy link
Copy Markdown
Contributor

@laundmo laundmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure the fact all registered systems have changed over from BoxedSystem to SystemArc is gonna cause some performance regressions.

See the comments for an idea which would not require this, tho i'm not sure how doable it is.

Comment on lines +173 to +187
fn build_template(&self, context: &mut TemplateContext) -> Result<Self::Output> {
match self {
Self::Id(id) => Ok(*id),
Self::System(system) => Ok(context
.entity
.world_scope(|world| world.register_system_arc(system.clone()))),
}
}

fn clone_template(&self) -> Self {
match self {
Self::Id(id) => Self::Id(*id),
Self::System(system) => Self::System(system.clone()),
}
}
Copy link
Copy Markdown
Contributor

@laundmo laundmo May 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, this reads like each time clone_template and then build_template are called, a new SystemId would be registered. I don't know if this has any negative effects, as the Arc means it'll be the same underlying dyn System, but at least it produces more SystemIds than i think should be necessary.

Idea: Would it be possible to store something like a Either<BoxedSystem, SystemId> in SystemIdTemplate::System (behind a mutex or similar), such that the SystemId from the first time this system is registered can be re-used for subsequent times this system gets registered?

If that works, it would, i think, remove the necessity for SystemArc by moving the Arc<Mutex> needed to make a system clone-able for clone_template into the SystemIdTemplate scope.

Another idea i had was only allowing a set kind of system in Self::System, or having 2-3 variants for different kinds of systems, as some like FunctionSystem implement Clone

// Wrap the system locking in a block to ensure it gets dropped before we flush commands.
// This is needed to allow systems to recursively call themselves.
let result = {
let mut system = system.lock();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the places i fear a performance regression especially for heavy users of one-shot systems might happen

Comment on lines +22 to 24
pub(crate) struct RegisteredSystem<I: SystemInput + 'static, O: 'static> {
system: SystemArcDyn<I, O>,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned that completely switching over every RegisteredSystem to SystemArc will have performance implications even during on-shot system calls.

@ItsDoot ItsDoot added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 2, 2026
@ItsDoot
Copy link
Copy Markdown
Contributor Author

ItsDoot commented May 3, 2026

Closed in favor of #24087

@ItsDoot ItsDoot closed this May 3, 2026
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in ECS May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ECS Entities, components, systems, and events A-Scenes Composing and serializing ECS objects C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants