From ef93a048c8981720647179b2ae138b849225bd3e Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 12 Mar 2026 11:19:29 -0700 Subject: [PATCH 1/9] Generic bundle using seperate world. --- src/bundle.rs | 10 +------ src/bundle_inspector.rs | 39 +++++++++++++++++++++++++++ src/command.rs | 59 +++++++++++++++++++---------------------- src/lib.rs | 3 +++ 4 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 src/bundle_inspector.rs diff --git a/src/bundle.rs b/src/bundle.rs index 64af949..cb51c16 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -1,4 +1,3 @@ -use crate::EffectMode; use bevy_ecs::prelude::*; /// A "bundle" of components/settings used when applying an effect. @@ -14,11 +13,4 @@ use bevy_ecs::prelude::*; /// ### [`EffectedBy::spawn`](SpawnRelated::spawn) #[doc = include_str!("../docs/effected_by_spawn_example.md")] #[derive(Default)] -pub struct EffectBundle { - /// The name/ID of the effect. Effects with different IDs have no effect on one another. - pub name: Name, - /// Describes the logic used when new effect collides with an existing one. - pub mode: EffectMode, - /// Components that will be added to the effect. This is where the actual effect components get added. - pub bundle: B, -} +pub struct EffectBundle(pub B); diff --git a/src/bundle_inspector.rs b/src/bundle_inspector.rs new file mode 100644 index 0000000..c96dbf8 --- /dev/null +++ b/src/bundle_inspector.rs @@ -0,0 +1,39 @@ +use crate::EffectMode; +use bevy_ecs::prelude::{Bundle, Entity, Name, Resource, World}; + +#[derive(Resource)] +pub(crate) struct BundleInspector { + world: World, + scratch_entity: Entity, +} + +impl Default for BundleInspector { + fn default() -> Self { + let mut world = World::new(); + let scratch_entity = world.spawn_empty().id(); + Self { + world, + scratch_entity, + } + } +} + +impl BundleInspector { + pub fn get_effect_meta(&mut self, bundle: B) -> (Option, EffectMode) { + let e = self.scratch_entity; + self.world.entity_mut(e).insert(bundle); + + let name = self.world.entity(e).get::().cloned(); + + let mode = self + .world + .entity_mut(e) + .get::() + .copied() + .unwrap_or_default(); + + self.world.entity_mut(e).clear(); + + (name, mode) + } +} diff --git a/src/command.rs b/src/command.rs index b526a57..dd61772 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,4 +1,5 @@ use crate::bundle::EffectBundle; +use crate::bundle_inspector::BundleInspector; use crate::registry::{EffectMergeFn, EffectMergeRegistry}; use crate::{EffectMode, EffectedBy, Effecting}; use bevy_ecs::entity_disabling::Disabled; @@ -17,24 +18,12 @@ pub struct AddEffectCommand { /// The entity to apply the effect to. pub target: Entity, /// The effect to apply. - pub bundle: EffectBundle, + pub bundle: B, } impl AddEffectCommand { - fn spawn(self, world: &mut World) -> Entity { - let entity = world.spawn_empty(); - let id = entity.id(); - self.insert(entity); - id - } - - fn insert(self, mut entity: EntityWorldMut) { - entity.insert(( - Effecting(self.target), - self.bundle.name, - self.bundle.mode, - self.bundle.bundle, - )); + fn bundle_full(self) -> (Effecting, B) { + (Effecting(self.target), self.bundle) } /// Inserts into the existing entity, and then merges the old effect into it using [`EffectMergeRegistry`]. @@ -70,7 +59,7 @@ impl AddEffectCommand { temp }; - self.insert(world.entity_mut(new_effect)); + world.entity_mut(new_effect).insert(self.bundle); // Call merge function on those copied components. { @@ -100,10 +89,13 @@ impl AddEffectCommand { } } -impl Command for AddEffectCommand { +impl Command for AddEffectCommand { fn apply(self, world: &mut World) { - if self.bundle.mode == EffectMode::Stack { - self.spawn(world); + let mut inspector = world.get_resource_or_init::(); + let (name, mode) = inspector.get_effect_meta(self.bundle.clone()); + + if mode == EffectMode::Stack { + world.spawn(self.bundle_full()); return; } @@ -111,7 +103,7 @@ impl Command for AddEffectCommand { .get::(self.target) .map(|e| e.collection().clone()) else { - self.spawn(world); + world.spawn(self.bundle_full()); return; }; @@ -122,13 +114,13 @@ impl Command for AddEffectCommand { let other_mode = world.get::(*entity)?; // Todo Think more about. - if self.bundle.mode != *other_mode { + if mode != *other_mode { return None; } - let name = world.get::(*entity)?; + let other_name = world.get::(*entity); - if name == &self.bundle.name { + if name == other_name.cloned() { return Some(*entity); } @@ -136,23 +128,28 @@ impl Command for AddEffectCommand { }); let Some(old_entity) = old_entity else { - self.spawn(world); + world.spawn(self.bundle_full()); return; }; - match self.bundle.mode { + match mode { EffectMode::Stack => unreachable!(), - EffectMode::Insert => self.insert(world.entity_mut(old_entity)), + EffectMode::Insert => { + world.entity_mut(old_entity).insert(self.bundle_full()); + } EffectMode::Merge => self.merge(world, old_entity), } } } // Todo This is probably bad practice/has larger performance cost. -impl SpawnableList for EffectBundle { +impl SpawnableList for EffectBundle { fn spawn(this: MovingPtr<'_, Self>, world: &mut World, target: Entity) { let bundle = this.read(); - world.commands().queue(AddEffectCommand { target, bundle }); + world.commands().queue(AddEffectCommand { + target, + bundle: bundle.0, + }); } fn size_hint(&self) -> usize { @@ -179,7 +176,7 @@ impl<'a> EffectSpawner<'a> { /// /// # Example #[doc = include_str!("../docs/with_effects_example.md")] - pub fn spawn(&mut self, bundle: EffectBundle) { + pub fn spawn(&mut self, bundle: B) { self.commands.queue(AddEffectCommand { target: self.target, bundle, @@ -196,7 +193,7 @@ pub trait EffectCommandsExt { /// /// # Example #[doc = include_str!("../docs/with_effect_example.md")] - fn with_effect(&mut self, bundle: EffectBundle) -> &mut Self; + fn with_effect(&mut self, bundle: B) -> &mut Self; /// Applies effects to this entity by taking a function that operates on a [`EffectSpawner`]. /// @@ -208,7 +205,7 @@ pub trait EffectCommandsExt { } impl EffectCommandsExt for EntityCommands<'_> { - fn with_effect(&mut self, bundle: EffectBundle) -> &mut Self { + fn with_effect(&mut self, bundle: B) -> &mut Self { let target = self.id(); self.commands().queue(AddEffectCommand { target, bundle }); self diff --git a/src/lib.rs b/src/lib.rs index 7771492..7fca5ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![doc = include_str!("../README.md")] mod bundle; +mod bundle_inspector; mod command; mod component; mod registry; @@ -11,6 +12,7 @@ use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use bevy_reflect::prelude::ReflectDefault; +use crate::bundle_inspector::BundleInspector; pub use bundle::*; pub use command::*; pub use component::*; @@ -28,6 +30,7 @@ impl Plugin for AlchemyPlugin { .register_type::() .register_type::() .register_type::() + .init_resource::() .init_resource::() .add_plugins(TimerPlugin) .add_plugins(StackPlugin); From c1552246c835c79b3b66e5e75f7c642352e8bbb7 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 12 Mar 2026 13:34:47 -0700 Subject: [PATCH 2/9] Update tests, examples, and benches. --- benches/insert_mode.rs | 35 ++-- benches/stack_mode.rs | 25 ++- docs/effected_by_spawn_example.md | 10 +- docs/with_effect_example.md | 8 +- docs/with_effects_example.md | 15 +- examples/immediate_stats/decaying_speed.rs | 23 +-- .../decaying_speed_auto_plugin.rs | 23 +-- examples/poison.rs | 21 +-- examples/poison_falloff.rs | 25 ++- tests/merge_mode.rs | 172 +++++++----------- tests/spawn_syntax.rs | 47 +---- 11 files changed, 151 insertions(+), 253 deletions(-) diff --git a/benches/insert_mode.rs b/benches/insert_mode.rs index c49bd73..d233a57 100644 --- a/benches/insert_mode.rs +++ b/benches/insert_mode.rs @@ -6,7 +6,7 @@ use bevy_ecs::name::Name; use bevy_ecs::prelude::{Component, Entity, SpawnRelated}; use criterion::{Criterion, criterion_group, criterion_main}; -#[derive(Component)] +#[derive(Component, Clone)] struct BenchEffect; fn init_app() -> (App, Entity) { @@ -17,11 +17,11 @@ fn init_app() -> (App, Entity) { .world_mut() .spawn(( Name::new("Target"), - EffectedBy::spawn(EffectBundle { - name: Name::new("Effect"), - mode: EffectMode::Insert, - bundle: BenchEffect, - }), + EffectedBy::spawn(EffectBundle(( + Name::new("Effect"), + EffectMode::Insert, + BenchEffect, + ))), )) .id(); @@ -33,14 +33,11 @@ fn with_effect(c: &mut Criterion) { c.bench_function("Insert mode matched `with_effect`", |b| { b.iter(|| { - app.world_mut() - .commands() - .entity(entity) - .with_effect(EffectBundle { - name: Name::new("Effect"), - mode: EffectMode::Insert, - bundle: BenchEffect, - }); + app.world_mut().commands().entity(entity).with_effect(( + Name::new("Effect"), + EffectMode::Insert, + BenchEffect, + )); app.world_mut().flush(); }) }); @@ -54,11 +51,11 @@ fn related_spawner(c: &mut Criterion) { app.world_mut() .commands() .entity(entity) - .insert(EffectedBy::spawn(EffectBundle { - name: Name::new("Effect"), - mode: EffectMode::Insert, - bundle: BenchEffect, - })); + .insert(EffectedBy::spawn(EffectBundle(( + Name::new("Effect"), + EffectMode::Insert, + BenchEffect, + )))); app.world_mut().flush(); }) }); diff --git a/benches/stack_mode.rs b/benches/stack_mode.rs index 2e17fd7..e99dc2d 100644 --- a/benches/stack_mode.rs +++ b/benches/stack_mode.rs @@ -6,7 +6,7 @@ use bevy_ecs::name::Name; use bevy_ecs::prelude::{Component, Entity, SpawnRelated}; use criterion::{Criterion, criterion_group, criterion_main}; -#[derive(Component)] +#[derive(Component, Clone)] struct BenchEffect; fn init_app() -> (App, Entity) { @@ -23,14 +23,11 @@ fn with_effect(c: &mut Criterion) { c.bench_function("Stack mode `with_effect`", |b| { b.iter(|| { - app.world_mut() - .commands() - .entity(entity) - .with_effect(EffectBundle { - name: Name::new("Effect"), - mode: EffectMode::Stack, - bundle: BenchEffect, - }); + app.world_mut().commands().entity(entity).with_effect(( + Name::new("Effect"), + EffectMode::Stack, + BenchEffect, + )); app.world_mut().flush(); }) }); @@ -44,11 +41,11 @@ fn related_spawner(c: &mut Criterion) { app.world_mut() .commands() .entity(entity) - .insert(EffectedBy::spawn(EffectBundle { - name: Name::new("Effect"), - mode: EffectMode::Stack, - bundle: BenchEffect, - })); + .insert(EffectedBy::spawn(EffectBundle(( + Name::new("Effect"), + EffectMode::Stack, + BenchEffect, + )))); app.world_mut().flush(); }) }); diff --git a/docs/effected_by_spawn_example.md b/docs/effected_by_spawn_example.md index ffc14d5..27485df 100644 --- a/docs/effected_by_spawn_example.md +++ b/docs/effected_by_spawn_example.md @@ -2,7 +2,7 @@ # use bevy::prelude::*; # use bevy_alchemy::*; # -# #[derive(Component, Default)] +# #[derive(Component, Default, Clone)] # struct MyEffect; # # fn main() { @@ -11,11 +11,9 @@ # let mut commands = world.commands(); commands.spawn(( Name::new("Target"), - EffectedBy::spawn(EffectBundle { - name: Name::new("Effect"), - bundle: MyEffect, - ..default() - }), + EffectedBy::spawn( + EffectBundle((Name::new("Effect"), MyEffect)) + ), )); # } ``` \ No newline at end of file diff --git a/docs/with_effect_example.md b/docs/with_effect_example.md index 9a4db57..cef1b71 100644 --- a/docs/with_effect_example.md +++ b/docs/with_effect_example.md @@ -2,17 +2,13 @@ # use bevy::prelude::*; # use bevy_alchemy::*; # -# #[derive(Component, Default)] +# #[derive(Component, Default, Clone)] # struct MyEffect; # # fn main() { # let mut world = World::new(); # let target = world.spawn_empty().id(); # let mut commands = world.commands(); -commands.entity(target).with_effect(EffectBundle { - name: Name::new("Effect"), - bundle: MyEffect, - ..default() -}); +commands.entity(target).with_effect((Name::new("Effect"), MyEffect)); # } ``` \ No newline at end of file diff --git a/docs/with_effects_example.md b/docs/with_effects_example.md index 535ee0c..ba7a8ce 100644 --- a/docs/with_effects_example.md +++ b/docs/with_effects_example.md @@ -2,7 +2,7 @@ # use bevy::prelude::*; # use bevy_alchemy::*; # -# #[derive(Component, Default)] +# #[derive(Component, Default, Clone)] # struct MyEffect; # # fn main() { @@ -10,17 +10,8 @@ # let target = world.spawn_empty().id(); # let mut commands = world.commands(); commands.entity(target).with_effects(|effects| { - effects.spawn(EffectBundle { - name: Name::new("EffectA"), - bundle: MyEffect, - ..default() - }); - - effects.spawn(EffectBundle { - name: Name::new("EffectB"), - bundle: MyEffect, - ..default() - }); + effects.spawn((Name::new("EffectA"), MyEffect)); + effects.spawn((Name::new("EffectB"), MyEffect)); }); # } ``` \ No newline at end of file diff --git a/examples/immediate_stats/decaying_speed.rs b/examples/immediate_stats/decaying_speed.rs index 2a95fc9..80a5402 100644 --- a/examples/immediate_stats/decaying_speed.rs +++ b/examples/immediate_stats/decaying_speed.rs @@ -26,7 +26,7 @@ fn main() { struct MovementSpeed(Stat); /// Applies a speed boost, which decreases throughout its duration. -#[derive(Component, Default)] +#[derive(Component, Default, Clone)] struct DecayingSpeed { start_speed_boost: Modifier, } @@ -48,19 +48,16 @@ fn on_space_pressed( return; } - commands.entity(*target).with_effect(EffectBundle { - mode: EffectMode::Insert, // Block having multiple of effect stacked on a single target. - bundle: ( - Lifetime::from_seconds(2.0), // The duration of the effect. - DecayingSpeed { - start_speed_boost: Modifier { - bonus: 10, - multiplier: 2.0, - }, + commands.entity(*target).with_effect(( + EffectMode::Insert, // Block having multiple of effect stacked on a single target. + Lifetime::from_seconds(2.0), // The duration of the effect. + DecayingSpeed { + start_speed_boost: Modifier { + bonus: 10, + multiplier: 2.0, }, - ), - ..default() - }); + }, + )); } /// Applies the effect to the target. Because of how Immediate Stats works, this needs to run every frame. diff --git a/examples/immediate_stats/decaying_speed_auto_plugin.rs b/examples/immediate_stats/decaying_speed_auto_plugin.rs index 71c0b5d..5fec8dc 100644 --- a/examples/immediate_stats/decaying_speed_auto_plugin.rs +++ b/examples/immediate_stats/decaying_speed_auto_plugin.rs @@ -28,7 +28,7 @@ struct DecayingSpeedPlugin; struct MovementSpeed(Stat); /// Applies a speed boost, which decreases throughout its duration. -#[derive(Component, Default)] +#[derive(Component, Default, Clone)] struct DecayingSpeed { start_speed_boost: Modifier, } @@ -52,19 +52,16 @@ fn on_space_pressed( return; } - commands.entity(*target).with_effect(EffectBundle { - mode: EffectMode::Insert, // Block having multiple of effect stacked on a single target. - bundle: ( - Lifetime::from_seconds(2.0), // The duration of the effect. - DecayingSpeed { - start_speed_boost: Modifier { - bonus: 10, - multiplier: 2.0, - }, + commands.entity(*target).with_effect(( + EffectMode::Insert, // Block having multiple of effect stacked on a single target. + Lifetime::from_seconds(2.0), // The duration of the effect. + DecayingSpeed { + start_speed_boost: Modifier { + bonus: 10, + multiplier: 2.0, }, - ), - ..default() - }); + }, + )); } /// Applies the effect to the target. Because of how Immediate Stats works, this needs to run every frame. diff --git a/examples/poison.rs b/examples/poison.rs index 05238e5..1276699 100644 --- a/examples/poison.rs +++ b/examples/poison.rs @@ -5,9 +5,7 @@ //! The `poison_falloff` example shows a different way to handle effect stacking. use bevy::prelude::*; -use bevy_alchemy::{ - AlchemyPlugin, Delay, EffectBundle, EffectCommandsExt, EffectTimer, Effecting, Lifetime, -}; +use bevy_alchemy::{AlchemyPlugin, Delay, EffectCommandsExt, EffectTimer, Effecting, Lifetime}; fn main() { App::new() @@ -22,7 +20,7 @@ fn main() { struct Health(i32); /// Deals damage over time to the target entity. -#[derive(Component, Default)] +#[derive(Component, Default, Clone)] struct Poison { damage: i32, } @@ -44,15 +42,12 @@ fn on_space_pressed( return; } - commands.entity(*target).with_effect(EffectBundle { - bundle: ( - Lifetime::from_seconds(3.0), // The duration of the effect. - Delay::from_seconds(1.0) // The time between damage ticks. - .trigger_immediately(), // Make damage tick immediately when the effect is applied. - Poison { damage: 1 }, // The amount of damage to apply per tick. - ), - ..default() - }); + commands.entity(*target).with_effect(( + Lifetime::from_seconds(3.0), // The duration of the effect. + Delay::from_seconds(1.0) // The time between damage ticks. + .trigger_immediately(), // Make damage tick immediately when the effect is applied. + Poison { damage: 1 }, // The amount of damage to apply per tick. + )); } /// Runs every frame and deals the poison damage. diff --git a/examples/poison_falloff.rs b/examples/poison_falloff.rs index 73ea919..f374a14 100644 --- a/examples/poison_falloff.rs +++ b/examples/poison_falloff.rs @@ -9,8 +9,8 @@ use bevy::prelude::*; use bevy_alchemy::{ - AlchemyPlugin, Delay, EffectBundle, EffectCommandsExt, EffectMode, EffectStacks, EffectTimer, - Effecting, Lifetime, + AlchemyPlugin, Delay, EffectCommandsExt, EffectMode, EffectStacks, EffectTimer, Effecting, + Lifetime, }; fn main() { @@ -26,7 +26,7 @@ fn main() { struct Health(i32); /// Deals damage over time to the target entity. -#[derive(Component, Default)] +#[derive(Component, Default, Clone)] struct Poison { damage: i32, } @@ -48,17 +48,14 @@ fn on_space_pressed( return; } - commands.entity(*target).with_effect(EffectBundle { - mode: EffectMode::Merge, // Stack tracking requires effect merging. - bundle: ( - EffectStacks::default(), // Enable stack tracking. - Lifetime::from_seconds(3.0), // The duration of the effect. - Delay::from_seconds(1.0) // The time between damage ticks. - .trigger_immediately(), // Make damage tick immediately when the effect is applied. - Poison { damage: 5 }, // The amount of damage to apply per tick. - ), - ..default() - }); + commands.entity(*target).with_effect(( + EffectMode::Merge, // Stack tracking requires effect merging. + EffectStacks::default(), // Enable stack tracking. + Lifetime::from_seconds(3.0), // The duration of the effect. + Delay::from_seconds(1.0) // The time between damage ticks. + .trigger_immediately(), // Make damage tick immediately when the effect is applied. + Poison { damage: 5 }, // The amount of damage to apply per tick. + )); } /// Runs every frame and deals the poison damage. diff --git a/tests/merge_mode.rs b/tests/merge_mode.rs index 5fe44ba..84c6347 100644 --- a/tests/merge_mode.rs +++ b/tests/merge_mode.rs @@ -5,7 +5,7 @@ use bevy_ecs::prelude::*; use bevy_time::*; use std::time::Duration; -#[derive(Component, Debug, Eq, PartialEq, Default)] +#[derive(Component, Debug, Eq, PartialEq, Default, Clone)] struct MyEffect(u8); fn init_world() -> World { @@ -28,15 +28,8 @@ fn stack() { let target = world.spawn_empty().id(); world.commands().entity(target).with_effects(|effects| { - effects.spawn(EffectBundle { - bundle: MyEffect(0), - ..Default::default() - }); - - effects.spawn(EffectBundle { - bundle: MyEffect(1), - ..Default::default() - }); + effects.spawn(MyEffect(0)); + effects.spawn(MyEffect(1)); }); world.flush(); @@ -57,16 +50,14 @@ fn insert() { let target = world.spawn_empty().id(); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(0), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(1), - ..Default::default() - }); + world + .commands() + .entity(target) + .with_effect((EffectMode::Insert, MyEffect(0))); + world + .commands() + .entity(target) + .with_effect((EffectMode::Insert, MyEffect(1))); world.flush(); @@ -86,25 +77,17 @@ fn mixed() { let target = world.spawn_empty().id(); - world.commands().entity(target).with_effect(EffectBundle { - bundle: MyEffect(0), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - bundle: MyEffect(1), - ..Default::default() - }); + world.commands().entity(target).with_effect(MyEffect(0)); + world.commands().entity(target).with_effect(MyEffect(1)); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(2), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(3), - ..Default::default() - }); + world + .commands() + .entity(target) + .with_effect((EffectMode::Insert, MyEffect(2))); + world + .commands() + .entity(target) + .with_effect((EffectMode::Insert, MyEffect(3))); world.flush(); @@ -126,19 +109,16 @@ fn timer_merge_replace() { let target = world.spawn_empty().id(); let second_lifetime = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Replace); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: ( - Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace), - MyEffect(0), - ), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: (second_lifetime.clone(), MyEffect(1)), - ..Default::default() - }); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace), + MyEffect(0), + )); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + second_lifetime.clone(), + MyEffect(1), + )); world.flush(); @@ -158,19 +138,16 @@ fn timer_merge_keep() { let target = world.spawn_empty().id(); let first_delay = Delay::from_seconds(1.0).with_mode(TimerMergeMode::Keep); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: (first_delay.clone(), MyEffect(0)), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: ( - Delay::from_seconds(2.0).with_mode(TimerMergeMode::Keep), - MyEffect(1), - ), - ..Default::default() - }); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + first_delay.clone(), + MyEffect(0), + )); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + Delay::from_seconds(2.0).with_mode(TimerMergeMode::Keep), + MyEffect(1), + )); world.flush(); @@ -192,25 +169,19 @@ fn timer_merge_fraction() { let mut first_timer = Timer::from_seconds(2.0, TimerMode::Once); first_timer.tick(Duration::from_secs_f32(1.0)); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: ( - Delay { - timer: first_timer, - mode: TimerMergeMode::Fraction, - }, - MyEffect(0), - ), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: ( - Delay::from_seconds(10.0).with_mode(TimerMergeMode::Fraction), - MyEffect(1), - ), - ..Default::default() - }); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + Delay { + timer: first_timer, + mode: TimerMergeMode::Fraction, + }, + MyEffect(0), + )); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + Delay::from_seconds(10.0).with_mode(TimerMergeMode::Fraction), + MyEffect(1), + )); world.flush(); @@ -237,27 +208,20 @@ fn timer_merge_max() { let target = world.spawn_empty().id(); let max = Delay::from_seconds(3.0).with_mode(TimerMergeMode::Max); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: ( - Delay::from_seconds(1.0).with_mode(TimerMergeMode::Max), - MyEffect(0), - ), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: (max.clone(), MyEffect(1)), - ..Default::default() - }); - world.commands().entity(target).with_effect(EffectBundle { - mode: EffectMode::Merge, - bundle: ( - Delay::from_seconds(2.0).with_mode(TimerMergeMode::Max), - MyEffect(2), - ), - ..Default::default() - }); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + Delay::from_seconds(1.0).with_mode(TimerMergeMode::Max), + MyEffect(0), + )); + world + .commands() + .entity(target) + .with_effect((EffectMode::Merge, max.clone(), MyEffect(1))); + world.commands().entity(target).with_effect(( + EffectMode::Merge, + Delay::from_seconds(2.0).with_mode(TimerMergeMode::Max), + MyEffect(2), + )); world.flush(); diff --git a/tests/spawn_syntax.rs b/tests/spawn_syntax.rs index 3e7d5ba..2605322 100644 --- a/tests/spawn_syntax.rs +++ b/tests/spawn_syntax.rs @@ -3,7 +3,7 @@ use bevy_alchemy::*; use bevy_ecs::prelude::*; -#[derive(Component, Debug, Eq, PartialEq, Default)] +#[derive(Component, Debug, Eq, PartialEq, Default, Clone)] struct MyEffect(u8); #[test] @@ -12,16 +12,7 @@ fn spawnable_list_stack() { world.spawn(( Name::new("Target"), - EffectedBy::spawn(( - EffectBundle { - bundle: MyEffect(0), - ..Default::default() - }, - EffectBundle { - bundle: MyEffect(1), - ..Default::default() - }, - )), + EffectedBy::spawn((EffectBundle(MyEffect(0)), EffectBundle(MyEffect(1)))), )); world.flush(); @@ -43,16 +34,8 @@ fn spawnable_list_insert() { world.spawn(( Name::new("Target"), EffectedBy::spawn(( - EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(0), - ..Default::default() - }, - EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(1), - ..Default::default() - }, + EffectBundle((EffectMode::Insert, MyEffect(0))), + EffectBundle((EffectMode::Insert, MyEffect(1))), )), )); @@ -75,24 +58,10 @@ fn spawnable_list_mixed() { world.spawn(( Name::new("Target"), EffectedBy::spawn(( - EffectBundle { - bundle: MyEffect(0), - ..Default::default() - }, - EffectBundle { - bundle: MyEffect(1), - ..Default::default() - }, - EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(2), - ..Default::default() - }, - EffectBundle { - mode: EffectMode::Insert, - bundle: MyEffect(3), - ..Default::default() - }, + EffectBundle(MyEffect(0)), + EffectBundle(MyEffect(1)), + EffectBundle((EffectMode::Insert, MyEffect(2))), + EffectBundle((EffectMode::Insert, MyEffect(3))), )), )); From de8f8dae4636f2aded6c99a1f9e5fee91526ffe2 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 12 Mar 2026 19:02:05 -0700 Subject: [PATCH 3/9] Rename `EffectBundle` to `Effect`. It is now only used during related spawning. --- benches/insert_mode.rs | 6 +++--- benches/stack_mode.rs | 4 ++-- docs/effected_by_spawn_example.md | 2 +- src/command.rs | 18 ------------------ src/lib.rs | 4 ++-- src/{bundle.rs => spawnable_list.rs} | 20 +++++++++++++++++++- tests/spawn_syntax.rs | 14 +++++++------- 7 files changed, 34 insertions(+), 34 deletions(-) rename src/{bundle.rs => spawnable_list.rs} (59%) diff --git a/benches/insert_mode.rs b/benches/insert_mode.rs index d233a57..20602c0 100644 --- a/benches/insert_mode.rs +++ b/benches/insert_mode.rs @@ -1,6 +1,6 @@ //! Benchmarks for applying insert-mode effects. -use bevy_alchemy::{AlchemyPlugin, EffectBundle, EffectCommandsExt, EffectMode, EffectedBy}; +use bevy_alchemy::{AlchemyPlugin, Effect, EffectCommandsExt, EffectMode, EffectedBy}; use bevy_app::App; use bevy_ecs::name::Name; use bevy_ecs::prelude::{Component, Entity, SpawnRelated}; @@ -17,7 +17,7 @@ fn init_app() -> (App, Entity) { .world_mut() .spawn(( Name::new("Target"), - EffectedBy::spawn(EffectBundle(( + EffectedBy::spawn(Effect(( Name::new("Effect"), EffectMode::Insert, BenchEffect, @@ -51,7 +51,7 @@ fn related_spawner(c: &mut Criterion) { app.world_mut() .commands() .entity(entity) - .insert(EffectedBy::spawn(EffectBundle(( + .insert(EffectedBy::spawn(Effect(( Name::new("Effect"), EffectMode::Insert, BenchEffect, diff --git a/benches/stack_mode.rs b/benches/stack_mode.rs index e99dc2d..370a6c3 100644 --- a/benches/stack_mode.rs +++ b/benches/stack_mode.rs @@ -1,6 +1,6 @@ //! Benchmarks for applying stack-mode effects. -use bevy_alchemy::{AlchemyPlugin, EffectBundle, EffectCommandsExt, EffectMode, EffectedBy}; +use bevy_alchemy::{AlchemyPlugin, Effect, EffectCommandsExt, EffectMode, EffectedBy}; use bevy_app::App; use bevy_ecs::name::Name; use bevy_ecs::prelude::{Component, Entity, SpawnRelated}; @@ -41,7 +41,7 @@ fn related_spawner(c: &mut Criterion) { app.world_mut() .commands() .entity(entity) - .insert(EffectedBy::spawn(EffectBundle(( + .insert(EffectedBy::spawn(Effect(( Name::new("Effect"), EffectMode::Stack, BenchEffect, diff --git a/docs/effected_by_spawn_example.md b/docs/effected_by_spawn_example.md index 27485df..300d1ab 100644 --- a/docs/effected_by_spawn_example.md +++ b/docs/effected_by_spawn_example.md @@ -12,7 +12,7 @@ commands.spawn(( Name::new("Target"), EffectedBy::spawn( - EffectBundle((Name::new("Effect"), MyEffect)) + Effect((Name::new("Effect"), MyEffect)) ), )); # } diff --git a/src/command.rs b/src/command.rs index dd61772..cd73c32 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,11 +1,8 @@ -use crate::bundle::EffectBundle; use crate::bundle_inspector::BundleInspector; use crate::registry::{EffectMergeFn, EffectMergeRegistry}; use crate::{EffectMode, EffectedBy, Effecting}; use bevy_ecs::entity_disabling::Disabled; use bevy_ecs::prelude::*; -use bevy_ecs::ptr::MovingPtr; -use bevy_ecs::spawn::SpawnableList; use bevy_log::warn_once; use std::any::TypeId; @@ -142,21 +139,6 @@ impl Command for AddEffectCommand { } } -// Todo This is probably bad practice/has larger performance cost. -impl SpawnableList for EffectBundle { - fn spawn(this: MovingPtr<'_, Self>, world: &mut World, target: Entity) { - let bundle = this.read(); - world.commands().queue(AddEffectCommand { - target, - bundle: bundle.0, - }); - } - - fn size_hint(&self) -> usize { - 0 - } -} - /// Uses commands to apply effects to a specific target entity. /// /// This is normally used during [`with_effects`](EffectCommandsExt::with_effects). diff --git a/src/lib.rs b/src/lib.rs index 7fca5ca..b9bda01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ #![doc = include_str!("../README.md")] -mod bundle; mod bundle_inspector; mod command; mod component; mod registry; mod relation; +mod spawnable_list; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; @@ -13,11 +13,11 @@ use bevy_reflect::Reflect; use bevy_reflect::prelude::ReflectDefault; use crate::bundle_inspector::BundleInspector; -pub use bundle::*; pub use command::*; pub use component::*; pub use registry::*; pub use relation::*; +pub use spawnable_list::*; /// Setup required types and systems for `bevy_alchemy`. pub struct AlchemyPlugin; diff --git a/src/bundle.rs b/src/spawnable_list.rs similarity index 59% rename from src/bundle.rs rename to src/spawnable_list.rs index cb51c16..b3ae230 100644 --- a/src/bundle.rs +++ b/src/spawnable_list.rs @@ -1,4 +1,7 @@ +use crate::{AddEffectCommand, Effecting}; use bevy_ecs::prelude::*; +use bevy_ecs::ptr::MovingPtr; +use bevy_ecs::spawn::SpawnableList; /// A "bundle" of components/settings used when applying an effect. /// Due to technical limitations, this doesn't actually implement [`Bundle`]. @@ -13,4 +16,19 @@ use bevy_ecs::prelude::*; /// ### [`EffectedBy::spawn`](SpawnRelated::spawn) #[doc = include_str!("../docs/effected_by_spawn_example.md")] #[derive(Default)] -pub struct EffectBundle(pub B); +pub struct Effect(pub B); + +// Todo This is probably bad practice/has larger performance cost. +impl SpawnableList for Effect { + fn spawn(this: MovingPtr<'_, Self>, world: &mut World, target: Entity) { + let bundle = this.read(); + world.commands().queue(AddEffectCommand { + target, + bundle: bundle.0, + }); + } + + fn size_hint(&self) -> usize { + 0 + } +} diff --git a/tests/spawn_syntax.rs b/tests/spawn_syntax.rs index 2605322..d41772f 100644 --- a/tests/spawn_syntax.rs +++ b/tests/spawn_syntax.rs @@ -12,7 +12,7 @@ fn spawnable_list_stack() { world.spawn(( Name::new("Target"), - EffectedBy::spawn((EffectBundle(MyEffect(0)), EffectBundle(MyEffect(1)))), + EffectedBy::spawn((Effect(MyEffect(0)), Effect(MyEffect(1)))), )); world.flush(); @@ -34,8 +34,8 @@ fn spawnable_list_insert() { world.spawn(( Name::new("Target"), EffectedBy::spawn(( - EffectBundle((EffectMode::Insert, MyEffect(0))), - EffectBundle((EffectMode::Insert, MyEffect(1))), + Effect((EffectMode::Insert, MyEffect(0))), + Effect((EffectMode::Insert, MyEffect(1))), )), )); @@ -58,10 +58,10 @@ fn spawnable_list_mixed() { world.spawn(( Name::new("Target"), EffectedBy::spawn(( - EffectBundle(MyEffect(0)), - EffectBundle(MyEffect(1)), - EffectBundle((EffectMode::Insert, MyEffect(2))), - EffectBundle((EffectMode::Insert, MyEffect(3))), + Effect(MyEffect(0)), + Effect(MyEffect(1)), + Effect((EffectMode::Insert, MyEffect(2))), + Effect((EffectMode::Insert, MyEffect(3))), )), )); From b5140fe8ef725831960dba4945567f0dc3e30c3b Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 12 Mar 2026 19:19:35 -0700 Subject: [PATCH 4/9] Update documentation. --- README.md | 16 ++++++---------- src/command.rs | 2 +- src/spawnable_list.rs | 14 ++++---------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 3d88008..11dc650 100644 --- a/README.md +++ b/README.md @@ -9,24 +9,20 @@ An experimental, status effects-as-entities system for Bevy. ### Applying Effects Effects can be applied using `with_effect` or `with_effects` (similar to `with_child` and `with_children` respectively). ```rust ignore -commands.entity(target).with_effect(EffectBundle { - name: Name::new("Effect"), - bundle: MyEffect, - ..default() -}); +commands.entity(target).with_effect((Name::new("Effect"), MyEffect)); ``` They can also be added using spawn-style syntax. ```rust ignore commands.spawn(( Name::new("Target"), - EffectedBy::spawn(EffectBundle { - name: Name::new("Effect"), - bundle: MyEffect, - ..default() - }), + EffectedBy::spawn( + Effect((Name::new("Effect"), MyEffect)) + ), )); ``` +Note that these methods *might* spawn a new entity, depending on what effects are already applied to the target. + ### Effect Modes For some effects it makes sense to allow stacking, so a single entity could be effected by an effect multiple times. Other effects should only be applied once, either replacing or merging with the previous one. diff --git a/src/command.rs b/src/command.rs index cd73c32..777a7e0 100644 --- a/src/command.rs +++ b/src/command.rs @@ -177,7 +177,7 @@ pub trait EffectCommandsExt { #[doc = include_str!("../docs/with_effect_example.md")] fn with_effect(&mut self, bundle: B) -> &mut Self; - /// Applies effects to this entity by taking a function that operates on a [`EffectSpawner`]. + /// Applies effects to this entity by taking a function that operates on an [`EffectSpawner`]. /// /// For applying a single effect, see [`with_effect`](Self::with_effect). /// diff --git a/src/spawnable_list.rs b/src/spawnable_list.rs index b3ae230..c46d204 100644 --- a/src/spawnable_list.rs +++ b/src/spawnable_list.rs @@ -3,17 +3,11 @@ use bevy_ecs::prelude::*; use bevy_ecs::ptr::MovingPtr; use bevy_ecs::spawn::SpawnableList; -/// A "bundle" of components/settings used when applying an effect. -/// Due to technical limitations, this doesn't actually implement [`Bundle`]. -/// Instead, purpose build commands ([`with_effect`](crate::command::EffectCommandsExt::with_effect)) -/// or related spawners ([`EffectedBy::spawn`](SpawnRelated::spawn)) should be used. +/// A wrapper over a [`Bundle`] indicating that an effect should be applied with that [`Bundle`]. +/// This is intended to be used in [`EffectedBy::spawn`](SpawnRelated::spawn). /// -/// # Examples -/// ### [`with_effect`](crate::command::EffectCommandsExt::with_effect) -#[doc = include_str!("../docs/with_effect_example.md")] -/// ### [`with_effects`](crate::command::EffectCommandsExt::with_effects) + [`EffectSpawner`](crate::command::EffectSpawner) -#[doc = include_str!("../docs/with_effects_example.md")] -/// ### [`EffectedBy::spawn`](SpawnRelated::spawn) +/// This *might* spawn a new entity, depending on what effects are already applied to the target. +/// # Example #[doc = include_str!("../docs/effected_by_spawn_example.md")] #[derive(Default)] pub struct Effect(pub B); From fd63eca3aa58c26c20349fdec35bf0e664e60a17 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 12 Mar 2026 19:31:34 -0700 Subject: [PATCH 5/9] Add margin to examples' UI. --- examples/immediate_stats/decaying_speed.rs | 8 +++++++- examples/immediate_stats/decaying_speed_auto_plugin.rs | 8 +++++++- examples/poison.rs | 8 +++++++- examples/poison_falloff.rs | 8 +++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/examples/immediate_stats/decaying_speed.rs b/examples/immediate_stats/decaying_speed.rs index 80a5402..2c4594d 100644 --- a/examples/immediate_stats/decaying_speed.rs +++ b/examples/immediate_stats/decaying_speed.rs @@ -34,7 +34,13 @@ struct DecayingSpeed { /// Spawn a target on startup. fn init_scene(mut commands: Commands) { commands.spawn((Name::new("Target"), MovementSpeed(Stat::new(100)))); - commands.spawn(Text::default()); + commands.spawn(( + Node { + margin: UiRect::all(Val::Px(10.0)), + ..default() + }, + Text::default(), + )); commands.spawn(Camera2d); } diff --git a/examples/immediate_stats/decaying_speed_auto_plugin.rs b/examples/immediate_stats/decaying_speed_auto_plugin.rs index 5fec8dc..215d056 100644 --- a/examples/immediate_stats/decaying_speed_auto_plugin.rs +++ b/examples/immediate_stats/decaying_speed_auto_plugin.rs @@ -37,7 +37,13 @@ struct DecayingSpeed { #[auto_system(plugin = DecayingSpeedPlugin, schedule = Startup)] fn init_scene(mut commands: Commands) { commands.spawn((Name::new("Target"), MovementSpeed(Stat::new(100)))); - commands.spawn(Text::default()); + commands.spawn(( + Node { + margin: UiRect::all(Val::Px(10.0)), + ..default() + }, + Text::default(), + )); commands.spawn(Camera2d); } diff --git a/examples/poison.rs b/examples/poison.rs index 1276699..9199922 100644 --- a/examples/poison.rs +++ b/examples/poison.rs @@ -28,7 +28,13 @@ struct Poison { /// Spawn a target on startup. fn init_scene(mut commands: Commands) { commands.spawn((Name::new("Target"), Health(100))); - commands.spawn(Text::default()); + commands.spawn(( + Node { + margin: UiRect::all(Val::Px(10.0)), + ..default() + }, + Text::default(), + )); commands.spawn(Camera2d); } diff --git a/examples/poison_falloff.rs b/examples/poison_falloff.rs index f374a14..ee6dfa8 100644 --- a/examples/poison_falloff.rs +++ b/examples/poison_falloff.rs @@ -34,7 +34,13 @@ struct Poison { /// Spawn a target on startup. fn init_scene(mut commands: Commands) { commands.spawn((Name::new("Target"), Health(500))); - commands.spawn(Text::default()); + commands.spawn(( + Node { + margin: UiRect::all(Val::Px(10.0)), + ..default() + }, + Text::default(), + )); commands.spawn(Camera2d); } From 68c481ecb599c5dbfa1cb516d710d11bd3f2ec99 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 12 Mar 2026 21:17:18 -0700 Subject: [PATCH 6/9] Improve insert mode performance. --- src/bundle_inspector.rs | 5 ++++- src/command.rs | 9 +++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bundle_inspector.rs b/src/bundle_inspector.rs index c96dbf8..49f2eaf 100644 --- a/src/bundle_inspector.rs +++ b/src/bundle_inspector.rs @@ -1,5 +1,6 @@ use crate::EffectMode; use bevy_ecs::prelude::{Bundle, Entity, Name, Resource, World}; +use bevy_ecs::relationship::RelationshipHookMode; #[derive(Resource)] pub(crate) struct BundleInspector { @@ -21,7 +22,9 @@ impl Default for BundleInspector { impl BundleInspector { pub fn get_effect_meta(&mut self, bundle: B) -> (Option, EffectMode) { let e = self.scratch_entity; - self.world.entity_mut(e).insert(bundle); + self.world + .entity_mut(e) + .insert_with_relationship_hook_mode(bundle, RelationshipHookMode::Skip); let name = self.world.entity(e).get::().cloned(); diff --git a/src/command.rs b/src/command.rs index 777a7e0..f94a1b3 100644 --- a/src/command.rs +++ b/src/command.rs @@ -96,10 +96,7 @@ impl Command for AddEffectCommand { return; } - let Some(effected_by) = world - .get::(self.target) - .map(|e| e.collection().clone()) - else { + let Some(effected_by) = world.get::(self.target).map(|e| e.collection()) else { world.spawn(self.bundle_full()); return; }; @@ -117,7 +114,7 @@ impl Command for AddEffectCommand { let other_name = world.get::(*entity); - if name == other_name.cloned() { + if name.as_ref() == other_name { return Some(*entity); } @@ -132,7 +129,7 @@ impl Command for AddEffectCommand { match mode { EffectMode::Stack => unreachable!(), EffectMode::Insert => { - world.entity_mut(old_entity).insert(self.bundle_full()); + world.entity_mut(old_entity).insert(self.bundle); } EffectMode::Merge => self.merge(world, old_entity), } From 7dac5f20b4495e8fb5f90171af0a037ec3bd370c Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Thu, 12 Mar 2026 21:17:18 -0700 Subject: [PATCH 7/9] Add tests to `BundleInspector`. --- src/bundle_inspector.rs | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/bundle_inspector.rs b/src/bundle_inspector.rs index 49f2eaf..035e18b 100644 --- a/src/bundle_inspector.rs +++ b/src/bundle_inspector.rs @@ -40,3 +40,67 @@ impl BundleInspector { (name, mode) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Effecting; + + #[test] + fn get_effect_meta() { + let mut inspector = BundleInspector::default(); + + let name = Name::new("Effect"); + let mode = EffectMode::Insert; + + assert_eq!( + inspector.get_effect_meta((name.clone(), mode)), + (Some(name), mode) + ); + } + + #[test] + fn get_effect_meta_no_name() { + let mut inspector = BundleInspector::default(); + + let mode = EffectMode::Insert; + + assert_eq!(inspector.get_effect_meta(mode), (None, mode)); + } + + #[test] + fn get_effect_meta_no_mode() { + let mut inspector = BundleInspector::default(); + + let name = Name::new("Effect"); + + assert_eq!( + inspector.get_effect_meta(name.clone()), + (Some(name), EffectMode::default()) + ); + } + + #[test] + fn get_effect_meta_nothing() { + let mut inspector = BundleInspector::default(); + + assert_eq!(inspector.get_effect_meta(()), (None, EffectMode::default())); + } + + #[test] + fn get_effect_mode_with_relation() { + let mut inspector = BundleInspector::default(); + + let name = Name::new("Effect"); + let mode = EffectMode::Insert; + + assert_eq!( + inspector.get_effect_meta(( + name.clone(), + mode, + Effecting(Entity::from_raw_u32(32).unwrap()) + )), + (Some(name), mode) + ); + } +} From 42d690f94e17454a0b370ca5c4a955f36099911a Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Fri, 6 Feb 2026 19:57:05 -0800 Subject: [PATCH 8/9] Update to Auto Plugin V0.10, Immediate Stats V0.5. --- Cargo.lock | 63 +++++-------------- Cargo.toml | 4 +- .../decaying_speed_auto_plugin.rs | 4 +- 3 files changed, 18 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 859b5b0..714800d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -544,9 +544,9 @@ dependencies = [ [[package]] name = "bevy_auto_plugin" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00355e2dd268a7f9c7453773753edfedda475896c0b4e7b2d015aa04f4849fd" +checksum = "f3c7761957a70588bf0b7c6120c97a47620dab684a622a0aa0a8c5c9161becd6" dependencies = [ "bevy_auto_plugin_proc_macros", "bevy_auto_plugin_shared", @@ -556,9 +556,9 @@ dependencies = [ [[package]] name = "bevy_auto_plugin_proc_macros" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85f2558b3a8f3d5e3e8df9debbff0665cad6c7c2bfde3537e7ff247fde3cd8f" +checksum = "582998ec7135267343cdcfc441128b73bbc995be640e48e046e2a33f3ff5703f" dependencies = [ "bevy_auto_plugin_shared", "proc-macro2", @@ -566,12 +566,12 @@ dependencies = [ [[package]] name = "bevy_auto_plugin_shared" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d825af42f08348a7c2307f50aabde362e774dd906c274556bd6b71794c8476" +checksum = "999eb3756d075ac95e169f71333ac211e54e961ac55708a89731eb6134f38b7e" dependencies = [ "bevy_app", - "darling 0.21.3", + "darling", "inventory", "linkme", "log", @@ -2264,18 +2264,8 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "darling_core", + "darling_macro", ] [[package]] @@ -2292,37 +2282,13 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.111", -] - [[package]] name = "darling_macro" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", + "darling_core", "quote", "syn 2.0.111", ] @@ -3014,9 +2980,9 @@ dependencies = [ [[package]] name = "immediate_stats" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8619adda62694b9bf085e6134215a9439279a6cb4b305ea008f89cedf9828a6" +checksum = "d2e9bfbd15d2ae09094aa16193e6f603b5f721d9ebaa6918fa112389c44b59c4" dependencies = [ "bevy_app", "bevy_auto_plugin", @@ -3027,11 +2993,10 @@ dependencies = [ [[package]] name = "immediate_stats_macros" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf0252f42204daee69054e2f4d8b102a99f9c1065cbc9e9826a0dfaef226aa22" +checksum = "94770b216cb1e5a378d3501e63314242e754c1b2c776fa1d9645eaec03fce0b0" dependencies = [ - "darling 0.23.0", "proc-macro-error", "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 058c8ac..fb013a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,9 +27,9 @@ bevy_log = { version = "0.18", default-features = false } [dev-dependencies] bevy = "0.18" -bevy_auto_plugin = { version = "0.9" } +bevy_auto_plugin = { version = "0.10.0" } criterion = { version = "0.8" } -immediate_stats = { version = "0.4", features = ["bevy_auto_plugin"] } +immediate_stats = { version = "0.5.0", features = ["bevy_auto_plugin"] } [lints.rust] missing_docs = "warn" diff --git a/examples/immediate_stats/decaying_speed_auto_plugin.rs b/examples/immediate_stats/decaying_speed_auto_plugin.rs index 215d056..f99e81e 100644 --- a/examples/immediate_stats/decaying_speed_auto_plugin.rs +++ b/examples/immediate_stats/decaying_speed_auto_plugin.rs @@ -8,7 +8,7 @@ use bevy::prelude::*; use bevy_alchemy::*; -use bevy_auto_plugin::prelude::{AutoPlugin, auto_component, auto_system}; +use bevy_auto_plugin::prelude::{AutoPlugin, auto_plugin_build_hook, auto_system}; use immediate_stats::*; fn main() { @@ -24,7 +24,7 @@ struct DecayingSpeedPlugin; /// Tracks an entities current movement speed. #[derive(Component, StatContainer)] -#[auto_component(plugin = DecayingSpeedPlugin)] +#[auto_plugin_build_hook(plugin = DecayingSpeedPlugin, hook = ResetComponentHook)] struct MovementSpeed(Stat); /// Applies a speed boost, which decreases throughout its duration. From 1882f1fedadd294fb8bd057f6b0788bb19803334 Mon Sep 17 00:00:00 2001 From: AlephCubed Date: Wed, 1 Apr 2026 22:01:05 -0700 Subject: [PATCH 9/9] Update version number. --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 714800d..4f06ed6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -359,7 +359,7 @@ dependencies = [ [[package]] name = "bevy_alchemy" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bevy", "bevy_app", diff --git a/Cargo.toml b/Cargo.toml index fb013a3..c6546ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_alchemy" -version = "0.3.0" +version = "0.4.0" edition = "2024" description = "An experimental, status effects-as-entities system for Bevy." categories = ["game-development"] diff --git a/README.md b/README.md index 11dc650..a86bd2e 100644 --- a/README.md +++ b/README.md @@ -74,5 +74,5 @@ A handful of components are included that are intended to make it easier to crea | Bevy | Bevy Alchemy | |--------|---------------| -| `0.18` | `0.2` - `0.3` | +| `0.18` | `0.2` - `0.4` | | `0.17` | `0.1` | \ No newline at end of file