1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
//! The dynamic action type.
//!
//! This type is primarily intended for use when declaring every single action in a single enum isn't feasible,
//! for example due to a complex crate hierarchy where various feature crates want to individually declare their own
//! actions without needing to coordinate with other crates.
//!
//! Example:
//! ```
//! # use leafwing_input_manager::dynamic_action::{DynActionMarker, DynActionRegistry, RegisterActionToAppExt};
//! # use bevy::prelude::*;
//! // In crate `feature_one`
//! #[derive(DynActionMarker)]
//! struct FeatureOneAction;
//! struct FeatureOnePlugin;
//!
//! impl Plugin for FeatureOnePlugin {
//! fn build(&self, app: &mut App) {
//! app.register_action::<FeatureOneAction>();
//! }
//! }
//!
//! // In crate `feature_two`
//! #[derive(DynActionMarker)]
//! struct FeatureTwoAction;
//! struct FeatureTwoPlugin;
//!
//! impl Plugin for FeatureTwoPlugin {
//! fn build(&self, app: &mut App) {
//! app.register_action::<FeatureTwoAction>();
//! }
//! }
//!
//! // In crate `top_level_crate`, which depends on `feature_one` and `feature_two`
//! let mut app = App::new();
//! app.insert_resource(DynActionRegistry::get().unwrap())
//! .add_plugins(FeatureOnePlugin)
//! .add_plugins(FeatureTwoPlugin);
//! app.world.remove_resource::<DynActionRegistry>().unwrap().finish();
//! ```
use std::any::TypeId;
use bevy::reflect::TypePath;
use bevy::{
prelude::{App, Resource},
utils::HashMap,
};
use once_cell::sync::OnceCell;
use crate::Actionlike;
pub use leafwing_input_manager_macros::DynActionMarker;
// Here, we use a pair of global variables to track `DynAction` state.
// `DYN_ACTION_MAP` needs to be a global to allow accessing it easily in arbitrary code,
// especially in the case of the `Actionlike` implementation of `DynAction`.
static DYN_ACTION_MAP: OnceCell<HashMap<TypeId, usize>> = OnceCell::new();
// This simply tracks whether a `DynActionRegistry` has been created, to ensure that `DynActionRegistry::finish` is infallible.
static REGISTRY_CREATED: OnceCell<()> = OnceCell::new();
/// The runtime representation of actions declared via marker types
#[derive(Copy, Clone, TypePath)]
pub struct DynAction(usize);
/// Coordinates the registration of dynamic action types
#[derive(Resource)]
pub struct DynActionRegistry(Vec<TypeId>);
impl DynActionRegistry {
/// Tries to get a [`DynActionRegistry`]. This will fail if this function has been called before!
pub fn get() -> Option<Self> {
REGISTRY_CREATED.set(()).is_ok().then(|| Self(vec![]))
}
/// Registers the given dynamic action type to enable its usage once registration is finalized using [`DynActionRegistry::finish`]
pub fn register<A: DynActionMarker>(&mut self) {
self.0.push(TypeId::of::<A>())
}
/// Puts the registered types in a global static and enables [`DynAction`] using systems to work.
///
/// Note: Do not create instances of any type in this crate that uses [`DynAction`] as its [`Actionlike`] type before calling this function.
pub fn finish(self) {
let map = self
.0
.into_iter()
.enumerate()
.map(|(i, type_id)| (type_id, i))
.collect();
// this cannot fail because this function is the only place where this static is set,
// and this function s self value can only be created once
DYN_ACTION_MAP.set(map).unwrap()
}
}
impl DynAction {
fn get<A: DynActionMarker>() -> Self {
DynAction(
*DYN_ACTION_MAP
.get()
.unwrap()
.get(&TypeId::of::<A>())
.unwrap(),
)
}
}
/// Trait implemented by marker types meant to be used as actions
pub trait DynActionMarker: Sized + 'static {
/// Gets the [`DynAction`] value associated with this type for use with other parts of this crate
fn get_action() -> DynAction {
DynAction::get::<Self>()
}
}
impl<A: DynActionMarker> From<A> for DynAction {
fn from(_: A) -> DynAction {
DynAction::get::<A>()
}
}
impl Actionlike for DynAction {
fn n_variants() -> usize {
DYN_ACTION_MAP.get().unwrap().len()
}
fn get_at(index: usize) -> Option<Self> {
(index < Self::n_variants()).then_some(DynAction(index))
}
fn index(&self) -> usize {
self.0
}
}
/// Helper trait for registering [`DynAction`] types to an app where the [`DynActionRegistry`] exists as a resource
pub trait RegisterActionToAppExt {
/// Calls [`DynActionRegistry::register`] on the [`DynActionRegistry`] resource if it exists, otherwise panics.
fn register_action<A: DynActionMarker>(&mut self) -> &mut Self;
}
impl RegisterActionToAppExt for App {
fn register_action<A: DynActionMarker>(&mut self) -> &mut Self {
self.world
.get_resource_mut::<DynActionRegistry>()
.expect("The `DynActionRegistry` isn't currently in the world!")
.register::<A>();
self
}
}