pub struct Machinery<M = (), K = DefaultKey>
where K: Clone + Hash + Eq,
{ /* private fields */ }
Expand description

Machinery (a.k.a. Finite State Machine).

Finite state machines are sets of states with possible transitions between them. Each transition contains ID of another state to change into and condition to succeed for that transition to happen. Yes, that’s all - FSM are the simplest AI techique of them all.

How it works

Imagine we define FSM like this:

  • Do nothing:
    • Eat (is hungry?)
    • Sleep (low energy?)
  • Eat:
    • Do nothing (always succeed)
  • Sleep:
    • Do nothing (always succeed)

And we start with initial memory state:

  • hungry: false
  • low energy: false

We start at doing nothing and we test all its transitions, but since agent neither is hungry nor has low energy no change occur.

Let’s change something in the state:

  • hungry: true
  • low energy: true

Now we test possible transitions again and we find that agent is both hungry and has low energy, but since eat state succeeds first, agent is gonna eat something. From that state the only possible change is to do nothing again since it always succeeds.

Although this is a really small network of states and changes, usually the more states it gets, the more connections and at some point we end up with very messy networks and at that point we should switch either to Hierarchical State Machines or to other decision makers that are designed to reduce number of changes.

See https://en.wikipedia.org/wiki/Finite-state_machine

Example

use emergent::prelude::*;
use std::hash::Hash;

#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
enum Action {
    None,
    Eat,
    Sleep,
}

struct IsAction(pub Action);

impl Condition<Action> for IsAction {
    fn validate(&self, memory: &Action) -> bool {
        *memory == self.0
    }
}

let mut machinery = MachineryBuilder::default()
    .state(
        Action::None,
        MachineryState::task(NoTask::default())
            .change(MachineryChange::new(Action::Eat, IsAction(Action::Eat)))
            .change(MachineryChange::new(Action::Sleep, IsAction(Action::Sleep))),
    )
    .state(
        Action::Eat,
        MachineryState::task(NoTask::default())
            .change(MachineryChange::new(Action::None, true)),
    )
    .state(
        Action::Sleep,
        MachineryState::task(NoTask::default())
            .change(MachineryChange::new(Action::None, true)),
    )
    .build();

let mut memory = Action::Eat;
machinery.change_active_state(Some(Action::None), &mut memory, true);
assert!(machinery.process(&mut memory));
assert_eq!(machinery.active_state(), Some(&Action::Eat));
assert!(machinery.process(&mut memory));
assert_eq!(machinery.active_state(), Some(&Action::None));
memory = Action::Sleep;
assert!(machinery.process(&mut memory));
assert_eq!(machinery.active_state(), Some(&Action::Sleep));
assert!(machinery.process(&mut memory));
assert_eq!(machinery.active_state(), Some(&Action::None));

Implementations§

source§

impl<M, K> Machinery<M, K>
where K: Clone + Hash + Eq,

source

pub fn new(states: HashMap<K, MachineryState<M, K>>) -> Self

Construct new machinery with states.

source

pub fn initial_state_decision_maker<DM>(self, decision_maker: DM) -> Self
where DM: DecisionMaker<M, K> + 'static,

Assigns decision maker that will set initial state when machinery gets activated.

This is useful when we want to use machinery in hierarchy.

source

pub fn initial_state_decision_maker_raw( self, decision_maker: Box<dyn DecisionMaker<M, K>> ) -> Self

Assigns decision maker that will set initial state when machinery gets activated.

This is useful when we want to use machinery in hierarchy.

source

pub fn active_state(&self) -> Option<&K>

Returns currently active state ID.

source

pub fn change_active_state( &mut self, id: Option<K>, memory: &mut M, forced: bool ) -> Result<bool, MachineryError<K>>

Change active state.

If currently active state is locked then state change will fail, unless we force it to change.

source

pub fn process(&mut self, memory: &mut M) -> bool

Performs decision making.

source

pub fn update(&mut self, memory: &mut M)

Updates active state.

Trait Implementations§

source§

impl<M, K> Debug for Machinery<M, K>
where K: Clone + Hash + Eq + Debug,

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<M, K> DecisionMaker<M, K> for Machinery<M, K>
where K: Clone + Hash + Eq + Send + Sync,

source§

fn decide(&mut self, memory: &mut M) -> Option<K>

Performs decision making and returns the key of the state it switched into.
source§

fn change_mind(&mut self, id: Option<K>, memory: &mut M) -> bool

Force switch into state (Some activates new state, None deactivates current state). Read more
source§

impl<M, K> Task<M> for Machinery<M, K>
where K: Clone + Hash + Eq + Send + Sync,

source§

fn is_locked(&self, memory: &M) -> bool

Tells if task is locked (it’s still running). Used by decision makers to tell if one can change its state (when current task is not locked).
source§

fn on_enter(&mut self, memory: &mut M)

Action performed when task starts its work.
source§

fn on_exit(&mut self, memory: &mut M)

Action performed when task stops its work.
source§

fn on_update(&mut self, memory: &mut M)

Action performed when task is active and gets updated.
source§

fn on_process(&mut self, memory: &mut M) -> bool

Action performed when task is active but decision maker did not changed its state. This one is applicable for making hierarchical decision makers (telling children decision makers to decide on new state, because some if not all decision makers are tasks). Read more

Auto Trait Implementations§

§

impl<M = (), K = String> !RefUnwindSafe for Machinery<M, K>

§

impl<M, K> Send for Machinery<M, K>
where K: Send,

§

impl<M, K> Sync for Machinery<M, K>
where K: Sync,

§

impl<M, K> Unpin for Machinery<M, K>
where K: Unpin,

§

impl<M = (), K = String> !UnwindSafe for Machinery<M, K>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T, M, K> DecisionMakingTask<M, K> for T
where T: DecisionMaker<M, K> + Task<M>,