Struct emergent::decision_makers::machinery::Machinery
source · pub struct Machinery<M = (), K = DefaultKey>{ /* 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>
impl<M, K> Machinery<M, K>
sourcepub fn new(states: HashMap<K, MachineryState<M, K>>) -> Self
pub fn new(states: HashMap<K, MachineryState<M, K>>) -> Self
Construct new machinery with states.
sourcepub fn initial_state_decision_maker<DM>(self, decision_maker: DM) -> Selfwhere
DM: DecisionMaker<M, K> + 'static,
pub fn initial_state_decision_maker<DM>(self, decision_maker: DM) -> Selfwhere
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.
sourcepub fn initial_state_decision_maker_raw(
self,
decision_maker: Box<dyn DecisionMaker<M, K>>
) -> Self
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.
sourcepub fn active_state(&self) -> Option<&K>
pub fn active_state(&self) -> Option<&K>
Returns currently active state ID.
sourcepub fn change_active_state(
&mut self,
id: Option<K>,
memory: &mut M,
forced: bool
) -> Result<bool, MachineryError<K>>
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.