Skip to content
Snippets Groups Projects
thinker.rs 12.7 KiB
Newer Older
/*!
Thinkers are the "brain" of an entity. You attach Scorers to it, and the Thinker picks the right Action to run based on the resulting Scores.
*/

use std::sync::Arc;
use bevy::{
    prelude::*,
    utils::{Duration, Instant},
};
Kat Marchán's avatar
Kat Marchán committed

use crate::{
    actions::{self, ActionBuilder, ActionBuilderWrapper, ActionState},
Kat Marchán's avatar
Kat Marchán committed
    choices::{Choice, ChoiceBuilder},
    pickers::Picker,
    scorers::{Score, ScorerBuilder},
/**
Wrapper for Actor entities. In terms of Scorers, Thinkers, and Actions, this is the [`Entity`] actually _performing_ the action, rather than the entity a Scorer/Thinker/Action is attached to. Generally, you will use this entity when writing Queries for Action and Scorer systems.
 */
#[derive(Debug, Clone, Component, Copy)]
pub struct Actor(pub Entity);

#[derive(Debug, Clone, Component, Copy)]
pub(crate) struct ActionEnt(pub Entity);
#[derive(Debug, Clone, Component, Copy)]
pub(crate) struct ScorerEnt(pub Entity);

/**
The "brains" behind this whole operation. A `Thinker` is what glues together `Actions` and `Scorers` and shapes larger, intelligent-seeming systems.

Note: Thinkers are also Actions, so anywhere you can pass in an Action (or [`ActionBuilder`]), you can pass in a Thinker (or [`ThinkerBuilder`]).
### Example

```no_run
pub fn init_entities(mut cmd: Commands) {
    cmd.spawn()
        .insert(Thirst::new(70.0, 2.0))
        .insert(Hunger::new(50.0, 3.0))
        .insert(
            Thinker::build()
                .picker(FirstToScore::new(80.0))
                .when(Thirsty::build(), Drink::build())
                .when(Hungry::build(), Eat::build())
                .otherwise(Meander::build()),
        );
}
```
 */
#[derive(Component, Debug)]
Kat Marchán's avatar
Kat Marchán committed
pub struct Thinker {
    picker: Arc<dyn Picker>,
    otherwise: Option<ActionBuilderWrapper>,
    choices: Vec<Choice>,
    current_action: Option<(ActionEnt, ActionBuilderWrapper)>,
Kat Marchán's avatar
Kat Marchán committed
}

impl Thinker {
    /**
    Make a new [`ThinkerBuilder`]. This is what you'll actually use to configure Thinker behavior.
     */
    pub fn build() -> ThinkerBuilder {
        ThinkerBuilder::new()
/**
This is what you actually use to configure Thinker behavior. It's a plain old [`ActionBuilder`], as well.
 */
#[derive(Component, Debug, Default)]
pub struct ThinkerBuilder {
    picker: Option<Arc<dyn Picker>>,
    otherwise: Option<ActionBuilderWrapper>,
    choices: Vec<ChoiceBuilder>,
impl ThinkerBuilder {
    pub(crate) fn new() -> Self {
        Self {
            picker: None,
            otherwise: None,
            choices: Vec::new(),
        }
    /**
    Define a [`Picker`](crate::pickers::Picker) for this Thinker.
     */
    pub fn picker(mut self, picker: impl Picker + 'static) -> Self {
        self.picker = Some(Arc::new(picker));
        self
    }

    /**
    Define an [`ActionBuilder`](crate::actions::ActionBuilder) and [`ScorerBuilder`](crate::scorers::ScorerBuilder) pair.
     */
    pub fn when(
        scorer: impl ScorerBuilder + 'static,
        action: impl ActionBuilder + 'static,
    ) -> Self {
        self.choices
            .push(ChoiceBuilder::new(Arc::new(scorer), Arc::new(action)));
    /**
    Default `Action` to execute if the `Picker` did not pick any of the given choices.
     */
    pub fn otherwise(mut self, otherwise: impl ActionBuilder + 'static) -> Self {
        self.otherwise = Some(ActionBuilderWrapper::new(Arc::new(otherwise)));
        self
impl ActionBuilder for ThinkerBuilder {
    fn build(&self, cmd: &mut Commands, action_ent: Entity, actor: Entity) {
        let choices = self
            .choices
            .map(|choice| choice.build(cmd, actor, action_ent))
            .collect();
        cmd.entity(action_ent)
            .insert(Thinker {
                // TODO: reasonable default?...
                picker: self
                    .picker
                    .clone()
                    .expect("ThinkerBuilder must have a Picker"),
                choices,
                otherwise: self.otherwise.clone(),
                current_action: None,
            })
Kat Marchán's avatar
Kat Marchán committed
            .insert(Name::new("Thinker"))
            .insert(ActionState::Requested);
pub fn thinker_component_attach_system(
    mut cmd: Commands,
    q: Query<(Entity, &ThinkerBuilder), Without<HasThinker>>,
    for (entity, thinker_builder) in q.iter() {
        let thinker = thinker_builder.attach(&mut cmd, entity);
        cmd.entity(entity).insert(HasThinker(thinker));
    }
}

pub fn thinker_component_detach_system(
    mut cmd: Commands,
    q: Query<(Entity, &HasThinker), Without<ThinkerBuilder>>,
) {
    for (actor, HasThinker(thinker)) in q.iter() {
        cmd.entity(*thinker).despawn_recursive();
        cmd.entity(actor).remove::<HasThinker>();
    }
}

pub fn actor_gone_cleanup(
    mut cmd: Commands,
    actors: Query<&ThinkerBuilder>,
    q: Query<(Entity, &Actor)>,
) {
    for (child, Actor(actor)) in q.iter() {
        if actors.get(*actor).is_err() {
            // Actor is gone. Let's clean up.
            cmd.entity(child).despawn_recursive();
        }
    }
}

#[derive(Component, Debug)]
pub struct HasThinker(Entity);

pub struct ThinkerIterations {
Kat Marchán's avatar
Kat Marchán committed
    index: usize,
    max_duration: Duration,
}
impl ThinkerIterations {
Kat Marchán's avatar
Kat Marchán committed
    pub fn new(max_duration: Duration) -> Self {
        Self {
            index: 0,
            max_duration,
        }
    }
}
impl Default for ThinkerIterations {
    fn default() -> Self {
        Self::new(Duration::from_millis(10))
    }
}
pub fn thinker_system(
    mut cmd: Commands,
    mut iterations: Local<ThinkerIterations>,
    mut thinker_q: Query<(Entity, &Actor, &mut Thinker)>,
    scores: Query<&Score>,
    mut action_states: Query<&mut actions::ActionState>,
) {
    let start = Instant::now();
    for (thinker_ent, Actor(actor), mut thinker) in thinker_q.iter_mut().skip(iterations.index) {
        iterations.index += 1;
        let thinker_state = action_states
            .get_mut(thinker_ent)
            .expect("Where is it?")
            .clone();
        match thinker_state {
            ActionState::Init => {
                let mut act_state = action_states.get_mut(thinker_ent).expect("???");
                *act_state = ActionState::Requested;
            ActionState::Requested => {
                let mut act_state = action_states.get_mut(thinker_ent).expect("???");
                *act_state = ActionState::Executing;
            }
            ActionState::Success | ActionState::Failure => {}
            ActionState::Cancelled => {
                if let Some(current) = &mut thinker.current_action {
                    let state = action_states.get_mut(current.0.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.").clone();
                    match state {
                        ActionState::Success | ActionState::Failure => {
                            cmd.entity(current.0 .0).despawn_recursive();
                            thinker.current_action = None;
                        }
                        _ => {
                            let mut state = action_states.get_mut(current.0.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
                            *state = ActionState::Cancelled;
                } else {
                    let mut act_state = action_states.get_mut(thinker_ent).expect("???");
                    *act_state = ActionState::Success;
Kat Marchán's avatar
Kat Marchán committed
                }
                if let Some(choice) = thinker.picker.pick(&thinker.choices, &scores) {
                    // Think about what action we're supposed to be taking. We do this
                    // every tick, because we might change our mind.
                    // ...and then execute it (details below).
                    exec_picked_action(
                        &mut cmd,
                        *actor,
                        &mut thinker,
                        &choice.action,
                        &mut action_states,
                    );
                } else if let Some(default_action_ent) = &thinker.otherwise {
                    // Otherwise, let's just execute the default one! (if it's there)
                    let default_action_ent = default_action_ent.clone();
                    exec_picked_action(
                        &mut cmd,
                        *actor,
                        &mut thinker,
                        &default_action_ent,
                        &mut action_states,
                    );
                } else if let Some(current) = &mut thinker.current_action {
                    // If we didn't pick anything, and there's no default action,
                    // we need to see if there's any action currently executing,
                    // and cancel it. We also use this opportunity to clean up
                    // stale action components so they don't slow down joins.
                    let mut state = action_states.get_mut(current.0.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
                    match *state {
                        actions::ActionState::Init
                        | actions::ActionState::Success
                        | actions::ActionState::Failure => {
                            cmd.entity(current.0 .0).despawn_recursive();
                            thinker.current_action = None;
                        }
                        _ => {
                            *state = ActionState::Cancelled;
        if iterations.index % 500 == 0 && start.elapsed() > iterations.max_duration {
            return;
        }
Kat Marchán's avatar
Kat Marchán committed
    }
    iterations.index = 0;
Kat Marchán's avatar
Kat Marchán committed
}

fn exec_picked_action(
    cmd: &mut Commands,
    actor: Entity,
    thinker: &mut Mut<Thinker>,
    picked_action: &ActionBuilderWrapper,
    states: &mut Query<&mut ActionState>,
Kat Marchán's avatar
Kat Marchán committed
) {
    // If we do find one, then we need to grab the corresponding
    // component for it. The "action" that `picker.pick()` returns
    // is just a newtype for an Entity.
    //

    // Now we check the current action. We need to check if we picked the same one as the previous tick.
    //
    // TODO: I don't know where the right place to put this is
    // (maybe not in this logic), but we do need some kind of
    // oscillation protection so we're not just bouncing back and
    // forth between the same couple of actions.
    if let Some((action_ent, ActionBuilderWrapper(current_id, _))) = &mut thinker.current_action {
        let mut curr_action_state = states.get_mut(action_ent.0).expect("Couldn't find a component corresponding to the current action. This is definitely a bug.");
        if !Arc::ptr_eq(current_id, &picked_action.0)
            || matches!(*curr_action_state, ActionState::Success)
            || matches!(*curr_action_state, ActionState::Failure)
        {
Kat Marchán's avatar
Kat Marchán committed
            // So we've picked a different action than we were
            // currently executing. Just like before, we grab the
            // actual Action component (and we assume it exists).
            // If the action is executing, or was requested, we
Kat Marchán's avatar
Kat Marchán committed
            // need to cancel it to make sure it stops.
            match *curr_action_state {
Kat Marchán's avatar
Kat Marchán committed
                ActionState::Executing | ActionState::Requested => {
                    *curr_action_state = ActionState::Cancelled;
                }
                ActionState::Init | ActionState::Success | ActionState::Failure => {
Kat Marchán's avatar
Kat Marchán committed
                    // Despawn the action itself.
                    cmd.entity(action_ent.0).despawn_recursive();
                    thinker.current_action = Some((
                        ActionEnt(picked_action.1.attach(cmd, actor)),
                        picked_action.clone(),
                    ));
Kat Marchán's avatar
Kat Marchán committed
                }
                ActionState::Cancelled => {}
            };
        } else {
            // Otherwise, it turns out we want to keep executing
            // the same action. Just in case, we go ahead and set
            // it as Requested if for some reason it had finished
            // but the Action System hasn't gotten around to
            // cleaning it up.
            if *curr_action_state == ActionState::Init {
                *curr_action_state = ActionState::Requested;
Kat Marchán's avatar
Kat Marchán committed
            }
        }
    } else {
        // This branch arm is called when there's no
        // current_action in the thinker. The logic here is pretty
        // straightforward -- we set the action, Request it, and
        // that's it.
        let new_action = picked_action.1.attach(cmd, actor);
        thinker.current_action = Some((ActionEnt(new_action), picked_action.clone()));