diff --git a/Cargo.toml b/Cargo.toml index c739de37f78ba1f990654be546c9028aeed66ab7..ab4404d012f8339ac971853d40d84da97fd37501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,5 @@ repository = "https://github.com/zkat/big-brain" homepage = "https://github.com/zkat/big-brain" [dependencies] -big-brain-derive = { path = "./big-brain-derive", version = "0.2" } -serde = "1.0.111" -typetag = "0.1.5" -specs-derive = "0.4.1" -ron = "0.6.0" bevy = "0.5.0" diff --git a/big-brain-derive/Cargo.toml b/big-brain-derive/Cargo.toml deleted file mode 100644 index 4a0a22a9203cc103a7e5898b6804f12145b89181..0000000000000000000000000000000000000000 --- a/big-brain-derive/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "big-brain-derive" -version = "0.2.1" -authors = ["Kat Marchán <kzm@zkat.tech>"] -edition = "2018" -keywords = ["utility-ai", "ai", "bevy", "ecs"] -categories = ["game-development"] -description = "Derive macros for the big-brain Utility AI library" -license-file = "../LICENSE.md" -repository = "https://github.com/zkat/big-brain" -homepage = "https://github.com/zkat/big-brain" - -[lib] -proc-macro = true - -[dependencies] -syn = "1.0.33" -quote = "1.0.7" -proc-macro2 = "1.0.18" -darling = "0.10.2" diff --git a/big-brain-derive/src/action.rs b/big-brain-derive/src/action.rs deleted file mode 100644 index e03bd477b246204695efb9c6b775aeeadce86740..0000000000000000000000000000000000000000 --- a/big-brain-derive/src/action.rs +++ /dev/null @@ -1,103 +0,0 @@ -use darling::{ast, FromDeriveInput, FromField, ToTokens}; -use proc_macro2::TokenStream; -use quote::quote; - -#[derive(Debug, FromDeriveInput)] -#[darling(attributes(action))] -pub struct Action { - ident: syn::Ident, - generics: syn::Generics, - data: ast::Data<(), ActionField>, -} - -#[derive(Debug, FromField)] -#[darling(attributes(action))] -struct ActionField { - ident: Option<syn::Ident>, - ty: syn::Type, - #[darling(default)] - param: bool, - #[darling(default)] - default: bool, -} - -impl ToTokens for Action { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Action { - ref ident, - ref data, - .. - } = *self; - let fields = data - .as_ref() - .take_struct() - .expect("Enums not supported") - .fields; - let field_defs = fields.clone().into_iter().filter_map(|field| { - let ActionField { - ident, ty, param, .. - } = field; - let ident = ident.clone().unwrap(); - if *param && ident != syn::Ident::new("actor", ident.span()) { - Some(quote! { #ident: #ty }) - } else { - None - } - }); - let field_assignments = fields.into_iter().map(|field| { - let ActionField { - ident, - param, - default, - .. - } = field; - let ident = ident.clone().unwrap(); - if *param { - quote! { - #ident: self.#ident - } - } else if *default { - quote! { - #ident: ::core::default::Default::default() - } - } else if ident == syn::Ident::new("actor", ident.span()) { - quote! { - #ident: actor - } - } else { - panic!("Field not state, default, or param: {}", ident); - } - }); - let ts = quote! { - mod big_brain_action_builder { - use super::#ident as Comp; - - use big_brain::{typetag, serde::Deserialize, Action, ActionRunner, bevy::prelude::{Entity, Commands}, ActionEnt}; - - #[derive(Debug, Deserialize)] - struct #ident { - #(#field_defs),* - } - - #[typetag::deserialize] - impl Action for #ident { - fn build(self: Box<Self>, actor: Entity, action_ent: ActionEnt, cmd: &mut Commands) -> Box<dyn ActionRunner> { - self - } - } - - impl ActionRunner for #ident { - fn activate(&self, actor: Entity, action_ent: ActionEnt, cmd: &mut Commands) { - cmd.entity(action_ent.0).insert(Comp { - #(#field_assignments),* - }); - } - fn deactivate(&self, action_ent: ActionEnt, cmd: &mut Commands) { - cmd.entity(action_ent.0).remove::<Comp>(); - } - } - } - }; - tokens.extend(ts); - } -} diff --git a/big-brain-derive/src/lib.rs b/big-brain-derive/src/lib.rs deleted file mode 100644 index 24e9d814afefa51c255498c57ae2d56e49940699..0000000000000000000000000000000000000000 --- a/big-brain-derive/src/lib.rs +++ /dev/null @@ -1,206 +0,0 @@ -use darling::FromDeriveInput; -use quote::quote; -use syn::{parse_macro_input, DeriveInput}; - -use action::Action; -use scorer::Scorer; - -mod action; -mod scorer; - -/** -`Action`s in `big-brain` are defined through this derive macro. Once defined, -they can be freely used in a .ron file. They define actual behaviors that an -`actor` will perform when the Thinker engine picks it as the active action, -based on [Considerations](derive.Consideration.html). - -## Definition Example - -```ignore -use specs::{Component, Entity, System, WriteStorage}; -use big_brain::{Action, ActionState}; - -// These are your game's components. -use crate::components; - -// This will be used to create `Action` components. They MUST implement the -// `specs::Component` trait. -#[derive(Debug, Clone, Component, Action)] -pub struct Eat { - // All actions **must** have a public `actor` field. This will be populated - // with the actual actor performing the Action. The `Entity` associated with - // the `Action` itself is distinct from the actor. - pub actor: Entity, - - // `default` fields will be populated using default::Default() when the - // Action is instantiated. These cannot be used as params. - #[action(default)] - pub foo: f32, - - // `param` fields will be populated using the value passed in through the - // `.ron` file. - #[action(param)] - pub reduce_by: f32, -} - -// Once an Action component is defined, we define a System that can act on it. -pub struct EatSystem; - -impl<'a> System<'a> for EatSystem { - type SystemData = ( - WriteStorage<'a, components::Hunger>, - // This is the actual Eat component. - WriteStorage<'a, Eat>, - // An ActionState component is attached to every Action Entity. - // It contains the current running status of the Action, and will be - // updated as needed by the actor's Thinker. - WriteStorage<'a, ActionState>, - ); - fn run(&mut self, (mut hungers, mut eat_actions, mut states): Self::SystemData) { - // You can join the Eat and ActionState together. They're on the same component. - for (state, eat_action) in (&mut states, &mut eat_actions).join() { - // Any components attached to the actor must be fetched separately. - if let Some(hunger) = hungers.get_mut(eat_action.actor.clone()) { - match state { - // At the very least, every Action should handle the - // `Requested` state. - ActionState::Requested => { - hunger.hunger -= eat_action.reduce_by; - // Success tells the Thinker that this action succeeded! - *state = ActionState::Success; - } - // Make sure to handle Cancelled for long-running Actions. - // The Thinker will not continue until the state is either - // Success or Failure. - ActionState::Cancelled => { - *state = ActionState::Failure; - } - _ => {} - } - } - } - } -} -``` - -## Usage Example - -```ignore -( - picker: {"FirstToScore": ()}, - // Actions are defined using the `then` param to Choices - choices: [( - consider: [{"Hunger": ()}], - // We can use the param defined in our derive definition here. - // The `foo` field will be defaulted and cannot be defined here. - then: {"Eat": (reduce_by: 80.0)}, - )] -) -``` - -*/ -#[proc_macro_derive(Action, attributes(action))] -pub fn derive_action_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let action = Action::from_derive_input(&input).unwrap(); - quote!(#action).into() -} - -/** -`Consideration`s in `big-brain` are defined through this derive macro. Once defined, -they can be freely used in a .ron file. While `Action`s define behaviors, -`Consideration`s are used to determine _whether_ to execute a certain action. - -`Consideration`s are responsible for determining a specific `Utility`, or score, -in Utility AI terms. This score is what sets Utility AI apart from plain old -Behavior Trees. - -Like anything else in an Entity system, considerations and their behaviors -consist of a `Component` and an associated `System`. - -## Definition Example - -```ignore -use specs::{Component, Entity, ReadStorage, System, WriteStorage}; -use big_brain::{Consideration, Utility}; - -// These are your game's components. -use crate::components; - -// `Consideration`s are defined by deriving them -- they MUST be Components. -#[derive(Debug, Component, Consideration)] -pub struct Hunger { - // All considerations **must** have a public `actor` field. This will be populated - // with the actual actor considering the world around it The `Entity` associated with - // the `Consideration` itself is distinct from the actor. - pub actor: Entity, - - // `default` fields will be populated using default::Default() when the - // Consideration is instantiated. These cannot be used as params. - #[consideration(default)] - pub evaluator: PowerEvaluator, - - // `param` fields will be populated using the value passed in through the - // `.ron` file. - #[consideration(param)] - pub weight: f32, -} - -pub struct ConsiderHunger; - -impl<'a> System<'a> for ConsiderHunger { - type SystemData = ( - ReadStorage<'a, components::Hunger>, - - // This is the actual `Consideration` component. - WriteStorage<'a, Hunger>, - - // The `Utility` component associated with this `Consideration` holds - // the current calculated score for that consideration. - WriteStorage<'a, Utility>, - ); - - fn run(&mut self, (hungers, mut considerers, mut utilities): Self::SystemData) { - // Join the considerations with the utilities -- they share an `Entity`. - for (conser, util) in (&mut considerers, &mut utilities).join() { - // Any actor-related components must be fetched separately, based on - // the consideration's `actor`. - if let Some(hunger) = hungers.get(conser.actor.clone()) { - *util = Utility { - // values and weights can be arbitrary numbers. The final - // score is based on combining these two values. - // - // Utilities with weight `0.0` are not used. - // - // For the formula, refer to the docs on `WeightedMeasure`. - value: conser.evaluator.evaluate(hunger.hunger), - weight: conser.weight, - }; - } - } - } -} -``` - -## Usage Example - -```ignore -( - picker: {"FirstToScore": ()}, - choices: [( - // Considerations to use are defined using the `consider` param in choices. - // A choice can have zero or more considerations. - consider: [{"Hunger": (weight: 1.0)}], - - // This is the action that will be executed if this choice "wins". - then: {"Eat": ()}, - )] -) -``` -*/ -#[proc_macro_derive(Scorer, attributes(scorer))] -pub fn derive_scorer_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let scorer = Scorer::from_derive_input(&input).unwrap(); - (quote!(#scorer)).into() -} diff --git a/big-brain-derive/src/scorer.rs b/big-brain-derive/src/scorer.rs deleted file mode 100644 index 824dd10e650668e83ece7ab1113c74beb33d9e5c..0000000000000000000000000000000000000000 --- a/big-brain-derive/src/scorer.rs +++ /dev/null @@ -1,99 +0,0 @@ -use darling::{ast, FromDeriveInput, FromField, ToTokens}; -use proc_macro2::TokenStream; -use quote::quote; - -#[derive(Debug, FromDeriveInput)] -#[darling(attributes(scorer))] -pub struct Scorer { - ident: syn::Ident, - generics: syn::Generics, - data: ast::Data<(), ScorerField>, -} - -#[derive(Debug, FromField)] -#[darling(attributes(scorer))] -struct ScorerField { - ident: Option<syn::Ident>, - ty: syn::Type, - #[darling(default)] - param: bool, - #[darling(default)] - default: bool, -} - -impl ToTokens for Scorer { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Scorer { - ref ident, - ref data, - .. - } = *self; - let fields = data - .as_ref() - .take_struct() - .expect("Enums not supported") - .fields; - let field_defs = fields.clone().into_iter().filter_map(|field| { - let ScorerField { - ident, ty, param, .. - } = field; - let ident = ident.clone().unwrap(); - if *param && ident != syn::Ident::new("parent", ident.span()) { - Some(quote! { #ident: #ty }) - } else { - None - } - }); - let field_assignments = fields.into_iter().map(|field| { - let ScorerField { - ident, - param, - default, - .. - } = field; - let ident = ident.clone().unwrap(); - if *param { - quote! { - #ident: self.#ident - } - } else if *default { - quote! { - #ident: ::core::default::Default::default() - } - } else if ident == syn::Ident::new("actor", ident.span()) { - quote! { - #ident: actor - } - } else { - panic!("Field not state, default, or param: {}", ident); - } - }); - let ts = quote! { - mod big_brain_scorer_builder { - use super::#ident as Comp; - - use big_brain::{typetag, serde::Deserialize, Scorer, bevy::prelude::*, ScorerEnt}; - // use typetag; - - #[derive(Debug, Deserialize)] - struct #ident { - #(#field_defs),* - } - #[typetag::deserialize] - impl Scorer for #ident { - fn build(&self, actor: Entity, cmd: &mut Commands) -> ScorerEnt { - let ent = ScorerEnt(cmd.spawn().id()); - cmd.entity(ent.0) - .insert(big_brain::Score::default()) - .insert(Comp { - #(#field_assignments),* - }); - cmd.entity(actor).push_children(&[ent.0]); - ent - } - } - } - }; - tokens.extend(ts); - } -} diff --git a/examples/basic.ron b/examples/basic.ron deleted file mode 100644 index 1dfd451c11e3bbf389a985ee347fcb8fb35ecdb4..0000000000000000000000000000000000000000 --- a/examples/basic.ron +++ /dev/null @@ -1,25 +0,0 @@ -( - // We have to use these special "externally tagged" specifiers instead of, - // say, `FirstToScore(threshold: 80.0)` due to a bug in RON itself: - // https://github.com/ron-rs/ron/issues/123 - picker: {"FirstToScore": (threshold: 80.0)}, - choices: [( - when: {"Bladder": ()}, - // Thinkers happen to also be actions, so you can nest theem! - then: {"Thinker": ( - picker: {"FirstToScore": (threshold: 80.0)}, - choices: [( - when: [{"Bladder": ()}], - then: {"Pee": ()} - )] - )} - ), ( - when: {"Thirst": ()}, - then: {"Drink": ()}, - ), ( - when: {"Hunger": ()}, - then: {"Eat": ()}, - )], - otherwise: Some({"Meander": ()}), -) - diff --git a/examples/thirst.rs b/examples/thirst.rs index b0e54154831609946dd99b99a3b644bfe6f50ab5..b9bab55cd44e0af7546f2acf4b102204a9b2aa81 100644 --- a/examples/thirst.rs +++ b/examples/thirst.rs @@ -31,16 +31,37 @@ pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) { // do it? This is the first bit involving Big Brain itself, and there's a few // pieces you need: -// First, you need an Action struct, and derive Action. +// First, you need an Action and an ActionBuilder struct. // // These actions will be spawned and queued by the game engine when their // conditions trigger (we'll configure what these are later). -#[derive(Debug, Action)] +#[derive(Debug, Clone)] pub struct Drink; +// The convention is to attach a `::build()` function to the Action type. +impl Drink { + pub fn build() -> DrinkBuilder { + DrinkBuilder + } +} + +// Then we define an ActionBuilder, which is responsible for making new +// Action components for us. +#[derive(Debug, Clone)] +pub struct DrinkBuilder; + +// All you need to implement heree is the `build()` method, which requires +// that you attach your actual component to the action Entity that was created +// and configured for you. +impl ActionBuilder for DrinkBuilder { + fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) { + cmd.entity(action).insert(Drink); + } +} + // Associated with that Drink Action, you then need to have a system that will // actually execute those actions when they're "spawned" by the Big Brain -// engine. +// engine. This is the actual "act" part of the Action. // // In our case, we want the Thirst components, since we'll be changing those. // Additionally, we want to pick up the DrinkAction components, as well as @@ -81,17 +102,32 @@ fn drink_action_system( // run in the background, calculating a "Score" value, which is what Big Brain // will use to pick which actions to execute. // -// Additionally, though, we pull in an evaluator and define a weight. Which is -// just mathy stuff you can tweak to get the behavior you want. More on this -// in the docs (later), but for now, just put them in there and trust the -// system. :) -#[derive(Debug, Scorer)] +// Just like with Actions, we use the convention of having separate +// ScorerBuilder and Scorer components. While it might seem like a lot of +// boilerplate, in a "real" application, you will almost certainly have data +// and configuration concerns. This pattern separates those nicely. +#[derive(Debug, Clone)] pub struct Thirsty; -// Look familiar? Similar dance to Actions here. +impl Thirsty { + fn build() -> ThirstyBuilder { + ThirstyBuilder + } +} + +#[derive(Debug, Clone)] +pub struct ThirstyBuilder; + +impl ScorerBuilder for ThirstyBuilder { + fn build(&self, cmd: &mut Commands, scorer: Entity, _actor: Entity) { + cmd.entity(scorer).insert(Thirsty); + } +} + +// Looks familiar? It's a lot likee Actions! pub fn thirsty_scorer_system( thirsts: Query<&Thirst>, - // Same dance with the Parent here, but now we've added a Score! + // Same dance with the Parent here, but now Big Brain has added a Score component! mut query: Query<(&Parent, &mut Score), With<Thirsty>>, ) { for (Parent(actor), mut score) in query.iter_mut() { @@ -109,36 +145,21 @@ pub fn thirsty_scorer_system( } } -// Now that we hav eall that defined, it's time to add a Thinker to an entity! +// Now that we have all that defined, it's time to add a Thinker to an entity! // The Thinker is the actual "brain" behind all the AI. Every entity you want // to have AI behavior should have one *or more* Thinkers attached to it. -// Thinkers are configured using RON right now, with a DSL that makes it easy -// to define, in data, the actual behavior you want. pub fn init_entities(mut cmd: Commands) { + // Create the entity and throw the Thirst component in there. Nothing special here. let actor = cmd.spawn().insert(Thirst::new(70.0, 2.0)).id(); - // Here's a very simple one that only has one scorer and one - // associated action. But you can have more of them, and even nest them by - // using more Thinkers (which are actually themselves Actions). See - // basic.ron in examples/ for a more involved Thinker definition. - // - // Ultimately, these Thinkers are meant to be usable by non-programmers: - // You, the developer, create Actions and Scorers, and someone else - // is then able to put them all together like LEGOs into all sorts of - // intricate logic. - Thinker::load_from_str( - r#" -( - picker: {"FirstToScore": (threshold: 80.0)}, - choices: [( - when: {"Thirsty": ()}, - // This action will fire when (and as long as) Thirsty scores >=80.0. - then: {"Drink": ()}, - )], -) -"#, - ) - .build(actor, &mut cmd); + // And finally, we put all the pieces together! + Thinker::build() + .picker(FirstToScore { threshold: 80.0 }) + // Note that what we pass in are _builders_, not components! + .when(Thirsty::build(), Drink::build()) + // .attach will do all the necessary work of attaching this component + // and hooking it up to the AI system. + .attach(&mut cmd, actor); } fn main() { diff --git a/src/actions.rs b/src/actions.rs index 6b8b2a7e7810f9532fc539b8a9071acc4a3b19c3..336540b989497926a1c886c4a397bc09bc0754c7 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -1,10 +1,9 @@ +use std::sync::Arc; + use bevy::prelude::*; use crate::ActionEnt; -#[derive(Debug)] -pub struct ActionRunnerWrapper(pub(crate) Box<dyn ActionRunner>); - #[derive(Debug, Clone, Eq, PartialEq)] pub enum ActionState { Init, @@ -15,57 +14,80 @@ pub enum ActionState { Failure, } +impl Default for ActionState { + fn default() -> Self { + Self::Init + } +} + impl ActionState { pub fn new() -> Self { Self::default() } +} - pub(crate) fn build(builder: Box<dyn Action>, actor: Entity, cmd: &mut Commands) -> ActionEnt { - let action_ent = ActionEnt(cmd.spawn().id()); - let manager_wrapper = ActionRunnerWrapper(builder.build(actor, action_ent, cmd)); - cmd.entity(action_ent.0) - .insert(ActionState::default()) - .insert(manager_wrapper); - cmd.entity(actor).push_children(&[action_ent.0]); - action_ent +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ActionBuilderId; + +#[derive(Debug, Clone)] +pub struct ActionBuilderWrapper(pub ActionBuilderId, pub Arc<dyn ActionBuilder>); + +impl ActionBuilderWrapper { + pub fn new(builder: Arc<dyn ActionBuilder>) -> Self { + ActionBuilderWrapper(ActionBuilderId, builder) } } -impl Default for ActionState { - fn default() -> Self { - Self::Init +pub trait ActionBuilder: std::fmt::Debug + Send + Sync { + fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity); + fn attach(&self, cmd: &mut Commands, actor: Entity) -> Entity { + let action_ent = ActionEnt(cmd.spawn().id()); + cmd.entity(action_ent.0).insert(ActionState::new()); + cmd.entity(actor).push_children(&[action_ent.0]); + self.build(cmd, action_ent.0, actor); + action_ent.0 } } -/** -This trait defines new actions. In general, you should use the [derive macro](derive.Action.html) instead. -*/ -#[typetag::deserialize] -pub trait Action: std::fmt::Debug + Send + Sync { - fn build( - self: Box<Self>, - actor: Entity, - action_ent: ActionEnt, - cmd: &mut Commands, - ) -> Box<dyn ActionRunner>; +#[derive(Debug)] +pub struct StepsBuilder { + steps: Vec<Arc<dyn ActionBuilder>>, } -pub trait ActionRunner: std::fmt::Debug + Send + Sync { - fn activate(&self, actor: Entity, action: ActionEnt, cmd: &mut Commands); - fn deactivate(&self, action: ActionEnt, cmd: &mut Commands); +impl StepsBuilder { + pub fn step(&mut self, action_builder: impl ActionBuilder + 'static) -> &mut Self { + self.steps.push(Arc::new(action_builder)); + self + } } +impl ActionBuilder for StepsBuilder { + fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) { + let child_action = self.steps[0].attach(cmd, actor); + cmd.entity(action).insert(Steps { + active_step: 0, + active_ent: ActionEnt(child_action), + steps: self.steps.clone(), + }); + } +} #[derive(Debug)] pub struct Steps { - steps: Vec<ActionEnt>, + steps: Vec<Arc<dyn ActionBuilder>>, active_step: usize, + active_ent: ActionEnt, +} + +impl Steps { + pub fn build() -> StepsBuilder { + StepsBuilder { steps: Vec::new() } + } } pub fn steps_system( mut cmd: Commands, mut steps_q: Query<(Entity, &Parent, &mut Steps)>, mut states: Query<&mut ActionState>, - runners: Query<&ActionRunnerWrapper>, ) { use ActionState::*; for (seq_ent, Parent(actor), mut steps_action) in steps_q.iter_mut() { @@ -73,18 +95,13 @@ pub fn steps_system( match current_state { Requested => { // Begin at the beginning - let step_ent = steps_action.steps[steps_action.active_step]; - let step_runner = runners.get(step_ent.0).expect("oops"); - let mut step_state = states.get_mut(step_ent.0).expect("oops"); - step_runner.0.activate(*actor, step_ent, &mut cmd); + let mut step_state = states.get_mut(steps_action.active_ent.0).expect("oops"); *step_state = Requested; let mut current_state = states.get_mut(seq_ent).expect("uh oh"); *current_state = Executing; } Executing => { - let mut step_state = states - .get_mut(steps_action.steps[steps_action.active_step].0) - .expect("bug"); + let mut step_state = states.get_mut(steps_action.active_ent.0).expect("bug"); match *step_state { Init => { // Request it! This... should not really happen? But just in case I'm missing something... :) @@ -95,43 +112,33 @@ pub fn steps_system( } Cancelled | Failure => { // Cancel ourselves - let step_ent = steps_action.steps[steps_action.active_step]; let step_state = step_state.clone(); - let step_runner = runners.get(step_ent.0).expect("oops"); - step_runner.0.deactivate(step_ent, &mut cmd); let mut seq_state = states.get_mut(seq_ent).expect("idk"); *seq_state = step_state; + cmd.entity(steps_action.active_ent.0).despawn_recursive(); } Success if steps_action.active_step == steps_action.steps.len() - 1 => { // We're done! Let's just be successful - let step_ent = steps_action.steps[steps_action.active_step]; let step_state = step_state.clone(); - let step_runner = runners.get(step_ent.0).expect("oops"); - step_runner.0.deactivate(step_ent, &mut cmd); let mut seq_state = states.get_mut(seq_ent).expect("idk"); *seq_state = step_state; + cmd.entity(steps_action.active_ent.0).despawn_recursive(); } Success => { // Deactivate current step and go to the next step - let step_ent = steps_action.steps[steps_action.active_step]; - let step_runner = runners.get(step_ent.0).expect("oops"); - step_runner.0.deactivate(step_ent, &mut cmd); + cmd.entity(steps_action.active_ent.0).despawn_recursive(); steps_action.active_step += 1; - let step_ent = steps_action.steps[steps_action.active_step]; - let step_runner = runners.get(step_ent.0).expect("oops"); - let mut step_state = states.get_mut(step_ent.0).expect("oops"); - step_runner.0.activate(*actor, step_ent, &mut cmd); + let step_builder = steps_action.steps[steps_action.active_step].clone(); + let step_ent = step_builder.attach(&mut cmd, *actor); + let mut step_state = states.get_mut(step_ent).expect("oops"); *step_state = ActionState::Requested; } } } Cancelled => { // Cancel current action - let step_ent = steps_action.steps[steps_action.active_step]; - let step_runner = runners.get(step_ent.0).expect("oops"); - let mut step_state = states.get_mut(step_ent.0).expect("oops"); - step_runner.0.activate(*actor, step_ent, &mut cmd); + let mut step_state = states.get_mut(steps_action.active_ent.0).expect("oops"); *step_state = ActionState::Cancelled; } Init | Success | Failure => { @@ -140,51 +147,3 @@ pub fn steps_system( } } } - -mod seq_action { - use super::*; - use serde::Deserialize; - - #[derive(Debug, Deserialize)] - struct Steps { - steps: Vec<Box<dyn Action>>, - } - - #[typetag::deserialize] - impl Action for Steps { - fn build( - self: Box<Self>, - actor: Entity, - _action_ent: ActionEnt, - cmd: &mut Commands, - ) -> Box<dyn ActionRunner> { - let runner = StepsRunner { - steps: self - .steps - .into_iter() - .map(|builder| ActionState::build(builder, actor, cmd)) - .collect(), - }; - let children: Vec<_> = runner.steps.iter().map(|x| x.0).collect(); - cmd.entity(actor).push_children(&children[..]); - Box::new(runner) - } - } - - #[derive(Debug)] - struct StepsRunner { - steps: Vec<ActionEnt>, - } - - impl ActionRunner for StepsRunner { - fn activate(&self, _actor: Entity, action_ent: ActionEnt, cmd: &mut Commands) { - cmd.entity(action_ent.0).insert(super::Steps { - active_step: 0, - steps: self.steps.clone(), - }); - } - fn deactivate(&self, action_ent: ActionEnt, cmd: &mut Commands) { - cmd.entity(action_ent.0).remove::<super::Steps>(); - } - } -} diff --git a/src/choices.rs b/src/choices.rs index bdc756f73aa56528648168b5480c3f88d1cad951..205896457e0dc786621768c4a9da3d71c20b7111 100644 --- a/src/choices.rs +++ b/src/choices.rs @@ -1,35 +1,45 @@ +use std::sync::Arc; + use bevy::prelude::*; -use serde::Deserialize; use crate::{ - actions::{Action, ActionState}, - scorers::{Scorer, Score}, - thinker::{ActionEnt, ScorerEnt}, + actions::{ActionBuilder, ActionBuilderWrapper}, + scorers::{Score, ScorerBuilder}, + thinker::ScorerEnt, }; // Contains different types of Considerations and Actions -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Choice { pub scorer: ScorerEnt, - pub action_state: ActionEnt, + pub action: ActionBuilderWrapper, } impl Choice { pub fn calculate(&self, scores: &Query<&Score>) -> f32 { - scores.get(self.scorer.0).expect("Where did the score go?").0 + scores + .get(self.scorer.0) + .expect("Where did the score go?") + .0 } } -#[derive(Debug, Deserialize)] +#[derive(Debug)] pub struct ChoiceBuilder { - pub when: Box<dyn Scorer>, - pub then: Box<dyn Action>, + pub when: Arc<dyn ScorerBuilder>, + pub then: Arc<dyn ActionBuilder>, } impl ChoiceBuilder { - pub fn build(self, actor: Entity, cmd: &mut Commands) -> Choice { - let action = self.then; + pub fn new(scorer: Arc<dyn ScorerBuilder>, action: Arc<dyn ActionBuilder>) -> Self { + Self { + when: scorer, + then: action, + } + } + + pub fn build(&self, cmd: &mut Commands, actor: Entity) -> Choice { Choice { - scorer: self.when.build(actor, cmd), - action_state: ActionState::build(action, actor, cmd), + scorer: ScorerEnt(self.when.attach(cmd, actor)), + action: ActionBuilderWrapper::new(self.then.clone()), } } } diff --git a/src/evaluators.rs b/src/evaluators.rs index 34711ce41dabce316e1f8a846cfc8c39fcfea225..e7d92c32f1971dbf9c51f3c17eae4dbc99cf8a76 100644 --- a/src/evaluators.rs +++ b/src/evaluators.rs @@ -1,11 +1,8 @@ -use serde::{Deserialize, Serialize}; - -#[typetag::serde] pub trait Evaluator: std::fmt::Debug + Sync + Send { fn evaluate(&self, value: f32) -> f32; } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] pub struct LinearEvaluator { xa: f32, ya: f32, @@ -36,14 +33,13 @@ impl Default for LinearEvaluator { } } -#[typetag::serde] impl Evaluator for LinearEvaluator { fn evaluate(&self, value: f32) -> f32 { clamp(self.ya + self.dy_over_dx * (value - self.xa), self.ya, self.yb) } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] pub struct PowerEvaluator { xa: f32, ya: f32, @@ -76,7 +72,6 @@ impl Default for PowerEvaluator { } } -#[typetag::serde] impl Evaluator for PowerEvaluator { fn evaluate(&self, value: f32) -> f32 { let cx = clamp(value, self.xa, self.xb); @@ -84,7 +79,7 @@ impl Evaluator for PowerEvaluator { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] pub struct SigmoidEvaluator { xa: f32, xb: f32, @@ -124,7 +119,6 @@ impl SigmoidEvaluator { } } -#[typetag::serde] impl Evaluator for SigmoidEvaluator { fn evaluate(&self, x: f32) -> f32 { let cx_minus_x_mean = clamp(x, self.xa, self.xb) - self.x_mean; diff --git a/src/lib.rs b/src/lib.rs index 22e33379a7cc59f84e49c5077ace13b622bf8487..c7b7de97ec0f8313e8cfe8dce18c2232022a28a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,13 @@ pub use bevy; -pub use big_brain_derive::*; -pub use serde; -pub use typetag; pub use actions::*; pub use choices::*; pub use scorers::*; pub use thinker::*; +pub use pickers::*; pub mod evaluators; -pub mod pickers; +mod pickers; mod actions; mod choices; diff --git a/src/pickers.rs b/src/pickers.rs index 468e7a14e96f2ffcdefa1871eed96650ec8fb9f4..0ac71e3687b22a3bec4e521d664e0402ec49e367 100644 --- a/src/pickers.rs +++ b/src/pickers.rs @@ -1,26 +1,22 @@ use bevy::prelude::*; -use serde::{Deserialize, Serialize}; +use crate::{choices::Choice, scorers::Score}; -use crate::{choices::Choice, scorers::Score, thinker::ActionEnt}; - -#[typetag::serde] pub trait Picker: std::fmt::Debug + Sync + Send { - fn pick(&self, _choices: &[Choice], _utilities: &Query<&Score>) -> Option<ActionEnt>; + fn pick(&self, _choices: &[Choice], _utilities: &Query<&Score>) -> Option<Choice>; } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default)] pub struct FirstToScore { pub threshold: f32, } -#[typetag::serde] impl Picker for FirstToScore { - fn pick(&self, choices: &[Choice], utilities: &Query<&Score>) -> Option<ActionEnt> { + fn pick(&self, choices: &[Choice], utilities: &Query<&Score>) -> Option<Choice> { for choice in choices { let value = choice.calculate(utilities); if value >= self.threshold { - return Some(choice.action_state); + return Some(choice.clone()); } } None diff --git a/src/scorers.rs b/src/scorers.rs index 0df09596b6cd976ceed21fec704c19b337a029af..19ee5356ec44e2602458b6aec62746a0a580c8a9 100644 --- a/src/scorers.rs +++ b/src/scorers.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use bevy::prelude::*; use crate::ScorerEnt; @@ -17,41 +19,38 @@ impl Score { } } -/** -This trait defines new Scorers. In general, you should use the [derive macro](derive.Scorer.html) instead. -*/ -#[typetag::deserialize] -pub trait Scorer: std::fmt::Debug + Sync + Send { - fn build(&self, entity: Entity, cmd: &mut Commands) -> ScorerEnt; +pub trait ScorerBuilder: std::fmt::Debug + Sync + Send { + fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity); + fn attach(&self, cmd: &mut Commands, actor: Entity) -> Entity { + let scorer_ent = cmd.spawn().id(); + cmd.entity(scorer_ent).insert(Score::default()); + cmd.entity(actor).push_children(&[scorer_ent]); + self.build(cmd, scorer_ent, actor); + scorer_ent + } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FixedScore(f32); +impl FixedScore { + pub fn build(score: f32) -> FixedScoreBuilder { + FixedScoreBuilder(score) + } +} + pub fn fixed_score_system(mut query: Query<(&FixedScore, &mut Score)>) { for (FixedScore(fixed), mut score) in query.iter_mut() { score.set(*fixed); } } -mod fixed_score { - use super::*; - - use serde::Deserialize; - - #[derive(Debug, Deserialize)] - struct FixedScore(f32); +#[derive(Debug, Clone)] +pub struct FixedScoreBuilder(f32); - #[typetag::deserialize] - impl Scorer for FixedScore { - fn build(&self, actor: Entity, cmd: &mut Commands) -> ScorerEnt { - let ent = ScorerEnt(cmd.spawn().id()); - cmd.entity(ent.0) - .insert(Score::default()) - .insert(super::FixedScore(self.0)); - cmd.entity(actor).push_children(&[ent.0]); - ent - } +impl ScorerBuilder for FixedScoreBuilder { + fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) { + cmd.entity(action).insert(FixedScore(self.0)); } } @@ -61,6 +60,15 @@ pub struct AllOrNothing { scorers: Vec<ScorerEnt>, } +impl AllOrNothing { + pub fn build(threshold: f32) -> AllOrNothingBuilder { + AllOrNothingBuilder { + threshold, + scorers: Vec::new(), + } + } +} + pub fn all_or_nothing_system(query: Query<(Entity, &AllOrNothing)>, mut scores: Query<&mut Score>) { for ( aon_ent, @@ -84,36 +92,32 @@ pub fn all_or_nothing_system(query: Query<(Entity, &AllOrNothing)>, mut scores: score.set(crate::evaluators::clamp(sum, 0.0, 100.0)); } } +#[derive(Debug, Clone)] +pub struct AllOrNothingBuilder { + threshold: f32, + scorers: Vec<Arc<dyn ScorerBuilder>>, +} -mod all_or_nothing { - use super::*; - - use serde::Deserialize; - - #[derive(Debug, Deserialize)] - struct AllOrNothing { - threshold: f32, - scorers: Vec<Box<dyn Scorer>>, +impl AllOrNothingBuilder { + pub fn when(&mut self, scorer: impl ScorerBuilder + 'static) -> &mut Self { + self.scorers.push(Arc::new(scorer)); + self } +} - #[typetag::deserialize] - impl Scorer for AllOrNothing { - fn build(&self, actor: Entity, cmd: &mut Commands) -> ScorerEnt { - let ent = ScorerEnt(cmd.spawn().id()); - let scorers: Vec<_> = self - .scorers - .iter() - .map(|scorer| scorer.build(actor, cmd).0) - .collect(); - cmd.entity(ent.0) - .insert(Score::default()) - .insert(super::AllOrNothing { - threshold: self.threshold, - scorers: scorers.into_iter().map(ScorerEnt).collect(), - }); - cmd.entity(actor).push_children(&[ent.0]); - ent - } +impl ScorerBuilder for AllOrNothingBuilder { + fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) { + let scorers: Vec<_> = self + .scorers + .iter() + .map(|scorer| scorer.attach(cmd, actor)) + .collect(); + cmd.entity(scorer) + .insert(Score::default()) + .insert(super::AllOrNothing { + threshold: self.threshold, + scorers: scorers.into_iter().map(ScorerEnt).collect(), + }); } } @@ -123,6 +127,15 @@ pub struct SumOfScorers { scorers: Vec<ScorerEnt>, } +impl SumOfScorers { + pub fn build(threshold: f32) -> SumOfScorersBuilder { + SumOfScorersBuilder { + threshold, + scorers: Vec::new(), + } + } +} + pub fn sum_of_scorers_system(query: Query<(Entity, &SumOfScorers)>, mut scores: Query<&mut Score>) { for ( sos_ent, @@ -145,34 +158,29 @@ pub fn sum_of_scorers_system(query: Query<(Entity, &SumOfScorers)>, mut scores: } } -mod sum_of_scorers { - use super::*; - - use serde::Deserialize; +#[derive(Debug, Clone)] +pub struct SumOfScorersBuilder { + threshold: f32, + scorers: Vec<Arc<dyn ScorerBuilder>>, +} - #[derive(Debug, Deserialize)] - struct SumOfScorers { - threshold: f32, - scorers: Vec<Box<dyn Scorer>>, +impl SumOfScorersBuilder { + pub fn when(&mut self, scorer: impl ScorerBuilder + 'static) -> &mut Self { + self.scorers.push(Arc::new(scorer)); + self } +} - #[typetag::deserialize] - impl Scorer for SumOfScorers { - fn build(&self, actor: Entity, cmd: &mut Commands) -> ScorerEnt { - let ent = ScorerEnt(cmd.spawn().id()); - let scorers: Vec<_> = self - .scorers - .iter() - .map(|scorer| scorer.build(actor, cmd).0) - .collect(); - cmd.entity(ent.0) - .insert(Score::default()) - .insert(super::AllOrNothing { - threshold: self.threshold, - scorers: scorers.into_iter().map(ScorerEnt).collect(), - }); - cmd.entity(actor).push_children(&[ent.0]); - ent - } +impl ScorerBuilder for SumOfScorersBuilder { + fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) { + let scorers: Vec<_> = self + .scorers + .iter() + .map(|scorer| scorer.attach(cmd, actor)) + .collect(); + cmd.entity(scorer).insert(AllOrNothing { + threshold: self.threshold, + scorers: scorers.into_iter().map(ScorerEnt).collect(), + }); } } diff --git a/src/thinker.rs b/src/thinker.rs index dc095d062723acd6b535c2336229d23805f85c90..442c2026725a2157aaa5414e1c196d7ef36043be 100644 --- a/src/thinker.rs +++ b/src/thinker.rs @@ -1,15 +1,15 @@ -use std::fs::File; -use std::path::Path; -use std::time::{Duration, Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use bevy::prelude::*; -use serde::Deserialize; use crate::{ - actions::{self, Action, ActionRunner, ActionRunnerWrapper, ActionState}, + actions::{self, ActionBuilder, ActionBuilderWrapper, ActionState}, choices::{Choice, ChoiceBuilder}, - scorers::Score, pickers::Picker, + scorers::{Score, ScorerBuilder}, }; #[derive(Debug, Clone, Copy)] @@ -20,87 +20,81 @@ pub struct ScorerEnt(pub Entity); #[derive(Debug)] pub struct Thinker { - pub picker: Box<dyn Picker>, - pub otherwise: Option<ActionEnt>, - pub choices: Vec<Choice>, - pub current_action: Option<ActionEnt>, + picker: Arc<dyn Picker>, + otherwise: Option<ActionBuilderWrapper>, + choices: Vec<Choice>, + current_action: Option<(ActionEnt, ActionBuilderWrapper)>, } impl Thinker { - pub fn load_from_str<S: AsRef<str>>(string: S) -> builder::Thinker { - ron::de::from_str(string.as_ref()).expect("Failed to parse RON") + pub fn build() -> ThinkerBuilder { + ThinkerBuilder::new() } +} - pub fn load_from_path<P: AsRef<Path>>(path: P) -> builder::Thinker { - let f = File::open(&path).expect("Failed to open file"); - ron::de::from_reader(f).expect("Failed to read .ron file") - } +#[derive(Debug, Default)] +pub struct ThinkerBuilder { + pub picker: Option<Arc<dyn Picker>>, + pub otherwise: Option<ActionBuilderWrapper>, // Arc<dyn ActionBuilder>? + pub choices: Vec<ChoiceBuilder>, } -mod builder { - use super::*; - #[derive(Debug, Deserialize)] - pub struct Thinker { - pub picker: Box<dyn Picker>, - pub otherwise: Option<Box<dyn Action>>, - pub choices: Vec<ChoiceBuilder>, +impl ThinkerBuilder { + pub(crate) fn new() -> Self { + Self { + picker: None, + otherwise: None, + choices: Vec::new(), + } } -} -impl builder::Thinker { - pub fn build(self, actor: Entity, cmd: &mut Commands) -> ActionEnt { - let action_ent = ActionState::build(Box::new(self), actor, cmd); - cmd.entity(action_ent.0) - .insert(ActiveThinker(true)) - .insert(ActionState::Requested); - action_ent + pub fn picker(&mut self, picker: impl Picker + 'static) -> &mut Self { + self.picker = Some(Arc::new(picker)); + self + } + + pub fn when( + &mut self, + scorer: impl ScorerBuilder + 'static, + action: impl ActionBuilder + 'static, + ) -> &mut Self { + self.choices.push(ChoiceBuilder::new(Arc::new(scorer), Arc::new(action))); + self + } + + pub fn otherwise(&mut self, otherwise: impl ActionBuilder + 'static) -> &mut Self { + self.otherwise = Some(ActionBuilderWrapper::new(Arc::new(otherwise))); + self } } -#[typetag::deserialize] -impl Action for builder::Thinker { - fn build( - self: Box<Self>, - actor: Entity, - action_ent: ActionEnt, - cmd: &mut Commands, - ) -> Box<dyn ActionRunner> { +impl ActionBuilder for ThinkerBuilder { + fn build(&self, cmd: &mut Commands, action_ent: Entity, actor: Entity) { + println!("building thinker"); let choices = self .choices - .into_iter() - .map(|choice| choice.build(actor, cmd)) + .iter() + .map(|choice| choice.build(cmd, actor)) .collect(); - let otherwise = self - .otherwise - .map(|builder| ActionState::build(builder, actor, cmd)); - cmd.entity(action_ent.0).insert(Thinker { - picker: self.picker, - choices, - otherwise, - current_action: None, - }); - cmd.entity(actor).push_children(&[action_ent.0]); - Box::new(ThinkerRunner) + cmd.entity(action_ent) + .insert(Thinker { + // TODO: reasonable default?... + picker: self + .picker + .clone() + .expect("ThinkerBuilder must have a Picker"), + choices, + otherwise: self.otherwise.clone(), + current_action: None, + }) + .insert(ActiveThinker(false)) + .insert(ActionState::Requested); } } #[derive(Debug)] pub struct ActiveThinker(bool); -#[derive(Debug)] -pub struct ThinkerRunner; - -impl ActionRunner for ThinkerRunner { - fn activate(&self, _: Entity, action_ent: ActionEnt, cmd: &mut Commands) { - cmd.entity(action_ent.0) - .insert(ActiveThinker(false)) - .insert(ActionState::Requested); - } - fn deactivate(&self, action_ent: ActionEnt, cmd: &mut Commands) { - cmd.entity(action_ent.0).remove::<ActiveThinker>(); - } -} - pub struct ThinkerIterations { index: usize, max_duration: Duration, @@ -125,10 +119,11 @@ pub fn thinker_system( mut thinker_q: Query<(Entity, &Parent, &mut Thinker, &ActiveThinker)>, utilities: Query<&Score>, mut action_states: Query<&mut actions::ActionState>, - builder_wrappers: Query<&ActionRunnerWrapper>, ) { let start = Instant::now(); - for (thinker_ent, Parent(actor), mut thinker, active_thinker) in thinker_q.iter_mut().skip(iterations.index) { + for (thinker_ent, Parent(actor), mut thinker, active_thinker) in + thinker_q.iter_mut().skip(iterations.index) + { iterations.index += 1; let thinker_state = action_states @@ -144,24 +139,24 @@ pub fn thinker_system( } ActionState::Cancelled => { if let Some(current) = &mut thinker.current_action { - let state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.").clone(); + 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 => { let mut act_state = action_states.get_mut(thinker_ent).expect("???"); *act_state = state.clone(); - let mut state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); + 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."); *state = ActionState::Init; thinker.current_action = None; } _ => { - let mut state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); + 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."); *state = ActionState::Cancelled; } } } } ActionState::Requested | ActionState::Executing => { - if let Some(picked_action_ent) = thinker.picker.pick(&thinker.choices, &utilities) { + if let Some(choice) = thinker.picker.pick(&thinker.choices, &utilities) { // 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). @@ -170,13 +165,12 @@ pub fn thinker_system( thinker_ent, *actor, &mut thinker, - &picked_action_ent, + &choice.action, &mut action_states, - &builder_wrappers, ); } 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; + let default_action_ent = default_action_ent.clone(); exec_picked_action( &mut cmd, thinker_ent, @@ -184,21 +178,18 @@ pub fn thinker_system( &mut thinker, &default_action_ent, &mut action_states, - &builder_wrappers, ); } else if let Some(current) = &mut thinker.current_action { // If we didn't pick anything, and there's no default action, // we need to see if there's any action currently executing, // and cancel it. We also use this opportunity to clean up // stale action components so they don't slow down joins. - let mut state = action_states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); - let factory = builder_wrappers.get(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); + 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."); match *state { actions::ActionState::Init | actions::ActionState::Success | actions::ActionState::Failure => { - factory.0.deactivate(*current, &mut cmd); - *state = ActionState::Init; + cmd.entity(current.0 .0).despawn_recursive(); thinker.current_action = None; } _ => { @@ -220,9 +211,8 @@ fn exec_picked_action( thinker_ent: Entity, actor: Entity, thinker: &mut Mut<Thinker>, - picked_action_ent: &ActionEnt, + picked_action: &ActionBuilderWrapper, states: &mut Query<&mut ActionState>, - builder_wrappers: &Query<&ActionRunnerWrapper>, ) { // If we do find one, then we need to grab the corresponding // component for it. The "action" that `picker.pick()` returns @@ -235,12 +225,12 @@ fn exec_picked_action( // (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. - if let Some(current) = &mut thinker.current_action { - if current.0 != picked_action_ent.0 { + if let Some((action_ent, ActionBuilderWrapper(current_id, _))) = &mut thinker.current_action { + if *current_id != picked_action.0 { // 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). - let mut curr_action_state = states.get_mut(current.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); + 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."); // If the action is executing, or was requested, we // need to cancel it to make sure it stops. The Action // system will take care of resetting its state as @@ -252,13 +242,11 @@ fn exec_picked_action( *thinker_state = ActionState::Cancelled; } ActionState::Init | ActionState::Success | ActionState::Failure => { - let current_action_factory = builder_wrappers.get(current.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug."); - current_action_factory - .0 - .deactivate(*picked_action_ent, cmd); let old_state = curr_action_state.clone(); - *curr_action_state = ActionState::Init; - *current = *picked_action_ent; + thinker.current_action = Some(( + ActionEnt(picked_action.1.attach(cmd, actor)), + picked_action.clone(), + )); let mut thinker_state = states.get_mut(thinker_ent).expect("Couldn't find a component corresponding to the current action. This is definitely a bug."); *thinker_state = old_state; } @@ -270,13 +258,9 @@ fn exec_picked_action( // it as Requested if for some reason it had finished // but the Action System hasn't gotten around to // cleaning it up. - let mut picked_action_state = states.get_mut(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug."); - if *picked_action_state == ActionState::Init { - let picked_action_factory = builder_wrappers.get(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug."); - picked_action_factory - .0 - .activate(actor, *picked_action_ent, cmd); - *picked_action_state = ActionState::Requested; + 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."); + if *curr_action_state == ActionState::Init { + *curr_action_state = ActionState::Requested; } } } else { @@ -284,12 +268,7 @@ fn exec_picked_action( // current_action in the thinker. The logic here is pretty // straightforward -- we set the action, Request it, and // that's it. - let picked_action_factory = builder_wrappers.get(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug."); - let mut picked_action_state = states.get_mut(picked_action_ent.0).expect("Couldn't find an Action component corresponding to an Action entity. This is definitely a bug."); - picked_action_factory - .0 - .activate(actor, *picked_action_ent, cmd); - thinker.current_action = Some(*picked_action_ent); - *picked_action_state = ActionState::Requested; + let new_action = picked_action.1.attach(cmd, actor); + thinker.current_action = Some((ActionEnt(new_action), picked_action.clone())); } }