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::NUM_VARIANTS).map(|_| Default::default()).collect(),
198 contender_action: None,
199 action_flow_status: TnuaActionFlowStatus::NoAction,
200 up_direction: None,
201 action_feeding_initiated: false,
202 current_action: None,
203 }
204 }
205}
206
207#[derive(Debug, Clone, Default)]
209#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
210pub enum TnuaActionFlowStatus<D: TnuaActionDiscriminant> {
211 #[default]
213 NoAction,
214
215 ActionStarted(D),
217
218 ActionOngoing(D),
220
221 ActionEnded(D),
225
226 Cancelled { old: D, new: D },
228}
229
230impl<D: TnuaActionDiscriminant> TnuaActionFlowStatus<D> {
231 pub fn ongoing(&self) -> Option<D> {
235 match self {
236 TnuaActionFlowStatus::NoAction | TnuaActionFlowStatus::ActionEnded(_) => None,
237 TnuaActionFlowStatus::ActionStarted(discriminant)
238 | TnuaActionFlowStatus::ActionOngoing(discriminant)
239 | TnuaActionFlowStatus::Cancelled {
240 old: _,
241 new: discriminant,
242 } => Some(*discriminant),
243 }
244 }
245
246 pub fn just_starting(&self) -> Option<D> {
251 match self {
252 TnuaActionFlowStatus::NoAction
253 | TnuaActionFlowStatus::ActionOngoing(_)
254 | TnuaActionFlowStatus::ActionEnded(_) => None,
255 TnuaActionFlowStatus::ActionStarted(discriminant)
256 | TnuaActionFlowStatus::Cancelled {
257 old: _,
258 new: discriminant,
259 } => Some(*discriminant),
260 }
261 }
262}
263
264impl<S: TnuaScheme> TnuaController<S> {
265 pub fn basis_access(
268 &'_ self,
269 ) -> Result<TnuaBasisAccess<'_, S::Basis>, TnuaControllerHasNotPulledConfiguration> {
270 Ok(TnuaBasisAccess {
271 input: &self.basis,
272 config: self
273 .basis_config
274 .as_ref()
275 .ok_or(TnuaControllerHasNotPulledConfiguration)?,
276 memory: &self.basis_memory,
277 })
278 }
279
280 pub fn initiate_action_feeding(&mut self) {
283 self.action_feeding_initiated = true;
284 }
285
286 pub fn action(&mut self, action: S) {
295 assert!(
296 self.action_feeding_initiated,
297 "Feeding action without invoking `initiate_action_feeding()`"
298 );
299 let fed_entry = &mut self.actions_being_fed[action.variant_idx()];
300
301 match fed_entry.status {
302 FedStatus::Lingering
303 | FedStatus::Fresh
304 | FedStatus::Trigger
305 | FedStatus::Interrupt
306 | FedStatus::Held => {
307 fed_entry.status = FedStatus::Fresh;
308 if let Some(current_action) = self.current_action.as_mut() {
309 match action.update_in_action_state(current_action) {
310 TnuaUpdateInActionStateResult::Success => {
311 }
313 TnuaUpdateInActionStateResult::WrongVariant(_) => {
314 }
317 }
318 } else if self.contender_action.is_none()
319 && fed_entry
320 .rescheduled_in
321 .as_ref()
322 .is_some_and(|timer| timer.is_finished())
323 {
324 self.contender_action = Some(ContenderAction {
327 action,
328 being_fed_for: Stopwatch::new(),
329 });
330 } else {
331 }
333 }
334 FedStatus::Not => {
335 *fed_entry = FedEntry {
336 status: FedStatus::Fresh,
337 rescheduled_in: None,
338 };
339 if let Some(contender_action) = self.contender_action.as_mut()
340 && action.discriminant() == contender_action.action.discriminant()
341 {
342 contender_action.action = action;
343 } else if let Some(contender_action) = self.contender_action.as_ref()
344 && self.actions_being_fed[contender_action.action.discriminant().variant_idx()]
345 .status
346 == FedStatus::Interrupt
347 {
348 } else {
350 self.contender_action = Some(ContenderAction {
351 action,
352 being_fed_for: Stopwatch::new(),
353 });
354 }
355 }
356 }
357 }
358
359 pub fn action_trigger(&mut self, action: S) {
366 let fed_entry = &mut self.actions_being_fed[action.variant_idx()];
367
368 match fed_entry.status {
369 FedStatus::Lingering
370 | FedStatus::Fresh
371 | FedStatus::Trigger
372 | FedStatus::Interrupt
373 | FedStatus::Held => {
374 }
376 FedStatus::Not => {
377 *fed_entry = FedEntry {
378 status: FedStatus::Trigger,
379 rescheduled_in: None,
380 };
381 if let Some(contender_action) = self.contender_action.as_mut()
382 && action.discriminant() == contender_action.action.discriminant()
383 {
384 contender_action.action = action;
385 } else if let Some(contender_action) = self.contender_action.as_ref()
386 && self.actions_being_fed[contender_action.action.discriminant().variant_idx()]
387 .status
388 == FedStatus::Interrupt
389 {
390 } else {
392 self.contender_action = Some(ContenderAction {
393 action,
394 being_fed_for: Stopwatch::new(),
395 });
396 }
397 }
398 }
399 }
400
401 pub fn action_interrupt(&mut self, action: S) {
404 self.actions_being_fed[action.variant_idx()] = FedEntry {
407 status: FedStatus::Interrupt,
408 rescheduled_in: None,
409 };
410
411 let action = if let Some(current_action) = self.current_action.as_mut() {
412 match action.update_in_action_state(current_action) {
413 TnuaUpdateInActionStateResult::Success => {
414 return;
415 }
416 TnuaUpdateInActionStateResult::WrongVariant(action) => {
417 action
419 }
420 }
421 } else {
422 action
423 };
424 self.contender_action = Some(ContenderAction {
426 action,
427 being_fed_for: Stopwatch::new(),
428 });
429 }
430
431 pub fn action_start(&mut self, action: S) {
434 let fed_entry = &mut self.actions_being_fed[action.variant_idx()];
435
436 match fed_entry.status {
437 FedStatus::Lingering | FedStatus::Fresh | FedStatus::Trigger | FedStatus::Interrupt => {
438 }
440 FedStatus::Held => {
441 let action = if let Some(current_action) = self.current_action.as_mut() {
443 match action.update_in_action_state(current_action) {
444 TnuaUpdateInActionStateResult::Success => {
445 return;
447 }
448 TnuaUpdateInActionStateResult::WrongVariant(action) => action,
449 }
450 } else {
451 action
452 };
453 if let Some(contender_action) = self.contender_action.as_mut()
454 && action.discriminant() == contender_action.action.discriminant()
455 {
456 contender_action.action = action;
458 }
459 }
460 FedStatus::Not => {
461 *fed_entry = FedEntry {
462 status: FedStatus::Held,
463 rescheduled_in: None,
464 };
465 if let Some(contender_action) = self.contender_action.as_mut()
466 && action.discriminant() == contender_action.action.discriminant()
467 {
468 contender_action.action = action;
469 } else if let Some(contender_action) = self.contender_action.as_ref()
470 && self.actions_being_fed[contender_action.action.discriminant().variant_idx()]
471 .status
472 == FedStatus::Interrupt
473 {
474 } else {
476 self.contender_action = Some(ContenderAction {
477 action,
478 being_fed_for: Stopwatch::new(),
479 });
480 }
481 }
482 }
483 }
484
485 pub fn action_end(&mut self, action: S::ActionDiscriminant) {
487 self.actions_being_fed[action.variant_idx()] = Default::default();
489 }
490
491 pub fn prolong_action(&mut self) {
496 if let Some(current_action) = self.action_discriminant() {
497 self.actions_being_fed[current_action.variant_idx()].status = FedStatus::Fresh;
498 }
499 }
500
501 pub fn action_discriminant(&self) -> Option<S::ActionDiscriminant> {
503 Some(self.current_action.as_ref()?.discriminant())
504 }
505
506 pub fn action_flow_status(&self) -> &TnuaActionFlowStatus<S::ActionDiscriminant> {
520 &self.action_flow_status
521 }
522
523 pub fn up_direction(&self) -> Option<Dir3> {
531 self.up_direction
532 }
533}
534
535impl<S: TnuaScheme> TnuaController<S>
536where
537 S::Basis: TnuaBasisWithGround,
538{
539 pub fn is_airborne(&self) -> Result<bool, TnuaControllerHasNotPulledConfiguration> {
544 Ok(S::Basis::is_airborne(&self.basis_access()?))
545 }
546}
547
548#[derive(thiserror::Error, Debug)]
549#[error("The Tnua controller did not pull the configuration asset yet")]
550pub struct TnuaControllerHasNotPulledConfiguration;
551
552fn apply_ghost_overwrites<S: TnuaScheme>(
553 mut query: Query<(&TnuaSensorsEntities<S>, &mut TnuaGhostOverwrites<S>)>,
554 mut proximity_sensors_query: Query<(&mut TnuaProximitySensor, &TnuaGhostSensor)>,
555) {
556 for (sensors_entities, mut ghost_overwrites) in query.iter_mut() {
557 for (ghost_overwrite, sensor_entity) in
558 S::Basis::ghost_sensor_overwrites(ghost_overwrites.as_mut().as_mut(), sensors_entities)
559 {
560 let Ok((mut proximity_sensor, ghost_sensor)) =
561 proximity_sensors_query.get_mut(sensor_entity)
562 else {
563 continue;
564 };
565 if let Some(ghost_output) = ghost_overwrite.find_in(&ghost_sensor.0) {
566 proximity_sensor.output = Some(ghost_output.clone());
567 } else {
568 ghost_overwrite.set(None);
569 }
570 }
571 }
572}
573
574#[allow(clippy::type_complexity)]
575fn apply_controller_system<S: TnuaScheme>(
576 time: Res<Time>,
577 mut query: Query<(
578 Entity,
579 &mut TnuaController<S>,
580 &TnuaConfig<S>,
581 &mut TnuaSensorsEntities<S>,
582 &TnuaRigidBodyTracker,
583 &mut TnuaMotor,
584 Option<&TnuaToggle>,
585 Has<TnuaGhostOverwrites<S>>,
586 )>,
587 proximity_sensors_query: Query<(&TnuaProximitySensor, Has<TnuaGhostSensor>)>,
588 config_assets: Res<Assets<S::Config>>,
589 mut commands: Commands,
590) {
591 let frame_duration = time.delta().as_secs_f64() as Float;
592 if frame_duration == 0.0 {
593 return;
594 }
595 for (
596 controller_entity,
597 mut controller,
598 config_handle,
599 mut sensors_entities,
600 tracker,
601 mut motor,
602 tnua_toggle,
603 has_ghost_overwrites,
604 ) in query.iter_mut()
605 {
606 match tnua_toggle.copied().unwrap_or_default() {
607 TnuaToggle::Disabled => continue,
608 TnuaToggle::SenseOnly => {}
609 TnuaToggle::Enabled => {}
610 }
611 let controller = controller.as_mut();
612
613 let Some(config) = config_assets.get(&config_handle.0) else {
614 continue;
615 };
616 controller.basis_config = Some({
617 let mut basis_config = config.basis_config().clone();
618 if let Some(current_action) = controller.current_action.as_ref() {
619 current_action.modify_basis_config(&mut basis_config);
620 }
621 basis_config
622 });
623 let basis_config = controller
624 .basis_config
625 .as_ref()
626 .expect("We just set it to Some");
627
628 let up_direction = Dir3::new(-tracker.gravity.f32()).ok();
629 controller.up_direction = up_direction;
630 let up_direction = up_direction.unwrap_or(Dir3::Y);
632
633 let Some(sensors) = S::Basis::get_or_create_sensors(
634 up_direction,
635 basis_config,
636 &controller.basis_memory,
637 &mut sensors_entities.as_mut().sensors_entities,
638 &proximity_sensors_query,
639 controller_entity,
640 &mut commands,
641 has_ghost_overwrites,
642 ) else {
643 continue;
644 };
645
646 match controller.action_flow_status {
647 TnuaActionFlowStatus::NoAction | TnuaActionFlowStatus::ActionOngoing(_) => {}
648 TnuaActionFlowStatus::ActionEnded(_) => {
649 controller.action_flow_status = TnuaActionFlowStatus::NoAction;
650 }
651 TnuaActionFlowStatus::ActionStarted(discriminant)
652 | TnuaActionFlowStatus::Cancelled {
653 old: _,
654 new: discriminant,
655 } => {
656 controller.action_flow_status = TnuaActionFlowStatus::ActionOngoing(discriminant);
657 }
658 }
659
660 controller.basis.apply(
661 basis_config,
662 &mut controller.basis_memory,
663 &sensors,
664 TnuaBasisContext {
665 frame_duration,
666 tracker,
667 up_direction,
668 },
669 &mut motor,
670 );
671
672 if controller.action_feeding_initiated {
673 controller.action_feeding_initiated = false;
674 for fed_entry in controller.actions_being_fed.iter_mut() {
675 match fed_entry.status {
676 FedStatus::Not | FedStatus::Held => {}
677 FedStatus::Lingering => {
678 *fed_entry = Default::default();
679 }
680 FedStatus::Fresh | FedStatus::Trigger | FedStatus::Interrupt => {
681 fed_entry.status = FedStatus::Lingering;
682 if let Some(rescheduled_in) = &mut fed_entry.rescheduled_in {
683 rescheduled_in.tick(time.delta());
684 }
685 }
686 }
687 }
688 }
689
690 let has_valid_contender =
691 if let Some(contender_action) = controller.contender_action.as_mut() {
692 if controller.actions_being_fed[contender_action.action.variant_idx()]
693 .status
694 .considered_fed()
695 {
696 let initiation_decision = contender_action.action.initiation_decision(
697 config,
698 &sensors,
699 TnuaActionContext {
700 frame_duration,
701 tracker,
702 up_direction,
703 basis: &TnuaBasisAccess {
704 input: &controller.basis,
705 config: basis_config,
706 memory: &controller.basis_memory,
707 },
708 },
709 &contender_action.being_fed_for,
710 );
711 contender_action.being_fed_for.tick(time.delta());
712 match initiation_decision {
713 TnuaActionInitiationDirective::Reject => {
714 controller.contender_action = None;
715 false
716 }
717 TnuaActionInitiationDirective::Delay => false,
718 TnuaActionInitiationDirective::Allow => true,
719 }
720 } else {
721 controller.contender_action = None;
722 false
723 }
724 } else {
725 false
726 };
727
728 if let Some(action_state) = controller.current_action.as_mut() {
729 let lifecycle_status = if has_valid_contender {
730 TnuaActionLifecycleStatus::CancelledInto
731 } else if controller.actions_being_fed[action_state.variant_idx()]
732 .status
733 .considered_fed()
734 {
735 TnuaActionLifecycleStatus::StillFed
736 } else {
737 TnuaActionLifecycleStatus::NoLongerFed
738 };
739
740 let directive = action_state.interface_mut().apply(
741 &sensors,
742 TnuaActionContext {
743 frame_duration,
744 tracker,
745 basis: &TnuaBasisAccess {
746 input: &controller.basis,
747 config: basis_config,
748 memory: &controller.basis_memory,
749 },
750 up_direction,
751 },
752 lifecycle_status,
753 motor.as_mut(),
754 );
755 action_state.interface_mut().influence_basis(
756 TnuaBasisContext {
757 frame_duration,
758 tracker,
759 up_direction,
760 },
761 &controller.basis,
762 basis_config,
763 &mut controller.basis_memory,
764 );
765 match directive {
766 TnuaActionLifecycleDirective::StillActive => {
767 if !lifecycle_status.is_active()
768 && let TnuaActionFlowStatus::ActionOngoing(action_discriminant) =
769 controller.action_flow_status
770 {
771 controller.action_flow_status =
772 TnuaActionFlowStatus::ActionEnded(action_discriminant);
773 }
774 }
775 TnuaActionLifecycleDirective::Finished
776 | TnuaActionLifecycleDirective::Reschedule { .. } => {
777 if let TnuaActionLifecycleDirective::Reschedule { after_seconds } = directive {
778 controller.actions_being_fed[action_state.variant_idx()].rescheduled_in =
779 Some(Timer::from_seconds(after_seconds.f32(), TimerMode::Once));
780 }
781 controller.current_action = if has_valid_contender {
782 let contender_action = controller.contender_action.take().expect(
783 "has_valid_contender can only be true if contender_action is Some",
784 );
785 let mut contender_action_state =
786 contender_action.action.into_action_state_variant(config);
787
788 controller.actions_being_fed[contender_action_state.variant_idx()]
789 .rescheduled_in = None;
790
791 let contender_directive = contender_action_state.interface_mut().apply(
792 &sensors,
793 TnuaActionContext {
794 frame_duration,
795 tracker,
796 basis: &TnuaBasisAccess {
797 input: &controller.basis,
798 config: basis_config,
799 memory: &controller.basis_memory,
800 },
801 up_direction,
802 },
803 TnuaActionLifecycleStatus::CancelledFrom,
804 motor.as_mut(),
805 );
806 contender_action_state.interface_mut().influence_basis(
807 TnuaBasisContext {
808 frame_duration,
809 tracker,
810 up_direction,
811 },
812 &controller.basis,
813 basis_config,
814 &mut controller.basis_memory,
815 );
816 match contender_directive {
817 TnuaActionLifecycleDirective::StillActive => {
818 controller.action_flow_status =
819 if let TnuaActionFlowStatus::ActionOngoing(discriminant) =
820 controller.action_flow_status
821 {
822 TnuaActionFlowStatus::Cancelled {
823 old: discriminant,
824 new: contender_action_state.discriminant(),
825 }
826 } else {
827 TnuaActionFlowStatus::ActionStarted(
828 contender_action_state.discriminant(),
829 )
830 };
831 Some(contender_action_state)
832 }
833 TnuaActionLifecycleDirective::Finished
834 | TnuaActionLifecycleDirective::Reschedule { after_seconds: _ } => {
835 if let TnuaActionLifecycleDirective::Reschedule { after_seconds } =
836 contender_directive
837 {
838 controller.actions_being_fed
839 [contender_action_state.variant_idx()]
840 .rescheduled_in = Some(Timer::from_seconds(
841 after_seconds.f32(),
842 TimerMode::Once,
843 ));
844 }
845 if let TnuaActionFlowStatus::ActionOngoing(discriminant) =
846 controller.action_flow_status
847 {
848 controller.action_flow_status =
849 TnuaActionFlowStatus::ActionEnded(discriminant);
850 }
851 None
852 }
853 }
854 } else {
855 controller.action_flow_status =
856 TnuaActionFlowStatus::ActionEnded(action_state.discriminant());
857 None
858 };
859 }
860 }
861 } else if has_valid_contender {
862 let contender_action = controller
863 .contender_action
864 .take()
865 .expect("has_valid_contender can only be true if contender_action is Some");
866 let mut contender_action_state =
867 contender_action.action.into_action_state_variant(config);
868
869 contender_action_state.interface_mut().apply(
870 &sensors,
871 TnuaActionContext {
872 frame_duration,
873 tracker,
874 basis: &TnuaBasisAccess {
875 input: &controller.basis,
876 config: basis_config,
877 memory: &controller.basis_memory,
878 },
879 up_direction,
880 },
881 TnuaActionLifecycleStatus::Initiated,
882 motor.as_mut(),
883 );
884 contender_action_state.interface_mut().influence_basis(
885 TnuaBasisContext {
886 frame_duration,
887 tracker,
888 up_direction,
889 },
890 &controller.basis,
891 basis_config,
892 &mut controller.basis_memory,
893 );
894 controller.action_flow_status =
895 TnuaActionFlowStatus::ActionStarted(contender_action_state.discriminant());
896 controller.current_action = Some(contender_action_state);
897 }
898
899 for fed_entry in controller.actions_being_fed.iter_mut() {
900 match fed_entry.status {
901 FedStatus::Not | FedStatus::Lingering | FedStatus::Fresh | FedStatus::Held => {}
902 FedStatus::Trigger | FedStatus::Interrupt => {
903 fed_entry.status = FedStatus::Not;
904 }
905 }
906 }
907 }
908}