1use std::marker::PhantomData;
2
3use crate::TnuaSensorsEntities;
4use crate::basis_capabilities::TnuaBasisWithGround;
5use crate::ghost_overrides::TnuaGhostOverwrites;
6use crate::{
7 TnuaActionInitiationDirective, TnuaActionLifecycleDirective, TnuaActionLifecycleStatus, math::*,
8};
9use bevy::ecs::schedule::{InternedScheduleLabel, ScheduleLabel};
10use bevy::prelude::*;
11use bevy::time::Stopwatch;
12use bevy_tnua_physics_integration_layer::data_for_backends::TnuaGhostSensor;
13#[cfg(feature = "serialize")]
14use serde::{Deserialize, Serialize};
15
16use crate::basis_action_traits::{
17 TnuaActionContext, TnuaActionDiscriminant, TnuaActionState, TnuaBasis, TnuaBasisAccess,
18 TnuaScheme, TnuaSchemeConfig, TnuaUpdateInActionStateResult,
19};
20use crate::{
21 TnuaBasisContext, TnuaMotor, TnuaPipelineSystems, TnuaProximitySensor, TnuaRigidBodyTracker,
22 TnuaSystems, TnuaToggle, TnuaUserControlsSystems,
23};
24
25pub struct TnuaControllerPlugin<S: TnuaScheme> {
26 schedule: InternedScheduleLabel,
27 _phantom: PhantomData<S>,
28}
29
30impl<S: TnuaScheme> TnuaControllerPlugin<S> {
42 pub fn new(schedule: impl ScheduleLabel) -> Self {
43 Self {
44 schedule: schedule.intern(),
45 _phantom: PhantomData,
46 }
47 }
48}
49
50impl<S: TnuaScheme> Plugin for TnuaControllerPlugin<S> {
51 fn build(&self, app: &mut App) {
52 app.init_asset::<S::Config>();
53 app.configure_sets(
54 self.schedule,
55 (
56 TnuaPipelineSystems::Sensors,
57 TnuaUserControlsSystems,
58 TnuaPipelineSystems::Logic,
59 TnuaPipelineSystems::Motors,
60 )
61 .chain()
62 .in_set(TnuaSystems),
63 );
64 app.register_required_components_with::<TnuaController<S>, _>(
65 || TnuaSensorsEntities::<S> {
66 sensors_entities: Default::default(),
67 },
68 );
69 app.add_systems(
70 self.schedule,
71 (apply_ghost_overwrites::<S>, apply_controller_system::<S>)
72 .chain()
73 .in_set(TnuaPipelineSystems::Logic),
74 );
75 }
76}
77
78#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
79#[derive(Clone)]
80struct ContenderAction<S: TnuaScheme> {
81 action: S,
82 being_fed_for: Stopwatch,
83}
84
85#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
86#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
87enum FedStatus {
88 #[default]
89 Not,
90 Lingering,
91 Fresh,
92 Trigger,
93 Interrupt,
94 Held,
95}
96
97impl FedStatus {
98 fn considered_fed(&self) -> bool {
99 match self {
100 FedStatus::Not => false,
101 FedStatus::Lingering => true,
102 FedStatus::Fresh => true,
103 FedStatus::Trigger => true,
104 FedStatus::Interrupt => true,
105 FedStatus::Held => true,
106 }
107 }
108}
109
110#[derive(Default, Debug, Clone, Reflect)]
111#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
112struct FedEntry {
113 status: FedStatus,
114 rescheduled_in: Option<Timer>,
115}
116
117#[derive(Component, FromTemplate, Reflect)]
119pub struct TnuaConfig<S: TnuaScheme>(pub Handle<S::Config>);
120
121#[derive(Component, Reflect)]
147#[require(TnuaMotor, TnuaRigidBodyTracker)]
148#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
149pub struct TnuaController<S: TnuaScheme> {
150 pub basis: S::Basis,
152 #[cfg_attr(
153 feature = "serialize",
154 serde(bound(
155 serialize = "<S::Basis as TnuaBasis>::Memory: Serialize",
156 deserialize = "<S::Basis as TnuaBasis>::Memory: Deserialize<'de>",
157 ))
158 )]
159 pub basis_config: Option<<S::Basis as TnuaBasis>::Config>,
161 pub basis_memory: <S::Basis as TnuaBasis>::Memory,
163 actions_being_fed: Vec<FedEntry>,
165 contender_action: Option<ContenderAction<S>>,
166 #[cfg_attr(
167 feature = "serialize",
168 serde(bound(
169 serialize = "S::ActionDiscriminant: Serialize",
170 deserialize = "S::ActionDiscriminant: Deserialize<'de>",
171 ))
172 )]
173 action_flow_status: TnuaActionFlowStatus<S::ActionDiscriminant>,
174 up_direction: Option<Dir3>,
175 action_feeding_initiated: bool,
176 #[cfg_attr(
177 feature = "serialize",
178 serde(bound(
179 serialize = "S::ActionState: Serialize",
180 deserialize = "S::ActionState: Deserialize<'de>",
181 ))
182 )]
183 pub current_action: Option<S::ActionState>,
190}
191
192impl<S: TnuaScheme> Default for TnuaController<S> {
193 fn default() -> Self {
194 Self {
195 basis: Default::default(),
196 basis_config: None,
197 basis_memory: Default::default(),
198 actions_being_fed: (0..S::ActionDiscriminant::NUM_FEED_STATUS_SLOTS)
199 .map(|_| Default::default())
200 .collect(),
201 contender_action: None,
202 action_flow_status: TnuaActionFlowStatus::NoAction,
203 up_direction: None,
204 action_feeding_initiated: false,
205 current_action: None,
206 }
207 }
208}
209
210pub struct TnuaControllerTemplate<S: TnuaScheme> {
211 _phantom: PhantomData<S>,
212}
213
214impl<S: TnuaScheme> Default for TnuaControllerTemplate<S> {
215 fn default() -> Self {
216 Self {
217 _phantom: Default::default(),
218 }
219 }
220}
221
222impl<S: TnuaScheme> Template for TnuaControllerTemplate<S> {
223 type Output = TnuaController<S>;
224
225 fn build_template(
226 &self,
227 _context: &mut bevy::ecs::template::TemplateContext,
228 ) -> Result<Self::Output> {
229 Ok(TnuaController::default())
230 }
231
232 fn clone_template(&self) -> Self {
233 Self {
234 _phantom: Default::default(),
235 }
236 }
237}
238
239impl<S: TnuaScheme> FromTemplate for TnuaController<S> {
240 type Template = TnuaControllerTemplate<S>;
241}
242
243#[derive(Debug, Clone, Default)]
245#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
246pub enum TnuaActionFlowStatus<D: TnuaActionDiscriminant> {
247 #[default]
249 NoAction,
250
251 ActionStarted(D),
253
254 ActionOngoing(D),
256
257 ActionEnded(D),
261
262 Cancelled { old: D, new: D },
264}
265
266impl<D: TnuaActionDiscriminant> TnuaActionFlowStatus<D> {
267 pub fn ongoing(&self) -> Option<D> {
271 match self {
272 TnuaActionFlowStatus::NoAction | TnuaActionFlowStatus::ActionEnded(_) => None,
273 TnuaActionFlowStatus::ActionStarted(discriminant)
274 | TnuaActionFlowStatus::ActionOngoing(discriminant)
275 | TnuaActionFlowStatus::Cancelled {
276 old: _,
277 new: discriminant,
278 } => Some(*discriminant),
279 }
280 }
281
282 pub fn just_starting(&self) -> Option<D> {
287 match self {
288 TnuaActionFlowStatus::NoAction
289 | TnuaActionFlowStatus::ActionOngoing(_)
290 | TnuaActionFlowStatus::ActionEnded(_) => None,
291 TnuaActionFlowStatus::ActionStarted(discriminant)
292 | TnuaActionFlowStatus::Cancelled {
293 old: _,
294 new: discriminant,
295 } => Some(*discriminant),
296 }
297 }
298}
299
300impl<S: TnuaScheme> TnuaController<S> {
301 pub fn basis_access(
304 &'_ self,
305 ) -> Result<TnuaBasisAccess<'_, S::Basis>, TnuaControllerHasNotPulledConfiguration> {
306 Ok(TnuaBasisAccess {
307 input: &self.basis,
308 config: self
309 .basis_config
310 .as_ref()
311 .ok_or(TnuaControllerHasNotPulledConfiguration)?,
312 memory: &self.basis_memory,
313 })
314 }
315
316 pub fn initiate_action_feeding(&mut self) {
319 self.action_feeding_initiated = true;
320 }
321
322 pub fn action(&mut self, action: S) {
331 assert!(
332 self.action_feeding_initiated,
333 "Feeding action without invoking `initiate_action_feeding()`"
334 );
335 let fed_entry = &mut self.actions_being_fed[action.discriminant().feed_status_slot()];
336
337 match fed_entry.status {
338 FedStatus::Lingering
339 | FedStatus::Fresh
340 | FedStatus::Trigger
341 | FedStatus::Interrupt
342 | FedStatus::Held => {
343 fed_entry.status = FedStatus::Fresh;
344 if let Some(current_action) = self.current_action.as_mut() {
345 match action.update_in_action_state(current_action) {
346 TnuaUpdateInActionStateResult::Success => {
347 }
349 TnuaUpdateInActionStateResult::WrongVariant(_) => {
350 }
353 }
354 } else if self.contender_action.is_none()
355 && fed_entry
356 .rescheduled_in
357 .as_ref()
358 .is_some_and(|timer| timer.is_finished())
359 {
360 self.contender_action = Some(ContenderAction {
363 action,
364 being_fed_for: Stopwatch::new(),
365 });
366 } else {
367 }
369 }
370 FedStatus::Not => {
371 *fed_entry = FedEntry {
372 status: FedStatus::Fresh,
373 rescheduled_in: None,
374 };
375 if let Some(contender_action) = self.contender_action.as_mut()
376 && action.discriminant() == contender_action.action.discriminant()
377 {
378 contender_action.action = action;
379 } else if let Some(contender_action) = self.contender_action.as_ref()
380 && self.actions_being_fed
381 [contender_action.action.discriminant().feed_status_slot()]
382 .status
383 == FedStatus::Interrupt
384 {
385 } else {
387 self.contender_action = Some(ContenderAction {
388 action,
389 being_fed_for: Stopwatch::new(),
390 });
391 }
392 }
393 }
394 }
395
396 pub fn action_fake(&mut self, action: S::ActionDiscriminant) {
404 let fed_entry = &mut self.actions_being_fed[action.feed_status_slot()];
405 match fed_entry.status {
406 FedStatus::Not => {
407 *fed_entry = FedEntry {
408 status: FedStatus::Fresh,
409 rescheduled_in: None,
410 };
411 }
412 FedStatus::Lingering
413 | FedStatus::Fresh
414 | FedStatus::Trigger
415 | FedStatus::Interrupt
416 | FedStatus::Held => fed_entry.status = FedStatus::Fresh,
417 }
418 }
419
420 pub fn action_trigger(&mut self, action: S) {
427 let fed_entry = &mut self.actions_being_fed[action.discriminant().feed_status_slot()];
428
429 match fed_entry.status {
430 FedStatus::Lingering
431 | FedStatus::Fresh
432 | FedStatus::Trigger
433 | FedStatus::Interrupt
434 | FedStatus::Held => {
435 }
437 FedStatus::Not => {
438 *fed_entry = FedEntry {
439 status: FedStatus::Trigger,
440 rescheduled_in: None,
441 };
442 if let Some(contender_action) = self.contender_action.as_mut()
443 && action.discriminant() == contender_action.action.discriminant()
444 {
445 contender_action.action = action;
446 } else if let Some(contender_action) = self.contender_action.as_ref()
447 && self.actions_being_fed
448 [contender_action.action.discriminant().feed_status_slot()]
449 .status
450 == FedStatus::Interrupt
451 {
452 } else {
454 self.contender_action = Some(ContenderAction {
455 action,
456 being_fed_for: Stopwatch::new(),
457 });
458 }
459 }
460 }
461 }
462
463 pub fn action_interrupt(&mut self, action: S) {
466 self.actions_being_fed[action.discriminant().feed_status_slot()] = FedEntry {
469 status: FedStatus::Interrupt,
470 rescheduled_in: None,
471 };
472
473 let action = if let Some(current_action) = self.current_action.as_mut() {
474 match action.update_in_action_state(current_action) {
475 TnuaUpdateInActionStateResult::Success => {
476 return;
477 }
478 TnuaUpdateInActionStateResult::WrongVariant(action) => {
479 action
481 }
482 }
483 } else {
484 action
485 };
486 self.contender_action = Some(ContenderAction {
488 action,
489 being_fed_for: Stopwatch::new(),
490 });
491 }
492
493 pub fn action_start(&mut self, action: S) {
496 let fed_entry = &mut self.actions_being_fed[action.discriminant().feed_status_slot()];
497
498 match fed_entry.status {
499 FedStatus::Lingering | FedStatus::Fresh | FedStatus::Trigger | FedStatus::Interrupt => {
500 }
502 FedStatus::Held => {
503 let action = if let Some(current_action) = self.current_action.as_mut() {
505 match action.update_in_action_state(current_action) {
506 TnuaUpdateInActionStateResult::Success => {
507 return;
509 }
510 TnuaUpdateInActionStateResult::WrongVariant(action) => action,
511 }
512 } else {
513 action
514 };
515 if let Some(contender_action) = self.contender_action.as_mut()
516 && action.discriminant() == contender_action.action.discriminant()
517 {
518 contender_action.action = action;
520 }
521 }
522 FedStatus::Not => {
523 *fed_entry = FedEntry {
524 status: FedStatus::Held,
525 rescheduled_in: None,
526 };
527 if let Some(contender_action) = self.contender_action.as_mut()
528 && action.discriminant() == contender_action.action.discriminant()
529 {
530 contender_action.action = action;
531 } else if let Some(contender_action) = self.contender_action.as_ref()
532 && self.actions_being_fed
533 [contender_action.action.discriminant().feed_status_slot()]
534 .status
535 == FedStatus::Interrupt
536 {
537 } else {
539 self.contender_action = Some(ContenderAction {
540 action,
541 being_fed_for: Stopwatch::new(),
542 });
543 }
544 }
545 }
546 }
547
548 pub fn action_end(&mut self, action: S::ActionDiscriminant) {
550 self.actions_being_fed[action.feed_status_slot()] = Default::default();
552 }
553
554 pub fn prolong_action(&mut self) {
559 if let Some(current_action) = self.action_discriminant() {
560 self.actions_being_fed[current_action.feed_status_slot()].status = FedStatus::Fresh;
561 }
562 }
563
564 pub fn action_discriminant(&self) -> Option<S::ActionDiscriminant> {
566 Some(self.current_action.as_ref()?.discriminant())
567 }
568
569 pub fn action_flow_status(&self) -> &TnuaActionFlowStatus<S::ActionDiscriminant> {
583 &self.action_flow_status
584 }
585
586 pub fn up_direction(&self) -> Option<Dir3> {
594 self.up_direction
595 }
596}
597
598impl<S: TnuaScheme> TnuaController<S>
599where
600 S::Basis: TnuaBasisWithGround,
601{
602 pub fn is_airborne(&self) -> Result<bool, TnuaControllerHasNotPulledConfiguration> {
607 Ok(S::Basis::is_airborne(&self.basis_access()?))
608 }
609}
610
611#[derive(thiserror::Error, Debug)]
612#[error("The Tnua controller did not pull the configuration asset yet")]
613pub struct TnuaControllerHasNotPulledConfiguration;
614
615fn apply_ghost_overwrites<S: TnuaScheme>(
616 mut query: Query<(&TnuaSensorsEntities<S>, &mut TnuaGhostOverwrites<S>)>,
617 mut proximity_sensors_query: Query<(&mut TnuaProximitySensor, &TnuaGhostSensor)>,
618) {
619 for (sensors_entities, mut ghost_overwrites) in query.iter_mut() {
620 for (ghost_overwrite, sensor_entity) in
621 S::Basis::ghost_sensor_overwrites(ghost_overwrites.as_mut().as_mut(), sensors_entities)
622 {
623 let Ok((mut proximity_sensor, ghost_sensor)) =
624 proximity_sensors_query.get_mut(sensor_entity)
625 else {
626 continue;
627 };
628 if let Some(ghost_output) = ghost_overwrite.find_in(&ghost_sensor.0) {
629 proximity_sensor.output = Some(ghost_output.clone());
630 } else {
631 ghost_overwrite.set(None);
632 }
633 }
634 }
635}
636
637#[allow(clippy::type_complexity)]
638fn apply_controller_system<S: TnuaScheme>(
639 time: Res<Time>,
640 mut query: Query<(
641 Entity,
642 &mut TnuaController<S>,
643 &TnuaConfig<S>,
644 &mut TnuaSensorsEntities<S>,
645 &TnuaRigidBodyTracker,
646 &mut TnuaMotor,
647 Option<&TnuaToggle>,
648 Has<TnuaGhostOverwrites<S>>,
649 )>,
650 proximity_sensors_query: Query<(&TnuaProximitySensor, Has<TnuaGhostSensor>)>,
651 config_assets: Res<Assets<S::Config>>,
652 mut commands: Commands,
653) {
654 let frame_duration = time.delta().as_secs_f64() as Float;
655 if frame_duration == 0.0 {
656 return;
657 }
658 for (
659 controller_entity,
660 mut controller,
661 config_handle,
662 mut sensors_entities,
663 tracker,
664 mut motor,
665 tnua_toggle,
666 has_ghost_overwrites,
667 ) in query.iter_mut()
668 {
669 match tnua_toggle.copied().unwrap_or_default() {
670 TnuaToggle::Disabled => continue,
671 TnuaToggle::SenseOnly => {}
672 TnuaToggle::Enabled => {}
673 }
674 let controller = controller.as_mut();
675
676 let Some(config) = config_assets.get(&config_handle.0) else {
677 continue;
678 };
679 controller.basis_config = Some({
680 let mut basis_config = config.basis_config().clone();
681 if let Some(current_action) = controller.current_action.as_ref() {
682 current_action.modify_basis_config(&mut basis_config);
683 }
684 basis_config
685 });
686 let basis_config = controller
687 .basis_config
688 .as_ref()
689 .expect("We just set it to Some");
690
691 let up_direction = Dir3::new(-tracker.gravity.f32()).ok();
692 controller.up_direction = up_direction;
693 let up_direction = up_direction.unwrap_or(Dir3::Y);
695
696 let Some(sensors) = S::Basis::get_or_create_sensors(
697 up_direction,
698 basis_config,
699 &controller.basis_memory,
700 &mut sensors_entities.as_mut().sensors_entities,
701 &proximity_sensors_query,
702 controller_entity,
703 &mut commands,
704 has_ghost_overwrites,
705 ) else {
706 continue;
707 };
708
709 match controller.action_flow_status {
710 TnuaActionFlowStatus::NoAction | TnuaActionFlowStatus::ActionOngoing(_) => {}
711 TnuaActionFlowStatus::ActionEnded(_) => {
712 controller.action_flow_status = TnuaActionFlowStatus::NoAction;
713 }
714 TnuaActionFlowStatus::ActionStarted(discriminant)
715 | TnuaActionFlowStatus::Cancelled {
716 old: _,
717 new: discriminant,
718 } => {
719 controller.action_flow_status = TnuaActionFlowStatus::ActionOngoing(discriminant);
720 }
721 }
722
723 controller.basis.apply(
724 basis_config,
725 &mut controller.basis_memory,
726 &sensors,
727 TnuaBasisContext {
728 frame_duration,
729 tracker,
730 up_direction,
731 },
732 &mut motor,
733 );
734
735 if controller.action_feeding_initiated {
736 controller.action_feeding_initiated = false;
737 for fed_entry in controller.actions_being_fed.iter_mut() {
738 match fed_entry.status {
739 FedStatus::Not | FedStatus::Held => {}
740 FedStatus::Lingering => {
741 *fed_entry = Default::default();
742 }
743 FedStatus::Fresh | FedStatus::Trigger | FedStatus::Interrupt => {
744 fed_entry.status = FedStatus::Lingering;
745 if let Some(rescheduled_in) = &mut fed_entry.rescheduled_in {
746 rescheduled_in.tick(time.delta());
747 }
748 }
749 }
750 }
751 }
752
753 let has_valid_contender =
754 if let Some(contender_action) = controller.contender_action.as_mut() {
755 if controller.actions_being_fed
756 [contender_action.action.discriminant().feed_status_slot()]
757 .status
758 .considered_fed()
759 {
760 let initiation_decision = contender_action.action.initiation_decision(
761 config,
762 &sensors,
763 TnuaActionContext {
764 frame_duration,
765 tracker,
766 up_direction,
767 basis: &TnuaBasisAccess {
768 input: &controller.basis,
769 config: basis_config,
770 memory: &controller.basis_memory,
771 },
772 },
773 &contender_action.being_fed_for,
774 );
775 contender_action.being_fed_for.tick(time.delta());
776 match initiation_decision {
777 TnuaActionInitiationDirective::Reject => {
778 controller.contender_action = None;
779 false
780 }
781 TnuaActionInitiationDirective::Delay => false,
782 TnuaActionInitiationDirective::Allow => true,
783 }
784 } else {
785 controller.contender_action = None;
786 false
787 }
788 } else {
789 false
790 };
791
792 if let Some(action_state) = controller.current_action.as_mut() {
793 let lifecycle_status = if has_valid_contender {
794 TnuaActionLifecycleStatus::CancelledInto
795 } else if controller.actions_being_fed[action_state.discriminant().feed_status_slot()]
796 .status
797 .considered_fed()
798 {
799 TnuaActionLifecycleStatus::StillFed
800 } else {
801 TnuaActionLifecycleStatus::NoLongerFed
802 };
803
804 let directive = action_state.interface_mut().apply(
805 &sensors,
806 TnuaActionContext {
807 frame_duration,
808 tracker,
809 basis: &TnuaBasisAccess {
810 input: &controller.basis,
811 config: basis_config,
812 memory: &controller.basis_memory,
813 },
814 up_direction,
815 },
816 lifecycle_status,
817 motor.as_mut(),
818 );
819 action_state.interface_mut().influence_basis(
820 TnuaBasisContext {
821 frame_duration,
822 tracker,
823 up_direction,
824 },
825 &controller.basis,
826 basis_config,
827 &mut controller.basis_memory,
828 );
829 match directive {
830 TnuaActionLifecycleDirective::StillActive => {
831 if !lifecycle_status.is_active()
832 && let TnuaActionFlowStatus::ActionOngoing(action_discriminant) =
833 controller.action_flow_status
834 {
835 controller.action_flow_status =
836 TnuaActionFlowStatus::ActionEnded(action_discriminant);
837 }
838 }
839 TnuaActionLifecycleDirective::Finished
840 | TnuaActionLifecycleDirective::Reschedule { .. } => {
841 if let TnuaActionLifecycleDirective::Reschedule { after_seconds } = directive {
842 controller.actions_being_fed
843 [action_state.discriminant().feed_status_slot()]
844 .rescheduled_in =
845 Some(Timer::from_seconds(after_seconds.f32(), TimerMode::Once));
846 }
847 controller.current_action = if has_valid_contender {
848 let contender_action = controller.contender_action.take().expect(
849 "has_valid_contender can only be true if contender_action is Some",
850 );
851 let mut contender_action_state =
852 contender_action.action.into_action_state_variant(config);
853
854 controller.actions_being_fed
855 [contender_action_state.discriminant().feed_status_slot()]
856 .rescheduled_in = None;
857
858 let contender_directive = contender_action_state.interface_mut().apply(
859 &sensors,
860 TnuaActionContext {
861 frame_duration,
862 tracker,
863 basis: &TnuaBasisAccess {
864 input: &controller.basis,
865 config: basis_config,
866 memory: &controller.basis_memory,
867 },
868 up_direction,
869 },
870 TnuaActionLifecycleStatus::CancelledFrom,
871 motor.as_mut(),
872 );
873 contender_action_state.interface_mut().influence_basis(
874 TnuaBasisContext {
875 frame_duration,
876 tracker,
877 up_direction,
878 },
879 &controller.basis,
880 basis_config,
881 &mut controller.basis_memory,
882 );
883 match contender_directive {
884 TnuaActionLifecycleDirective::StillActive => {
885 controller.action_flow_status =
886 if let TnuaActionFlowStatus::ActionOngoing(discriminant) =
887 controller.action_flow_status
888 {
889 TnuaActionFlowStatus::Cancelled {
890 old: discriminant,
891 new: contender_action_state.discriminant(),
892 }
893 } else {
894 TnuaActionFlowStatus::ActionStarted(
895 contender_action_state.discriminant(),
896 )
897 };
898 Some(contender_action_state)
899 }
900 TnuaActionLifecycleDirective::Finished
901 | TnuaActionLifecycleDirective::Reschedule { after_seconds: _ } => {
902 if let TnuaActionLifecycleDirective::Reschedule { after_seconds } =
903 contender_directive
904 {
905 controller.actions_being_fed[contender_action_state
906 .discriminant()
907 .feed_status_slot()]
908 .rescheduled_in = Some(Timer::from_seconds(
909 after_seconds.f32(),
910 TimerMode::Once,
911 ));
912 }
913 if let TnuaActionFlowStatus::ActionOngoing(discriminant) =
914 controller.action_flow_status
915 {
916 controller.action_flow_status =
917 TnuaActionFlowStatus::ActionEnded(discriminant);
918 }
919 None
920 }
921 }
922 } else {
923 controller.action_flow_status =
924 TnuaActionFlowStatus::ActionEnded(action_state.discriminant());
925 None
926 };
927 }
928 }
929 } else if has_valid_contender {
930 let contender_action = controller
931 .contender_action
932 .take()
933 .expect("has_valid_contender can only be true if contender_action is Some");
934 let mut contender_action_state =
935 contender_action.action.into_action_state_variant(config);
936
937 contender_action_state.interface_mut().apply(
938 &sensors,
939 TnuaActionContext {
940 frame_duration,
941 tracker,
942 basis: &TnuaBasisAccess {
943 input: &controller.basis,
944 config: basis_config,
945 memory: &controller.basis_memory,
946 },
947 up_direction,
948 },
949 TnuaActionLifecycleStatus::Initiated,
950 motor.as_mut(),
951 );
952 contender_action_state.interface_mut().influence_basis(
953 TnuaBasisContext {
954 frame_duration,
955 tracker,
956 up_direction,
957 },
958 &controller.basis,
959 basis_config,
960 &mut controller.basis_memory,
961 );
962 controller.action_flow_status =
963 TnuaActionFlowStatus::ActionStarted(contender_action_state.discriminant());
964 controller.current_action = Some(contender_action_state);
965 }
966
967 for fed_entry in controller.actions_being_fed.iter_mut() {
968 match fed_entry.status {
969 FedStatus::Not | FedStatus::Lingering | FedStatus::Fresh | FedStatus::Held => {}
970 FedStatus::Trigger | FedStatus::Interrupt => {
971 fed_entry.status = FedStatus::Not;
972 }
973 }
974 }
975 }
976}