From f33315c9b7b769a94baab17e3a9df9f5ebe924d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gilbert=20R=C3=B6hrbein?= <payload.git@mailbox.org>
Date: Sun, 19 Sep 2021 17:33:32 +0200
Subject: [PATCH] fix(systems): Fix steps, add a test and explicit systems
 ordering (#27)

Fixes: #26

* add test for steps and fixes
* define system ordering
---
 src/actions.rs |  22 ++++----
 src/lib.rs     |  37 ++++++++----
 tests/steps.rs | 150 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 188 insertions(+), 21 deletions(-)
 create mode 100644 tests/steps.rs

diff --git a/src/actions.rs b/src/actions.rs
index 5475a54..9a0c09d 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -175,17 +175,16 @@ pub fn steps_system(
 ) {
     use ActionState::*;
     for (seq_ent, Actor(actor), mut steps_action) in steps_q.iter_mut() {
-        let current_state = states.get_mut(seq_ent).expect("uh oh").clone();
+        let active_ent = steps_action.active_ent.0;
+        let current_state = states.get_mut(seq_ent).unwrap().clone();
         match current_state {
             Requested => {
                 // Begin at the beginning
-                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;
+                *states.get_mut(active_ent).unwrap() = Requested;
+                *states.get_mut(seq_ent).unwrap() = Executing;
             }
             Executing => {
-                let mut step_state = states.get_mut(steps_action.active_ent.0).expect("bug");
+                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... :)
@@ -216,15 +215,18 @@ pub fn steps_system(
                         let step_builder = steps_action.steps[steps_action.active_step].clone();
                         let step_ent = step_builder.attach(&mut cmd, *actor);
                         cmd.entity(seq_ent).push_children(&[step_ent]);
-                        let mut step_state = states.get_mut(step_ent).expect("oops");
-                        *step_state = ActionState::Requested;
+                        steps_action.active_ent.0 = step_ent;
                     }
                 }
             }
             Cancelled => {
                 // Cancel current action
-                let mut step_state = states.get_mut(steps_action.active_ent.0).expect("oops");
-                *step_state = ActionState::Cancelled;
+                let mut step_state = states.get_mut(active_ent).expect("oops");
+                if *step_state == Requested || *step_state == Executing {
+                    *step_state = Cancelled;
+                } else if *step_state == Failure || *step_state == Success {
+                    *states.get_mut(seq_ent).unwrap() = step_state.clone();
+                }
             }
             Init | Success | Failure => {
                 // Do nothing.
diff --git a/src/lib.rs b/src/lib.rs
index 3a0c6ed..55aa2d6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -193,16 +193,31 @@ pub struct BigBrainPlugin;
 
 impl Plugin for BigBrainPlugin {
     fn build(&self, app: &mut AppBuilder) {
-        app.add_system(thinker::thinker_system.system());
-        app.add_system(thinker::thinker_component_attach_system.system());
-        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());
-        app.add_system(scorers::winning_scorer_system.system());
-        app.add_system(scorers::evaluating_scorer_system.system());
+        use CoreStage::*;
+        app.add_system_set_to_stage(
+            First,
+            SystemSet::new()
+                .with_system(scorers::fixed_score_system.system())
+                .with_system(scorers::all_or_nothing_system.system())
+                .with_system(scorers::sum_of_scorers_system.system())
+                .with_system(scorers::winning_scorer_system.system())
+                .with_system(scorers::evaluating_scorer_system.system())
+                .label("scorers"),
+        );
+        app.add_system_to_stage(First, thinker::thinker_system.system().after("scorers"));
+
+        app.add_system_set_to_stage(
+            PreUpdate,
+            SystemSet::new()
+                .with_system(actions::steps_system.system())
+                .with_system(actions::concurrent_system.system())
+                .label("aggregate-actions"),
+        );
+
+        // run your actions in PreUpdate after aggregate-actions or in a later stage
+
+        app.add_system_to_stage(Last, thinker::thinker_component_attach_system.system());
+        app.add_system_to_stage(Last, thinker::thinker_component_detach_system.system());
+        app.add_system_to_stage(Last, thinker::actor_gone_cleanup.system());
     }
 }
diff --git a/tests/steps.rs b/tests/steps.rs
new file mode 100644
index 0000000..7767818
--- /dev/null
+++ b/tests/steps.rs
@@ -0,0 +1,150 @@
+use bevy::{app::AppExit, prelude::*};
+use big_brain::{pickers, prelude::*};
+
+#[test]
+fn steps() {
+    println!("steps test");
+    App::build()
+        .add_plugins(MinimalPlugins)
+        .add_plugin(BigBrainPlugin)
+        .init_resource::<GlobalState>()
+        .add_startup_system(setup.system())
+        .add_system_to_stage(CoreStage::First, no_failure_score.system())
+        .add_system(action1.system())
+        .add_system(action2.system())
+        .add_system(exit_action.system())
+        .add_system(failure_action.system())
+        .add_system_to_stage(CoreStage::Last, last.system())
+        .run();
+    println!("end");
+}
+
+fn setup(mut cmds: Commands) {
+    cmds.spawn().insert(
+        Thinker::build()
+            .picker(pickers::FirstToScore::new(0.5))
+            .when(NoFailureScore, Steps::build().step(FailureAction))
+            .otherwise(Steps::build().step(Action1).step(Action2).step(ExitAction)),
+    );
+}
+
+#[derive(Default, Debug, Clone)]
+struct Action1;
+impl ActionBuilder for Action1 {
+    fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
+        cmd.entity(action)
+            .insert(self.clone())
+            .insert(ActionState::Requested);
+    }
+}
+
+fn action1(mut query: Query<(&Actor, &mut ActionState), With<Action1>>) {
+    for (Actor(_actor), mut state) in query.iter_mut() {
+        println!("action1 {:?}", state);
+        if *state == ActionState::Requested {
+            *state = ActionState::Executing;
+        }
+        if *state == ActionState::Executing {
+            *state = ActionState::Success;
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone)]
+struct Action2;
+impl ActionBuilder for Action2 {
+    fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
+        cmd.entity(action)
+            .insert(self.clone())
+            .insert(ActionState::Requested);
+    }
+}
+
+fn action2(mut query: Query<(&Actor, &mut ActionState), With<Action2>>) {
+    for (Actor(_actor), mut state) in query.iter_mut() {
+        println!("action2 {:?}", state);
+        if *state == ActionState::Requested {
+            *state = ActionState::Executing;
+        }
+        if *state == ActionState::Executing {
+            *state = ActionState::Success;
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone)]
+struct ExitAction;
+impl ActionBuilder for ExitAction {
+    fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
+        cmd.entity(action)
+            .insert(self.clone())
+            .insert(ActionState::Requested);
+    }
+}
+
+fn exit_action(
+    mut query: Query<(&Actor, &mut ActionState), With<ExitAction>>,
+    mut app_exit_events: EventWriter<AppExit>,
+) {
+    for (Actor(_actor), mut state) in query.iter_mut() {
+        println!("exit_action {:?}", state);
+        if *state == ActionState::Requested {
+            *state = ActionState::Executing;
+        }
+        if *state == ActionState::Executing {
+            app_exit_events.send(AppExit);
+        }
+    }
+}
+
+fn last() {
+    println!();
+}
+
+#[derive(Default, Debug, Clone)]
+struct FailureAction;
+impl ActionBuilder for FailureAction {
+    fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
+        cmd.entity(action)
+            .insert(self.clone())
+            .insert(ActionState::Requested);
+    }
+}
+
+fn failure_action(
+    mut query: Query<(&Actor, &mut ActionState), With<FailureAction>>,
+    mut global_state: ResMut<GlobalState>,
+) {
+    for (Actor(_actor), mut state) in query.iter_mut() {
+        println!("failure_action {:?}", state);
+        if *state == ActionState::Requested {
+            *state = ActionState::Executing;
+        }
+        if *state == ActionState::Executing {
+            global_state.failure = true;
+            *state = ActionState::Failure;
+        }
+    }
+}
+
+#[derive(Default)]
+struct GlobalState {
+    failure: bool,
+}
+
+#[derive(Debug, Clone)]
+struct NoFailureScore;
+impl ScorerBuilder for NoFailureScore {
+    fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
+        cmd.entity(action).insert(self.clone());
+    }
+}
+
+fn no_failure_score(
+    mut query: Query<(&NoFailureScore, &mut Score)>,
+    global_state: Res<GlobalState>,
+) {
+    for (_, mut score) in query.iter_mut() {
+        score.set(if global_state.failure { 0.0 } else { 1.0 });
+    }
+}
-- 
GitLab