From 82a6a2b30d889668e15caf3f2b52fa62c22953dc Mon Sep 17 00:00:00 2001 From: M1thieu Date: Wed, 14 May 2025 21:01:24 +0200 Subject: [PATCH 01/20] Reduce Actions Code Length All remains the same as before just way shorter The only "changes" were: - Using matches! macro instead of long match statements (same behavior) - Removing verbose comments - Condensing whitespace --- crates/systems/ai/src/core/actions.rs | 326 +++++++------------------- 1 file changed, 82 insertions(+), 244 deletions(-) diff --git a/crates/systems/ai/src/core/actions.rs b/crates/systems/ai/src/core/actions.rs index d3dd33a..c5d346e 100644 --- a/crates/systems/ai/src/core/actions.rs +++ b/crates/systems/ai/src/core/actions.rs @@ -1,47 +1,28 @@ //! Defines Action-related functionality. This module includes the //! ActionBuilder trait and some Composite Actions for utility. use std::sync::Arc; - use bevy::prelude::*; - use crate::core::thinkers::{Action, ActionSpan, Actor}; /// The current state for an Action. These states are changed by a combination /// of the Thinker that spawned it, and the actual Action system executing the /// Action itself. -/// -/// Action system implementors should be mindful of taking appropriate action -/// on all of these states, and be particularly careful when ignoring -/// variants. #[derive(Debug, Clone, Component, Eq, PartialEq, Reflect)] #[component(storage = "SparseSet")] pub enum ActionState { /// Initial state. No action should be performed. Init, - - /// Action requested. The Action-handling system should start executing - /// this Action ASAP and change the status to the next state. + /// Action requested. The Action-handling system should start executing this Action ASAP. Requested, - - /// The action has ongoing execution. The associated Thinker will try to - /// keep executing this Action as-is until it changes state or it gets - /// Cancelled. + /// The action has ongoing execution. Executing, - - /// An ongoing Action has been cancelled. The Thinker might set this - /// action for you, so for Actions that execute for longer than a single - /// tick, **you must check whether the Cancelled state was set** and - /// change do either Success or Failure. Thinkers will wait on Cancelled - /// actions to do any necessary cleanup work, so this can hang your AI if - /// you don't look for it. + /// An ongoing Action has been cancelled. **You must check whether the Cancelled state was set** + /// and change to either Success or Failure. Thinkers will wait on Cancelled actions to do + /// cleanup work, so this can hang your AI if you don't look for it. Cancelled, - - /// The Action was a success. This is used by Composite Actions to - /// determine whether to continue execution. + /// The Action was a success. Success, - - /// The Action failed. This is used by Composite Actions to determine - /// whether to halt execution. + /// The Action failed. Failure, } @@ -70,63 +51,19 @@ impl ActionBuilderWrapper { } /// Trait that must be defined by types in order to be `ActionBuilder`s. -/// `ActionBuilder`s' job is to spawn new `Action` entities on demand. In -/// general, most of this is already done for you, and the only method you -/// really have to implement is `.build()`. -/// -/// The `build()` method MUST be implemented for any `ActionBuilder`s you want -/// to define. +/// The `build()` method MUST be implemented for any `ActionBuilder`s you want to define. #[reflect_trait] pub trait ActionBuilder: std::fmt::Debug + Send + Sync { /// MUST insert your concrete Action component into the Scorer [`Entity`], - /// using `cmd`. You _may_ use `actor`, but it's perfectly normal to just - /// ignore it. - /// - /// In most cases, your `ActionBuilder` and `Action` can be the same type. - /// The only requirement is that your struct implements `Debug`, - /// `Component, `Clone`. You can then use the derive macro `ActionBuilder` - /// to turn your struct into a `ActionBuilder` - /// - /// ### Example - /// - /// Using the derive macro (the easy way): - /// - /// ``` - /// # use bevy::prelude::*; - /// # use big_brain::prelude::*; - /// #[derive(Debug, Clone, Component, ActionBuilder)] - /// #[action_label = "MyActionLabel"] // Optional. Defaults to type name. - /// struct MyAction; - /// ``` - /// - /// Implementing it manually: - /// - /// ``` - /// # use bevy::prelude::*; - /// # use big_brain::prelude::*; - /// #[derive(Debug)] - /// struct MyBuilder; - /// #[derive(Debug, Component)] - /// struct MyAction; - /// - /// impl ActionBuilder for MyBuilder { - /// fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) { - /// cmd.entity(action).insert(MyAction); - /// } - /// } - /// ``` + /// using `cmd`. You _may_ use `actor`, but it's perfectly normal to just ignore it. fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity); - /** - * A label to display when logging using the Action's tracing span. - */ fn label(&self) -> Option<&str> { None } } -/// Spawns a new Action Component, using the given ActionBuilder. This is -/// useful when you're doing things like writing composite Actions. +/// Spawns a new Action Component, using the given ActionBuilder. pub fn spawn_action( builder: &T, cmd: &mut Commands, @@ -146,8 +83,7 @@ pub fn spawn_action( action_ent.entity() } -/// [`ActionBuilder`] for the [`Steps`] component. Constructed through -/// `Steps::build()`. +/// [`ActionBuilder`] for the [`Steps`] component. #[derive(Debug, Reflect)] #[reflect(ActionBuilder)] pub struct StepsBuilder { @@ -158,13 +94,11 @@ pub struct StepsBuilder { } impl StepsBuilder { - /// Sets the logging label for the Action pub fn label>(mut self, label: S) -> Self { self.label = Some(label.into()); self } - /// Adds an action step. Order matters. pub fn step(mut self, action_builder: impl ActionBuilder + 'static) -> Self { if let Some(label) = action_builder.label() { self.steps_labels.push(label.into()); @@ -197,31 +131,7 @@ impl ActionBuilder for StepsBuilder { } } -/// Composite Action that executes a series of steps in sequential order, as -/// long as each step results in a `Success`ful [`ActionState`]. -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyNextAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// MyScorer, -/// Steps::build() -/// .step(MyAction) -/// .step(MyNextAction) -/// ) -/// # ; -/// # } -/// ``` +/// Composite Action that executes a series of steps in sequential order. #[derive(Component, Debug, Reflect)] pub struct Steps { #[reflect(ignore)] @@ -232,7 +142,6 @@ pub struct Steps { } impl Steps { - /// Construct a new [`StepsBuilder`] to define the steps to take. pub fn build() -> StepsBuilder { StepsBuilder { steps: Vec::new(), @@ -242,7 +151,7 @@ impl Steps { } } -/// System that takes care of executing any existing [`Steps`] Actions. +/// System that executes [`Steps`] Actions. pub fn steps_system( mut cmd: Commands, mut steps_q: Query<(Entity, &Actor, &mut Steps, &ActionSpan)>, @@ -254,32 +163,21 @@ pub fn steps_system( let current_state = states.get_mut(seq_ent).unwrap().clone(); #[cfg(feature = "trace")] let _guard = _span.span().enter(); + match current_state { Requested => { - // Begin at the beginning #[cfg(feature = "trace")] - trace!( - "Initializing StepsAction and requesting first step: {:?}", - active_ent - ); + trace!("Initializing StepsAction and requesting first step: {:?}", active_ent); *states.get_mut(active_ent).unwrap() = Requested; *states.get_mut(seq_ent).unwrap() = Executing; } Executing => { let mut step_state = states.get_mut(active_ent).unwrap(); match *step_state { - Init => { - // Request it! This... should not really happen? But just in case I'm missing something... :) - *step_state = Requested; - } - Executing | Requested => { - // do nothing. Everything's running as it should. - } - Cancelled => { - // Wait for the step to wrap itself up, and we'll decide what to do at that point. - } + Init => *step_state = Requested, + Executing | Requested => {} + Cancelled => {} Failure => { - // Fail ourselves #[cfg(feature = "trace")] trace!("Step {:?} failed. Failing entire StepsAction.", active_ent); let step_state = step_state.clone(); @@ -290,7 +188,6 @@ pub fn steps_system( } } Success if steps_action.active_step == steps_action.steps.len() - 1 => { - // We're done! Let's just be successful #[cfg(feature = "trace")] trace!("StepsAction completed all steps successfully."); let step_state = step_state.clone(); @@ -303,11 +200,9 @@ pub fn steps_system( Success => { #[cfg(feature = "trace")] trace!("Step succeeded, but there's more steps. Spawning next action."); - // Deactivate current step and go to the next step if let Ok(mut ent) = cmd.get_entity(steps_action.active_ent.entity()) { ent.despawn(); } - steps_action.active_step += 1; let step_builder = steps_action.steps[steps_action.active_step].clone(); let step_ent = spawn_action(step_builder.as_ref(), &mut cmd, *actor); @@ -319,19 +214,16 @@ pub fn steps_system( } } Cancelled => { - // Cancel current action #[cfg(feature = "trace")] trace!("StepsAction has been cancelled. Cancelling current step {:?} before finalizing.", active_ent); let mut step_state = states.get_mut(active_ent).expect("oops"); - if *step_state == Requested || *step_state == Executing || *step_state == Init { + if matches!(*step_state, Requested | Executing | Init) { *step_state = Cancelled; - } else if *step_state == Failure || *step_state == Success { + } else if matches!(*step_state, Failure | Success) { *states.get_mut(seq_ent).unwrap() = step_state.clone(); } } - Init | Success | Failure => { - // Do nothing. - } + Init | Success | Failure => {} } } } @@ -339,14 +231,11 @@ pub fn steps_system( /// Configures what mode the [`Concurrently`] action will run in. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Reflect)] pub enum ConcurrentMode { - /// Reaches success when any of the concurrent actions reaches [`ActionState::Success`]. Race, - /// Reaches success when all of the concurrent actions reach [`ActionState::Success`]. Join, } -/// [`ActionBuilder`] for the [`Concurrently`] component. Constructed through -/// `Concurrently::build()`. +/// [`ActionBuilder`] for the [`Concurrently`] component. #[derive(Debug, Reflect)] pub struct ConcurrentlyBuilder { mode: ConcurrentMode, @@ -357,13 +246,11 @@ pub struct ConcurrentlyBuilder { } impl ConcurrentlyBuilder { - /// Sets the logging label for the Action pub fn label>(mut self, label: S) -> Self { self.label = Some(label.into()); self } - /// Add an action to execute. Order does not matter. pub fn push(mut self, action_builder: impl ActionBuilder + 'static) -> Self { if let Some(label) = action_builder.label() { self.action_labels.push(label.into()); @@ -374,7 +261,6 @@ impl ConcurrentlyBuilder { self } - /// Sets the [`ConcurrentMode`] for this action. pub fn mode(mut self, mode: ConcurrentMode) -> Self { self.mode = mode; self @@ -403,36 +289,7 @@ impl ActionBuilder for ConcurrentlyBuilder { } } -/// Composite Action that executes a number of Actions concurrently. Whether -/// this action succeeds depends on its [`ConcurrentMode`]: -/// -/// * [`ConcurrentMode::Join`] (default) succeeds when **all** of the actions -/// succeed. -/// * [`ConcurrentMode::Race`] succeeds when **any** of the actions succeed. -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyOtherAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// MyScorer, -/// Concurrently::build() -/// .push(MyAction) -/// .push(MyOtherAction) -/// ) -/// # ; -/// # } -/// ``` -/// +/// Composite Action that executes a number of Actions concurrently. #[derive(Component, Debug, Reflect)] pub struct Concurrently { mode: ConcurrentMode, @@ -441,7 +298,6 @@ pub struct Concurrently { } impl Concurrently { - /// Construct a new [`ConcurrentlyBuilder`] to define the actions to take. pub fn build() -> ConcurrentlyBuilder { ConcurrentlyBuilder { actions: Vec::new(), @@ -452,7 +308,7 @@ impl Concurrently { } } -/// System that takes care of executing any existing [`Concurrently`] Actions. +/// System that executes [`Concurrently`] Actions. pub fn concurrent_system( concurrent_q: Query<(Entity, &Concurrently, &ActionSpan)>, mut states_q: Query<&mut ActionState>, @@ -462,14 +318,11 @@ pub fn concurrent_system( let current_state = states_q.get_mut(seq_ent).expect("uh oh").clone(); #[cfg(feature = "trace")] let _guard = _span.span.enter(); + match current_state { Requested => { #[cfg(feature = "trace")] - trace!( - "Initializing Concurrently action with {} children.", - concurrent_action.actions.len() - ); - // Begin at the beginning + trace!("Initializing Concurrently action with {} children.", concurrent_action.actions.len()); let mut current_state = states_q.get_mut(seq_ent).expect("uh oh"); *current_state = Executing; for action in concurrent_action.actions.iter() { @@ -478,90 +331,81 @@ pub fn concurrent_system( *child_state = Requested; } } - Executing => match concurrent_action.mode { - ConcurrentMode::Join => { - let mut all_success = true; - let mut failed_idx = None; - for (idx, action) in concurrent_action.actions.iter().enumerate() { - let child_ent = action.entity(); - let mut child_state = states_q.get_mut(child_ent).expect("uh oh"); - match *child_state { - Failure => { - failed_idx = Some(idx); - all_success = false; - #[cfg(feature = "trace")] - trace!("Join action has failed. Cancelling all other actions that haven't completed yet."); - } - Success => {} - _ => { - all_success = false; - if failed_idx.is_some() { - *child_state = Cancelled; - } - } - } - } - if all_success { - let mut state_var = states_q.get_mut(seq_ent).expect("uh oh"); - *state_var = Success; - } else if let Some(idx) = failed_idx { - for action in concurrent_action.actions.iter().take(idx) { + Executing => { + match concurrent_action.mode { + ConcurrentMode::Join => { + let mut all_success = true; + let mut failed_idx = None; + for (idx, action) in concurrent_action.actions.iter().enumerate() { let child_ent = action.entity(); let mut child_state = states_q.get_mut(child_ent).expect("uh oh"); match *child_state { - Failure | Success => {} + Failure => { + failed_idx = Some(idx); + all_success = false; + #[cfg(feature = "trace")] + trace!("Join action has failed. Cancelling all other actions that haven't completed yet."); + } + Success => {} _ => { - *child_state = Cancelled; + all_success = false; + if failed_idx.is_some() { + *child_state = Cancelled; + } } } } - let mut state_var = states_q.get_mut(seq_ent).expect("uh oh"); - *state_var = Failure; - } - } - ConcurrentMode::Race => { - let mut all_failure = true; - let mut succeed_idx = None; - for (idx, action) in concurrent_action.actions.iter().enumerate() { - let child_ent = action.entity(); - let mut child_state = states_q.get_mut(child_ent).expect("uh oh"); - match *child_state { - Failure => {} - Success => { - succeed_idx = Some(idx); - all_failure = false; - #[cfg(feature = "trace")] - trace!("Race action has succeeded. Cancelling all other actions that haven't completed yet."); - } - _ => { - all_failure = false; - if succeed_idx.is_some() { + if all_success { + *states_q.get_mut(seq_ent).expect("uh oh") = Success; + } else if let Some(idx) = failed_idx { + for action in concurrent_action.actions.iter().take(idx) { + let child_ent = action.entity(); + let mut child_state = states_q.get_mut(child_ent).expect("uh oh"); + if !matches!(*child_state, Failure | Success) { *child_state = Cancelled; } } + *states_q.get_mut(seq_ent).expect("uh oh") = Failure; } } - if all_failure { - let mut state_var = states_q.get_mut(seq_ent).expect("uh oh"); - *state_var = Failure; - } else if let Some(idx) = succeed_idx { - for action in concurrent_action.actions.iter().take(idx) { + ConcurrentMode::Race => { + let mut all_failure = true; + let mut succeed_idx = None; + for (idx, action) in concurrent_action.actions.iter().enumerate() { let child_ent = action.entity(); let mut child_state = states_q.get_mut(child_ent).expect("uh oh"); match *child_state { - Failure | Success => {} + Failure => {} + Success => { + succeed_idx = Some(idx); + all_failure = false; + #[cfg(feature = "trace")] + trace!("Race action has succeeded. Cancelling all other actions that haven't completed yet."); + } _ => { + all_failure = false; + if succeed_idx.is_some() { + *child_state = Cancelled; + } + } + } + } + if all_failure { + *states_q.get_mut(seq_ent).expect("uh oh") = Failure; + } else if let Some(idx) = succeed_idx { + for action in concurrent_action.actions.iter().take(idx) { + let child_ent = action.entity(); + let mut child_state = states_q.get_mut(child_ent).expect("uh oh"); + if !matches!(*child_state, Failure | Success) { *child_state = Cancelled; } } + *states_q.get_mut(seq_ent).expect("uh oh") = Success; } - let mut state_var = states_q.get_mut(seq_ent).expect("uh oh"); - *state_var = Success; } } - }, + } Cancelled => { - // Cancel all actions let mut all_done = true; let mut any_failed = false; let mut any_success = false; @@ -570,12 +414,8 @@ pub fn concurrent_system( let mut child_state = states_q.get_mut(child_ent).expect("uh oh"); match *child_state { Init => {} - Success => { - any_success = true; - } - Failure => { - any_failed = true; - } + Success => any_success = true, + Failure => any_failed = true, _ => { all_done = false; *child_state = Cancelled; @@ -610,9 +450,7 @@ pub fn concurrent_system( } } } - Init | Success | Failure => { - // Do nothing. - } + Init | Success | Failure => {} } } -} +} \ No newline at end of file From 90a414731a81bd3b9a669cf912ba661720747df1 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Wed, 14 May 2025 22:25:05 +0200 Subject: [PATCH 02/20] Reduce Scorers Code Length I try to keep the codebase clearner now yet I'm wondering, especially with how dense the AI system is planned to be in LP if I shouldn't just be importing the whole big-brain library at this point, at least as a base I still plan to modify heavily which is why I copied their main files I will need to decide if big-brain can suit LP on the long run despite its flaws, and also its good designs as well tho --- crates/systems/ai/src/core/scorers.rs | 208 ++------------------------ 1 file changed, 12 insertions(+), 196 deletions(-) diff --git a/crates/systems/ai/src/core/scorers.rs b/crates/systems/ai/src/core/scorers.rs index f378359..99ce4fc 100644 --- a/crates/systems/ai/src/core/scorers.rs +++ b/crates/systems/ai/src/core/scorers.rs @@ -43,13 +43,7 @@ impl Score { } } -/// Trait that must be defined by types in order to be `ScorerBuilder`s. -/// `ScorerBuilder`s' job is to spawn new `Scorer` entities. In general, most -/// of this is already done for you, and the only method you really have to -/// implement is `.build()`. -/// -/// The `build()` method MUST be implemented for any `ScorerBuilder`s you want -/// to define. +/// Trait for building scorer components. Must implement `build()`. #[reflect_trait] pub trait ScorerBuilder: std::fmt::Debug + Sync + Send { /// MUST insert your concrete Scorer component into the Scorer [`Entity`], @@ -91,7 +85,7 @@ pub trait ScorerBuilder: std::fmt::Debug + Sync + Send { /// ``` fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity); - /// A label to display when logging using the Scorer's tracing span. + /// Optional label for logging. fn label(&self) -> Option<&str> { None } @@ -116,8 +110,8 @@ pub fn spawn_scorer( scorer_ent } -/// Scorer that always returns the same, fixed score. Good for combining with -/// things creatively! +/// Scorer that always returns the same, fixed score. + #[derive(Clone, Component, Debug, Reflect)] pub struct FixedScore(pub f32); @@ -161,31 +155,7 @@ impl ScorerBuilder for FixedScorerBuilder { } } -/// Composite Scorer that takes any number of other Scorers and returns the -/// sum of their [`Score`] values if each _individual_ [`Score`] is at or -/// above the configured `threshold`. -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyOtherScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// AllOrNothing::build(0.8) -/// .push(MyScorer) -/// .push(MyOtherScorer), -/// MyAction); -/// # ; -/// # } -/// ``` +/// Composite Scorer: sums scores if all individual scores >= threshold. #[derive(Component, Debug, Reflect)] pub struct AllOrNothing { threshold: f32, @@ -218,7 +188,7 @@ pub fn all_or_nothing_system( { let mut sum = 0.0; for Scorer(child) in children.iter() { - let score = scores.get_mut(*child).expect("where is it?"); + let score = scores.get_mut(*child).expect("score missing"); if score.0 < *threshold { sum = 0.0; break; @@ -226,7 +196,7 @@ pub fn all_or_nothing_system( sum += score.0; } } - let mut score = scores.get_mut(aon_ent).expect("where did it go?"); + let mut score = scores.get_mut(aon_ent).expect("score missing"); score.set(crate::core::evaluators::clamp(sum, 0.0, 1.0)); #[cfg(feature = "trace")] { @@ -257,7 +227,6 @@ impl AllOrNothingBuilder { self } - /// Set a label for this Action. pub fn label(mut self, label: impl AsRef) -> Self { self.label = Some(label.as_ref().into()); self @@ -286,29 +255,7 @@ impl ScorerBuilder for AllOrNothingBuilder { } } -/// Composite Scorer that takes any number of other Scorers and returns the sum of their [`Score`] values if the _total_ summed [`Score`] is at or above the configured `threshold`. -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyOtherScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// SumOfScorers::build(0.8) -/// .push(MyScorer) -/// .push(MyOtherScorer), -/// MyAction) -/// # ; -/// # } -/// ``` +/// Composite Scorer: sums all scores, returns 0 if total < threshold. #[derive(Component, Debug, Reflect)] pub struct SumOfScorers { threshold: f32, @@ -413,37 +360,7 @@ impl ScorerBuilder for SumOfScorersBuilder { } } -/// Composite Scorer that takes any number of other Scorers and returns the -/// product of their [`Score`]. If the resulting score is less than the -/// threshold, it returns 0. -/// -/// The Scorer can also apply a compensation factor based on the number of -/// Scores passed to it. This can be enabled by passing `true` to the -/// `use_compensation` method on the builder. -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyOtherScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// ProductOfScorers::build(0.5) -/// .use_compensation(true) -/// .push(MyScorer) -/// .push(MyOtherScorer), -/// MyAction) -/// # ; -/// # } -/// ``` - +/// Composite Scorer: multiplies all scores with optional compensation factor. #[derive(Component, Debug, Reflect)] pub struct ProductOfScorers { threshold: f32, @@ -573,32 +490,7 @@ impl ScorerBuilder for ProductOfScorersBuilder { } } -/// Composite Scorer that takes any number of other Scorers and returns the -/// single highest value [`Score`] if _any_ [`Score`]s are at or above the -/// configured `threshold`. -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyOtherScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// WinningScorer::build(0.8) -/// .push(MyScorer) -/// .push(MyOtherScorer), -/// MyAction) -/// # ; -/// # } -/// ``` - +/// Composite Scorer: returns highest score if any score >= threshold. #[derive(Component, Debug, Reflect)] pub struct WinningScorer { threshold: f32, @@ -703,34 +595,7 @@ impl ScorerBuilder for WinningScorerBuilder { } } -/// Composite scorer that takes a `ScorerBuilder` and applies an `Evaluator`. -/// Note that unlike other composite scorers, `EvaluatingScorer` only takes -/// one scorer upon building. -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # #[derive(Debug, Clone)] -/// # struct MyEvaluator; -/// # impl Evaluator for MyEvaluator { -/// # fn evaluate(&self, score: f32) -> f32 { -/// # score -/// # } -/// # } -/// # fn main() { -/// Thinker::build() -/// .when( -/// EvaluatingScorer::build(MyScorer, MyEvaluator), -/// MyAction) -/// # ; -/// # } -/// ``` +/// Applies an Evaluator to transform a single scorer's output. #[derive(Component, Debug, Reflect)] #[reflect(from_reflect = false)] pub struct EvaluatingScorer { @@ -812,56 +677,7 @@ impl ScorerBuilder for EvaluatingScorerBuilder { } } -/// Composite Scorer that allows more fine-grained control of how the scores -/// are combined. The default is to apply a weighting -/// -/// ### Example -/// -/// Using the default measure ([`WeightedMeasure`]): -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyOtherScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// MeasuredScorer::build(0.5) -/// .push(MyScorer, 0.9) -/// .push(MyOtherScorer, 0.4), -/// MyAction) -/// # ; -/// # } -/// ``` -/// -/// Customising the measure: -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyScorer; -/// # #[derive(Debug, Clone, Component, ScorerBuilder)] -/// # struct MyOtherScorer; -/// # #[derive(Debug, Clone, Component, ActionBuilder)] -/// # struct MyAction; -/// # fn main() { -/// Thinker::build() -/// .when( -/// MeasuredScorer::build(0.5) -/// .measure(ChebyshevDistance) -/// .push(MyScorer, 0.8) -/// .push(MyOtherScorer, 0.2), -/// MyAction) -/// # ; -/// # } -/// ``` - +/// Composite Scorer: combines scores using customizable Measure (default: weighted). #[derive(Component, Debug, Reflect)] #[reflect(from_reflect = false)] pub struct MeasuredScorer { From 43223c6a2cbcdde222bdaf887ab8e36a07ccfa56 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 18 May 2025 17:48:49 +0200 Subject: [PATCH 03/20] Thinkers Code Length Reduction --- crates/systems/ai/src/core/thinkers.rs | 930 +++++++++++-------------- 1 file changed, 426 insertions(+), 504 deletions(-) diff --git a/crates/systems/ai/src/core/thinkers.rs b/crates/systems/ai/src/core/thinkers.rs index a5dfd9b..1160cc0 100644 --- a/crates/systems/ai/src/core/thinkers.rs +++ b/crates/systems/ai/src/core/thinkers.rs @@ -2,30 +2,28 @@ //! Thinker picks the right Action to run based on the resulting Scores. use std::{ - collections::VecDeque, - sync::Arc, - time::{Duration, Instant}, + collections::VecDeque, + sync::Arc, + time::{Duration, Instant}, }; use bevy::{ - log::{ - tracing::{field, span, Span}, - Level, - }, - prelude::*, + log::{ + tracing::{field, span, Span}, + Level, + }, + prelude::*, }; use crate::core::{ - actions::{self, ActionBuilder, ActionBuilderWrapper, ActionState}, - choices::{Choice, ChoiceBuilder}, - pickers::Picker, - scorers::{Score, ScorerBuilder}, + actions::{self, ActionBuilder, ActionBuilderWrapper, ActionState}, + choices::{Choice, ChoiceBuilder}, + pickers::Picker, + scorers::{Score, ScorerBuilder}, }; /// Wrapper for Actor entities. In terms of Scorers, Thinkers, and Actions, -/// this is the [`Entity`] actually _performing_ the action, rather than the -/// entity a Scorer/Thinker/Action is attached to. Generally, you will use -/// this entity when writing Queries for Action and Scorer systems. +/// this is the Entity actually performing the action. #[derive(Debug, Clone, Component, Copy, Reflect)] pub struct Actor(pub Entity); @@ -33,33 +31,33 @@ pub struct Actor(pub Entity); pub struct Action(pub Entity); impl Action { - pub fn entity(&self) -> Entity { - self.0 - } + pub fn entity(&self) -> Entity { + self.0 + } } #[derive(Debug, Clone, Component)] pub struct ActionSpan { - pub(crate) span: Span, + pub(crate) span: Span, } impl ActionSpan { - pub(crate) fn new(action: Entity, label: Option<&str>) -> Self { - let span = span!( - Level::DEBUG, - "action", - ent = ?action, - label = field::Empty, - ); - if let Some(label) = label { - span.record("label", label); - } - Self { span } - } - - pub fn span(&self) -> &Span { - &self.span - } + pub(crate) fn new(action: Entity, label: Option<&str>) -> Self { + let span = span!( + Level::DEBUG, + "action", + ent = ?action, + label = field::Empty, + ); + if let Some(label) = label { + span.record("label", label); + } + Self { span } + } + + pub fn span(&self) -> &Span { + &self.span + } } #[derive(Debug, Clone, Copy, Reflect)] @@ -67,534 +65,458 @@ pub struct Scorer(pub Entity); #[derive(Debug, Clone, Component)] pub struct ScorerSpan { - pub(crate) span: Span, + pub(crate) span: Span, } impl ScorerSpan { - pub(crate) fn new(scorer: Entity, label: Option<&str>) -> Self { - let span = span!( - Level::DEBUG, - "scorer", - ent = ?scorer, - label = field::Empty, - ); - - if let Some(label) = label { - span.record("label", label); - } - Self { span } - } - - pub fn span(&self) -> &Span { - &self.span - } + pub(crate) fn new(scorer: Entity, label: Option<&str>) -> Self { + let span = span!( + Level::DEBUG, + "scorer", + ent = ?scorer, + label = field::Empty, + ); + + if let Some(label) = label { + span.record("label", label); + } + Self { span } + } + + pub fn span(&self) -> &Span { + &self.span + } } /// The "brains" behind this whole operation. A `Thinker` is what glues /// together `Actions` and `Scorers` and shapes larger, intelligent-seeming /// systems. -/// -/// Note: Thinkers are also Actions, so anywhere you can pass in an Action (or -/// [`ActionBuilder`]), you can pass in a Thinker (or [`ThinkerBuilder`]). -/// -/// ### Example -/// -/// ``` -/// # use bevy::prelude::*; -/// # use big_brain::prelude::*; -/// # #[derive(Component, Debug)] -/// # struct Thirst(f32, f32); -/// # #[derive(Component, Debug)] -/// # struct Hunger(f32, f32); -/// # #[derive(Clone, Component, Debug, ScorerBuilder)] -/// # struct Thirsty; -/// # #[derive(Clone, Component, Debug, ScorerBuilder)] -/// # struct Hungry; -/// # #[derive(Clone, Component, Debug, ActionBuilder)] -/// # struct Drink; -/// # #[derive(Clone, Component, Debug, ActionBuilder)] -/// # struct Eat; -/// # #[derive(Clone, Component, Debug, ActionBuilder)] -/// # struct Meander; -/// pub fn init_entities(mut cmd: Commands) { -/// cmd.spawn(( -/// Thirst(70.0, 2.0), -/// Hunger(50.0, 3.0), -/// Thinker::build() -/// .picker(FirstToScore::new(80.0)) -/// .when(Thirsty, Drink) -/// .when(Hungry, Eat) -/// .otherwise(Meander), -/// )); -/// } -/// ``` #[derive(Component, Debug, Reflect)] #[reflect(from_reflect = false)] pub struct Thinker { - #[reflect(ignore)] - picker: Arc, - #[reflect(ignore)] - otherwise: Option, - #[reflect(ignore)] - choices: Vec, - #[reflect(ignore)] - current_action: Option<(Action, ActionBuilderWrapper)>, - current_action_label: Option>, - #[reflect(ignore)] - span: Span, - #[reflect(ignore)] - scheduled_actions: VecDeque, + #[reflect(ignore)] + picker: Arc, + #[reflect(ignore)] + otherwise: Option, + #[reflect(ignore)] + choices: Vec, + #[reflect(ignore)] + current_action: Option<(Action, ActionBuilderWrapper)>, + current_action_label: Option>, + #[reflect(ignore)] + span: Span, + #[reflect(ignore)] + scheduled_actions: VecDeque, } impl Thinker { - /// Make a new [`ThinkerBuilder`]. This is what you'll actually use to - /// configure Thinker behavior. - pub fn build() -> ThinkerBuilder { - ThinkerBuilder::new() - } - - pub fn schedule_action(&mut self, action: impl ActionBuilder + 'static) { - self.scheduled_actions - .push_back(ActionBuilderWrapper::new(Arc::new(action))); - } + /// Make a new [`ThinkerBuilder`]. + pub fn build() -> ThinkerBuilder { + ThinkerBuilder::new() + } + + pub fn schedule_action(&mut self, action: impl ActionBuilder + 'static) { + self.scheduled_actions + .push_back(ActionBuilderWrapper::new(Arc::new(action))); + } } -/// This is what you actually use to configure Thinker behavior. It's a plain -/// old [`ActionBuilder`], as well. +/// This is what you actually use to configure Thinker behavior. #[derive(Component, Clone, Debug, Default)] pub struct ThinkerBuilder { - picker: Option>, - otherwise: Option, - choices: Vec, - label: Option, + picker: Option>, + otherwise: Option, + choices: Vec, + label: Option, } impl ThinkerBuilder { - pub(crate) fn new() -> Self { - Self { - picker: None, - otherwise: None, - choices: Vec::new(), - label: None, - } - } - - /// Define a [`Picker`](crate::pickers::Picker) for this Thinker. - pub fn picker(mut self, picker: impl Picker + 'static) -> Self { - self.picker = Some(Arc::new(picker)); - self - } - - /// Define an [`ActionBuilder`](crate::actions::ActionBuilder) and - /// [`ScorerBuilder`](crate::scorers::ScorerBuilder) pair. - pub fn when( - mut self, - scorer: impl ScorerBuilder + 'static, - action: impl ActionBuilder + 'static, - ) -> Self { - self.choices - .push(ChoiceBuilder::new(Arc::new(scorer), Arc::new(action))); - self - } - - /// Default `Action` to execute if the `Picker` did not pick any of the - /// given choices. - pub fn otherwise(mut self, otherwise: impl ActionBuilder + 'static) -> Self { - self.otherwise = Some(ActionBuilderWrapper::new(Arc::new(otherwise))); - self - } - - /// * Configures a label to use for the thinker when logging. - pub fn label(mut self, label: impl AsRef) -> Self { - self.label = Some(label.as_ref().to_string()); - self - } + pub(crate) fn new() -> Self { + Self { + picker: None, + otherwise: None, + choices: Vec::new(), + label: None, + } + } + + /// Define a [`Picker`] for this Thinker. + pub fn picker(mut self, picker: impl Picker + 'static) -> Self { + self.picker = Some(Arc::new(picker)); + self + } + + /// Define an [`ActionBuilder`] and [`ScorerBuilder`] pair. + pub fn when( + mut self, + scorer: impl ScorerBuilder + 'static, + action: impl ActionBuilder + 'static, + ) -> Self { + self.choices + .push(ChoiceBuilder::new(Arc::new(scorer), Arc::new(action))); + self + } + + /// Default `Action` to execute if the `Picker` did not pick any choices. + pub fn otherwise(mut self, otherwise: impl ActionBuilder + 'static) -> Self { + self.otherwise = Some(ActionBuilderWrapper::new(Arc::new(otherwise))); + self + } + + /// Configures a label to use for the thinker when logging. + pub fn label(mut self, label: impl AsRef) -> Self { + self.label = Some(label.as_ref().to_string()); + self + } } impl ActionBuilder for ThinkerBuilder { - fn build(&self, cmd: &mut Commands, action_ent: Entity, actor: Entity) { - let span = span!( - Level::DEBUG, - "thinker", - actor = ?actor, - ); - let _guard = span.enter(); - debug!("Spawning Thinker."); - let choices = self - .choices - .iter() - .map(|choice| choice.build(cmd, actor, action_ent)) - .collect(); - std::mem::drop(_guard); - cmd.entity(action_ent) - .insert(Thinker { - // TODO: reasonable default?... - picker: self - .picker - .clone() - .expect("ThinkerBuilder must have a Picker"), - otherwise: self.otherwise.clone(), - choices, - current_action: None, - current_action_label: None, - span, - scheduled_actions: VecDeque::new(), - }) - .insert(Name::new("Thinker")) - .insert(ActionState::Requested); - } - - fn label(&self) -> Option<&str> { - self.label.as_deref() - } + fn build(&self, cmd: &mut Commands, action_ent: Entity, actor: Entity) { + let span = span!( + Level::DEBUG, + "thinker", + actor = ?actor, + ); + let _guard = span.enter(); + debug!("Spawning Thinker."); + let choices = self + .choices + .iter() + .map(|choice| choice.build(cmd, actor, action_ent)) + .collect(); + std::mem::drop(_guard); + cmd.entity(action_ent) + .insert(Thinker { + picker: self + .picker + .clone() + .expect("ThinkerBuilder must have a Picker"), + otherwise: self.otherwise.clone(), + choices, + current_action: None, + current_action_label: None, + span, + scheduled_actions: VecDeque::new(), + }) + .insert(Name::new("Thinker")) + .insert(ActionState::Requested); + } + + fn label(&self) -> Option<&str> { + self.label.as_deref() + } } pub fn thinker_component_attach_system( - mut cmd: Commands, - q: Query<(Entity, &ThinkerBuilder), Without>, + mut cmd: Commands, + q: Query<(Entity, &ThinkerBuilder), Without>, ) { - for (entity, thinker_builder) in q.iter() { - let thinker = actions::spawn_action(thinker_builder, &mut cmd, entity); - cmd.entity(entity).insert(HasThinker(thinker)); - } + for (entity, thinker_builder) in q.iter() { + let thinker = actions::spawn_action(thinker_builder, &mut cmd, entity); + cmd.entity(entity).insert(HasThinker(thinker)); + } } pub fn thinker_component_detach_system( - mut cmd: Commands, - q: Query<(Entity, &HasThinker), Without>, + mut cmd: Commands, + q: Query<(Entity, &HasThinker), Without>, ) { - for (actor, HasThinker(thinker)) in q.iter() { - if let Ok(mut ent) = cmd.get_entity(*thinker) { - ent.despawn(); - } - cmd.entity(actor).remove::(); - } + for (actor, HasThinker(thinker)) in q.iter() { + if let Ok(mut ent) = cmd.get_entity(*thinker) { + ent.despawn(); + } + cmd.entity(actor).remove::(); + } } pub fn actor_gone_cleanup( - mut cmd: Commands, - actors: Query<&ThinkerBuilder>, - q: Query<(Entity, &Actor)>, + mut cmd: Commands, + actors: Query<&ThinkerBuilder>, + q: Query<(Entity, &Actor)>, ) { - for (child, Actor(actor)) in q.iter() { - if actors.get(*actor).is_err() { - // Actor is gone. Let's clean up. - if let Ok(mut ent) = cmd.get_entity(child) { - ent.despawn(); - } - } - } + for (child, Actor(actor)) in q.iter() { + if actors.get(*actor).is_err() { + if let Ok(mut ent) = cmd.get_entity(child) { + ent.despawn(); + } + } + } } #[derive(Component, Debug, Reflect)] pub struct HasThinker(Entity); impl HasThinker { - pub fn entity(&self) -> Entity { - self.0 - } + pub fn entity(&self) -> Entity { + self.0 + } } pub struct ThinkerIterations { - index: usize, - max_duration: Duration, + index: usize, + max_duration: Duration, } impl ThinkerIterations { - pub fn new(max_duration: Duration) -> Self { - Self { - index: 0, - max_duration, - } - } + pub fn new(max_duration: Duration) -> Self { + Self { + index: 0, + max_duration, + } + } } impl Default for ThinkerIterations { - fn default() -> Self { - Self::new(Duration::from_millis(10)) - } + fn default() -> Self { + Self::new(Duration::from_millis(10)) + } } pub fn thinker_system( - mut cmd: Commands, - mut iterations: Local, - mut thinker_q: Query<(Entity, &Actor, &mut Thinker)>, - scores: Query<&Score>, - mut action_states: Query<&mut actions::ActionState>, - action_spans: Query<&ActionSpan>, - scorer_spans: Query<&ScorerSpan>, + mut cmd: Commands, + mut iterations: Local, + mut thinker_q: Query<(Entity, &Actor, &mut Thinker)>, + scores: Query<&Score>, + mut action_states: Query<&mut actions::ActionState>, + action_spans: Query<&ActionSpan>, + scorer_spans: Query<&ScorerSpan>, ) { - let start = Instant::now(); - for (thinker_ent, Actor(actor), mut thinker) in thinker_q.iter_mut().skip(iterations.index) { - iterations.index += 1; - - let thinker_state = action_states - .get_mut(thinker_ent) - .expect("Where is it?") - .clone(); - - let thinker_span = thinker.span.clone(); - let _thinker_span_guard = thinker_span.enter(); - - match thinker_state { - ActionState::Init => { - let mut act_state = action_states.get_mut(thinker_ent).expect("???"); - debug!("Initializing thinker."); - *act_state = ActionState::Requested; - } - ActionState::Requested => { - let mut act_state = action_states.get_mut(thinker_ent).expect("???"); - debug!("Thinker requested. Starting execution."); - *act_state = ActionState::Executing; - } - ActionState::Success | ActionState::Failure => {} - ActionState::Cancelled => { - debug!("Thinker cancelled. Cleaning up."); - if let Some(current) = &mut thinker.current_action { - let action_span = action_spans.get(current.0 .0).expect("Where is it?"); - debug!("Cancelling current action because thinker was cancelled."); - let state = action_states.get_mut(current.0.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.").clone(); - match state { - ActionState::Success | ActionState::Failure => { - debug!("Action already wrapped up on its own. Cleaning up action in Thinker."); - if let Ok(mut ent) = cmd.get_entity(current.0 .0) { - ent.despawn(); - } - thinker.current_action = None; - } - ActionState::Cancelled => { - debug!("Current action already cancelled."); - } - _ => { - let mut state = action_states.get_mut(current.0.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); - debug!( "Action is still executing. Attempting to cancel it before wrapping up Thinker cancellation."); - action_span.span.in_scope(|| { - debug!("Parent thinker was cancelled. Cancelling action."); - }); - *state = ActionState::Cancelled; - } - } - } else { - let mut act_state = action_states.get_mut(thinker_ent).expect("???"); - debug!("No current thinker action. Wrapping up Thinker as Succeeded."); - *act_state = ActionState::Success; - } - } - ActionState::Executing => { - #[cfg(feature = "trace")] - trace!("Thinker is executing. Thinking..."); - if let Some(choice) = thinker.picker.pick(&thinker.choices, &scores) { - // Think about what action we're supposed to be taking. We do this - // every tick, because we might change our mind. - // ...and then execute it (details below). - #[cfg(feature = "trace")] - trace!("Action picked. Executing picked action."); - let action = choice.action.clone(); - let scorer = choice.scorer; - let score = scores.get(choice.scorer.0).expect("Where is it?"); - exec_picked_action( - &mut cmd, - *actor, - &mut thinker, - &action, - &mut action_states, - &action_spans, - Some((&scorer, score)), - &scorer_spans, - true, - ); - } else if should_schedule_action(&mut thinker, &mut action_states) { - debug!("Spawning scheduled action."); - let action = thinker - .scheduled_actions - .pop_front() - .expect("we literally just checked if it was there."); - let new_action = actions::spawn_action(action.1.as_ref(), &mut cmd, *actor); - thinker.current_action = Some((Action(new_action), action.clone())); - thinker.current_action_label = Some(action.1.label().map(|s| s.into())); - } else if let Some(default_action_ent) = &thinker.otherwise { - // Otherwise, let's just execute the default one! (if it's there) - let default_action_ent = default_action_ent.clone(); - exec_picked_action( - &mut cmd, - *actor, - &mut thinker, - &default_action_ent, - &mut action_states, - &action_spans, - None, - &scorer_spans, - false, - ); - } else if let Some((action_ent, _)) = &thinker.current_action { - let action_span = action_spans.get(action_ent.0).expect("Where is it?"); - let _guard = action_span.span.enter(); - let mut curr_action_state = action_states.get_mut(action_ent.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); - let previous_done = matches!( - *curr_action_state, - ActionState::Success | ActionState::Failure - ); - if previous_done { - debug!( - "Action completed and nothing was picked. Despawning action entity.", - ); - // Despawn the action itself. - if let Ok(mut ent) = cmd.get_entity(action_ent.0) { - ent.despawn(); - } - thinker.current_action = None; - } else if *curr_action_state == ActionState::Init { - *curr_action_state = ActionState::Requested; - } - } - } - } - if iterations.index % 500 == 0 && start.elapsed() > iterations.max_duration { - return; - } - } - iterations.index = 0; + let start = Instant::now(); + for (thinker_ent, Actor(actor), mut thinker) in thinker_q.iter_mut().skip(iterations.index) { + iterations.index += 1; + + let thinker_state = action_states + .get_mut(thinker_ent) + .expect("Where is it?") + .clone(); + + let thinker_span = thinker.span.clone(); + let _thinker_span_guard = thinker_span.enter(); + + match thinker_state { + ActionState::Init => { + let mut act_state = action_states.get_mut(thinker_ent).expect("???"); + debug!("Initializing thinker."); + *act_state = ActionState::Requested; + } + ActionState::Requested => { + let mut act_state = action_states.get_mut(thinker_ent).expect("???"); + debug!("Starting execution."); + *act_state = ActionState::Executing; + } + ActionState::Success | ActionState::Failure => {} + ActionState::Cancelled => { + debug!("Cleaning up."); + if let Some(current) = &mut thinker.current_action { + let action_span = action_spans.get(current.0 .0).expect("Where is it?"); + debug!("Cancelling current action."); + let state = action_states.get_mut(current.0.0).expect("Missing current action").clone(); + match state { + ActionState::Success | ActionState::Failure => { + debug!("Action already wrapped up."); + if let Ok(mut ent) = cmd.get_entity(current.0 .0) { + ent.despawn(); + } + thinker.current_action = None; + } + ActionState::Cancelled => { + debug!("Already cancelled."); + } + _ => { + let mut state = action_states.get_mut(current.0.0).expect("Missing action"); + debug!("Action still executing. Cancelling it."); + action_span.span.in_scope(|| { + debug!("Cancelling action."); + }); + *state = ActionState::Cancelled; + } + } + } else { + let mut act_state = action_states.get_mut(thinker_ent).expect("???"); + debug!("No current action. Completing as Success."); + *act_state = ActionState::Success; + } + } + ActionState::Executing => { + #[cfg(feature = "trace")] + trace!("Thinker is executing. Thinking..."); + if let Some(choice) = thinker.picker.pick(&thinker.choices, &scores) { + #[cfg(feature = "trace")] + trace!("Action picked. Executing picked action."); + let action = choice.action.clone(); + let scorer = choice.scorer; + let score = scores.get(choice.scorer.0).expect("Where is it?"); + exec_picked_action( + &mut cmd, + *actor, + &mut thinker, + &action, + &mut action_states, + &action_spans, + Some((&scorer, score)), + &scorer_spans, + true, + ); + } else if should_schedule_action(&mut thinker, &mut action_states) { + debug!("Spawning scheduled action."); + let action = thinker + .scheduled_actions + .pop_front() + .expect("we literally just checked if it was there."); + let new_action = actions::spawn_action(action.1.as_ref(), &mut cmd, *actor); + thinker.current_action = Some((Action(new_action), action.clone())); + thinker.current_action_label = Some(action.1.label().map(|s| s.into())); + } else if let Some(default_action_ent) = &thinker.otherwise { + let default_action_ent = default_action_ent.clone(); + exec_picked_action( + &mut cmd, + *actor, + &mut thinker, + &default_action_ent, + &mut action_states, + &action_spans, + None, + &scorer_spans, + false, + ); + } else if let Some((action_ent, _)) = &thinker.current_action { + let action_span = action_spans.get(action_ent.0).expect("Where is it?"); + let _guard = action_span.span.enter(); + let mut curr_action_state = action_states.get_mut(action_ent.0).expect("Missing current action"); + let previous_done = matches!( + *curr_action_state, + ActionState::Success | ActionState::Failure + ); + if previous_done { + debug!("Action completed. Despawning."); + if let Ok(mut ent) = cmd.get_entity(action_ent.0) { + ent.despawn(); + } + thinker.current_action = None; + } else if *curr_action_state == ActionState::Init { + *curr_action_state = ActionState::Requested; + } + } + } + } + if iterations.index % 500 == 0 && start.elapsed() > iterations.max_duration { + return; + } + } + iterations.index = 0; } fn should_schedule_action( - thinker: &mut Mut, - states: &mut Query<&mut ActionState>, + thinker: &mut Mut, + states: &mut Query<&mut ActionState>, ) -> bool { - #[cfg(feature = "trace")] - let thinker_span = thinker.span.clone(); - #[cfg(feature = "trace")] - let _thinker_span_guard = thinker_span.enter(); - if thinker.scheduled_actions.is_empty() { - #[cfg(feature = "trace")] - trace!("No scheduled actions. Not scheduling anything."); - false - } else if let Some((action_ent, _)) = &mut thinker.current_action { - let curr_action_state = states.get_mut(action_ent.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); - - let action_done = matches!( - *curr_action_state, - ActionState::Success | ActionState::Failure - ); - - #[cfg(feature = "trace")] - if action_done { - trace!("Current action is already done. Can schedule."); - } else { - trace!("Current action is still executing. Not scheduling anything."); - } - - action_done - } else { - #[cfg(feature = "trace")] - trace!("No current action actions. Can schedule."); - true - } + #[cfg(feature = "trace")] + let thinker_span = thinker.span.clone(); + #[cfg(feature = "trace")] + let _thinker_span_guard = thinker_span.enter(); + if thinker.scheduled_actions.is_empty() { + #[cfg(feature = "trace")] + trace!("No scheduled actions. Not scheduling anything."); + false + } else if let Some((action_ent, _)) = &mut thinker.current_action { + let curr_action_state = states.get_mut(action_ent.0).expect("Missing current action"); + + let action_done = matches!( + *curr_action_state, + ActionState::Success | ActionState::Failure + ); + + #[cfg(feature = "trace")] + if action_done { + trace!("Current action is already done. Can schedule."); + } else { + trace!("Current action is still executing. Not scheduling anything."); + } + + action_done + } else { + #[cfg(feature = "trace")] + trace!("No current action actions. Can schedule."); + true + } } #[allow(clippy::too_many_arguments)] fn exec_picked_action( - cmd: &mut Commands, - actor: Entity, - thinker: &mut Mut, - picked_action: &ActionBuilderWrapper, - states: &mut Query<&mut ActionState>, - action_spans: &Query<&ActionSpan>, - scorer_info: Option<(&Scorer, &Score)>, - scorer_spans: &Query<&ScorerSpan>, - override_current: bool, + cmd: &mut Commands, + actor: Entity, + thinker: &mut Mut, + picked_action: &ActionBuilderWrapper, + states: &mut Query<&mut ActionState>, + action_spans: &Query<&ActionSpan>, + scorer_info: Option<(&Scorer, &Score)>, + scorer_spans: &Query<&ScorerSpan>, + override_current: bool, ) { - // If we do find one, then we need to grab the corresponding - // component for it. The "action" that `picker.pick()` returns - // is just a newtype for an Entity. - // - - // Now we check the current action. We need to check if we picked the same one as the previous tick. - // - // TODO: I don't know where the right place to put this is - // (maybe not in this logic), but we do need some kind of - // oscillation protection so we're not just bouncing back and - // forth between the same couple of actions. - let thinker_span = thinker.span.clone(); - let _thinker_span_guard = thinker_span.enter(); - if let Some((action_ent, ActionBuilderWrapper(current_id, _))) = &mut thinker.current_action { - let mut curr_action_state = states.get_mut(action_ent.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); - let previous_done = matches!( - *curr_action_state, - ActionState::Success | ActionState::Failure - ); - let action_span = action_spans.get(action_ent.0).expect("Where is it?"); - let _guard = action_span.span.enter(); - if (!Arc::ptr_eq(current_id, &picked_action.0) && override_current) || previous_done { - // So we've picked a different action than we were - // currently executing. Just like before, we grab the - // actual Action component (and we assume it exists). - // If the action is executing, or was requested, we - // need to cancel it to make sure it stops. - if !previous_done { - if override_current { - #[cfg(feature = "trace")] - trace!("Falling back to `otherwise` clause.",); - } else { - #[cfg(feature = "trace")] - trace!("Picked a different action than the current one.",); - } - } - match *curr_action_state { - ActionState::Executing | ActionState::Requested => { - debug!("Previous action is still executing. Requesting action cancellation.",); - *curr_action_state = ActionState::Cancelled; - } - ActionState::Init | ActionState::Success | ActionState::Failure => { - debug!("Previous action already completed. Despawning action entity.",); - // Despawn the action itself. - if let Ok(mut ent) = cmd.get_entity(action_ent.0) { - ent.despawn(); - } - if let Some((Scorer(ent), score)) = scorer_info { - let scorer_span = scorer_spans.get(*ent).expect("Where is it?"); - let _guard = scorer_span.span.enter(); - debug!("Winning scorer chosen with score {}", score.get()); - } - std::mem::drop(_guard); - debug!("Spawning next action"); - let new_action = - Action(actions::spawn_action(picked_action.1.as_ref(), cmd, actor)); - thinker.current_action = Some((new_action, picked_action.clone())); - thinker.current_action_label = Some(picked_action.1.label().map(|s| s.into())); - } - ActionState::Cancelled => { - #[cfg(feature = "trace")] - trace!( - "Cancellation already requested. Waiting for action to be marked as completed.", - ) - } - }; - } else { - // Otherwise, it turns out we want to keep executing - // the same action. Just in case, we go ahead and set - // it as Requested if for some reason it had finished - // but the Action System hasn't gotten around to - // cleaning it up. - if *curr_action_state == ActionState::Init { - *curr_action_state = ActionState::Requested; - } - #[cfg(feature = "trace")] - trace!("Continuing execution of current action.",) - } - } else { - #[cfg(feature = "trace")] - trace!("Falling back to `otherwise` clause.",); - - // This branch arm is called when there's no - // current_action in the thinker. The logic here is pretty - // straightforward -- we set the action, Request it, and - // that's it. - if let Some((Scorer(ent), score)) = scorer_info { - let scorer_span = scorer_spans.get(*ent).expect("Where is it?"); - let _guard = scorer_span.span.enter(); - debug!("Winning scorer chosen with score {}", score.get()); - } - debug!("No current action. Spawning new action."); - let new_action = actions::spawn_action(picked_action.1.as_ref(), cmd, actor); - thinker.current_action = Some((Action(new_action), picked_action.clone())); - thinker.current_action_label = Some(picked_action.1.label().map(|s| s.into())); - } -} + let thinker_span = thinker.span.clone(); + let _thinker_span_guard = thinker_span.enter(); + if let Some((action_ent, ActionBuilderWrapper(current_id, _))) = &mut thinker.current_action { + let mut curr_action_state = states.get_mut(action_ent.0).expect("Missing current action"); + let previous_done = matches!( + *curr_action_state, + ActionState::Success | ActionState::Failure + ); + let action_span = action_spans.get(action_ent.0).expect("Where is it?"); + let _guard = action_span.span.enter(); + if (!Arc::ptr_eq(current_id, &picked_action.0) && override_current) || previous_done { + if !previous_done { + if override_current { + #[cfg(feature = "trace")] + trace!("Falling back to `otherwise` clause.",); + } else { + #[cfg(feature = "trace")] + trace!("Picked a different action than the current one.",); + } + } + match *curr_action_state { + ActionState::Executing | ActionState::Requested => { + debug!("Requesting cancellation."); + *curr_action_state = ActionState::Cancelled; + } + ActionState::Init | ActionState::Success | ActionState::Failure => { + debug!("Previous action completed. Despawning."); + if let Ok(mut ent) = cmd.get_entity(action_ent.0) { + ent.despawn(); + } + if let Some((Scorer(ent), score)) = scorer_info { + let scorer_span = scorer_spans.get(*ent).expect("Where is it?"); + let _guard = scorer_span.span.enter(); + debug!("Winning score: {}", score.get()); + } + std::mem::drop(_guard); + debug!("Spawning next action"); + let new_action = + Action(actions::spawn_action(picked_action.1.as_ref(), cmd, actor)); + thinker.current_action = Some((new_action, picked_action.clone())); + thinker.current_action_label = Some(picked_action.1.label().map(|s| s.into())); + } + ActionState::Cancelled => { + #[cfg(feature = "trace")] + trace!("Cancellation already requested. Waiting."); + } + }; + } else { + if *curr_action_state == ActionState::Init { + *curr_action_state = ActionState::Requested; + } + #[cfg(feature = "trace")] + trace!("Continuing execution of current action.",) + } + } else { + #[cfg(feature = "trace")] + trace!("Falling back to `otherwise` clause.",); + + if let Some((Scorer(ent), score)) = scorer_info { + let scorer_span = scorer_spans.get(*ent).expect("Where is it?"); + let _guard = scorer_span.span.enter(); + debug!("Winning score: {}", score.get()); + } + debug!("No current action. Spawning new."); + let new_action = actions::spawn_action(picked_action.1.as_ref(), cmd, actor); + thinker.current_action = Some((Action(new_action), picked_action.clone())); + thinker.current_action_label = Some(picked_action.1.label().map(|s| s.into())); + } +} \ No newline at end of file From 27491363816aff51b43de18eff999605e4c8860a Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 18 May 2025 22:21:15 +0200 Subject: [PATCH 04/20] Update ci.yaml --- .github/workflows/ci.yaml | 68 +++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fcad9ff..b197bf2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,24 +16,48 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: + - name: Free disk space + run: | + df -h + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/local/share/boost + sudo apt clean + df -h - name: Checkout sources uses: actions/checkout@v4 - - name: Cache + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + - name: Cache cargo registry uses: actions/cache@v4 with: path: | - ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.toml') }} - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}- + ${{ runner.os }}-cargo- + - name: Cache build output dependencies + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }}- + ${{ runner.os }}-build- - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev - name: Run cargo test run: cargo test + - name: Clean cargo cache + run: | + cargo clean -p "LP" || true + rm -rf target/debug/.fingerprint/LP-* + rm -rf target/debug/deps/LP-* + rm -rf target/debug/incremental/LP-* # Run cargo clippy -- -D warnings clippy_check: @@ -41,22 +65,40 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: + - name: Free disk space + run: | + df -h + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/local/share/boost + sudo apt clean + df -h - name: Checkout sources uses: actions/checkout@v4 - - name: Cache + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Cache cargo registry uses: actions/cache@v4 with: path: | - ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.toml') }} - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}- + ${{ runner.os }}-cargo- + - name: Cache build output dependencies + uses: actions/cache@v4 with: - components: clippy + path: target + key: ${{ runner.os }}-build-clippy-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-build-clippy-${{ hashFiles('**/Cargo.toml') }}- + ${{ runner.os }}-build-clippy- - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev - name: Run clippy From 8f3394dff65c45e7c1312fb3e45934749c3ff1a6 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 18 May 2025 22:31:05 +0200 Subject: [PATCH 05/20] Fast Compile Configuration --- .cargo/config.toml | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..1f7df66 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,43 @@ +# Optimized build configuration for LP +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = [ + "-Clink-arg=-fuse-ld=lld", + "-Zshare-generics=y", +] + +[target.aarch64-unknown-linux-gnu] +linker = "clang" +rustflags = [ + "-Clink-arg=-fuse-ld=lld", + "-Zshare-generics=y", +] + +[target.x86_64-apple-darwin] +rustflags = [ + "-Zshare-generics=y", +] + +[target.aarch64-apple-darwin] +rustflags = [ + "-Zshare-generics=y", +] + +[target.x86_64-pc-windows-msvc] +linker = "rust-lld.exe" +rustflags = [ + "-Ctarget-feature=+crt-static", + "-Zshare-generics=n", +] + +# Optimize debug builds +[profile.dev] +debug = 1 +opt-level = 1 +incremental = true + +[profile.dev.package."*"] +opt-level = 3 + +[build] +rustc-wrapper = "sccache" \ No newline at end of file From 1860aa8b7ed419cc382f56ea701dc9576b2bf38b Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 18 May 2025 22:37:37 +0200 Subject: [PATCH 06/20] Cache Fix --- .cargo/config.toml | 26 +++++----------------- .github/workflows/ci.yaml | 45 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 1f7df66..1b1a61f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,34 +1,21 @@ # Optimized build configuration for LP [target.x86_64-unknown-linux-gnu] linker = "clang" -rustflags = [ - "-Clink-arg=-fuse-ld=lld", - "-Zshare-generics=y", -] +rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] [target.aarch64-unknown-linux-gnu] linker = "clang" -rustflags = [ - "-Clink-arg=-fuse-ld=lld", - "-Zshare-generics=y", -] +rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] [target.x86_64-apple-darwin] -rustflags = [ - "-Zshare-generics=y", -] +rustflags = ["-Zshare-generics=y"] [target.aarch64-apple-darwin] -rustflags = [ - "-Zshare-generics=y", -] +rustflags = ["-Zshare-generics=y"] [target.x86_64-pc-windows-msvc] linker = "rust-lld.exe" -rustflags = [ - "-Ctarget-feature=+crt-static", - "-Zshare-generics=n", -] +rustflags = ["-Ctarget-feature=+crt-static", "-Zshare-generics=n"] # Optimize debug builds [profile.dev] @@ -38,6 +25,3 @@ incremental = true [profile.dev.package."*"] opt-level = 3 - -[build] -rustc-wrapper = "sccache" \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b197bf2..4aae83c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,9 +8,11 @@ on: env: CARGO_TERM_COLOR: always + SCCACHE_VERSION: 0.4.2 + RUSTC_WRAPPER: sccache + SCCACHE_CACHE_SIZE: 2G jobs: - # Run cargo test test: name: Test Suite runs-on: ubuntu-latest @@ -27,8 +29,21 @@ jobs: df -h - name: Checkout sources uses: actions/checkout@v4 + - name: Install sccache + run: | + SCCACHE_FILE=sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl + curl -L "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${SCCACHE_FILE}.tar.gz" | tar xz + sudo mv ${SCCACHE_FILE}/sccache /usr/local/bin/sccache + sudo chmod +x /usr/local/bin/sccache - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable + - name: Cache sccache + uses: actions/cache@v4 + with: + path: ~/.cache/sccache + key: ${{ runner.os }}-sccache-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-sccache- - name: Cache cargo registry uses: actions/cache@v4 with: @@ -50,8 +65,14 @@ jobs: ${{ runner.os }}-build- - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + - name: Start sccache server + run: sccache --start-server - name: Run cargo test run: cargo test + - name: Show sccache stats + run: sccache --show-stats + - name: Stop sccache server + run: sccache --stop-server || true - name: Clean cargo cache run: | cargo clean -p "LP" || true @@ -59,7 +80,6 @@ jobs: rm -rf target/debug/deps/LP-* rm -rf target/debug/incremental/LP-* - # Run cargo clippy -- -D warnings clippy_check: name: Clippy runs-on: ubuntu-latest @@ -76,10 +96,24 @@ jobs: df -h - name: Checkout sources uses: actions/checkout@v4 + - name: Install sccache + run: | + SCCACHE_FILE=sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl + curl -L "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${SCCACHE_FILE}.tar.gz" | tar xz + sudo mv ${SCCACHE_FILE}/sccache /usr/local/bin/sccache + sudo chmod +x /usr/local/bin/sccache - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable with: components: clippy + - name: Cache sccache + uses: actions/cache@v4 + with: + path: ~/.cache/sccache + key: ${{ runner.os }}-sccache-clippy-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-sccache-clippy- + ${{ runner.os }}-sccache- - name: Cache cargo registry uses: actions/cache@v4 with: @@ -101,10 +135,15 @@ jobs: ${{ runner.os }}-build-clippy- - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + - name: Start sccache server + run: sccache --start-server - name: Run clippy run: cargo clippy -- -A clippy::upper-case-acronyms -A clippy::new-without-default -A clippy::manual-flatten -A clippy::excessive-precision -A clippy::too-many-arguments + - name: Show sccache stats + run: sccache --show-stats + - name: Stop sccache server + run: sccache --stop-server || true - # Run cargo fmt with check flag but allow failures format: name: Format runs-on: ubuntu-latest From dce2e185de1d9f7f41325a83eb7184be1739db02 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 18 May 2025 22:42:43 +0200 Subject: [PATCH 07/20] Another try --- .cargo/config.toml | 39 +++++++++++++++---------------- .github/workflows/ci.yaml | 48 ++++++++++----------------------------- 2 files changed, 32 insertions(+), 55 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 1b1a61f..16ee5c4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,27 +1,28 @@ -# Optimized build configuration for LP +[build] +codegen-units = 16 + +[profile.dev] +split-debuginfo = "unpacked" +opt-level = 1 +debug = 1 +incremental = true + +[profile.dev.package."*"] +opt-level = 3 +codegen-units = 1 + +[profile.dev.package.bevy] +opt-level = 3 +codegen-units = 1 + [target.x86_64-unknown-linux-gnu] linker = "clang" -rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] +rustflags = ["-Clink-arg=-fuse-ld=lld", "-Ctarget-cpu=native"] [target.aarch64-unknown-linux-gnu] linker = "clang" -rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] - -[target.x86_64-apple-darwin] -rustflags = ["-Zshare-generics=y"] - -[target.aarch64-apple-darwin] -rustflags = ["-Zshare-generics=y"] +rustflags = ["-Clink-arg=-fuse-ld=lld", "-Ctarget-cpu=native"] [target.x86_64-pc-windows-msvc] linker = "rust-lld.exe" -rustflags = ["-Ctarget-feature=+crt-static", "-Zshare-generics=n"] - -# Optimize debug builds -[profile.dev] -debug = 1 -opt-level = 1 -incremental = true - -[profile.dev.package."*"] -opt-level = 3 +rustflags = ["-Ctarget-feature=+crt-static"] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4aae83c..24bf44d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,6 +11,7 @@ env: SCCACHE_VERSION: 0.4.2 RUSTC_WRAPPER: sccache SCCACHE_CACHE_SIZE: 2G + CARGO_INCREMENTAL: 1 jobs: test: @@ -21,10 +22,7 @@ jobs: - name: Free disk space run: | df -h - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/local/share/boost + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost sudo apt clean df -h - name: Checkout sources @@ -42,8 +40,7 @@ jobs: with: path: ~/.cache/sccache key: ${{ runner.os }}-sccache-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-sccache- + restore-keys: ${{ runner.os }}-sccache- - name: Cache cargo registry uses: actions/cache@v4 with: @@ -51,20 +48,14 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}- - ${{ runner.os }}-cargo- - - name: Cache build output dependencies + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} + - name: Cache build output uses: actions/cache@v4 with: path: target - key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }}- - ${{ runner.os }}-build- + key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }} - name: Install Dependencies - run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev clang lld - name: Start sccache server run: sccache --start-server - name: Run cargo test @@ -73,12 +64,6 @@ jobs: run: sccache --show-stats - name: Stop sccache server run: sccache --stop-server || true - - name: Clean cargo cache - run: | - cargo clean -p "LP" || true - rm -rf target/debug/.fingerprint/LP-* - rm -rf target/debug/deps/LP-* - rm -rf target/debug/incremental/LP-* clippy_check: name: Clippy @@ -88,10 +73,7 @@ jobs: - name: Free disk space run: | df -h - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/local/share/boost + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost sudo apt clean df -h - name: Checkout sources @@ -121,20 +103,14 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}- - ${{ runner.os }}-cargo- - - name: Cache build output dependencies + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} + - name: Cache build output uses: actions/cache@v4 with: path: target - key: ${{ runner.os }}-build-clippy-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-build-clippy-${{ hashFiles('**/Cargo.toml') }}- - ${{ runner.os }}-build-clippy- + key: ${{ runner.os }}-clippy-${{ hashFiles('**/Cargo.toml') }} - name: Install Dependencies - run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev clang lld - name: Start sccache server run: sccache --start-server - name: Run clippy From ea6f499eff450ed49560c88ac8a5470d81d6e821 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 18 May 2025 23:07:12 +0200 Subject: [PATCH 08/20] final update to CI hopefully --- .github/workflows/ci.yaml | 77 +++++++++------------------------------ 1 file changed, 17 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 24bf44d..43b05c3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,11 +6,12 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always - SCCACHE_VERSION: 0.4.2 - RUSTC_WRAPPER: sccache - SCCACHE_CACHE_SIZE: 2G CARGO_INCREMENTAL: 1 jobs: @@ -27,43 +28,22 @@ jobs: df -h - name: Checkout sources uses: actions/checkout@v4 - - name: Install sccache - run: | - SCCACHE_FILE=sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl - curl -L "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${SCCACHE_FILE}.tar.gz" | tar xz - sudo mv ${SCCACHE_FILE}/sccache /usr/local/bin/sccache - sudo chmod +x /usr/local/bin/sccache - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable - - name: Cache sccache - uses: actions/cache@v4 - with: - path: ~/.cache/sccache - key: ${{ runner.os }}-sccache-${{ hashFiles('**/Cargo.lock') }} - restore-keys: ${{ runner.os }}-sccache- - - name: Cache cargo registry + - name: Cache uses: actions/cache@v4 with: path: | + ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} - - name: Cache build output - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }} + target/ + key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.toml') }} + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev clang lld - - name: Start sccache server - run: sccache --start-server - name: Run cargo test run: cargo test - - name: Show sccache stats - run: sccache --show-stats - - name: Stop sccache server - run: sccache --stop-server || true clippy_check: name: Clippy @@ -78,47 +58,24 @@ jobs: df -h - name: Checkout sources uses: actions/checkout@v4 - - name: Install sccache - run: | - SCCACHE_FILE=sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl - curl -L "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/${SCCACHE_FILE}.tar.gz" | tar xz - sudo mv ${SCCACHE_FILE}/sccache /usr/local/bin/sccache - sudo chmod +x /usr/local/bin/sccache - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - name: Cache sccache - uses: actions/cache@v4 - with: - path: ~/.cache/sccache - key: ${{ runner.os }}-sccache-clippy-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-sccache-clippy- - ${{ runner.os }}-sccache- - - name: Cache cargo registry + - name: Cache uses: actions/cache@v4 with: path: | + ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }} - - name: Cache build output - uses: actions/cache@v4 + target/ + key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.toml') }} + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable with: - path: target - key: ${{ runner.os }}-clippy-${{ hashFiles('**/Cargo.toml') }} + components: clippy - name: Install Dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev clang lld - - name: Start sccache server - run: sccache --start-server - name: Run clippy run: cargo clippy -- -A clippy::upper-case-acronyms -A clippy::new-without-default -A clippy::manual-flatten -A clippy::excessive-precision -A clippy::too-many-arguments - - name: Show sccache stats - run: sccache --show-stats - - name: Stop sccache server - run: sccache --stop-server || true format: name: Format From c33d3b0cf133d48e3b3989e8ba6ebabb33fb3882 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Tue, 20 May 2025 01:59:50 +0200 Subject: [PATCH 09/20] Implement plugin architecture for energy crate Establishes a modular plugin structure for energy physics: - Add `EnergyPlugin` to combine all domain plugins - Create domain-specific plugins for thermodynamics, electromagnetism, waves And that's about it I'll see to refine later I'm starting to get bored of architecture a little and plan to get soon physics & such in place --- .../src/electromagnetism/interactions.rs | 4 ++-- crates/energy/src/electromagnetism/mod.rs | 15 ++++++++++++ crates/energy/src/lib.rs | 17 +++++++++++++ crates/energy/src/thermodynamics/entropy.rs | 4 ++-- .../energy/src/thermodynamics/equilibrium.rs | 8 ++++--- crates/energy/src/thermodynamics/mod.rs | 24 ++++++++++++++++--- crates/energy/src/waves/mod.rs | 20 ++++++++++++++++ crates/energy/src/waves/propagation.rs | 6 ++--- crates/energy/src/waves/wave_equation.rs | 4 ++-- 9 files changed, 87 insertions(+), 15 deletions(-) diff --git a/crates/energy/src/electromagnetism/interactions.rs b/crates/energy/src/electromagnetism/interactions.rs index e6e6449..1667a7a 100644 --- a/crates/energy/src/electromagnetism/interactions.rs +++ b/crates/energy/src/electromagnetism/interactions.rs @@ -6,7 +6,7 @@ use bevy::prelude::*; const C: f32 = 299_792_458.0; /// Represents an electromagnetic wave component -#[derive(Debug, Component)] +#[derive(Debug, Component, Reflect)] pub struct ElectromagneticWave { /// Wave frequency in Hertz pub frequency: f32, @@ -74,7 +74,7 @@ impl ElectromagneticWave { } /// Material electromagnetic properties component -#[derive(Debug, Clone, Copy, Component)] +#[derive(Debug, Clone, Copy, Component, Reflect)] pub struct MaterialProperties { /// Electric permittivity pub permittivity: f32, diff --git a/crates/energy/src/electromagnetism/mod.rs b/crates/energy/src/electromagnetism/mod.rs index 5a198d9..1b049d0 100644 --- a/crates/energy/src/electromagnetism/mod.rs +++ b/crates/energy/src/electromagnetism/mod.rs @@ -1,6 +1,21 @@ pub mod fields; pub mod interactions; +use bevy::prelude::*; + +pub struct ElectromagnetismPlugin; + +impl Plugin for ElectromagnetismPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .add_event::() + .add_systems(Update, fields::calculate_field_interactions); + } +} + /// The electromagnetism prelude. /// /// This includes the most common types for electromagnetic systems. diff --git a/crates/energy/src/lib.rs b/crates/energy/src/lib.rs index 1d45886..eddda76 100644 --- a/crates/energy/src/lib.rs +++ b/crates/energy/src/lib.rs @@ -5,6 +5,11 @@ pub mod thermodynamics; pub mod electromagnetism; pub mod waves; +pub use thermodynamics::ThermodynamicsPlugin; +pub use conservation::EnergyConservationPlugin; +pub use electromagnetism::ElectromagnetismPlugin; +pub use waves::WavesPlugin; + // Add these new energy system related definitions #[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum EnergyType { @@ -69,6 +74,18 @@ pub trait EnergySystem { } } +pub struct EnergyPlugin; + +impl Plugin for EnergyPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .add_plugins(EnergyConservationPlugin) + .add_plugins(ThermodynamicsPlugin) + .add_plugins(ElectromagnetismPlugin) + .add_plugins(WavesPlugin); + } +} + /// Root energy prelude that re-exports all important items pub mod prelude { // Add the new trait and types to the prelude diff --git a/crates/energy/src/thermodynamics/entropy.rs b/crates/energy/src/thermodynamics/entropy.rs index 1314e5c..ad49e5e 100644 --- a/crates/energy/src/thermodynamics/entropy.rs +++ b/crates/energy/src/thermodynamics/entropy.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; /// Entropy component for thermodynamic systems -#[derive(Component, Debug, Clone, Copy)] +#[derive(Component, Debug, Clone, Copy, Reflect)] pub struct Entropy { /// Entropy in J/K pub value: f32, @@ -16,7 +16,7 @@ impl Entropy { } /// Process reversibility characteristic -#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum Reversibility { Reversible, Irreversible, diff --git a/crates/energy/src/thermodynamics/equilibrium.rs b/crates/energy/src/thermodynamics/equilibrium.rs index e0fd21a..42f9c34 100644 --- a/crates/energy/src/thermodynamics/equilibrium.rs +++ b/crates/energy/src/thermodynamics/equilibrium.rs @@ -1,13 +1,15 @@ use bevy::prelude::*; /// Component marking systems in thermal equilibrium -#[derive(Component, Debug)] +#[derive(Component, Debug, Reflect)] pub struct ThermalEquilibrium { pub connected_entities: Vec, } -/// Component for phase state of matter that will use the matter crate later once implemented -#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] +/// Component for phase state of matter that will use the matter crate later once implemented, soon once PBMPM will be in place +/// and the matter crate is implemented +/// This is a placeholder for the actual phase state representation +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum PhaseState { Solid, Liquid, diff --git a/crates/energy/src/thermodynamics/mod.rs b/crates/energy/src/thermodynamics/mod.rs index f23ebfe..86d429c 100644 --- a/crates/energy/src/thermodynamics/mod.rs +++ b/crates/energy/src/thermodynamics/mod.rs @@ -2,8 +2,26 @@ pub mod entropy; pub mod equilibrium; pub mod thermal; +use bevy::prelude::*; + +pub struct ThermodynamicsPlugin; + +impl Plugin for ThermodynamicsPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .add_event::() + .add_systems(Update, thermal::calculate_thermal_transfer); + } +} + pub mod prelude { - pub use super::thermal::{Temperature, ThermalConductivity, ThermalDiffusivity, thermal_utils::heat_conduction}; - pub use super::entropy::{Entropy, Reversibility, entropy_change_heat_transfer, entropy_change_irreversible, is_valid_process, total_entropy_change}; - pub use super::equilibrium::{ThermalEquilibrium, PhaseState, ThermalProperties, is_in_equilibrium, equilibrium_time_estimate}; + pub use super::thermal::{Temperature, ThermalConductivity, ThermalDiffusivity, thermal_utils::heat_conduction}; + pub use super::entropy::{Entropy, Reversibility, entropy_change_heat_transfer, entropy_change_irreversible, is_valid_process, total_entropy_change}; + pub use super::equilibrium::{ThermalEquilibrium, PhaseState, ThermalProperties, is_in_equilibrium, equilibrium_time_estimate}; } \ No newline at end of file diff --git a/crates/energy/src/waves/mod.rs b/crates/energy/src/waves/mod.rs index 8078979..2eceb40 100644 --- a/crates/energy/src/waves/mod.rs +++ b/crates/energy/src/waves/mod.rs @@ -3,6 +3,26 @@ pub mod propagation; pub mod superposition; pub mod wave_equation; +use bevy::prelude::*; + +pub struct WavesPlugin; + +impl Plugin for WavesPlugin { + fn build(&self, app: &mut App) { + // Register wave components + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .add_event::() + .add_systems(Update, propagation::update_wave_displacements) + .add_systems(Update, superposition::update_standing_waves) + .add_systems(Update, wave_equation::update_wave_equation); + } +} + /// The waves prelude. /// /// This includes the most common types for wave systems. diff --git a/crates/energy/src/waves/propagation.rs b/crates/energy/src/waves/propagation.rs index 48ebd9a..6b51b64 100644 --- a/crates/energy/src/waves/propagation.rs +++ b/crates/energy/src/waves/propagation.rs @@ -18,7 +18,7 @@ pub fn dispersive_angular_frequency(params: &WaveParameters, k: f32) -> f32 { } /// Component to store position for wave calculations -#[derive(Component, Debug, Clone)] +#[derive(Component, Debug, Clone, Reflect)] pub struct WavePosition(pub Vec2); impl WavePosition { @@ -36,7 +36,7 @@ impl WavePosition { } /// Wave type marker component -#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum WaveType { Traveling, Radial, @@ -44,7 +44,7 @@ pub enum WaveType { } // Marker component for wave centers (for radial waves) -#[derive(Component)] +#[derive(Component, Reflect)] pub struct WaveCenterMarker; #[inline] diff --git a/crates/energy/src/waves/wave_equation.rs b/crates/energy/src/waves/wave_equation.rs index 3517c78..a776d3b 100644 --- a/crates/energy/src/waves/wave_equation.rs +++ b/crates/energy/src/waves/wave_equation.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; /// 2D Wave equation solver (∂²u/∂t² = c²(∂²u/∂x² + ∂²u/∂y²)) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflect)] pub struct WaveEquation2D { /// Grid dimensions pub nx: usize, @@ -131,7 +131,7 @@ impl WaveEquation2D { } /// Component wrapper for the wave equation -#[derive(Component)] +#[derive(Component, Reflect)] pub struct WaveEquationComponent { pub solver: WaveEquation2D, } From da97eb4eb1b107c502274116390fdca96a620c80 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Fri, 23 May 2025 16:18:20 +0200 Subject: [PATCH 10/20] Referral Screen Referrals and trying to reduce potential firebase costs as well! --- crates/energy/src/lib.rs | 56 ++++++++++++++----------------- crates/forces/src/core/gravity.rs | 48 ++------------------------ 2 files changed, 28 insertions(+), 76 deletions(-) diff --git a/crates/energy/src/lib.rs b/crates/energy/src/lib.rs index eddda76..7d05a26 100644 --- a/crates/energy/src/lib.rs +++ b/crates/energy/src/lib.rs @@ -1,16 +1,15 @@ use bevy::prelude::*; pub mod conservation; -pub mod thermodynamics; pub mod electromagnetism; +pub mod thermodynamics; pub mod waves; -pub use thermodynamics::ThermodynamicsPlugin; pub use conservation::EnergyConservationPlugin; pub use electromagnetism::ElectromagnetismPlugin; +pub use thermodynamics::ThermodynamicsPlugin; pub use waves::WavesPlugin; -// Add these new energy system related definitions #[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum EnergyType { Generic, @@ -33,33 +32,37 @@ pub enum EnergyTransferError { /// Core trait for all energy-based systems in the simulation /// This complements the existing EnergyQuantity component pub trait EnergySystem { - // Core energy tracking + // Core energy tracking fn total_energy(&self) -> f32; - + // Energy transfer with entropy consideration fn transfer_energy(&mut self, energy: f32) -> Result { // Default implementation could track basic conservation Ok(energy) } - + // Transformation efficiency fn transformation_efficiency(&self) -> f32 { 1.0 // Default full efficiency } - + // Entropy generation during energy transfer fn entropy_generation(&self, _energy_transfer: f32) -> f32 { 0.0 // Default no entropy generation } - + // Energy type for this system fn energy_type(&self) -> EnergyType { EnergyType::Generic } - + // Create an EnergyTransaction for the ledger (optional) - fn create_transaction(&self, amount: f32, source: Option, - destination: Option) -> conservation::EnergyTransaction { + fn create_transaction( + &self, + amount: f32, + source: Option, + destination: Option, + ) -> conservation::EnergyTransaction { conservation::EnergyTransaction { transaction_type: if amount > 0.0 { conservation::TransactionType::Input @@ -79,32 +82,23 @@ pub struct EnergyPlugin; impl Plugin for EnergyPlugin { fn build(&self, app: &mut App) { app.register_type::() - .add_plugins(EnergyConservationPlugin) - .add_plugins(ThermodynamicsPlugin) - .add_plugins(ElectromagnetismPlugin) - .add_plugins(WavesPlugin); + .add_plugins(EnergyConservationPlugin) + .add_plugins(ThermodynamicsPlugin) + .add_plugins(ElectromagnetismPlugin) + .add_plugins(WavesPlugin); } } -/// Root energy prelude that re-exports all important items pub mod prelude { - // Add the new trait and types to the prelude pub use super::{EnergySystem, EnergyTransferError, EnergyType}; - - // Re-export from conservation + pub use crate::conservation::{ - EnergyQuantity, - EnergyTransferEvent, - EnergyAccountingLedger, - TransactionType, - EnergyConservationTracker, - verify_conservation, - conversion_efficiency, - EnergyConservationPlugin + conversion_efficiency, verify_conservation, EnergyAccountingLedger, + EnergyConservationPlugin, EnergyConservationTracker, EnergyQuantity, EnergyTransferEvent, + TransactionType, }; - - // Re-export from submodules + + pub use crate::electromagnetism::prelude::*; pub use crate::thermodynamics::prelude::*; pub use crate::waves::prelude::*; - pub use crate::electromagnetism::prelude::*; -} \ No newline at end of file +} diff --git a/crates/forces/src/core/gravity.rs b/crates/forces/src/core/gravity.rs index d47ebea..3ad876c 100644 --- a/crates/forces/src/core/gravity.rs +++ b/crates/forces/src/core/gravity.rs @@ -1,7 +1,6 @@ use super::newton_laws::{AppliedForce, Mass}; use bevy::prelude::*; -/// A trait for computing the squared norm of a vector efficiently trait Norm { type Output; fn norm_squared(self) -> Self::Output; @@ -18,7 +17,6 @@ impl Norm for Vec3 { /// Modified gravitational constant for simulation scale pub const GRAVITATIONAL_CONSTANT: f32 = 0.1; -/// Calculate softened gravitational acceleration between two bodies #[inline] fn calculate_softened_acceleration(direction: Vec3, mass: f32, softening_squared: f32) -> Vec3 { let distance_squared = direction.norm_squared(); @@ -74,12 +72,11 @@ pub struct MassiveBody; // Spatial partitioning structures for Barnes-Hut algorithm mod spatial { - use bevy::prelude::*; //TODO: Redundant and may need to make this a clearer section instead + use bevy::prelude::*; const MAX_DEPTH: usize = 8; const MAX_BODIES_PER_NODE: usize = 8; - /// 2D axis-aligned bounding box #[derive(Clone, Debug)] pub struct AABB { pub center: Vec2, @@ -105,8 +102,6 @@ mod spatial { pub fn get_quadrant_aabb(&self, quadrant: usize) -> AABB { let quarter_size = self.half_size * 0.5; - // x-offset: negative for left (quadrants 0,2), positive for right (quadrants 1,3) - // y-offset: positive for top (quadrants 0,1), negative for bottom (quadrants 2,3) let x_sign = if (quadrant & 1) == 0 { -1.0 } else { 1.0 }; let y_sign = if (quadrant & 2) == 0 { 1.0 } else { -1.0 }; @@ -117,7 +112,6 @@ mod spatial { } } - /// Mass properties for Barnes-Hut approximation #[derive(Clone, Debug)] pub struct MassProperties { pub total_mass: f32, @@ -142,13 +136,12 @@ mod spatial { } } - /// Node in the quadtree #[derive(Debug)] pub struct QuadtreeNode { pub aabb: AABB, pub depth: usize, pub mass_properties: MassProperties, - pub bodies: Vec<(Entity, Vec3, f32)>, // Entity, position, mass + pub bodies: Vec<(Entity, Vec3, f32)>, pub children: [Option>; 4], } @@ -210,7 +203,6 @@ mod spatial { } } - /// Main quadtree structure #[derive(Debug)] pub struct Quadtree { pub root: QuadtreeNode, @@ -228,7 +220,6 @@ mod spatial { return Self::new(AABB::new(Vec2::ZERO, Vec2::new(1000.0, 1000.0))); } - // Find bounds let mut min_x = f32::MAX; let mut min_y = f32::MAX; let mut max_x = f32::MIN; @@ -241,7 +232,6 @@ mod spatial { max_y = max_y.max(pos.y); } - // Add padding and make square let padding = ((max_x - min_x) + (max_y - min_y)) * 0.1; min_x -= padding; min_y -= padding; @@ -267,7 +257,6 @@ mod spatial { } } -/// System to apply uniform gravity forces to entities pub fn apply_uniform_gravity( gravity: Res, mut query: Query<(Entity, &Mass, &mut AppliedForce), With>, @@ -283,7 +272,6 @@ pub fn apply_uniform_gravity( } } -/// System to calculate gravitational attraction between entities pub fn calculate_gravitational_attraction( gravity_params: Res, query: Query<(Entity, &Transform, &Mass), With>, @@ -292,20 +280,16 @@ pub fn calculate_gravitational_attraction( With, >, ) { - // Get softening parameter squared once let softening_squared = gravity_params.softening * gravity_params.softening; - // Collect all gravity sources once let sources: Vec<(Entity, Vec3, f32)> = query .iter() .map(|(e, t, m)| (e, t.translation, m.value)) .collect(); - // Using Bevy's built-in parallelization - processes all affected entities in parallel affected_query.par_iter_mut().for_each(|(affected_entity, affected_transform, affected_mass, mut force)| { let affected_pos = affected_transform.translation; - // Calculate the force from all sources on this affected entity for &(source_entity, source_pos, source_mass) in &sources { if source_entity == affected_entity { continue; @@ -321,7 +305,6 @@ pub fn calculate_gravitational_attraction( }); } -/// System to calculate gravitational attraction using Barnes-Hut algorithm pub fn calculate_barnes_hut_attraction( gravity_params: Res, query: Query<(Entity, &Transform, &Mass), With>, @@ -337,7 +320,6 @@ pub fn calculate_barnes_hut_attraction( return; } - // Create quadtree from gravity sources let bodies: Vec<(Entity, Vec3, f32)> = query .iter() .map(|(e, t, m)| (e, t.translation, m.value)) @@ -345,16 +327,13 @@ pub fn calculate_barnes_hut_attraction( let quadtree = spatial::Quadtree::from_bodies(&bodies); - // Using Bevy's built-in parallelization for the affected bodies affected_query.par_iter_mut().for_each(|(entity, transform, _, mut force)| { let position = transform.translation; - // Skip self-attraction by checking if this entity is in the tree if bodies.iter().any(|&(e, _, _)| e == entity) { - return; // Skip this iteration + return; } - // Calculate force using Barnes-Hut algorithm let force_vector = calculate_barnes_hut_force( position, &quadtree.root, @@ -366,17 +345,14 @@ pub fn calculate_barnes_hut_attraction( }); } -/// Calculate gravitational force using the Barnes-Hut approximation method pub fn calculate_barnes_hut_force( affected_position: Vec3, node: &spatial::QuadtreeNode, theta: f32, softening: f32, ) -> Vec3 { - // Get softening squared once let softening_squared = softening * softening; - // If the node is far enough, use approximation if node.is_far_enough(affected_position, theta) { let direction = node.mass_properties.center_of_mass - affected_position; return calculate_softened_acceleration( @@ -386,7 +362,6 @@ pub fn calculate_barnes_hut_force( ); } - // If leaf node, calculate force from each body if node.children.iter().all(|c| c.is_none()) { let mut total_force = Vec3::ZERO; @@ -404,7 +379,6 @@ pub fn calculate_barnes_hut_force( return total_force; } - // Sum forces from children let mut total_force = Vec3::ZERO; for child in &node.children { if let Some(child_node) = child { @@ -416,12 +390,10 @@ pub fn calculate_barnes_hut_force( total_force } -/// Calculate orbital velocity for circular orbit pub fn calculate_orbital_velocity(central_mass: f32, orbit_radius: f32) -> f32 { (GRAVITATIONAL_CONSTANT * central_mass / orbit_radius).sqrt() } -/// Calculate initial velocity for an elliptical orbit with given eccentricity pub fn calculate_elliptical_orbit_velocity( central_mass: f32, distance: f32, @@ -433,12 +405,10 @@ pub fn calculate_elliptical_orbit_velocity( (mu * (2.0 / distance - 1.0 / semimajor_axis)).sqrt() } -/// Calculate escape velocity from a massive body pub fn calculate_escape_velocity(central_mass: f32, distance: f32) -> f32 { (2.0 * GRAVITATIONAL_CONSTANT * central_mass / distance).sqrt() } -/// Plugin for gravity systems #[derive(Default)] pub struct GravityPlugin { /// Use Barnes-Hut optimization for n-body simulations @@ -448,7 +418,6 @@ pub struct GravityPlugin { } impl GravityPlugin { - /// Create new gravity plugin with default settings pub fn new() -> Self { Self { use_barnes_hut: true, @@ -456,20 +425,17 @@ impl GravityPlugin { } } - /// Configure whether to use Barnes-Hut optimization pub fn with_barnes_hut(mut self, enabled: bool) -> Self { self.use_barnes_hut = enabled; self } - /// Set the Barnes-Hut theta parameter pub fn with_theta(mut self, theta: f32) -> Self { self.barnes_hut_theta = theta.clamp(0.1, 1.0); self } } -/// System set for gravity calculations #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub enum GravitySet { /// Apply uniform gravity forces (like Earth's gravity) @@ -481,30 +447,25 @@ pub enum GravitySet { impl Plugin for GravityPlugin { fn build(&self, app: &mut App) { app - // Register default resources if not present .init_resource::() .init_resource::() - // Configure gravity system sets .configure_sets( Update, (GravitySet::UniformGravity, GravitySet::NBodyGravity).chain() ) - // Add gravity systems .add_systems( Update, apply_uniform_gravity .in_set(GravitySet::UniformGravity) ); - // Add n-body gravity systems with run conditions if self.use_barnes_hut { let theta = self.barnes_hut_theta; app.add_systems( Update, - // Use a closure to pass the theta parameter (move | gravity_params: Res, query: Query<(Entity, &Transform, &Mass), With>, @@ -513,13 +474,11 @@ impl Plugin for GravityPlugin { calculate_barnes_hut_attraction(gravity_params, query, affected_query, theta); }) .in_set(GravitySet::NBodyGravity) - // Use run condition to only use Barnes-Hut for larger simulations .run_if(|query: Query<(Entity, &Transform, &Mass), With>| { query.iter().count() >= 20 }) ); - // Fallback to simple n-body for small simulations app.add_systems( Update, calculate_gravitational_attraction @@ -529,7 +488,6 @@ impl Plugin for GravityPlugin { }) ); } else { - // Always use simple n-body calculations if Barnes-Hut is disabled app.add_systems( Update, calculate_gravitational_attraction From 35e5d23a7f9b1408c5f8f926b5b976fc16b89e74 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 25 May 2025 12:05:22 +0200 Subject: [PATCH 11/20] Little Refactoring --- crates/forces/src/core/gravity.rs | 173 ++++++++++++-------------- crates/forces/src/core/newton_laws.rs | 6 +- crates/systems/ai/src/core/utility.rs | 103 +++++++++------ 3 files changed, 145 insertions(+), 137 deletions(-) diff --git a/crates/forces/src/core/gravity.rs b/crates/forces/src/core/gravity.rs index 3ad876c..79113df 100644 --- a/crates/forces/src/core/gravity.rs +++ b/crates/forces/src/core/gravity.rs @@ -1,32 +1,9 @@ use super::newton_laws::{AppliedForce, Mass}; use bevy::prelude::*; -trait Norm { - type Output; - fn norm_squared(self) -> Self::Output; -} - -impl Norm for Vec3 { - type Output = f32; - #[inline] - fn norm_squared(self) -> f32 { - self.length_squared() - } -} - /// Modified gravitational constant for simulation scale pub const GRAVITATIONAL_CONSTANT: f32 = 0.1; -#[inline] -fn calculate_softened_acceleration(direction: Vec3, mass: f32, softening_squared: f32) -> Vec3 { - let distance_squared = direction.norm_squared(); - // Apply softening to prevent singularities - let softened_distance_squared = distance_squared + softening_squared; - let force_magnitude = GRAVITATIONAL_CONSTANT * mass / softened_distance_squared; - - direction.normalize() * force_magnitude -} - /// Resource for gravity simulation parameters #[derive(Resource, Clone, Debug)] pub struct GravityParams { @@ -55,19 +32,19 @@ impl Default for UniformGravity { } /// Component for entities affected by gravity -#[derive(Component, Debug, Clone, Copy)] +#[derive(Component, Debug, Clone, Copy, Reflect)] pub struct GravityAffected; /// Marker component for gravity field measurement points -#[derive(Component, Debug, Clone, Copy)] +#[derive(Component, Debug, Clone, Copy, Reflect)] pub struct GravityFieldMarker; /// Component for objects that generate gravitational attraction -#[derive(Component, Debug, Clone, Copy)] +#[derive(Component, Debug, Clone, Copy, Reflect)] pub struct GravitySource; /// Marker for bodies with significant mass -#[derive(Component, Debug, Clone, Copy)] +#[derive(Component, Debug, Clone, Copy, Reflect)] pub struct MassiveBody; // Spatial partitioning structures for Barnes-Hut algorithm @@ -169,7 +146,6 @@ mod spatial { } pub fn insert(&mut self, entity: Entity, position: Vec3, mass: f32) { - let pos_2d = Vec2::new(position.x, position.y); self.mass_properties.add_body(position, mass); if self.depth >= MAX_DEPTH @@ -189,14 +165,14 @@ mod spatial { let existing_bodies = std::mem::take(&mut self.bodies); for (e, p, m) in existing_bodies { - let q = self.aabb.get_quadrant(Vec2::new(p.x, p.y)); + let q = self.aabb.get_quadrant(p.truncate()); if let Some(child) = &mut self.children[q] { child.insert(e, p, m); } } } - let quadrant = self.aabb.get_quadrant(pos_2d); + let quadrant = self.aabb.get_quadrant(position.truncate()); if let Some(child) = &mut self.children[quadrant] { child.insert(entity, position, mass); } @@ -287,22 +263,24 @@ pub fn calculate_gravitational_attraction( .map(|(e, t, m)| (e, t.translation, m.value)) .collect(); - affected_query.par_iter_mut().for_each(|(affected_entity, affected_transform, affected_mass, mut force)| { - let affected_pos = affected_transform.translation; - - for &(source_entity, source_pos, source_mass) in &sources { - if source_entity == affected_entity { - continue; + affected_query.par_iter_mut().for_each( + |(affected_entity, affected_transform, affected_mass, mut force)| { + let affected_pos = affected_transform.translation; + + for &(source_entity, source_pos, source_mass) in &sources { + if source_entity == affected_entity { + continue; + } + + let direction = source_pos - affected_pos; + let distance_squared = direction.length_squared(); + let softened_distance_squared = distance_squared + softening_squared; + let force_magnitude = GRAVITATIONAL_CONSTANT * source_mass * affected_mass.value + / softened_distance_squared; + force.force += direction.normalize() * force_magnitude; } - - let direction = source_pos - affected_pos; - force.force += calculate_softened_acceleration( - direction, - source_mass * affected_mass.value, - softening_squared, - ); - } - }); + }, + ); } pub fn calculate_barnes_hut_attraction( @@ -327,22 +305,24 @@ pub fn calculate_barnes_hut_attraction( let quadtree = spatial::Quadtree::from_bodies(&bodies); - affected_query.par_iter_mut().for_each(|(entity, transform, _, mut force)| { - let position = transform.translation; - - if bodies.iter().any(|&(e, _, _)| e == entity) { - return; - } - - let force_vector = calculate_barnes_hut_force( - position, - &quadtree.root, - theta, - gravity_params.softening - ); - - force.force += force_vector; - }); + affected_query + .par_iter_mut() + .for_each(|(entity, transform, _, mut force)| { + let position = transform.translation; + + if bodies.iter().any(|&(e, _, _)| e == entity) { + return; + } + + let force_vector = calculate_barnes_hut_force( + position, + &quadtree.root, + theta, + gravity_params.softening, + ); + + force.force += force_vector; + }); } pub fn calculate_barnes_hut_force( @@ -355,11 +335,11 @@ pub fn calculate_barnes_hut_force( if node.is_far_enough(affected_position, theta) { let direction = node.mass_properties.center_of_mass - affected_position; - return calculate_softened_acceleration( - direction, - node.mass_properties.total_mass, - softening_squared, - ); + let distance_squared = direction.length_squared(); + let softened_distance_squared = distance_squared + softening_squared; + let force_magnitude = + GRAVITATIONAL_CONSTANT * node.mass_properties.total_mass / softened_distance_squared; + return direction.normalize() * force_magnitude; } if node.children.iter().all(|c| c.is_none()) { @@ -367,13 +347,18 @@ pub fn calculate_barnes_hut_force( for &(_, position, mass) in &node.bodies { let direction = position - affected_position; - let distance_squared = direction.norm_squared(); + let distance_squared = direction.length_squared(); if distance_squared < 0.001 { continue; } - total_force += calculate_softened_acceleration(direction, mass, softening_squared); + total_force += { + let distance_squared = direction.length_squared(); + let softened_distance_squared = distance_squared + softening_squared; + let force_magnitude = GRAVITATIONAL_CONSTANT * mass / softened_distance_squared; + direction.normalize() * force_magnitude + }; } return total_force; @@ -424,12 +409,12 @@ impl GravityPlugin { barnes_hut_theta: 0.5, } } - + pub fn with_barnes_hut(mut self, enabled: bool) -> Self { self.use_barnes_hut = enabled; self } - + pub fn with_theta(mut self, theta: f32) -> Self { self.barnes_hut_theta = theta.clamp(0.1, 1.0); self @@ -446,53 +431,53 @@ pub enum GravitySet { impl Plugin for GravityPlugin { fn build(&self, app: &mut App) { - app - .init_resource::() + app.init_resource::() .init_resource::() - .configure_sets( Update, - (GravitySet::UniformGravity, GravitySet::NBodyGravity).chain() + (GravitySet::UniformGravity, GravitySet::NBodyGravity).chain(), ) - .add_systems( Update, - apply_uniform_gravity - .in_set(GravitySet::UniformGravity) + apply_uniform_gravity.in_set(GravitySet::UniformGravity), ); - + if self.use_barnes_hut { let theta = self.barnes_hut_theta; - + app.add_systems( Update, - (move | - gravity_params: Res, - query: Query<(Entity, &Transform, &Mass), With>, - affected_query: Query<(Entity, &Transform, &Mass, &mut AppliedForce), With>, - | { + (move |gravity_params: Res, + query: Query<(Entity, &Transform, &Mass), With>, + affected_query: Query< + (Entity, &Transform, &Mass, &mut AppliedForce), + With, + >| { calculate_barnes_hut_attraction(gravity_params, query, affected_query, theta); }) .in_set(GravitySet::NBodyGravity) - .run_if(|query: Query<(Entity, &Transform, &Mass), With>| { - query.iter().count() >= 20 - }) + .run_if( + |query: Query<(Entity, &Transform, &Mass), With>| { + query.iter().count() >= 20 + }, + ), ); - + app.add_systems( Update, calculate_gravitational_attraction .in_set(GravitySet::NBodyGravity) - .run_if(|query: Query<(Entity, &Transform, &Mass), With>| { - query.iter().count() < 20 - }) + .run_if( + |query: Query<(Entity, &Transform, &Mass), With>| { + query.iter().count() < 20 + }, + ), ); } else { app.add_systems( Update, - calculate_gravitational_attraction - .in_set(GravitySet::NBodyGravity) + calculate_gravitational_attraction.in_set(GravitySet::NBodyGravity), ); } } -} \ No newline at end of file +} diff --git a/crates/forces/src/core/newton_laws.rs b/crates/forces/src/core/newton_laws.rs index 5f0cb89..dd8afed 100644 --- a/crates/forces/src/core/newton_laws.rs +++ b/crates/forces/src/core/newton_laws.rs @@ -36,7 +36,7 @@ impl Norm for Vec2 { impl Distance for Vec2 {} /// Component for mass properties of an entity -#[derive(Component, Debug, Clone, Copy)] +#[derive(Component, Debug, Clone, Copy, Reflect)] pub struct Mass { /// Mass in kilograms pub value: f32, @@ -86,7 +86,7 @@ impl Mass { } /// Component representing a force applied to an entity -#[derive(Component, Debug, Clone)] +#[derive(Component, Debug, Clone, Reflect)] pub struct AppliedForce { /// Force vector in Newtons pub force: Vec3, @@ -128,7 +128,7 @@ impl AppliedForce { } /// Component for velocity (both linear and angular) -#[derive(Component, Debug, Clone, Copy)] +#[derive(Component, Debug, Clone, Copy, Reflect)] pub struct Velocity { /// Linear velocity in meters per second pub linvel: Vec3, diff --git a/crates/systems/ai/src/core/utility.rs b/crates/systems/ai/src/core/utility.rs index 79055da..995aae2 100644 --- a/crates/systems/ai/src/core/utility.rs +++ b/crates/systems/ai/src/core/utility.rs @@ -1,6 +1,6 @@ +use crate::prelude::*; use bevy::prelude::*; use rand::prelude::*; //still plan to use bevy_rand instead -use crate::prelude::*; use std::collections::HashMap; /// Represents a utility score for decision-making @@ -28,7 +28,11 @@ impl UtilityScore { } } else { // If all scores are 0, distribute evenly - let equal_value = if scores.is_empty() { 0.0 } else { 1.0 / scores.len() as f32 }; + let equal_value = if scores.is_empty() { + 0.0 + } else { + 1.0 / scores.len() as f32 + }; for score in scores.iter_mut() { *score = UtilityScore::new(equal_value); } @@ -65,7 +69,10 @@ impl UtilityScore { } /// Perform weighted random selection between multiple options - pub fn weighted_select(options: &[(T, UtilityScore)], rng: &mut R) -> Option { + pub fn weighted_select( + options: &[(T, UtilityScore)], + rng: &mut R, + ) -> Option { let total_weight: f32 = options.iter().map(|(_, score)| score.value()).sum(); if total_weight <= 0.0 || options.is_empty() { return None; @@ -102,22 +109,23 @@ pub fn determine_behavior<'a>( if modules.is_empty() { return (Behavior::Idle, UtilityScore::new(0.0)); } - + // Create normalized version of the scores for better decision making - let mut normalized_scores: Vec = modules.iter().map(|(_, score, _)| *score).collect(); + let mut normalized_scores: Vec = + modules.iter().map(|(_, score, _)| *score).collect(); UtilityScore::normalize_scores(&mut normalized_scores); - + // Find behavior with highest normalized score let mut best_index = 0; let mut best_score = normalized_scores[0]; - + for (i, score) in normalized_scores.iter().enumerate().skip(1) { if score > &best_score { best_score = *score; best_index = i; } } - + // Return the original behavior and original score (not normalized) // This preserves the absolute utility value while making selection based on normalized scores let (_, original_score, behavior) = modules[best_index]; @@ -154,7 +162,7 @@ impl UtilityCache { ttl, } } - + /// Get cached value if not expired pub fn get(&self, key: &str, current_time: f32) -> Option { self.cache.get(key).and_then(|cached| { @@ -165,31 +173,41 @@ impl UtilityCache { } }) } - + /// Store value with timestamp pub fn insert(&mut self, key: String, value: UtilityScore, current_time: f32) { - self.cache.insert(key, CachedValue { value, timestamp: current_time }); + self.cache.insert( + key, + CachedValue { + value, + timestamp: current_time, + }, + ); } - + /// Get value if cached, otherwise calculate and store - pub fn get_or_insert_with(&mut self, key: String, calculator: F, current_time: f32) -> UtilityScore + pub fn get_or_insert_with( + &mut self, + key: String, + calculator: F, + current_time: f32, + ) -> UtilityScore where F: FnOnce() -> UtilityScore, { if let Some(value) = self.get(&key, current_time) { return value; } - + let value = calculator(); self.insert(key, value, current_time); value } - + /// Clean up expired entries pub fn cleanup(&mut self, current_time: f32) { - self.cache.retain(|_, cached| { - current_time - cached.timestamp < self.ttl - }); + self.cache + .retain(|_, cached| current_time - cached.timestamp < self.ttl); } } @@ -210,17 +228,22 @@ impl EntityUtilityCache { } }) } - + /// Store value with timestamp pub fn insert(&mut self, key: String, value: UtilityScore, current_time: f32) { - self.cache.insert(key, CachedValue { value, timestamp: current_time }); + self.cache.insert( + key, + CachedValue { + value, + timestamp: current_time, + }, + ); } - + /// Clean up expired entries pub fn cleanup(&mut self, current_time: f32, ttl: f32) { - self.cache.retain(|_, cached| { - current_time - cached.timestamp < ttl - }); + self.cache + .retain(|_, cached| current_time - cached.timestamp < ttl); } } @@ -230,44 +253,44 @@ pub trait CacheableModule: AIModule { fn cache_key(&self) -> Option { None // Default implementation returns None (no caching) } - + /// Get utility with caching if available fn cached_utility( - &self, + &self, entity_cache: Option<&mut EntityUtilityCache>, - global_cache: Option<&mut UtilityCache>, - current_time: f32 + global_cache: Option<&mut UtilityCache>, + current_time: f32, ) -> UtilityScore { // Try to get a cache key - let Some(key) = self.cache_key() else { + let Some(key) = self.cache_key() else { return self.utility(); // No key means no caching }; - + // Try entity cache first (better locality and parallelism) if let Some(entity_cache) = entity_cache { let ttl = global_cache.as_ref().map(|c| c.ttl).unwrap_or(0.5); - + if let Some(value) = entity_cache.get(&key, current_time, ttl) { return value; } - + // Not in entity cache, calculate and store let value = self.utility(); entity_cache.insert(key.clone(), value, current_time); - + // Also update global cache if available if let Some(global_cache) = global_cache { global_cache.insert(key, value, current_time); } - + return value; } - + // No entity cache, try global cache if let Some(global_cache) = global_cache { return global_cache.get_or_insert_with(key, || self.utility(), current_time); } - + // No caching available, calculate directly self.utility() } @@ -280,10 +303,10 @@ pub fn cleanup_utility_cache_system( mut entity_caches: Query<&mut EntityUtilityCache>, ) { let current_time = time.elapsed_secs_f64() as f32; - + // Clean up global cache global_cache.cleanup(current_time); - + // Clean up entity caches for mut entity_cache in &mut entity_caches { entity_cache.cleanup(current_time, global_cache.ttl); @@ -298,5 +321,5 @@ pub fn get_current_time(time: &Time) -> f32 { /// Simple function to initialize the caching system pub fn setup_utility_caching(app: &mut App, ttl: f32) { app.insert_resource(UtilityCache::new(ttl)) - .add_systems(Last, cleanup_utility_cache_system); -} \ No newline at end of file + .add_systems(Last, cleanup_utility_cache_system); +} From ca54de6b6ce77ac12df926895b8a3671ce0f3f24 Mon Sep 17 00:00:00 2001 From: M1thieu Date: Sun, 25 May 2025 12:18:24 +0200 Subject: [PATCH 12/20] Another refactoring Quick one here gotta go I'll make things better later on --- crates/forces/src/core/newton_laws.rs | 40 ++++++++++----------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/crates/forces/src/core/newton_laws.rs b/crates/forces/src/core/newton_laws.rs index dd8afed..2463c5c 100644 --- a/crates/forces/src/core/newton_laws.rs +++ b/crates/forces/src/core/newton_laws.rs @@ -128,7 +128,7 @@ impl AppliedForce { } /// Component for velocity (both linear and angular) -#[derive(Component, Debug, Clone, Copy, Reflect)] +#[derive(Component, Debug, Clone, Copy, Reflect, Default)] pub struct Velocity { /// Linear velocity in meters per second pub linvel: Vec3, @@ -136,15 +136,6 @@ pub struct Velocity { pub angvel: Vec3, } -impl Default for Velocity { - fn default() -> Self { - Self { - linvel: Vec3::ZERO, - angvel: Vec3::ZERO, - } - } -} - /// System to apply forces according to Newton's Second Law (F = ma) pub fn apply_forces(time: Res