Skip to content

Instantly share code, notes, and snippets.

@sum-catnip
Created May 2, 2025 13:25
Show Gist options
  • Save sum-catnip/dcaf3f1fd09015336d95b2c040fa1f39 to your computer and use it in GitHub Desktop.
Save sum-catnip/dcaf3f1fd09015336d95b2c040fa1f39 to your computer and use it in GitHub Desktop.
bevy "effectbundle"
use bevy::{
ecs::{
bundle::{BundleEffect, DynamicBundle},
component::{
ComponentId, Components, ComponentsRegistrator, RequiredComponents, StorageType,
},
},
prelude::*,
ptr::OwningPtr,
};
pub struct EffectBundleFn(Box<dyn FnOnce(&mut EntityWorldMut) + Sync + Send + 'static>);
impl EffectBundleFn {
pub fn new<F: FnOnce(&mut EntityWorldMut) + Sync + Send + 'static>(f: F) -> Self {
Self(Box::new(f))
}
}
unsafe impl Bundle for EffectBundleFn {
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)) {
<() as Bundle>::get_component_ids(components, ids);
}
fn register_required_components(
components: &mut ComponentsRegistrator,
required_components: &mut RequiredComponents,
) {
<() as Bundle>::register_required_components(components, required_components);
}
fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)) {
<() as Bundle>::component_ids(components, ids);
}
}
impl DynamicBundle for EffectBundleFn {
type Effect = Self;
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect {
<() as DynamicBundle>::get_components((), func);
self
}
}
impl BundleEffect for EffectBundleFn {
fn apply(self, entity: &mut EntityWorldMut) {
(self.0)(entity)
}
}
@sum-catnip
Copy link
Author

can be used for this:

fn uistuff() -> impl Bundle {
    (
        Button,
        EffectBundleFn::new(|e| {
            e.observe(click);
        }),
    )
}

or this:

// type erases the bundle so you can return multiple different bundle types
fn uistuff(thing: Thing) -> impl Bundle {
    match Thing {
        Thing::A => EffectBundleFn::new(|e| {
            e.insert(BundleA);
        },
        Thing::B => EffectBundleFn::new(|e| {
            e.insert(BundleB);
        }
    }
}

@rparrett
Copy link

rparrett commented May 3, 2025

This feels incredibly nice to use. Thanks for sharing!

Out of curiosity, do we know if anything even remotely shaped like this is part of the "grand BSN plan"?

@sum-catnip
Copy link
Author

Thank you :). For everyone seeing this here i should mention that ole from the bevy discord made something like this but specific to observers and its much nicer for when you want to attach an observer. I like using both for different use cases.

This effectbundlefn is also special in that it erases the type of the fn you pass in which is good and bad:

  • good: it erases the type so you can use it to return different impl bundles from one function
  • bad: you cant add multiple of theese to one bundle because

I will probably make a generic non type erased version too for when you don't need the type erasure.

@sum-catnip
Copy link
Author

For the second question i have no idea but there is an open github discussion about bsn so maybe ask there or in the ui channel on discord

@DylanRJohnston
Copy link

@sum-catnip I think you need to be careful with this because you report to the ECS system that the bundle contains no components

fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)) {
    <() as Bundle>::get_component_ids(components, ids);
}

but then in your type erased example demonstrate how EffectBundleFn can be used to insert components, does this lead to undefined behavior? Or is the insertion of components in the BundleEffect completely separate from the logic of Bundle?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment