bevy_tnua/basis_action_traits.rs
1use std::fs::File;
2use std::io::ErrorKind;
3use std::path::Path;
4use std::time::Duration;
5
6use crate::TnuaMotor;
7use crate::action_state::TnuaActionStateInterface;
8use crate::ghost_overrides::TnuaGhostOverwrite;
9use crate::sensor_sets::TnuaSensors;
10use bevy::prelude::*;
11use bevy::time::Stopwatch;
12use bevy_tnua_physics_integration_layer::data_for_backends::TnuaGhostSensor;
13use bevy_tnua_physics_integration_layer::data_for_backends::TnuaProximitySensor;
14use bevy_tnua_physics_integration_layer::data_for_backends::TnuaRigidBodyTracker;
15use serde::Deserialize;
16use serde::Serialize;
17
18use crate::math::*;
19
20/// See [the derive macro](bevy_tnua_macros::TnuaScheme).
21pub trait TnuaScheme: 'static + Send + Sync + Sized {
22 /// The base motion controller of the character. Typically
23 /// [`TnuaBuiltinWalk`](crate::builtins::TnuaBuiltinWalk)
24 type Basis: TnuaBasis;
25 /// A big configuration struct, containing the configuration of the basis and of all the
26 /// actions.
27 type Config: TnuaSchemeConfig<Scheme = Self> + Asset;
28 /// An enum with a unit variant for each variant of the control scheme.
29 type ActionDiscriminant: TnuaActionDiscriminant;
30 /// An enum mirroring the control scheme, except instead of just having the input of each
31 /// action each variant has a [`TnuaActionState`] which contains the action's input,
32 /// configuration and memoery. If the variant in the control scheme has payload, they are
33 /// copied to the variant in action state as is.
34 type ActionState: TnuaActionState<Basis = Self::Basis, Discriminant = Self::ActionDiscriminant>;
35
36 #[doc(hidden)]
37 const NUM_VARIANTS: usize;
38
39 /// The action without the input and payloads.
40 fn discriminant(&self) -> Self::ActionDiscriminant;
41
42 #[doc(hidden)]
43 fn variant_idx(&self) -> usize {
44 self.discriminant().variant_idx()
45 }
46
47 #[doc(hidden)]
48 fn into_action_state_variant(self, config: &Self::Config) -> Self::ActionState;
49
50 #[doc(hidden)]
51 fn update_in_action_state(
52 self,
53 action_state_enum: &mut Self::ActionState,
54 ) -> TnuaUpdateInActionStateResult<Self>;
55
56 #[doc(hidden)]
57 fn initiation_decision(
58 &self,
59 config: &Self::Config,
60 sensors: &<Self::Basis as TnuaBasis>::Sensors<'_>,
61 ctx: TnuaActionContext<Self::Basis>,
62 being_fed_for: &Stopwatch,
63 ) -> TnuaActionInitiationDirective;
64}
65
66#[doc(hidden)]
67pub enum TnuaUpdateInActionStateResult<S: TnuaScheme> {
68 Success,
69 WrongVariant(S),
70}
71
72/// A big configuration struct, containing the configuration of the basis and of all the actions of
73/// a control scheme.
74pub trait TnuaSchemeConfig: Serialize + for<'de> Deserialize<'de> {
75 /// The control scheme this configuration belongs to.
76 type Scheme: TnuaScheme<Config = Self>;
77
78 #[doc(hidden)]
79 fn basis_config(&self) -> &<<Self::Scheme as TnuaScheme>::Basis as TnuaBasis>::Config;
80
81 /// Use to create a template of the configuration in the assets directory.
82 ///
83 /// The call to this function should be removed after the file is created, to avoid checking it
84 /// on every run and to avoid breaking WASM (which cannot access the assets directory using the
85 /// filesystem)
86 fn write_if_not_exist(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
87 let serialized = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::new())
88 .expect("Should be able to serialize all configs to RON");
89 let file = File::options().write(true).create_new(true).open(path);
90 if let Err(err) = &file
91 && err.kind() == ErrorKind::AlreadyExists
92 {
93 return Ok(());
94 }
95 use std::io::Write;
96 write!(file?, "{}", serialized)
97 }
98}
99
100/// Various data passed to [`TnuaBasis::apply`].
101#[derive(Debug)]
102pub struct TnuaBasisContext<'a> {
103 /// The duration of the current frame.
104 pub frame_duration: Float,
105
106 /// A sensor that collects data about the rigid body from the physics backend.
107 pub tracker: &'a TnuaRigidBodyTracker,
108
109 /// The direction considered as "up".
110 pub up_direction: Dir3,
111}
112
113/// The main movement command of a character.
114///
115/// A basis handles the character's motion when the user is not feeding it any input, or when it
116/// just moves around without doing anything special. A simple game would only need one basis -
117/// [`TnuaBuiltinWalk`](crate::builtins::TnuaBuiltinWalk) - but more complex games can have bases
118/// for things like swimming or driving.
119///
120/// The type that implements this trait is called the basis _input_, and is expected to be
121/// overwritten each frame by the controller system of the game code. Configuration is considered
122/// as part of the input. Configuration is stored in an asset, as part of a struct implementing
123/// [`TnuaSchemeConfig`] which also holds the configuration for all the actions. If the basis needs
124/// to persist data between frames it must keep it in its [memory](TnuaBasis::Memory).
125pub trait TnuaBasis: Default + 'static + Send + Sync {
126 type Config: Send + Sync + Clone + Serialize + for<'de> Deserialize<'de>;
127 type Memory: Send + Sync + Default;
128 type Sensors<'a>: TnuaSensors<'a>;
129
130 /// This is where the basis affects the character's motion.
131 ///
132 /// This method gets called each frame to let the basis control the [`TnuaMotor`] that will
133 /// later move the character.
134 ///
135 /// Note that after the motor is set in this method, if there is an action going on, the
136 /// action's [`apply`](TnuaAction::apply) will also run and typically change some of the things
137 /// the basis did to the motor.
138 ///
139 /// It can also update the memory.
140 fn apply(
141 &self,
142 config: &Self::Config,
143 memory: &mut Self::Memory,
144 sensors: &Self::Sensors<'_>,
145 ctx: TnuaBasisContext,
146 motor: &mut TnuaMotor,
147 );
148
149 /// This is where the basis initiates its sensors.
150 ///
151 /// * Use the helper
152 /// [`ProximitySensorPreparationHelper`](crate::sensor_sets::ProximitySensorPreparationHelper)
153 /// to create the sensors.
154 /// * Return `None` if - and only if - some _essential_ sensors are missing, because it'll
155 /// cause the controller to skip frames until it returns `Some`.
156 ///
157 /// An example of non-essential sensor is the headroom sensor in
158 /// [`TnuaBuiltinWalkSensors`](crate::builtins::TnuaBuiltinWalkSensors) - the controller can
159 /// function without it (it just won't be able to do crouch enforcement) so if it's absence
160 /// does not cause prevent the controller from running (unlike the `ground` sensor, which is
161 /// essential)
162 /// * Even if some essential sensors are missing, make sure to prepare _all_ the sensors that
163 /// need preparation before returning `None`. No point preparing one sensor per frame.
164 /// * If a sensor exists but wrongly configured - launch the command to reconfigure it and
165 /// return the existing sensor. Unless the configuration is totally off (e.g. - sensor points
166 /// in a very wrong direction) it's probably better to use the misconfigured one than to skip
167 /// a frame.
168 #[allow(clippy::too_many_arguments)]
169 fn get_or_create_sensors<'a: 'b, 'b>(
170 up_direction: Dir3,
171 config: &'a Self::Config,
172 memory: &Self::Memory,
173 entities: &'a mut <Self::Sensors<'static> as TnuaSensors<'static>>::Entities,
174 proximity_sensors_query: &'b Query<(&TnuaProximitySensor, Has<TnuaGhostSensor>)>,
175 controller_entity: Entity,
176 commands: &mut Commands,
177 has_ghost_overwrites: bool,
178 ) -> Option<Self::Sensors<'b>>;
179
180 /// Iterate over each ghost overwrite and the sensor entity of the relevant sensor.
181 ///
182 /// Note that not all sensors need to have ghost overwrites.
183 fn ghost_sensor_overwrites<'a>(
184 ghost_overwrites: &'a mut <Self::Sensors<'static> as TnuaSensors<'static>>::GhostOverwrites,
185 entities: &<Self::Sensors<'static> as TnuaSensors<'static>>::Entities,
186 ) -> impl Iterator<Item = (&'a mut TnuaGhostOverwrite, Entity)>;
187}
188
189/// Input for [`TnuaAction::apply`] that informs it about the long-term feeding of the input.
190#[derive(PartialEq, Eq, Debug, Clone, Copy)]
191pub enum TnuaActionLifecycleStatus {
192 /// There was no action in the previous frame
193 Initiated,
194 /// There was a different action in the previous frame
195 CancelledFrom,
196 /// This action was already active in the previous frame, and it keeps getting fed
197 StillFed,
198 /// This action was fed up until the previous frame, and now no action is fed
199 NoLongerFed,
200 /// This action was fed up until the previous frame, and now a different action tries to override it
201 CancelledInto,
202}
203
204impl TnuaActionLifecycleStatus {
205 /// Continue if the action is still fed, finish if its not fed or if some other action gets
206 /// fed.
207 pub fn directive_simple(&self) -> TnuaActionLifecycleDirective {
208 match self {
209 TnuaActionLifecycleStatus::Initiated => TnuaActionLifecycleDirective::StillActive,
210 TnuaActionLifecycleStatus::CancelledFrom => TnuaActionLifecycleDirective::StillActive,
211 TnuaActionLifecycleStatus::StillFed => TnuaActionLifecycleDirective::StillActive,
212 TnuaActionLifecycleStatus::NoLongerFed => TnuaActionLifecycleDirective::Finished,
213 TnuaActionLifecycleStatus::CancelledInto => TnuaActionLifecycleDirective::Finished,
214 }
215 }
216
217 /// Similar to [`directive_simple`](Self::directive_simple), but if some other action gets fed
218 /// and this action is still being fed, reschedule this action once the other action finishes,
219 /// as long as more time than `after_seconds` has passed.
220 pub fn directive_simple_reschedule(
221 &self,
222 after_seconds: Float,
223 ) -> TnuaActionLifecycleDirective {
224 match self {
225 TnuaActionLifecycleStatus::Initiated => TnuaActionLifecycleDirective::StillActive,
226 TnuaActionLifecycleStatus::CancelledFrom => TnuaActionLifecycleDirective::StillActive,
227 TnuaActionLifecycleStatus::StillFed => TnuaActionLifecycleDirective::StillActive,
228 TnuaActionLifecycleStatus::NoLongerFed => {
229 // The rescheduling will probably go away, but in case things happen too fast and
230 // it doesn't - pass it anyway.
231 TnuaActionLifecycleDirective::Reschedule { after_seconds }
232 }
233 TnuaActionLifecycleStatus::CancelledInto => {
234 TnuaActionLifecycleDirective::Reschedule { after_seconds }
235 }
236 }
237 }
238
239 /// Continue - unless the action is cancelled into another action.
240 pub fn directive_linger(&self) -> TnuaActionLifecycleDirective {
241 match self {
242 TnuaActionLifecycleStatus::Initiated => TnuaActionLifecycleDirective::StillActive,
243 TnuaActionLifecycleStatus::CancelledFrom => TnuaActionLifecycleDirective::StillActive,
244 TnuaActionLifecycleStatus::StillFed => TnuaActionLifecycleDirective::StillActive,
245 TnuaActionLifecycleStatus::NoLongerFed => TnuaActionLifecycleDirective::StillActive,
246 TnuaActionLifecycleStatus::CancelledInto => TnuaActionLifecycleDirective::Finished,
247 }
248 }
249
250 /// Determine if the action just started, whether from no action or to replace another action.
251 pub fn just_started(&self) -> bool {
252 match self {
253 TnuaActionLifecycleStatus::Initiated => true,
254 TnuaActionLifecycleStatus::CancelledFrom => true,
255 TnuaActionLifecycleStatus::StillFed => false,
256 TnuaActionLifecycleStatus::NoLongerFed => false,
257 TnuaActionLifecycleStatus::CancelledInto => false,
258 }
259 }
260
261 /// Determine if the action is currently active - still fed and not replaced by another.
262 pub fn is_active(&self) -> bool {
263 match self {
264 TnuaActionLifecycleStatus::Initiated => true,
265 TnuaActionLifecycleStatus::CancelledFrom => true,
266 TnuaActionLifecycleStatus::StillFed => true,
267 TnuaActionLifecycleStatus::NoLongerFed => false,
268 TnuaActionLifecycleStatus::CancelledInto => false,
269 }
270 }
271}
272
273/// A decision by [`TnuaAction::apply`] that determines if the action should be continued or not.
274///
275/// Note that an action may continue (probably with different state) after no longer being fed, or
276/// stopped while still being fed. It's up to the action, and it should be responsible with it.
277#[derive(PartialEq, Debug, Clone, Copy)]
278pub enum TnuaActionLifecycleDirective {
279 /// The action should continue in the next frame.
280 StillActive,
281
282 /// The action should not continue in the next frame.
283 ///
284 /// If another action is pending, it will run in this frame. This means that two actions can
285 /// run in the same frame, as long as the first is finished (or
286 /// [rescheduled](Self::Reschedule))
287 ///
288 /// If [`TnuaAction::apply`] returns this but the action is still being fed, it will not run
289 /// again unless it stops being fed for one frame and then gets fed again. If this is not the
290 /// desired behavior, [`TnuaActionLifecycleDirective::Reschedule`] should be used instead.
291 Finished,
292
293 /// The action should not continue in the next frame, but if its still being fed it run again
294 /// later. The rescheduled action will be considered a new action.
295 ///
296 /// If another action is pending, it will run in this frame. This means that two actions can
297 /// run in the same frame, as long as the first is rescheduled (or [finished](Self::Finished))
298 Reschedule {
299 /// Only reschedule the action after this much time has passed.
300 after_seconds: Float,
301 },
302}
303
304/// A decision by [`TnuaAction::initiation_decision`] that determines if the action can start.
305#[derive(PartialEq, Eq, Debug, Clone, Copy)]
306pub enum TnuaActionInitiationDirective {
307 /// The action will not start as long as the input is still fed. In order to start it, the
308 /// input must be released for at least one frame and then start being fed again.
309 Reject,
310
311 /// The action will not start this frame, but if the input is still fed next frame
312 /// [`TnuaAction::initiation_decision`] will be checked again.
313 Delay,
314
315 /// The action can start this frame.
316 Allow,
317}
318
319/// A character movement command for performing special actions.
320///
321/// "Special" does not necessarily mean **that** special - even
322/// [jumping](crate::builtins::TnuaBuiltinJump) or [crouching](crate::builtins::TnuaBuiltinCrouch)
323/// are considered [`TnuaAction`]s. Unlike basis - which is something constant - an action is
324/// usually something more momentarily that has a flow.
325///
326/// The type that implements this trait is called the action _input_, and is expected to be
327/// overwritten each frame by the controller system of the game code - although unlike basis the
328/// input will probably be the exact same. Configuration is stored in an asset, as part of a struct
329/// implementing [`TnuaSchemeConfig`] which holds the configuration for the basis and all the
330/// actions. If the action needs to persist data between frames it must keep it in its
331/// [memory](TnuaAction::Memory).
332pub trait TnuaAction<B: TnuaBasis>: 'static + Send + Sync {
333 type Config: Send + Sync + Clone + Serialize + for<'de> Deserialize<'de>;
334
335 /// Data that the action can persist between frames.
336 ///
337 /// The action will typically update this in its [`apply`](Self::apply). It has three purposes:
338 ///
339 /// 1. Store data that cannot be calculated on the spot. For example - the part of the jump
340 /// the character is currently at.
341 ///
342 /// 2. Pass data from the action to Tnua's internal mechanisms.
343 ///
344 /// 3. Inspect the action from game code systems, like an animation controlling system that
345 /// needs to know which animation to play based on the action's current state.
346 type Memory: Send + Sync + Default;
347
348 /// Decides whether the action can start.
349 ///
350 /// The difference between rejecting the action here with
351 /// [`TnuaActionInitiationDirective::Reject`] or [`TnuaActionInitiationDirective::Delay`] and
352 /// approving it with [`TnuaActionInitiationDirective::Allow`] only to do nothing in it and
353 /// terminate with [`TnuaActionLifecycleDirective::Finished`] on the first frame, is that if
354 /// some other action is currently running, in the former that action will continue to be
355 /// active, while in the latter it'll be cancelled into this new action - which, having being
356 /// immediately finished, will leave the controller with no active action, or with some third
357 /// action if there is one.
358 fn initiation_decision(
359 &self,
360 config: &Self::Config,
361 sensors: &B::Sensors<'_>,
362 ctx: TnuaActionContext<B>,
363 being_fed_for: &Stopwatch,
364 ) -> TnuaActionInitiationDirective;
365
366 /// This is where the action affects the character's motion.
367 ///
368 /// This method gets called each frame to let the action control the [`TnuaMotor`] that will
369 /// later move the character. Note that this happens the motor was set by the basis'
370 /// [`apply`](TnuaBasis::apply). Here the action can modify some aspects of or even completely
371 /// overwrite what the basis did.
372 ///
373 /// It can also update the memory.
374 ///
375 /// The returned value of this action determines whether or not the action will continue in the
376 /// next frame.
377 fn apply(
378 &self,
379 config: &Self::Config,
380 memory: &mut Self::Memory,
381 sensors: &B::Sensors<'_>,
382 ctx: TnuaActionContext<B>,
383 lifecycle_status: TnuaActionLifecycleStatus,
384 motor: &mut TnuaMotor,
385 ) -> TnuaActionLifecycleDirective;
386
387 /// An action can use this method to send information back to the basis' memory.
388 ///
389 /// For example - a jump action can use that to violate the basis' coyote time.
390 #[allow(unused_variables)]
391 fn influence_basis(
392 &self,
393 config: &Self::Config,
394 memory: &Self::Memory,
395 ctx: TnuaBasisContext,
396 basis_input: &B,
397 basis_config: &B::Config,
398 basis_memory: &mut B::Memory,
399 ) {
400 }
401}
402
403/// An enum with a unit variant for each variant of the control scheme.
404pub trait TnuaActionDiscriminant:
405 'static + Send + Sync + Copy + Clone + PartialEq + Eq + core::fmt::Debug
406{
407 #[doc(hidden)]
408 fn variant_idx(&self) -> usize;
409}
410
411/// An enum mirroring the control scheme, except instead of just having the input of each action
412/// each variant has a [`TnuaActionState`] which contains the action's input, configuration and
413/// memoery. If the variant in the control scheme has payload, they are copied to the variant in
414/// action state as is.
415pub trait TnuaActionState: 'static + Send + Sync {
416 /// The basis of the control scheme this action state enum represents.
417 type Basis: TnuaBasis;
418 /// An enum with a unit variant for each variant of the control scheme.
419 type Discriminant: TnuaActionDiscriminant;
420
421 /// The action without the input and payloads.
422 fn discriminant(&self) -> Self::Discriminant;
423
424 #[doc(hidden)]
425 fn variant_idx(&self) -> usize {
426 self.discriminant().variant_idx()
427 }
428
429 #[doc(hidden)]
430 fn interface(&self) -> &dyn TnuaActionStateInterface<Self::Basis>;
431 #[doc(hidden)]
432 fn interface_mut(&mut self) -> &mut dyn TnuaActionStateInterface<Self::Basis>;
433
434 #[doc(hidden)]
435 fn modify_basis_config(&self, basis_config: &mut <Self::Basis as TnuaBasis>::Config);
436}
437
438/// References to the full state of the basis in the controller.
439#[derive(Clone)]
440pub struct TnuaBasisAccess<'a, B: TnuaBasis> {
441 /// The data that the user control system sets in [the `basis` field of the
442 /// `TnuaController`](crate::TnuaController::basis).
443 pub input: &'a B,
444 /// The basis configuration, retrieved from an asset and can be modified by payloads that
445 /// implement [`TnuaConfigModifier`].
446 pub config: &'a B::Config,
447 /// Initiated when the controller is created, and gets updated by the basis itself or by an
448 /// action (using [`influence_basis`](TnuaAction::influence_basis))
449 pub memory: &'a B::Memory,
450}
451
452/// Various data passed to [`TnuaAction::apply`].
453pub struct TnuaActionContext<'a, B: TnuaBasis> {
454 /// The duration of the current frame.
455 pub frame_duration: Float,
456
457 /// A sensor that collects data about the rigid body from the physics backend.
458 pub tracker: &'a TnuaRigidBodyTracker,
459
460 /// The direction considered as "up".
461 pub up_direction: Dir3,
462
463 /// An accessor to the basis.
464 pub basis: &'a TnuaBasisAccess<'a, B>,
465}
466
467impl<'a, B: TnuaBasis> TnuaActionContext<'a, B> {
468 /// "Downgrade" to a basis context.
469 ///
470 /// This is useful for some helper methods of that require a basis context.
471 pub fn as_basis_context(&self) -> TnuaBasisContext<'a> {
472 TnuaBasisContext {
473 frame_duration: self.frame_duration,
474 tracker: self.tracker,
475 up_direction: self.up_direction,
476 }
477 }
478
479 /// The duration of the current frame where the action is being applied.
480 pub fn frame_duration_as_duration(&self) -> Duration {
481 #[allow(clippy::useless_conversion)]
482 Duration::from_secs_f64(self.frame_duration.into())
483 }
484}
485
486/// Implement on payloads that can modify the configuration while the relevant action is running.
487///
488/// Note that the payload needs to have `#[scheme(modify_basis_config)]` on it in the control
489/// scheme in order for this to take effect.
490pub trait TnuaConfigModifier<C> {
491 /// Modify the configuration.
492 ///
493 /// This does not touch the asset itself - it modifies a copy of the configuration stored in
494 /// the controller. Each time this method is called it works on a fresh copy of the
495 /// configuration - one that was just cloned from the asset and was not already modified by
496 /// this payload (though it could have been modified by previous payloads in the action
497 /// variant definition). This means that it's okay to do things like `config.field *= 2.0;` -
498 /// it won't cause the number in the field to explode.
499 fn modify_config(&self, config: &mut C);
500}