From 6c736374b4afd60af592a357ad2403304d3638d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= <kzm@zkat.tech> Date: Mon, 26 Apr 2021 19:16:00 -0700 Subject: [PATCH] feat(actions): Add new Concurrently composite action Fixes: https://github.com/zkat/big-brain/issues/22 --- src/actions.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/actions.rs b/src/actions.rs index b569886..675044f 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -232,3 +232,144 @@ pub fn steps_system( } } } + +/** +[`ActionBuilder`] for the [`Concurrent`] component. Constructed through `Concurrent::build()`. +*/ +#[derive(Debug)] +pub struct ConcurrentlyBuilder { + actions: Vec<Arc<dyn ActionBuilder>>, +} + +impl ConcurrentlyBuilder { + /** + Add an action to execute. Order does not matter. + */ + pub fn push(mut self, action_builder: impl ActionBuilder + 'static) -> Self { + self.actions.push(Arc::new(action_builder)); + self + } +} + +impl ActionBuilder for ConcurrentlyBuilder { + fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) { + let children: Vec<Entity> = self + .actions + .iter() + .map(|action| action.attach(cmd, actor)) + .collect(); + cmd.entity(action) + .insert(Name::new("Concurrent Action")) + .push_children(&children[..]) + .insert(Concurrently { + actions: children.into_iter().map(ActionEnt).collect(), + }); + } +} + +/** +Composite Action that executes a number of Actions concurrently, as long as they all result in a `Success`ful [`ActionState`]. + +### Example + +```ignore +Thinker::build() + .when( + MyScorer, + Concurrent::build() + .push(MyAction::build()) + .push(MyOtherAction::build()) + ) +``` +*/ +#[derive(Debug)] +pub struct Concurrently { + actions: Vec<ActionEnt>, +} + +impl Concurrently { + /** + Construct a new [`ConcurrentBuilder`] to define the actions to take. + */ + pub fn build() -> ConcurrentlyBuilder { + ConcurrentlyBuilder { + actions: Vec::new(), + } + } +} + +/** +System that takes care of executing any existing [`Concurrent`] Actions. +*/ +pub fn concurrent_system( + concurrent_q: Query<(Entity, &Concurrently)>, + mut states_q: Query<&mut ActionState>, +) { + use ActionState::*; + for (seq_ent, concurrent_action) in concurrent_q.iter() { + let current_state = states_q.get_mut(seq_ent).expect("uh oh").clone(); + match current_state { + Requested => { + // Begin at the beginning + let mut current_state = states_q.get_mut(seq_ent).expect("uh oh"); + *current_state = Executing; + for ActionEnt(child_ent) in concurrent_action.actions.iter() { + let mut child_state = states_q.get_mut(*child_ent).expect("uh oh"); + *child_state = Requested; + } + } + Executing => { + let mut all_success = true; + let mut failed_idx = None; + for (idx, ActionEnt(child_ent)) in concurrent_action.actions.iter().enumerate() { + let mut child_state = states_q.get_mut(*child_ent).expect("uh oh"); + match *child_state { + Failure => { + failed_idx = Some(idx); + all_success = false; + } + 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 ActionEnt(child_ent) in concurrent_action.actions.iter().take(idx) { + let mut child_state = states_q.get_mut(*child_ent).expect("uh oh"); + match *child_state { + Failure | Success => {} + _ => { + *child_state = Cancelled; + } + } + } + } + + } + Cancelled => { + // Cancel all actions + for ActionEnt(child_ent) in concurrent_action.actions.iter() { + let mut child_state = states_q.get_mut(*child_ent).expect("uh oh"); + match *child_state { + Init | Success | Failure => { + // Do nothing + } + _ => { + *child_state = Cancelled; + } + } + } + } + Init | Success | Failure => { + // Do nothing. + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 5174ba6..a6d2eed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,7 +163,7 @@ pub mod prelude { use super::*; pub use super::BigBrainPlugin; - pub use actions::{ActionBuilder, ActionState, Steps}; + pub use actions::{ActionBuilder, ActionState, Steps, Concurrently}; pub use pickers::{FirstToScore, Picker}; pub use scorers::{AllOrNothing, FixedScore, Score, ScorerBuilder, SumOfScorers, WinningScorer}; pub use thinker::{Actor, Thinker, ThinkerBuilder}; @@ -196,6 +196,7 @@ impl Plugin for BigBrainPlugin { app.add_system(thinker::thinker_component_detach_system.system()); app.add_system(thinker::actor_gone_cleanup.system()); app.add_system(actions::steps_system.system()); + app.add_system(actions::concurrent_system.system()); app.add_system(scorers::fixed_score_system.system()); app.add_system(scorers::all_or_nothing_system.system()); app.add_system(scorers::sum_of_scorers_system.system()); -- GitLab