//! Defines Action-related functionality. This module includes the
//! ActionBuilder trait and some Composite Actions for utility.
use std::sync::Arc;
use bevy::prelude::*;
#[cfg(feature = "trace")]
use bevy::utils::tracing::trace;
use crate::thinker::{Action, ActionSpan, Actor};
/// The current state for an Action. These states are changed by a combination
/// of the Thinker that spawned it, and the actual Action system executing the
/// Action itself.
///
/// Action system implementors should be mindful of taking appropriate action
/// on all of these states, and be particularly careful when ignoring
/// variants.
#[derive(Debug, Clone, Component, Eq, PartialEq, Reflect)]
#[component(storage = "SparseSet")]
pub enum ActionState {
/// Initial state. No action should be performed.
Init,
/// Action requested. The Action-handling system should start executing
/// this Action ASAP and change the status to the next state.
Requested,
/// The action has ongoing execution. The associated Thinker will try to
/// keep executing this Action as-is until it changes state or it gets
/// Cancelled.
Executing,
/// An ongoing Action has been cancelled. The Thinker might set this
/// action for you, so for Actions that execute for longer than a single
/// tick, **you must check whether the Cancelled state was set** and
/// change do either Success or Failure. Thinkers will wait on Cancelled
/// actions to do any necessary cleanup work, so this can hang your AI if
/// you don't look for it.
Cancelled,
/// The Action was a success. This is used by Composite Actions to
/// determine whether to continue execution.
Success,
/// The Action failed. This is used by Composite Actions to determine
/// whether to halt execution.
Failure,
}
impl Default for ActionState {
fn default() -> Self {
Self::Init
}
}
impl ActionState {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct ActionBuilderId;
#[derive(Debug, Clone)]
pub(crate) struct ActionBuilderWrapper(pub Arc<ActionBuilderId>, pub Arc<dyn ActionBuilder>);
impl ActionBuilderWrapper {
pub fn new(builder: Arc<dyn ActionBuilder>) -> Self {
ActionBuilderWrapper(Arc::new(ActionBuilderId), builder)
}
}
/// Trait that must be defined by types in order to be `ActionBuilder`s.
/// `ActionBuilder`s' job is to spawn new `Action` entities on demand. In
/// general, most of this is already done for you, and the only method you
/// really have to implement is `.build()`.
///
/// The `build()` method MUST be implemented for any `ActionBuilder`s you want
/// to define.
#[reflect_trait]
pub trait ActionBuilder: std::fmt::Debug + Send + Sync {
/// MUST insert your concrete Action component into the Scorer [`Entity`],
/// using `cmd`. You _may_ use `actor`, but it's perfectly normal to just
/// ignore it.
///
/// In most cases, your `ActionBuilder` and `Action` can be the same type.
/// The only requirement is that your struct implements `Debug`,
/// `Component, `Clone`. You can then use the derive macro `ActionBuilder`
/// to turn your struct into a `ActionBuilder`
///
/// ### Example
///
/// Using the derive macro (the easy way):
///
/// ```
/// # use bevy::prelude::*;
/// # use big_brain::prelude::*;
/// #[derive(Debug, Clone, Component, ActionBuilder)]
/// #[action_label = "MyActionLabel"] // Optional. Defaults to type name.
/// struct MyAction;
/// ```
///
/// Implementing it manually:
///
/// ```
/// # use bevy::prelude::*;
/// # use big_brain::prelude::*;
/// #[derive(Debug)]
/// struct MyBuilder;
/// #[derive(Debug, Component)]
/// struct MyAction;
///
/// impl ActionBuilder for MyBuilder {
/// fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) {
/// cmd.entity(action).insert(MyAction);
/// }
/// }
/// ```
fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity);
/**
* A label to display when logging using the Action's tracing span.
*/
fn label(&self) -> Option<&str> {
None
}
}
/// Spawns a new Action Component, using the given ActionBuilder. This is
/// useful when you're doing things like writing composite Actions.
pub fn spawn_action<T: ActionBuilder + ?Sized>(
builder: &T,
cmd: &mut Commands,
actor: Entity,
) -> Entity {
let action_ent = Action(cmd.spawn_empty().id());
let span = ActionSpan::new(action_ent.entity(), ActionBuilder::label(builder));
let _guard = span.span().enter();
debug!("New Action spawned.");
cmd.entity(action_ent.entity())
.insert(Name::new("Action"))
.insert(ActionState::new())
.insert(Actor(actor));
builder.build(cmd, action_ent.entity(), actor);
std::mem::drop(_guard);
cmd.entity(action_ent.entity()).insert(span);
action_ent.entity()
}
/// [`ActionBuilder`] for the [`Steps`] component. Constructed through
/// `Steps::build()`.
#[derive(Debug, Reflect)]
#[reflect(ActionBuilder)]
pub struct StepsBuilder {
label: Option<String>,
steps_labels: Vec<String>,
#[reflect(ignore)]
steps: Vec<Arc<dyn ActionBuilder>>,
}
impl StepsBuilder {
/// Sets the logging label for the Action
pub fn label<S: Into<String>>(mut self, label: S) -> Self {
self.label = Some(label.into());
self
}
/// Adds an action step. Order matters.
pub fn step(mut self, action_builder: impl ActionBuilder + 'static) -> Self {
if let Some(label) = action_builder.label() {
self.steps_labels.push(label.into());
} else {
self.steps_labels.push("Unlabeled Action".into());
}
self.steps.push(Arc::new(action_builder));
self
}
}
impl ActionBuilder for StepsBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref()
}
fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) {
if let Some(step) = self.steps.get(0) {
let child_action = spawn_action(step.as_ref(), cmd, actor);
cmd.entity(action)
.insert(Name::new("Steps Action"))
.insert(Steps {
active_step: 0,
active_ent: Action(child_action),
steps: self.steps.clone(),
steps_labels: self.steps_labels.clone(),
})
.push_children(&[child_action]);
}
}
}
/// Composite Action that executes a series of steps in sequential order, as
/// long as each step results in a `Success`ful [`ActionState`].
///
/// ### Example
///
/// ```
/// # use bevy::prelude::*;
/// # use big_brain::prelude::*;
/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
/// # struct MyScorer;
/// # #[derive(Debug, Clone, Component, ActionBuilder)]
/// # struct MyAction;
/// # #[derive(Debug, Clone, Component, ActionBuilder)]
/// # struct MyNextAction;
/// # fn main() {
/// Thinker::build()
/// .when(
/// MyScorer,
/// Steps::build()
/// .step(MyAction)
/// .step(MyNextAction)
/// )
/// # ;
/// # }
/// ```
#[derive(Component, Debug, Reflect)]
pub struct Steps {
#[reflect(ignore)]
steps: Vec<Arc<dyn ActionBuilder>>,
steps_labels: Vec<String>,
active_step: usize,
active_ent: Action,
}
impl Steps {
/// Construct a new [`StepsBuilder`] to define the steps to take.
pub fn build() -> StepsBuilder {
StepsBuilder {
steps: Vec::new(),
steps_labels: Vec::new(),
label: None,
}
}
}
/// System that takes care of executing any existing [`Steps`] Actions.
pub fn steps_system(
mut cmd: Commands,
mut steps_q: Query<(Entity, &Actor, &mut Steps, &ActionSpan)>,
mut states: Query<&mut ActionState>,
) {
use ActionState::*;
for (seq_ent, Actor(actor), mut steps_action, _span) in steps_q.iter_mut() {
let active_ent = steps_action.active_ent.entity();
let current_state = states.get_mut(seq_ent).unwrap().clone();
#[cfg(feature = "trace")]
let _guard = _span.span().enter();
match current_state {
Requested => {
// Begin at the beginning
#[cfg(feature = "trace")]
trace!(
"Initializing StepsAction and requesting first step: {:?}",
active_ent
);
*states.get_mut(active_ent).unwrap() = Requested;
*states.get_mut(seq_ent).unwrap() = Executing;
}
Executing => {
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... :)
*step_state = Requested;
}
Executing | Requested => {
// do nothing. Everything's running as it should.
}
Cancelled => {
// Wait for the step to wrap itself up, and we'll decide what to do at that point.
}
Failure => {
// Fail ourselves
#[cfg(feature = "trace")]
trace!("Step {:?} failed. Failing entire StepsAction.", active_ent);
let step_state = step_state.clone();
let mut seq_state = states.get_mut(seq_ent).expect("idk");
*seq_state = step_state;
cmd.entity(steps_action.active_ent.entity())
.despawn_recursive();
}
Success if steps_action.active_step == steps_action.steps.len() - 1 => {
// We're done! Let's just be successful
#[cfg(feature = "trace")]
trace!("StepsAction completed all steps successfully.");
let step_state = step_state.clone();
let mut seq_state = states.get_mut(seq_ent).expect("idk");
*seq_state = step_state;
cmd.entity(steps_action.active_ent.entity())
.despawn_recursive();
}
Success => {
#[cfg(feature = "trace")]
trace!("Step succeeded, but there's more steps. Spawning next action.");
// Deactivate current step and go to the next step
cmd.entity(steps_action.active_ent.entity())
.despawn_recursive();
steps_action.active_step += 1;
let step_builder = steps_action.steps[steps_action.active_step].clone();
let step_ent = spawn_action(step_builder.as_ref(), &mut cmd, *actor);
#[cfg(feature = "trace")]
trace!("Spawned next step: {:?}", step_ent);
cmd.entity(seq_ent).push_children(&[step_ent]);
steps_action.active_ent = Action(step_ent);
}
}
}
Cancelled => {
// Cancel current action
#[cfg(feature = "trace")]
trace!("StepsAction has been cancelled. Cancelling current step {:?} before finalizing.", active_ent);
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.
}
}
}
}
/// Configures what mode the [`Concurrently`] action will run in.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Reflect)]
pub enum ConcurrentMode {
/// Reaches success when any of the concurrent actions reaches [`ActionState::Success`].
Race,
/// Reaches success when all of the concurrent actions reach [`ActionState::Success`].
Join,
}
/// [`ActionBuilder`] for the [`Concurrently`] component. Constructed through
/// `Concurrently::build()`.
#[derive(Debug, Reflect)]
pub struct ConcurrentlyBuilder {
mode: ConcurrentMode,
#[reflect(ignore)]
actions: Vec<Arc<dyn ActionBuilder>>,
action_labels: Vec<String>,
label: Option<String>,
}
impl ConcurrentlyBuilder {
/// Sets the logging label for the Action
pub fn label<S: Into<String>>(mut self, label: S) -> Self {
self.label = Some(label.into());
self
}
/// Add an action to execute. Order does not matter.
pub fn push(mut self, action_builder: impl ActionBuilder + 'static) -> Self {
if let Some(label) = action_builder.label() {
self.action_labels.push(label.into());
} else {
self.action_labels.push("Unnamed Action".into());
}
self.actions.push(Arc::new(action_builder));
self
}
/// Sets the [`ConcurrentMode`] for this action.
pub fn mode(mut self, mode: ConcurrentMode) -> Self {
self.mode = mode;
self
}
}
impl ActionBuilder for ConcurrentlyBuilder {
fn label(&self) -> Option<&str> {
self.label.as_deref()
}
fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) {
let children: Vec<Entity> = self
.actions
.iter()
.map(|action| spawn_action(action.as_ref(), cmd, actor))
.collect();
cmd.entity(action)
.insert(Name::new("Concurrent Action"))
.push_children(&children[..])
.insert(Concurrently {
actions: children.into_iter().map(Action).collect(),
action_labels: self.action_labels.clone(),
mode: self.mode,
});
}
}
/// Composite Action that executes a number of Actions concurrently. Whether
/// this action succeeds depends on its [`ConcurrentMode`]:
///
/// * [`ConcurrentMode::Join`] (default) succeeds when **all** of the actions
/// succeed.
/// * [`ConcurrentMode::Race`] succeeds when **any** of the actions succeed.
///
/// ### Example
///
/// ```
/// # use bevy::prelude::*;
/// # use big_brain::prelude::*;
/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
/// # struct MyScorer;
/// # #[derive(Debug, Clone, Component, ActionBuilder)]
/// # struct MyAction;
/// # #[derive(Debug, Clone, Component, ActionBuilder)]
/// # struct MyOtherAction;
/// # fn main() {
/// Thinker::build()
/// .when(
/// MyScorer,
/// Concurrently::build()
/// .push(MyAction)
/// .push(MyOtherAction)
/// )
/// # ;
/// # }
/// ```
///
#[derive(Component, Debug, Reflect)]
pub struct Concurrently {
mode: ConcurrentMode,
actions: Vec<Action>,
action_labels: Vec<String>,
}
impl Concurrently {
/// Construct a new [`ConcurrentlyBuilder`] to define the actions to take.
pub fn build() -> ConcurrentlyBuilder {
ConcurrentlyBuilder {
actions: Vec::new(),
action_labels: Vec::new(),
mode: ConcurrentMode::Join,
label: None,
}
}
}
/// System that takes care of executing any existing [`Concurrently`] Actions.
pub fn concurrent_system(
concurrent_q: Query<(Entity, &Concurrently, &ActionSpan)>,
mut states_q: Query<&mut ActionState>,
) {
use ActionState::*;
for (seq_ent, concurrent_action, _span) in concurrent_q.iter() {
let current_state = states_q.get_mut(seq_ent).expect("uh oh").clone();
#[cfg(feature = "trace")]
let _guard = _span.span.enter();
match current_state {
Requested => {
#[cfg(feature = "trace")]
trace!(
"Initializing Concurrently action with {} children.",
concurrent_action.actions.len()
);
// Begin at the beginning
let mut current_state = states_q.get_mut(seq_ent).expect("uh oh");
*current_state = Executing;
for action in concurrent_action.actions.iter() {
let child_ent = action.entity();
let mut child_state = states_q.get_mut(child_ent).expect("uh oh");
*child_state = Requested;
}
}
Executing => match concurrent_action.mode {
ConcurrentMode::Join => {
let mut all_success = true;
let mut failed_idx = None;
for (idx, action) in concurrent_action.actions.iter().enumerate() {
let child_ent = action.entity();
let mut child_state = states_q.get_mut(child_ent).expect("uh oh");
match *child_state {
Failure => {
failed_idx = Some(idx);
all_success = false;
#[cfg(feature = "trace")]
trace!("Join action has failed. Cancelling all other actions that haven't completed yet.");
}
Success => {}
_ => {
all_success = false;
if failed_idx.is_some() {
*child_state = Cancelled;
}
}
}
}
if all_success {
let mut state_var = states_q.get_mut(seq_ent).expect("uh oh");
*state_var = Success;
} else if let Some(idx) = failed_idx {
for action in concurrent_action.actions.iter().take(idx) {
let child_ent = action.entity();
let mut child_state = states_q.get_mut(child_ent).expect("uh oh");
match *child_state {
Failure | Success => {}
_ => {
*child_state = Cancelled;
}
}
}
let mut state_var = states_q.get_mut(seq_ent).expect("uh oh");
*state_var = Failure;
}
}
ConcurrentMode::Race => {
let mut all_failure = true;
let mut succeed_idx = None;
for (idx, action) in concurrent_action.actions.iter().enumerate() {
let child_ent = action.entity();
let mut child_state = states_q.get_mut(child_ent).expect("uh oh");
match *child_state {
Failure => {}
Success => {
succeed_idx = Some(idx);
all_failure = false;
#[cfg(feature = "trace")]
trace!("Race action has succeeded. Cancelling all other actions that haven't completed yet.");
}
_ => {
all_failure = false;
if succeed_idx.is_some() {
*child_state = Cancelled;
}
}
}
}
if all_failure {
let mut state_var = states_q.get_mut(seq_ent).expect("uh oh");
*state_var = Failure;
} else if let Some(idx) = succeed_idx {
for action in concurrent_action.actions.iter().take(idx) {
let child_ent = action.entity();
let mut child_state = states_q.get_mut(child_ent).expect("uh oh");
match *child_state {
Failure | Success => {}
_ => {
*child_state = Cancelled;
}
}
}
let mut state_var = states_q.get_mut(seq_ent).expect("uh oh");
*state_var = Success;
}
}
},
Cancelled => {
// Cancel all actions
let mut all_done = true;
let mut any_failed = false;
let mut any_success = false;
for action in concurrent_action.actions.iter() {
let child_ent = action.entity();
let mut child_state = states_q.get_mut(child_ent).expect("uh oh");
match *child_state {
Init => {}
Success => {
any_success = true;
}
Failure => {
any_failed = true;
}
_ => {
all_done = false;
*child_state = Cancelled;
}
}
}
if all_done {
let mut state_var = states_q.get_mut(seq_ent).expect("uh oh");
match concurrent_action.mode {
ConcurrentMode::Race => {
if any_success {
#[cfg(feature = "trace")]
trace!("Race action has succeeded due to succeeded children.");
*state_var = Success;
} else {
#[cfg(feature = "trace")]
trace!("No race children has completed Successfully.");
*state_var = Failure;
}
}
ConcurrentMode::Join => {
if any_failed {
#[cfg(feature = "trace")]
trace!("Join action has failed due to failed children.");
*state_var = Failure;
} else {
#[cfg(feature = "trace")]
trace!("All Join children have completed Successfully.");
*state_var = Success;
}
}
}
}
}
Init | Success | Failure => {
// Do nothing.
}
}
}
}