#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![allow(clippy::match_single_binding)] #![allow(clippy::match_like_matches_macro)]
pub mod prelude {
pub use crate::{Behavior, BehaviorIndex, BehaviorItem, BehaviorMut, BehaviorRef};
pub use moonshine_kind::{Instance, InstanceCommands};
pub use crate::transition::{
transition, Interrupt, Interruption, Next, Previous, Transition, TransitionQueue,
};
pub use crate::events::{OnActivate, OnPause, OnResume, OnStart, OnStop};
pub use crate::plugin::BehaviorPlugin;
pub use crate::match_next;
#[allow(deprecated)]
pub use crate::transition::TransitionSequence;
}
pub mod events;
pub mod transition;
mod plugin;
#[cfg(test)]
mod tests;
use std::fmt::Debug;
use std::mem::{replace, swap};
use std::ops::{Deref, DerefMut, Index, IndexMut};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::change_detection::{MaybeLocation, Tick};
use bevy_ecs::component::Mutable;
use bevy_ecs::event::EntityTrigger;
use bevy_ecs::{prelude::*, query::QueryData};
use bevy_log::prelude::*;
use bevy_reflect::prelude::*;
use moonshine_kind::prelude::*;
pub use plugin::BehaviorPlugin;
use crate::events::{Activate, Error, Pause, Resume, Start, Stop};
use self::transition::*;
pub trait Behavior: Component<Mutability = Mutable> + Debug + Sized {
fn filter_yield(&self, next: &Self) -> bool {
match_next! {
self => next,
_ => _,
}
}
fn filter_next(&self, next: &Self) -> bool {
match_next! {
self => next,
_ => _,
}
}
fn is_resumable(&self) -> bool {
match self {
_ => true,
}
}
fn on_start(&self, _previous: Option<&Self>, _commands: InstanceCommands<Self>) {}
fn on_pause(&self, _current: &Self, _commands: InstanceCommands<Self>) {}
fn on_resume(&self, _previous: &Self, _commands: InstanceCommands<Self>) {}
fn on_stop(&self, _current: &Self, _commands: InstanceCommands<Self>) {}
fn on_activate(&self, _previous: Option<&Self>, _commands: InstanceCommands<Self>) {}
fn on_suspend(&self, _current: &Self, _commands: InstanceCommands<Self>) {}
}
#[doc(hidden)]
trait BehaviorHooks: Behavior {
fn invoke_start(&self, previous: Option<&Self>, mut commands: InstanceCommands<Self>) {
self.on_start(previous, commands.reborrow());
self.on_activate(previous, commands);
}
fn invoke_pause(&self, current: &Self, mut commands: InstanceCommands<Self>) {
self.on_suspend(current, commands.reborrow());
self.on_pause(current, commands);
}
fn invoke_resume(&self, previous: &Self, mut commands: InstanceCommands<Self>) {
self.on_resume(previous, commands.reborrow());
self.on_activate(Some(previous), commands);
}
fn invoke_stop(&self, current: &Self, mut commands: InstanceCommands<Self>) {
self.on_suspend(current, commands.reborrow());
self.on_stop(current, commands);
}
}
impl<T: Behavior> BehaviorHooks for T {}
pub trait BehaviorItem {
#[doc(hidden)]
type Behavior: Behavior;
fn current(&self) -> &Self::Behavior;
fn previous(&self) -> Option<&Self::Behavior>;
#[deprecated(since = "0.3.1", note = "use `current_index` instead")]
fn index(&self) -> BehaviorIndex {
self.current_index()
}
fn current_index(&self) -> BehaviorIndex;
fn has_index(&self, index: BehaviorIndex) -> bool {
index <= self.current_index()
}
fn enumerate(&self) -> impl Iterator<Item = (BehaviorIndex, &Self::Behavior)> + '_;
fn iter(&self) -> impl Iterator<Item = &Self::Behavior> + '_ {
self.enumerate().map(|(_, behavior)| behavior)
}
fn get(&self, index: BehaviorIndex) -> Option<&Self::Behavior>;
fn has_transition(&self) -> bool;
}
#[derive(QueryData)]
pub struct BehaviorRef<T: Behavior> {
current: Ref<'static, T>,
memory: &'static Memory<T>,
transition: &'static Transition<T>,
}
impl<T: Behavior> BehaviorRef<T> {
pub fn from_entity(entity: EntityRef) -> Option<BehaviorRefItem<T>> {
Some(BehaviorRefItem {
current: entity.get_ref::<T>()?,
memory: entity.get::<Memory<T>>()?,
transition: entity.get::<Transition<T>>()?,
})
}
}
impl<T: Behavior> BehaviorItem for BehaviorRefItem<'_, '_, T> {
type Behavior = T;
fn current(&self) -> &T {
&self.current
}
fn current_index(&self) -> BehaviorIndex {
BehaviorIndex(self.memory.len())
}
fn previous(&self) -> Option<&T> {
self.memory.last()
}
fn enumerate(&self) -> impl Iterator<Item = (BehaviorIndex, &T)> + '_ {
self.memory
.iter()
.enumerate()
.map(|(index, item)| (BehaviorIndex(index), item))
.chain(std::iter::once((self.current_index(), self.current())))
}
fn get(&self, BehaviorIndex(index): BehaviorIndex) -> Option<&T> {
if index == self.memory.stack.len() {
Some(self.current())
} else {
self.memory.get(index)
}
}
fn has_transition(&self) -> bool {
!self.transition.is_none()
}
}
impl<T: Behavior> Deref for BehaviorRefItem<'_, '_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.current()
}
}
impl<T: Behavior> AsRef<T> for BehaviorRefItem<'_, '_, T> {
fn as_ref(&self) -> &T {
&self.current
}
}
impl<T: Behavior> DetectChanges for BehaviorRefItem<'_, '_, T> {
fn is_added(&self) -> bool {
self.current.is_added()
}
fn is_changed(&self) -> bool {
self.current.is_changed()
}
fn last_changed(&self) -> Tick {
self.current.last_changed()
}
fn added(&self) -> Tick {
self.current.added()
}
fn changed_by(&self) -> MaybeLocation {
self.current.changed_by()
}
}
impl<T: Behavior> Index<BehaviorIndex> for BehaviorRefItem<'_, '_, T> {
type Output = T;
fn index(&self, BehaviorIndex(index): BehaviorIndex) -> &Self::Output {
if index == self.memory.stack.len() {
self.current()
} else {
&self.memory[index]
}
}
}
#[derive(QueryData)]
#[query_data(mutable)]
pub struct BehaviorMut<T: Behavior> {
current: Mut<'static, T>,
memory: &'static mut Memory<T>,
transition: &'static mut Transition<T>,
}
impl<T: Behavior> BehaviorItem for BehaviorMutReadOnlyItem<'_, '_, T> {
type Behavior = T;
fn current(&self) -> &T {
&self.current
}
fn current_index(&self) -> BehaviorIndex {
BehaviorIndex(self.memory.len())
}
fn previous(&self) -> Option<&T> {
self.memory.last()
}
fn enumerate(&self) -> impl Iterator<Item = (BehaviorIndex, &T)> + '_ {
self.memory
.iter()
.enumerate()
.map(|(index, item)| (BehaviorIndex(index), item))
.chain(std::iter::once((self.current_index(), self.current())))
}
fn get(&self, BehaviorIndex(index): BehaviorIndex) -> Option<&T> {
if index == self.memory.stack.len() {
Some(self.current())
} else {
self.memory.get(index)
}
}
fn has_transition(&self) -> bool {
!self.transition.is_none()
}
}
impl<T: Behavior> Deref for BehaviorMutReadOnlyItem<'_, '_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.current()
}
}
impl<T: Behavior> AsRef<T> for BehaviorMutReadOnlyItem<'_, '_, T> {
fn as_ref(&self) -> &T {
self.current.as_ref()
}
}
impl<T: Behavior> DetectChanges for BehaviorMutReadOnlyItem<'_, '_, T> {
fn is_added(&self) -> bool {
self.current.is_added()
}
fn is_changed(&self) -> bool {
self.current.is_changed()
}
fn last_changed(&self) -> Tick {
self.current.last_changed()
}
fn added(&self) -> Tick {
self.current.added()
}
fn changed_by(&self) -> MaybeLocation {
self.current.changed_by()
}
}
impl<T: Behavior> Index<BehaviorIndex> for BehaviorMutReadOnlyItem<'_, '_, T> {
type Output = T;
fn index(&self, BehaviorIndex(index): BehaviorIndex) -> &Self::Output {
if index == self.memory.stack.len() {
self.current()
} else {
&self.memory[index]
}
}
}
impl<T: Behavior> BehaviorItem for BehaviorMutItem<'_, '_, T> {
type Behavior = T;
fn current(&self) -> &T {
&self.current
}
fn current_index(&self) -> BehaviorIndex {
BehaviorIndex(self.memory.len())
}
fn previous(&self) -> Option<&T> {
self.memory.last()
}
fn enumerate(&self) -> impl Iterator<Item = (BehaviorIndex, &Self::Behavior)> + '_ {
self.memory
.iter()
.enumerate()
.map(|(index, item)| (BehaviorIndex(index), item))
.chain(std::iter::once((self.current_index(), self.current())))
}
fn get(&self, BehaviorIndex(index): BehaviorIndex) -> Option<&T> {
if index == self.memory.stack.len() {
Some(self.current())
} else {
self.memory.get(index)
}
}
fn has_transition(&self) -> bool {
!self.transition.is_none()
}
}
impl<T: Behavior> BehaviorMutItem<'_, '_, T> {
pub fn current_mut(&mut self) -> &mut T {
self.current.as_mut()
}
pub fn previous_mut(&mut self) -> Option<&mut T> {
self.memory.last_mut()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> + '_ {
self.memory
.iter_mut()
.chain(std::iter::once(self.current.as_mut()))
}
pub fn enumerate_mut(&mut self) -> impl Iterator<Item = (BehaviorIndex, &mut T)> + '_ {
let current_index = self.current_index();
self.memory
.iter_mut()
.enumerate()
.map(|(index, item)| (BehaviorIndex(index), item))
.chain(std::iter::once((current_index, self.current.as_mut())))
}
#[track_caller]
pub fn start(&mut self, next: T) {
self.start_with_caller(next, MaybeLocation::caller());
}
#[track_caller]
pub fn try_start(&mut self, next: T) -> Result<(), T> {
if self.has_transition() || !self.filter_next(&next) {
return Err(next);
}
self.start_with_caller(next, MaybeLocation::caller());
Ok(())
}
fn start_with_caller(&mut self, next: T, caller: MaybeLocation) {
self.set_transition(Next(next), caller);
}
#[track_caller]
pub fn interrupt_start(&mut self, next: T) {
self.interrupt_start_with_caller(next, MaybeLocation::caller());
}
#[track_caller]
pub fn try_interrupt_start(&mut self, next: T) -> Result<(), T> {
if self.has_transition() {
return Err(next);
}
self.interrupt_start_with_caller(next, MaybeLocation::caller());
Ok(())
}
fn interrupt_start_with_caller(&mut self, next: T, caller: MaybeLocation) {
self.set_transition(Interrupt(Interruption::Start(next)), caller);
}
#[track_caller]
pub fn interrupt_stop(&mut self, index: BehaviorIndex) {
self.interrupt_resume_with_caller(index.previous().unwrap(), MaybeLocation::caller());
}
#[track_caller]
pub fn try_interrupt_stop(&mut self, index: BehaviorIndex) -> Result<(), BehaviorIndex> {
if self.has_transition() || !self.has_index(index) {
return Err(index);
}
let Some(previous_index) = index.previous() else {
return Err(index);
};
self.interrupt_resume_with_caller(previous_index, MaybeLocation::caller());
Ok(())
}
#[track_caller]
pub fn interrupt_resume(&mut self, index: BehaviorIndex) {
self.interrupt_resume_with_caller(index, MaybeLocation::caller());
}
#[track_caller]
pub fn try_interrupt_resume(&mut self, index: BehaviorIndex) -> Result<(), BehaviorIndex> {
if self.has_transition() || !self.has_index(index) {
return Err(index);
}
self.interrupt_resume_with_caller(index, MaybeLocation::caller());
Ok(())
}
fn interrupt_resume_with_caller(&mut self, index: BehaviorIndex, caller: MaybeLocation) {
self.set_transition(Interrupt(Interruption::Resume(index)), caller);
}
#[track_caller]
pub fn stop(&mut self) {
self.stop_with_caller(MaybeLocation::caller());
}
#[track_caller]
pub fn try_stop(&mut self) -> bool {
if self.has_transition() || self.memory.is_empty() {
return false;
}
self.stop_with_caller(MaybeLocation::caller());
true
}
fn stop_with_caller(&mut self, caller: MaybeLocation) {
self.set_transition(Previous, caller);
}
#[track_caller]
pub fn reset(&mut self) {
self.interrupt_resume_with_caller(BehaviorIndex::initial(), MaybeLocation::caller());
}
#[track_caller]
pub fn try_reset(&mut self) -> bool {
if self.has_transition() {
return false;
}
self.interrupt_resume_with_caller(BehaviorIndex::initial(), MaybeLocation::caller());
true
}
fn set_transition(&mut self, transition: Transition<T>, caller: MaybeLocation) {
let previous = replace(self.transition.as_mut(), transition);
if !previous.is_none() {
warn!(
"transition override ({caller})): {previous:?} -> {:?}",
*self.transition
);
}
}
fn push(
&mut self,
instance: Instance<T>,
mut next: T,
initial: bool,
commands: &mut Commands,
) -> bool {
if self.filter_next(&next) {
let previous = {
swap(self.current.as_mut(), &mut next);
next
};
self.invoke_start(Some(&previous), commands.instance(instance));
let index = self.memory.len();
if previous.is_resumable() {
let next_index = index + 1;
debug!(
"{instance:?}: {previous:?} (#{index}) -> {:?} (#{next_index})",
*self.current
);
previous.invoke_pause(&self.current, commands.instance(instance));
commands.queue(move |world: &mut World| {
world.trigger_with(
Pause {
instance,
index: BehaviorIndex(index),
},
EntityTrigger,
);
world.trigger_with(
Start {
instance,
index: BehaviorIndex(next_index),
initial,
},
EntityTrigger,
);
world.trigger_with(
Activate {
instance,
index: BehaviorIndex(next_index),
resume: false,
initial,
},
EntityTrigger,
);
});
self.memory.push(previous);
} else {
debug!(
"{instance:?}: {previous:?} (#{index}) -> {:?} (#{index})",
*self.current
);
previous.invoke_stop(&self.current, commands.instance(instance));
commands.queue(move |world: &mut World| {
world.trigger_with(
Stop {
instance,
index: BehaviorIndex(index),
behavior: previous,
interrupt: false,
},
EntityTrigger,
);
world.trigger_with(
Start {
instance,
index: BehaviorIndex(index),
initial,
},
EntityTrigger,
);
world.trigger_with(
Activate {
instance,
index: BehaviorIndex(index),
resume: false,
initial,
},
EntityTrigger,
);
})
}
true
} else {
warn!(
"{instance:?}: transition {:?} -> {next:?} is not allowed",
*self.current
);
commands.queue(move |world: &mut World| {
world.trigger_with(
Error {
instance,
error: TransitionError::RejectedNext(next),
},
EntityTrigger,
);
});
false
}
}
fn interrupt(&mut self, instance: Instance<T>, next: T, commands: &mut Commands) {
while self.filter_yield(&next) && !self.memory.is_empty() {
let index = self.memory.len();
if let Some(mut next) = self.memory.pop() {
let previous = {
swap(self.current.as_mut(), &mut next);
next
};
debug!("{instance:?}: {:?} (#{index}) -> Interrupt", previous);
previous.invoke_stop(&self.current, commands.instance(instance));
commands.queue(move |world: &mut World| {
world.trigger_with(
Stop {
instance,
index: BehaviorIndex(index),
behavior: previous,
interrupt: true,
},
EntityTrigger,
);
});
}
}
self.push(instance, next, false, commands);
}
fn pop(&mut self, instance: Instance<T>, commands: &mut Commands) -> bool {
let index = self.memory.len();
if let Some(mut next) = self.memory.pop() {
let next_index = self.memory.len();
debug!(
"{instance:?}: {:?} (#{index}) -> {next:?} (#{next_index})",
*self.current
);
let previous = {
swap(self.current.as_mut(), &mut next);
next
};
self.invoke_resume(&previous, commands.instance(instance));
previous.invoke_stop(&self.current, commands.instance(instance));
commands.queue(move |world: &mut World| {
world.trigger_with(
Stop {
instance,
index: BehaviorIndex(index),
behavior: previous,
interrupt: false,
},
EntityTrigger,
);
world.trigger_with(
Resume {
instance,
index: BehaviorIndex(next_index),
},
EntityTrigger,
);
world.trigger_with(
Activate {
instance,
index: BehaviorIndex(next_index),
resume: true,
initial: false,
},
EntityTrigger,
);
});
true
} else {
warn!(
"{instance:?}: transition {:?} -> None is not allowed",
*self.current
);
commands.queue(move |world: &mut World| {
world.trigger_with(
Error {
instance,
error: TransitionError::<T>::NoPrevious,
},
EntityTrigger,
);
});
false
}
}
fn clear(&mut self, instance: Instance<T>, index: BehaviorIndex, commands: &mut Commands) {
while self.current_index() > index.next() {
let index = self.memory.len();
let previous = self.memory.pop().unwrap();
let next = self.memory.last().unwrap();
debug!("{instance:?}: {:?} (#{index}) -> Interrupt", previous);
previous.invoke_stop(next, commands.instance(instance));
commands.queue(move |world: &mut World| {
world.trigger_with(
Stop {
instance,
index: BehaviorIndex(index),
behavior: previous,
interrupt: true,
},
EntityTrigger,
);
});
}
self.pop(instance, commands);
}
}
impl<T: Behavior> Deref for BehaviorMutItem<'_, '_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.current()
}
}
impl<T: Behavior> DerefMut for BehaviorMutItem<'_, '_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.current_mut()
}
}
impl<T: Behavior> DetectChanges for BehaviorMutItem<'_, '_, T> {
fn is_added(&self) -> bool {
self.current.is_added()
}
fn is_changed(&self) -> bool {
self.current.is_changed()
}
fn last_changed(&self) -> Tick {
self.current.last_changed()
}
fn added(&self) -> Tick {
self.current.added()
}
fn changed_by(&self) -> MaybeLocation {
self.current.changed_by()
}
}
impl<T: Behavior> DetectChangesMut for BehaviorMutItem<'_, '_, T> {
type Inner = T;
fn set_changed(&mut self) {
self.current.set_changed()
}
fn set_last_changed(&mut self, last_changed: Tick) {
self.current.set_last_changed(last_changed)
}
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
self.current.bypass_change_detection()
}
fn set_added(&mut self) {
self.current.set_added()
}
fn set_last_added(&mut self, last_added: Tick) {
self.current.set_last_added(last_added)
}
}
impl<T: Behavior> AsRef<T> for BehaviorMutItem<'_, '_, T> {
fn as_ref(&self) -> &T {
self.current.as_ref()
}
}
impl<T: Behavior> AsMut<T> for BehaviorMutItem<'_, '_, T> {
fn as_mut(&mut self) -> &mut T {
self.current.as_mut()
}
}
impl<T: Behavior> Index<BehaviorIndex> for BehaviorMutItem<'_, '_, T> {
type Output = T;
fn index(&self, BehaviorIndex(index): BehaviorIndex) -> &Self::Output {
if index == self.memory.stack.len() {
self.current()
} else {
&self.memory[index]
}
}
}
impl<T: Behavior> IndexMut<BehaviorIndex> for BehaviorMutItem<'_, '_, T> {
fn index_mut(&mut self, BehaviorIndex(index): BehaviorIndex) -> &mut Self::Output {
if index == self.memory.stack.len() {
self.current_mut()
} else {
&mut self.memory[index]
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Reflect)]
pub struct BehaviorIndex(usize);
impl BehaviorIndex {
pub fn initial() -> Self {
Self(0)
}
pub fn previous(self) -> Option<Self> {
if self == BehaviorIndex::initial() {
return None;
}
Some(Self(self.0.saturating_sub(1)))
}
fn next(self) -> Self {
Self(self.0.saturating_add(1))
}
}
#[derive(Component, Deref, DerefMut, Reflect)]
#[reflect(Component)]
struct Memory<T: Behavior> {
stack: Vec<T>,
}
impl<T: Behavior> Default for Memory<T> {
fn default() -> Self {
Self {
stack: Vec::default(),
}
}
}
#[macro_export]
macro_rules! match_next {
{ $curr:ident => $next:ident, $($from:pat => $to:pat),* $(,)? } => {
match $curr {
$(
$from => matches!($next, $to),
)*
#[allow(unreachable_patterns)]
_ => false,
}
};
}