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))]
79struct ContenderAction<S: TnuaScheme> {
80 action: S,
81 being_fed_for: Stopwatch,
82}
83
84#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
85#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
86enum FedStatus {
87 #[default]
88 Not,
89 Lingering,
90 Fresh,
91 Trigger,
92 Interrupt,
93 Held,
94}
95
96impl FedStatus {
97 fn considered_fed(&self) -> bool {
98 match self {
99 FedStatus::Not => false,
100 FedStatus::Lingering => true,
101 FedStatus::Fresh => true,
102 FedStatus::Trigger => true,
103 FedStatus::Interrupt => true,
104 FedStatus::Held => true,
105 }
106 }
107}
108
109#[derive(Default, Debug)]
110#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
111struct FedEntry {
112 status: FedStatus,
113 rescheduled_in: Option<Timer>,
114}
115
116#[derive(Component)]
118pub struct TnuaConfig<S: TnuaScheme>(pub Handle<S::Config>);
119
120#[derive(Component)]
146#[require(TnuaMotor, TnuaRigidBodyTracker)]
147#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
148pub struct TnuaController<S: TnuaScheme> {
149 pub basis: S::Basis,
151 #[cfg_attr(
152 feature = "serialize",
153 serde(bound(
154 serialize = "<S::Basis as TnuaBasis>::Memory: Serialize",
155 deserialize = "<S::Basis as TnuaBasis>::Memory: Deserialize<'de>",
156 ))
157 )]
158 pub basis_config: Option<<S::Basis as TnuaBasis>::Config>,
160 pub basis_memory: <S::Basis as TnuaBasis>::Memory,
162 actions_being_fed: Vec<FedEntry>,
164 contender_action: Option<ContenderAction<S>>,
165 #[cfg_attr(
166 feature = "serialize",
167 serde(bound(
168 serialize = "S::ActionDiscriminant: Serialize",
169 deserialize = "S::ActionDiscriminant: Deserialize<'de>",
170 ))
171 )]
172 action_flow_status: TnuaActionFlowStatus<S::ActionDiscriminant>,
173 up_direction: Option<Dir3>,
174 action_feeding_initiated: bool,
175 #[cfg_attr(
176 feature = "serialize",
177 serde(bound(
178 serialize = "S::ActionState: Serialize",
179 deserialize = "S::ActionState: Deserialize<'de>",
180 ))
181 )]
182 pub current_action: Option<S::ActionState>,
189}
190
191impl<S: TnuaScheme> Default for TnuaController<S> {
192 fn default() -> Self {
193 Self {
194 basis: Default::default(),
195 basis_config: None,
196 basis_memory: Default::default(),
197 actions_being_fed: (0..S::ActionDiscriminant::NUM_FEED_STATUS_SLOTS)
198 .map(|_| Default::default())
199 .collect(),
200 contender_action: None,
201 action_flow_status: TnuaActionFlowStatus::NoAction,
202 up_direction: None,
203 action_feeding_initiated: false,
204 current_action: None,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Default)]
211#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
212pub enum TnuaActionFlowStatus<D: TnuaActionDiscriminant> {
213 #[default]
215 NoAction,
216
217 ActionStarted(D),
219
220 ActionOngoing(D),
222
223 ActionEnded(D),
227
228 Cancelled { old: D, new: D },
230}
231
232impl<D: TnuaActionDiscriminant> TnuaActionFlowStatus<D> {
233 pub fn ongoing(&self) -> Option<D> {
237 match self {
238 TnuaActionFlowStatus::NoAction | TnuaActionFlowStatus::ActionEnded(_) => None,
239 TnuaActionFlowStatus::ActionStarted(discriminant)
240 | TnuaActionFlowStatus::ActionOngoing(discriminant)
241 | TnuaActionFlowStatus::Cancelled {
242 old: _,
243 new: discriminant,
244 } => Some(*discriminant),
245 }
246 }
247
248 pub fn just_starting(&self) -> Option<D> {
253 match self {
254 TnuaActionFlowStatus::NoAction
255 | TnuaActionFlowStatus::ActionOngoing(_)
256 | TnuaActionFlowStatus::ActionEnded(_) => None,
257 TnuaActionFlowStatus::ActionStarted(discriminant)
258 | TnuaActionFlowStatus::Cancelled {
259 old: _,
260 new: discriminant,
261 } => Some(*discriminant),
262 }
263 }
264}
265
266impl<S: TnuaScheme> TnuaController<S> {
267 pub fn basis_access(
270 &'_ self,
271 ) -> Result<TnuaBasisAccess<'_, S::Basis>, TnuaControllerHasNotPulledConfiguration> {
272 Ok(TnuaBasisAccess {
273 input: &self.basis,
274 config: self
275 .basis_config
276 .as_ref()
277 .ok_or(TnuaControllerHasNotPulledConfiguration)?,
278 memory: &self.basis_memory,
279 })
280 }
281
282 pub fn initiate_action_feeding(&mut self) {
285 self.action_feeding_initiated = true;
286 }
287
288 pub fn action(&mut self, action: S) {
297 assert!(
298 self.action_feeding_initiated,
299 "Feeding action without invoking `initiate_action_feeding()`"
300 );
301 let fed_entry = &mut self.actions_being_fed[action.discriminant().feed_status_slot()];
302
303 match fed_entry.status {
304 FedStatus::Lingering
305 | FedStatus::Fresh
306 | FedStatus::Trigger
307 | FedStatus::Interrupt
308 | FedStatus::Held => {
309 fed_entry.status = FedStatus::Fresh;
310 if let Some(current_action) = self.current_action.as_mut() {
311 match action.update_in_action_state(current_action) {
312 TnuaUpdateInActionStateResult::Success => {
313 }
315 TnuaUpdateInActionStateResult::WrongVariant(_) => {
316 }
319 }
320 } else if self.contender_action.is_none()
321 && fed_entry
322 .rescheduled_in
323 .as_ref()
324 .is_some_and(|timer| timer.is_finished())
325 {
326 self.contender_action = Some(ContenderAction {
329 action,
330 being_fed_for: Stopwatch::new(),
331 });
332 } else {
333 }
335 }
336 FedStatus::Not => {
337 *fed_entry = FedEntry {
338 status: FedStatus::Fresh,
339 rescheduled_in: None,
340 };
341 if let Some(contender_action) = self.contender_action.as_mut()
342 && action.discriminant() == contender_action.action.discriminant()
343 {
344 contender_action.action = action;
345 } else if let Some(contender_action) = self.contender_action.as_ref()
346 && self.actions_being_fed
347 [contender_action.action.discriminant().feed_status_slot()]
348 .status
349 == FedStatus::Interrupt
350 {
351 } else {
353 self.contender_action = Some(ContenderAction {
354 action,
355 being_fed_for: Stopwatch::new(),
356 });
357 }
358 }
359 }
360 }
361
362 pub fn action_fake(&mut self, action: S::ActionDiscriminant) {
370 let fed_entry = &mut self.actions_being_fed[action.feed_status_slot()];
371 match fed_entry.status {
372 FedStatus::Not => {
373 *fed_entry = FedEntry {
374 status: FedStatus::Fresh,
375 rescheduled_in: None,
376 };
377 }
378 FedStatus::Lingering
379 | FedStatus::Fresh
380 | FedStatus::Trigger
381 | FedStatus::Interrupt
382 | FedStatus::Held => fed_entry.status = FedStatus::Fresh,
383 }
384 }
385
386 pub fn action_trigger(&mut self, action: S) {
393 let fed_entry = &mut self.actions_being_fed[action.discriminant().feed_status_slot()];
394
395 match fed_entry.status {
396 FedStatus::Lingering
397 | FedStatus::Fresh
398 | FedStatus::Trigger
399 | FedStatus::Interrupt
400 | FedStatus::Held => {
401 }
403 FedStatus::Not => {
404 *fed_entry = FedEntry {
405 status: FedStatus::Trigger,
406 rescheduled_in: None,
407 };
408 if let Some(contender_action) = self.contender_action.as_mut()
409 && action.discriminant() == contender_action.action.discriminant()
410 {
411 contender_action.action = action;
412 } else if let Some(contender_action) = self.contender_action.as_ref()
413 && self.actions_being_fed
414 [contender_action.action.discriminant().feed_status_slot()]
415 .status
416 == FedStatus::Interrupt
417 {
418 } else {
420 self.contender_action = Some(ContenderAction {
421 action,
422 being_fed_for: Stopwatch::new(),
423 });
424 }
425 }
426 }
427 }
428
429 pub fn action_interrupt(&mut self, action: S) {
432 self.actions_being_fed[action.discriminant().feed_status_slot()] = FedEntry {
435 status: FedStatus::Interrupt,
436 rescheduled_in: None,
437 };
438
439 let action = if let Some(current_action) = self.current_action.as_mut() {
440 match action.update_in_action_state(current_action) {
441 TnuaUpdateInActionStateResult::Success => {
442 return;
443 }
444 TnuaUpdateInActionStateResult::WrongVariant(action) => {
445 action
447 }
448 }
449 } else {
450 action
451 };
452 self.contender_action = Some(ContenderAction {
454 action,
455 being_fed_for: Stopwatch::new(),
456 });
457 }
458
459 pub fn action_start(&mut self, action: S) {
462 let fed_entry = &mut self.actions_being_fed[action.discriminant().feed_status_slot()];
463
464 match fed_entry.status {
465 FedStatus::Lingering | FedStatus::Fresh | FedStatus::Trigger | FedStatus::Interrupt => {
466 }
468 FedStatus::Held => {
469 let action = if let Some(current_action) = self.current_action.as_mut() {
471 match action.update_in_action_state(current_action) {
472 TnuaUpdateInActionStateResult::Success => {
473 return;
475 }
476 TnuaUpdateInActionStateResult::WrongVariant(action) => action,
477 }
478 } else {
479 action
480 };
481 if let Some(contender_action) = self.contender_action.as_mut()
482 && action.discriminant() == contender_action.action.discriminant()
483 {
484 contender_action.action = action;
486 }
487 }
488 FedStatus::Not => {
489 *fed_entry = FedEntry {
490 status: FedStatus::Held,
491 rescheduled_in: None,
492 };
493 if let Some(contender_action) = self.contender_action.as_mut()
494 && action.discriminant() == contender_action.action.discriminant()
495 {
496 contender_action.action = action;
497 } else if let Some(contender_action) = self.contender_action.as_ref()
498 && self.actions_being_fed
499 [contender_action.action.discriminant().feed_status_slot()]
500 .status
501 == FedStatus::Interrupt
502 {
503 } else {
505 self.contender_action = Some(ContenderAction {
506 action,
507 being_fed_for: Stopwatch::new(),
508 });
509 }
510 }
511 }
512 }
513
514 pub fn action_end(&mut self, action: S::ActionDiscriminant) {
516 self.actions_being_fed[action.feed_status_slot()] = Default::default();
518 }
519
520 pub fn prolong_action(&mut self) {
525 if let Some(current_action) = self.action_discriminant() {
526 self.actions_being_fed[current_action.feed_status_slot()].status = FedStatus::Fresh;
527 }
528 }
529
530 pub fn action_discriminant(&self) -> Option<S::ActionDiscriminant> {
532 Some(self.current_action.as_ref()?.discriminant())
533 }
534
535 pub fn action_flow_status(&self) -> &TnuaActionFlowStatus<S::ActionDiscriminant> {
549 &self.action_flow_status
550 }
551
552 pub fn up_direction(&self) -> Option<Dir3> {
560 self.up_direction
561 }
562}
563
564impl<S: TnuaScheme> TnuaController<S>
565where
566 S::Basis: TnuaBasisWithGround,
567{
568 pub fn is_airborne(&self) -> Result<bool, TnuaControllerHasNotPulledConfiguration> {
573 Ok(S::Basis::is_airborne(&self.basis_access()?))
574 }
575}
576
577#[derive(thiserror::Error, Debug)]
578#[error("The Tnua controller did not pull the configuration asset yet")]
579pub struct TnuaControllerHasNotPulledConfiguration;
580
581fn apply_ghost_overwrites<S: TnuaScheme>(
582 mut query: Query<(&TnuaSensorsEntities<S>, &mut TnuaGhostOverwrites<S>)>,
583 mut proximity_sensors_query: Query<(&mut TnuaProximitySensor, &TnuaGhostSensor)>,
584) {
585 for (sensors_entities, mut ghost_overwrites) in query.iter_mut() {
586 for (ghost_overwrite, sensor_entity) in
587 S::Basis::ghost_sensor_overwrites(ghost_overwrites.as_mut().as_mut(), sensors_entities)
588 {
589 let Ok((mut proximity_sensor, ghost_sensor)) =
590 proximity_sensors_query.get_mut(sensor_entity)
591 else {
592 continue;
593 };
594 if let Some(ghost_output) = ghost_overwrite.find_in(&ghost_sensor.0) {
595 proximity_sensor.output = Some(ghost_output.clone());
596 } else {
597 ghost_overwrite.set(None);
598 }
599 }
600 }
601}
602
603#[allow(clippy::type_complexity)]
604fn apply_controller_system<S: TnuaScheme>(
605 time: Res<Time>,
606 mut query: Query<(
607 Entity,
608 &mut TnuaController<S>,
609 &TnuaConfig<S>,
610 &mut TnuaSensorsEntities<S>,
611 &TnuaRigidBodyTracker,
612 &mut TnuaMotor,
613 Option<&TnuaToggle>,
614 Has<TnuaGhostOverwrites<S>>,
615 )>,
616 proximity_sensors_query: Query<(&TnuaProximitySensor, Has<TnuaGhostSensor>)>,
617 config_assets: Res<Assets<S::Config>>,
618 mut commands: Commands,
619) {
620 let frame_duration = time.delta().as_secs_f64() as Float;
621 if frame_duration == 0.0 {
622 return;
623 }
624 for (
625 controller_entity,
626 mut controller,
627 config_handle,
628 mut sensors_entities,
629 tracker,
630 mut motor,
631 tnua_toggle,
632 has_ghost_overwrites,
633 ) in query.iter_mut()
634 {
635 match tnua_toggle.copied().unwrap_or_default() {
636 TnuaToggle::Disabled => continue,
637 TnuaToggle::SenseOnly => {}
638 TnuaToggle::Enabled => {}
639 }
640 let controller = controller.as_mut();
641
642 let Some(config) = config_assets.get(&config_handle.0) else {
643 continue;
644 };
645 controller.basis_config = Some({
646 let mut basis_config = config.basis_config().clone();
647 if let Some(current_action) = controller.current_action.as_ref() {
648 current_action.modify_basis_config(&mut basis_config);
649 }
650 basis_config
651 });
652 let basis_config = controller
653 .basis_config
654 .as_ref()
655 .expect("We just set it to Some");
656
657 let up_direction = Dir3::new(-tracker.gravity.f32()).ok();
658 controller.up_direction = up_direction;
659 let up_direction = up_direction.unwrap_or(Dir3::Y);
661
662 let Some(sensors) = S::Basis::get_or_create_sensors(
663 up_direction,
664 basis_config,
665 &controller.basis_memory,
666 &mut sensors_entities.as_mut().sensors_entities,
667 &proximity_sensors_query,
668 controller_entity,
669 &mut commands,
670 has_ghost_overwrites,
671 ) else {
672 continue;
673 };
674
675 match controller.action_flow_status {
676 TnuaActionFlowStatus::NoAction | TnuaActionFlowStatus::ActionOngoing(_) => {}
677 TnuaActionFlowStatus::ActionEnded(_) => {
678 controller.action_flow_status = TnuaActionFlowStatus::NoAction;
679 }
680 TnuaActionFlowStatus::ActionStarted(discriminant)
681 | TnuaActionFlowStatus::Cancelled {
682 old: _,
683 new: discriminant,
684 } => {
685 controller.action_flow_status = TnuaActionFlowStatus::ActionOngoing(discriminant);
686 }
687 }
688
689 controller.basis.apply(
690 basis_config,
691 &mut controller.basis_memory,
692 &sensors,
693 TnuaBasisContext {
694 frame_duration,
695 tracker,
696 up_direction,
697 },
698 &mut motor,
699 );
700
701 if controller.action_feeding_initiated {
702 controller.action_feeding_initiated = false;
703 for fed_entry in controller.actions_being_fed.iter_mut() {
704 match fed_entry.status {
705 FedStatus::Not | FedStatus::Held => {}
706 FedStatus::Lingering => {
707 *fed_entry = Default::default();
708 }
709 FedStatus::Fresh | FedStatus::Trigger | FedStatus::Interrupt => {
710 fed_entry.status = FedStatus::Lingering;
711 if let Some(rescheduled_in) = &mut fed_entry.rescheduled_in {
712 rescheduled_in.tick(time.delta());
713 }
714 }
715 }
716 }
717 }
718
719 let has_valid_contender =
720 if let Some(contender_action) = controller.contender_action.as_mut() {
721 if controller.actions_being_fed
722 [contender_action.action.discriminant().feed_status_slot()]
723 .status
724 .considered_fed()
725 {
726 let initiation_decision = contender_action.action.initiation_decision(
727 config,
728 &sensors,
729 TnuaActionContext {
730 frame_duration,
731 tracker,
732 up_direction,
733 basis: &TnuaBasisAccess {
734 input: &controller.basis,
735 config: basis_config,
736 memory: &controller.basis_memory,
737 },
738 },
739 &contender_action.being_fed_for,
740 );
741 contender_action.being_fed_for.tick(time.delta());
742 match initiation_decision {
743 TnuaActionInitiationDirective::Reject => {
744 controller.contender_action = None;
745 false
746 }
747 TnuaActionInitiationDirective::Delay => false,
748 TnuaActionInitiationDirective::Allow => true,
749 }
750 } else {
751 controller.contender_action = None;
752 false
753 }
754 } else {
755 false
756 };
757
758 if let Some(action_state) = controller.current_action.as_mut() {
759 let lifecycle_status = if has_valid_contender {
760 TnuaActionLifecycleStatus::CancelledInto
761 } else if controller.actions_being_fed[action_state.discriminant().feed_status_slot()]
762 .status
763 .considered_fed()
764 {
765 TnuaActionLifecycleStatus::StillFed
766 } else {
767 TnuaActionLifecycleStatus::NoLongerFed
768 };
769
770 let directive = action_state.interface_mut().apply(
771 &sensors,
772 TnuaActionContext {
773 frame_duration,
774 tracker,
775 basis: &TnuaBasisAccess {
776 input: &controller.basis,
777 config: basis_config,
778 memory: &controller.basis_memory,
779 },
780 up_direction,
781 },
782 lifecycle_status,
783 motor.as_mut(),
784 );
785 action_state.interface_mut().influence_basis(
786 TnuaBasisContext {
787 frame_duration,
788 tracker,
789 up_direction,
790 },
791 &controller.basis,
792 basis_config,
793 &mut controller.basis_memory,
794 );
795 match directive {
796 TnuaActionLifecycleDirective::StillActive => {
797 if !lifecycle_status.is_active()
798 && let TnuaActionFlowStatus::ActionOngoing(action_discriminant) =
799 controller.action_flow_status
800 {
801 controller.action_flow_status =
802 TnuaActionFlowStatus::ActionEnded(action_discriminant);
803 }
804 }
805 TnuaActionLifecycleDirective::Finished
806 | TnuaActionLifecycleDirective::Reschedule { .. } => {
807 if let TnuaActionLifecycleDirective::Reschedule { after_seconds } = directive {
808 controller.actions_being_fed
809 [action_state.discriminant().feed_status_slot()]
810 .rescheduled_in =
811 Some(Timer::from_seconds(after_seconds.f32(), TimerMode::Once));
812 }
813 controller.current_action = if has_valid_contender {
814 let contender_action = controller.contender_action.take().expect(
815 "has_valid_contender can only be true if contender_action is Some",
816 );
817 let mut contender_action_state =
818 contender_action.action.into_action_state_variant(config);
819
820 controller.actions_being_fed
821 [contender_action_state.discriminant().feed_status_slot()]
822 .rescheduled_in = None;
823
824 let contender_directive = contender_action_state.interface_mut().apply(
825 &sensors,
826 TnuaActionContext {
827 frame_duration,
828 tracker,
829 basis: &TnuaBasisAccess {
830 input: &controller.basis,
831 config: basis_config,
832 memory: &controller.basis_memory,
833 },
834 up_direction,
835 },
836 TnuaActionLifecycleStatus::CancelledFrom,
837 motor.as_mut(),
838 );
839 contender_action_state.interface_mut().influence_basis(
840 TnuaBasisContext {
841 frame_duration,
842 tracker,
843 up_direction,
844 },
845 &controller.basis,
846 basis_config,
847 &mut controller.basis_memory,
848 );
849 match contender_directive {
850 TnuaActionLifecycleDirective::StillActive => {
851 controller.action_flow_status =
852 if let TnuaActionFlowStatus::ActionOngoing(discriminant) =
853 controller.action_flow_status
854 {
855 TnuaActionFlowStatus::Cancelled {
856 old: discriminant,
857 new: contender_action_state.discriminant(),
858 }
859 } else {
860 TnuaActionFlowStatus::ActionStarted(
861 contender_action_state.discriminant(),
862 )
863 };
864 Some(contender_action_state)
865 }
866 TnuaActionLifecycleDirective::Finished
867 | TnuaActionLifecycleDirective::Reschedule { after_seconds: _ } => {
868 if let TnuaActionLifecycleDirective::Reschedule { after_seconds } =
869 contender_directive
870 {
871 controller.actions_being_fed[contender_action_state
872 .discriminant()
873 .feed_status_slot()]
874 .rescheduled_in = Some(Timer::from_seconds(
875 after_seconds.f32(),
876 TimerMode::Once,
877 ));
878 }
879 if let TnuaActionFlowStatus::ActionOngoing(discriminant) =
880 controller.action_flow_status
881 {
882 controller.action_flow_status =
883 TnuaActionFlowStatus::ActionEnded(discriminant);
884 }
885 None
886 }
887 }
888 } else {
889 controller.action_flow_status =
890 TnuaActionFlowStatus::ActionEnded(action_state.discriminant());
891 None
892 };
893 }
894 }
895 } else if has_valid_contender {
896 let contender_action = controller
897 .contender_action
898 .take()
899 .expect("has_valid_contender can only be true if contender_action is Some");
900 let mut contender_action_state =
901 contender_action.action.into_action_state_variant(config);
902
903 contender_action_state.interface_mut().apply(
904 &sensors,
905 TnuaActionContext {
906 frame_duration,
907 tracker,
908 basis: &TnuaBasisAccess {
909 input: &controller.basis,
910 config: basis_config,
911 memory: &controller.basis_memory,
912 },
913 up_direction,
914 },
915 TnuaActionLifecycleStatus::Initiated,
916 motor.as_mut(),
917 );
918 contender_action_state.interface_mut().influence_basis(
919 TnuaBasisContext {
920 frame_duration,
921 tracker,
922 up_direction,
923 },
924 &controller.basis,
925 basis_config,
926 &mut controller.basis_memory,
927 );
928 controller.action_flow_status =
929 TnuaActionFlowStatus::ActionStarted(contender_action_state.discriminant());
930 controller.current_action = Some(contender_action_state);
931 }
932
933 for fed_entry in controller.actions_being_fed.iter_mut() {
934 match fed_entry.status {
935 FedStatus::Not | FedStatus::Lingering | FedStatus::Fresh | FedStatus::Held => {}
936 FedStatus::Trigger | FedStatus::Interrupt => {
937 fed_entry.status = FedStatus::Not;
938 }
939 }
940 }
941 }
942}