use super::*;
pub struct SequentialActionsPlugin;
impl Plugin for SequentialActionsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Last, Self::check_actions::<()>);
app.world_mut()
.register_component_hooks::<CurrentAction>()
.on_remove(CurrentAction::on_remove_hook);
app.world_mut()
.register_component_hooks::<ActionQueue>()
.on_remove(ActionQueue::on_remove_hook);
}
}
impl SequentialActionsPlugin {
pub fn check_actions<F: QueryFilter>(
action_q: Query<(Entity, &CurrentAction), F>,
world: &World,
mut commands: Commands,
) {
action_q
.iter()
.filter_map(|(agent, current_action)| {
current_action
.as_ref()
.and_then(|action| action.is_finished(agent, world).then_some(agent))
})
.for_each(|agent| {
commands.queue(move |world: &mut World| {
Self::stop_current_action(agent, StopReason::Finished, world);
Self::start_next_action(agent, world);
});
});
}
pub fn add_action(
agent: Entity,
config: AddConfig,
action: impl IntoBoxedAction,
world: &mut World,
) {
let mut action = action.into_boxed_action();
if world.get_entity(agent).is_err() {
warn!("Cannot add action {action:?} to non-existent agent {agent}.");
return;
}
debug!("Adding action {action:?} for agent {agent} with {config:?}.");
action.on_add(agent, world);
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!(
"Cannot enqueue action {action:?} to non-existent agent {agent}. \
Action is therefore dropped immediately."
);
action.on_remove(None, world);
action.on_drop(None, world, DropReason::Skipped);
return;
};
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot enqueue action {action:?} to agent {agent} due to missing component {}. \
Action is therefore dropped immediately.",
std::any::type_name::<ActionQueue>()
);
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Skipped);
return;
};
match config.order {
AddOrder::Back => action_queue.push_back(action),
AddOrder::Front => action_queue.push_front(action),
}
if config.start {
let Some(current_action) = agent_ref.get::<CurrentAction>() else {
warn!(
"Could not start next action for agent {agent} due to missing component {}.",
std::any::type_name::<CurrentAction>()
);
return;
};
if current_action.is_none() {
Self::start_next_action(agent, world);
}
}
}
pub fn add_actions<I>(agent: Entity, config: AddConfig, actions: I, world: &mut World)
where
I: DoubleEndedIterator<Item = BoxedAction> + ExactSizeIterator + Debug,
{
let actions = actions.into_iter();
let len = actions.len();
if len == 0 {
return;
}
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!("Cannot add actions {actions:?} to non-existent agent {agent}.");
return;
};
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot add actions {actions:?} to agent {agent} due to missing component {}.",
std::any::type_name::<ActionQueue>()
);
return;
};
debug!("Adding actions {actions:?} for agent {agent} with {config:?}.");
action_queue.reserve(len);
match config.order {
AddOrder::Back => {
for mut action in actions {
action.on_add(agent, world);
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!(
"Cannot enqueue action {action:?} to non-existent agent {agent}. \
Action is therefore dropped immediately."
);
action.on_remove(None, world);
action.on_drop(None, world, DropReason::Skipped);
return;
};
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot enqueue action {action:?} to agent {agent} due to missing component {}. \
Action is therefore dropped immediately.",
std::any::type_name::<ActionQueue>()
);
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Skipped);
return;
};
action_queue.push_back(action);
}
}
AddOrder::Front => {
for mut action in actions.rev() {
action.on_add(agent, world);
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!(
"Cannot enqueue action {action:?} to non-existent agent {agent}. \
Action is therefore dropped immediately."
);
action.on_remove(None, world);
action.on_drop(None, world, DropReason::Skipped);
return;
};
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot enqueue action {action:?} to agent {agent} due to missing component {}. \
Action is therefore dropped immediately.",
std::any::type_name::<ActionQueue>()
);
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Skipped);
return;
};
action_queue.push_front(action);
}
}
}
if config.start {
let Some(current_action) = world.get::<CurrentAction>(agent) else {
warn!(
"Could not start next action for agent {agent} due to missing component {}.",
std::any::type_name::<CurrentAction>()
);
return;
};
if current_action.is_none() {
Self::start_next_action(agent, world);
}
}
}
pub fn execute_actions(agent: Entity, world: &mut World) {
let Ok(agent_ref) = world.get_entity(agent) else {
warn!("Cannot execute actions for non-existent agent {agent}.");
return;
};
let Some(current_action) = agent_ref.get::<CurrentAction>() else {
warn!(
"Cannot execute actions for agent {agent} due to missing component {}.",
std::any::type_name::<CurrentAction>()
);
return;
};
if current_action.is_none() {
debug!("Executing actions for agent {agent}.");
Self::start_next_action(agent, world);
}
}
pub fn stop_current_action(agent: Entity, reason: StopReason, world: &mut World) {
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!(
"Cannot stop current action for non-existent agent {agent} with reason {reason:?}."
);
return;
};
let Some(mut current_action) = agent_ref.get_mut::<CurrentAction>() else {
warn!(
"Cannot stop current action for agent {agent} with reason {reason:?} \
due to missing component {}.",
std::any::type_name::<CurrentAction>()
);
return;
};
if let Some(mut action) = current_action.take() {
debug!("Stopping current action {action:?} for agent {agent} with reason {reason:?}.");
action.on_stop(agent.into(), world, reason);
match reason {
StopReason::Finished | StopReason::Canceled => {
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Done);
}
StopReason::Paused => {
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!(
"Cannot enqueue paused action {action:?} to non-existent agent {agent}. \
Action is therefore dropped immediately."
);
action.on_remove(None, world);
action.on_drop(None, world, DropReason::Skipped);
return;
};
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot enqueue paused action {action:?} to agent {agent} due to missing component {}. \
Action is therefore dropped immediately.",
std::any::type_name::<ActionQueue>()
);
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Skipped);
return;
};
action_queue.push_front(action);
}
}
}
}
pub fn start_next_action(agent: Entity, world: &mut World) {
#[cfg(debug_assertions)]
let mut counter: u16 = 0;
loop {
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!("Cannot start next action for non-existent agent {agent}.");
break;
};
let Some(current_action) = agent_ref.get::<CurrentAction>() else {
warn!(
"Cannot start next action for agent {agent} due to missing component {}.",
std::any::type_name::<CurrentAction>()
);
break;
};
if let Some(action) = current_action.0.as_ref() {
warn!(
"Cannot start next action for agent {agent} \
as it already has current action {action:?}."
);
break;
}
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot start next action for agent {agent} due to missing component {}.",
std::any::type_name::<ActionQueue>()
);
break;
};
let Some(mut action) = action_queue.pop_front() else {
break;
};
debug!("Starting action {action:?} for agent {agent}.");
if !action.on_start(agent, world) {
match world.get_mut::<CurrentAction>(agent) {
Some(mut current_action) => {
current_action.0 = Some(action);
}
None => {
debug!("Canceling action {action:?} due to missing agent {agent}.");
action.on_stop(None, world, StopReason::Canceled);
action.on_remove(None, world);
action.on_drop(None, world, DropReason::Done);
}
}
break;
};
debug!("Finishing action {action:?} for agent {agent}.");
let agent = world.get_entity(agent).map(|_| agent).ok();
action.on_stop(agent, world, StopReason::Finished);
action.on_remove(agent, world);
action.on_drop(agent, world, DropReason::Done);
if agent.is_none() {
break;
}
#[cfg(debug_assertions)]
{
counter += 1;
if counter == u16::MAX {
panic!("infinite loop detected in starting next action");
}
}
}
}
pub fn skip_actions(agent: Entity, mut n: usize, world: &mut World) {
loop {
if n == 0 {
break;
}
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!("Cannot skip next action for non-existent agent {agent}.");
break;
};
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot skip next action for agent {agent} due to missing component {}.",
std::any::type_name::<ActionQueue>()
);
break;
};
let Some(mut action) = action_queue.pop_front() else {
break;
};
debug!("Skipping action {action:?} for agent {agent}.");
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Skipped);
n -= 1;
}
}
pub fn clear_actions(agent: Entity, world: &mut World) {
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!("Cannot clear actions for non-existent agent {agent}.");
return;
};
let Some(mut current_action) = agent_ref.get_mut::<CurrentAction>() else {
warn!(
"Cannot clear current action for agent {agent} due to missing component {}.",
std::any::type_name::<CurrentAction>()
);
return;
};
if let Some(mut action) = current_action.take() {
debug!("Clearing current action {action:?} for agent {agent}.");
action.on_stop(agent.into(), world, StopReason::Canceled);
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Cleared);
}
let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
warn!("Cannot clear action queue for non-existent agent {agent}.");
return;
};
let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
warn!(
"Cannot clear action queue for agent {agent} due to missing component {}.",
std::any::type_name::<ActionQueue>()
);
return;
};
if action_queue.is_empty() {
return;
}
debug!("Clearing action queue {:?} for {agent}.", **action_queue);
let actions = std::mem::take(&mut action_queue.0);
for mut action in actions {
action.on_remove(agent.into(), world);
action.on_drop(agent.into(), world, DropReason::Cleared);
}
}
}