Hierarchical use of Machinery decision maker from emergent crate

By default all emergent decision makers are designed to be used in hierarchies so building HFSM is really simple - you just put one Machinery as a state in another. What's new comparing to the flat Machinery setup is we have to assign initial state decision maker that whenever Machinery gets activated, it will activate some starting state for that Machinery.

#![allow(unused)]
fn main() {
extern crate emergent;
use emergent::prelude::*;
use std::hash::Hash;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Target {
  None,
  Found,
  Reached,
}

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
enum EnemyState {
  Patrol,
  Combat,
  FindWaypoint,
  WalkTowardsWaypoint,
  WalkTowardsPlayer,
  AttackPlayer,
}

struct EnemyData {
  waypoint: Target,
  player: Target,
}

struct Enemy {
  data: EnemyData,
  machinery: Machinery<EnemyData, EnemyState>,
}

impl Enemy {
  fn new() -> Self {
    let mut data = EnemyData {
      waypoint: Target::None,
      player: Target::None,
    };

    let patrol = MachineryBuilder::default()
      .state(
        EnemyState::FindWaypoint,
        MachineryState::task(EnemyFindWaypointTask)
          .change(MachineryChange::new(
            EnemyState::WalkTowardsWaypoint,
            ClosureCondition::new(waypoint_found),
          )),
      )
      .state(
        EnemyState::WalkTowardsWaypoint,
        MachineryState::task(EnemyWalkTowardsWaypointTask)
          .change(MachineryChange::new(
            EnemyState::FindWaypoint,
            ClosureCondition::new(waypoint_reached),
          )),
      )
      .build()
      // We assign initial state decision maker if we want to make sure that
      // whenever machinery gets activated it will start at some state (useful
      // when building hierarchies, by default machineries start with no active
      // state hence we assign initialization decision maker to automate setting
      // initial state).
      .initial_state_decision_maker(
        SingleDecisionMaker::new(EnemyState::FindWaypoint),
      );

    let combat = MachineryBuilder::default()
      .state(
        EnemyState::WalkTowardsPlayer,
        MachineryState::task(EnemyWalkTowardsPlayerTask)
          .change(MachineryChange::new(
            EnemyState::AttackPlayer,
            ClosureCondition::new(player_reached),
          )),
      )
      .state(
        EnemyState::AttackPlayer,
        MachineryState::task(EnemyAttackPlayerTask)
          .change(MachineryChange::new(
            EnemyState::WalkTowardsPlayer,
            ClosureCondition::new(player_found),
          )),
      )
      .build()
      .initial_state_decision_maker(
        SingleDecisionMaker::new(EnemyState::WalkTowardsPlayer),
      );

    let mut machinery = MachineryBuilder::default()
      .state(
        EnemyState::Patrol,
        MachineryState::task(patrol)
          .change(MachineryChange::new(
            EnemyState::Combat,
            ClosureCondition::new(player_found),
          )),
      )
      .state(
        EnemyState::Combat,
        MachineryState::task(combat)
          .change(MachineryChange::new(
            EnemyState::Patrol,
            ClosureCondition::new(player_dead),
          )),
      )
      .build()
      .initial_state_decision_maker(
        SingleDecisionMaker::new(EnemyState::Patrol),
      );

    // Since we have assigned initial state decision maker we can activate root
    // machinery to activate its initial state.
    machinery.on_enter(&mut data);
    Self { data, machinery }
  }

  fn tick(&mut self) {
    self.machinery.process(&mut self.data);
    self.machinery.update(&mut self.data);
  }
}

// Condition functions that are used in testing possible state changes:

fn waypoint_found(data: &EnemyData) -> bool {
  data.waypoint == Target::Found
}

fn waypoint_reached(data: &EnemyData) -> bool {
  data.waypoint == Target::Reached
}

fn player_found(data: &EnemyData) -> bool {
  data.player == Target::Found
}

fn player_reached(data: &EnemyData) -> bool {
  data.player == Target::Reached
}

fn player_dead(data: &EnemyData) -> bool {
  data.player == Target::None
}

// Enemy state tasks that will process enemy data:

struct EnemyFindWaypointTask;

impl Task<EnemyData> for EnemyFindWaypointTask {
  fn on_enter(&mut self, memory: &mut EnemyData) {
    memory.waypoint = Target::Found;
  }
}

struct EnemyWalkTowardsWaypointTask;

impl Task<EnemyData> for EnemyWalkTowardsWaypointTask {
  fn on_enter(&mut self, memory: &mut EnemyData) {
    memory.waypoint = Target::Reached;
  }
}

struct EnemyWalkTowardsPlayerTask;

impl Task<EnemyData> for EnemyWalkTowardsPlayerTask {
  fn on_enter(&mut self, memory: &mut EnemyData) {
    memory.player = Target::Reached;
  }
}

struct EnemyAttackPlayerTask;

impl Task<EnemyData> for EnemyAttackPlayerTask {
  fn on_enter(&mut self, memory: &mut EnemyData) {
    memory.player = Target::None;
  }
}

let mut enemy = Enemy::new();

enemy.tick();
assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Patrol));
assert_eq!(enemy.data.waypoint, Target::Reached);
assert_eq!(enemy.data.player, Target::None);
enemy.tick();
assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Patrol));
assert_eq!(enemy.data.waypoint, Target::Found);
assert_eq!(enemy.data.player, Target::None);

enemy.data.player = Target::Found;

enemy.tick();
assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Combat));
assert_eq!(enemy.data.waypoint, Target::Found);
assert_eq!(enemy.data.player, Target::Reached);
enemy.tick();
assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Combat));
assert_eq!(enemy.data.waypoint, Target::Found);
assert_eq!(enemy.data.player, Target::None);
enemy.tick();
assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Patrol));
assert_eq!(enemy.data.waypoint, Target::Found);
assert_eq!(enemy.data.player, Target::None);
enemy.tick();
assert_eq!(enemy.machinery.active_state(), Some(&EnemyState::Patrol));
assert_eq!(enemy.data.waypoint, Target::Reached);
assert_eq!(enemy.data.player, Target::None);
}