bevy_stat_query/
lib.rs

1#![allow(clippy::type_complexity)]
2#![allow(clippy::too_many_arguments)]
3#![doc = include_str!("../README.md")]
4#[allow(unused)]
5use bevy_ecs::{component::Component, query::QueryData, system::SystemParam};
6
7pub(crate) static TYPE_ERROR: &str = "Error: a stat does not have the appropriate type. \
8This is almost certainly a bug since we do not provide a type erased api.";
9
10#[doc(hidden)]
11pub use bevy_app::{App, Plugin};
12
13mod fraction;
14mod num_traits;
15pub use fraction::Fraction;
16pub use num_traits::{Flags, Float, Int, NumCast, Number};
17mod stream;
18pub use stream::*;
19mod querier;
20pub use querier::*;
21mod qualifier;
22pub mod types;
23pub use qualifier::{Qualifier, QualifierFlag, QualifierQuery};
24mod stat;
25#[cfg(feature = "derive")]
26pub use bevy_stat_query_derive::{Attribute, Stat};
27pub(crate) use stat::StatExt;
28pub(crate) use stat::StatInst;
29pub use stat::{Stat, StatVTable, StatValuePair};
30pub mod operations;
31pub use operations::StatValue;
32mod plugin;
33pub use plugin::{
34    GlobalStatDefaults, GlobalStatRelations, StatDeserializers, StatExtension, STAT_DESERIALIZERS,
35};
36mod stat_map;
37pub use stat_map::StatMap;
38mod buffer;
39pub mod rounding;
40use std::fmt::Debug;
41mod attribute;
42pub use attribute::Attribute;
43mod cowstr;
44
45mod sealed {
46    pub trait Sealed {}
47
48    impl<T: ?Sized> Sealed for T {}
49}
50
51/// Alias for `Clone + Debug + Send + Sync + 'static`.
52pub trait Shareable: Clone + Debug + Send + Sync + 'static {}
53impl<T> Shareable for T where T: Clone + Debug + Send + Sync + 'static {}
54
55/// Construct a reference to a static [`StatVTable`] with serialization support.
56/// ```
57/// vtable!(Type);
58/// ```
59/// Equivalent to
60/// ```
61/// {
62///     static VTABLE: StatVTable<Type> = StatVTable::of::<Type>();
63///     &VTABLE
64/// }
65/// ```
66#[macro_export]
67macro_rules! vtable {
68    ($ty: ty) => {{
69        #[used]
70        static _VTABLE: $crate::StatVTable<$ty> = $crate::StatVTable::of::<$ty>();
71        &_VTABLE
72    }};
73}
74
75/// Downcast [`StatValuePair`] to a concrete pair of stat and value.
76///
77/// # Syntax
78///
79/// ```
80/// # /*
81/// match_stat!(stat_value_pair => {
82///     // if stat is `MyStat::A`, downcast the value to `MyStat::Value` as `value`.
83///     (MyStat::A, value) => {
84///         value.add(1);
85///     },
86///     // if stat is `MyStat`, downcast the stat as `stat` and the value as `value`.
87///     (stat @ MyStat, value) => {
88///         value.add(1);
89///     },
90/// }
91/// # */
92/// ```
93#[macro_export]
94macro_rules! match_stat {
95    ($stat_value: expr => {($ident: ident @ $ty: ty, $value: pat) => $expr: expr $(, $($tt: tt)*)?}) => {
96        if let Some(($ident, $value)) = $stat_value.cast::<$ty>() {
97            $expr
98        } $(
99            else {
100                $crate::match_stat!($stat_value => {$($tt)*})
101            }
102        )?
103    };
104    ($stat_value: expr => {($is: expr, $value: pat) => $expr: expr $(, $($tt: tt)*)?}) => {
105        if let Some($value) = $stat_value.is_then_cast(&$is) {
106            $expr
107        } $(
108            else {
109                $crate::match_stat!($stat_value => {$($tt)*})
110            }
111        )?
112    };
113    ($stat_value: expr => {_ => $expr: expr $(,)?}) => {
114        $expr
115    };
116    // Matches the last comma case.
117    ($stat_value: expr => {}) => {()};
118}
119
120use buffer::{validate, Buffer};
121
122#[cfg(test)]
123mod test {
124    use bevy_ecs::component::Component;
125    use num_enum::{FromPrimitive, IntoPrimitive};
126    use strum::{EnumIter, IntoEnumIterator, IntoStaticStr};
127
128    use crate::{
129        stat::StatValuePair,
130        types::{StatFlags, StatIntPercentAdditive},
131        Querier, Stat, StatStream, StatValue,
132    };
133
134    #[derive(Component)]
135    pub struct X;
136
137    #[derive(Debug, Clone, Copy, IntoStaticStr, EnumIter, FromPrimitive, IntoPrimitive)]
138    #[repr(u64)]
139    pub enum IntStat {
140        #[default]
141        A,
142        B,
143        C,
144        D,
145    }
146
147    impl Stat for IntStat {
148        type Value = StatIntPercentAdditive<i32>;
149
150        fn name(&self) -> &'static str {
151            self.into()
152        }
153
154        fn vtable() -> &'static crate::StatVTable<Self> {
155            vtable!(IntStat)
156        }
157
158        fn as_index(&self) -> u64 {
159            (*self).into()
160        }
161
162        fn from_index(index: u64) -> Self {
163            index.into()
164        }
165
166        fn values() -> impl IntoIterator<Item = Self> {
167            IntStat::iter()
168        }
169    }
170
171    #[derive(Debug, Clone, Copy, IntoStaticStr, EnumIter, FromPrimitive, IntoPrimitive)]
172    #[repr(u64)]
173    pub enum FlagsStat {
174        #[default]
175        E,
176        F,
177        G,
178        H,
179    }
180
181    impl Stat for FlagsStat {
182        type Value = StatFlags<i32>;
183
184        fn name(&self) -> &'static str {
185            self.into()
186        }
187
188        fn vtable() -> &'static crate::StatVTable<Self> {
189            vtable!(FlagsStat)
190        }
191
192        fn as_index(&self) -> u64 {
193            (*self).into()
194        }
195
196        fn from_index(index: u64) -> Self {
197            index.into()
198        }
199
200        fn values() -> impl IntoIterator<Item = Self> {
201            FlagsStat::iter()
202        }
203    }
204
205    impl StatStream for X {
206        type Qualifier = u32;
207
208        fn stream_stat(
209            &self,
210            _: bevy::prelude::Entity,
211            _: &crate::QualifierQuery<Self::Qualifier>,
212            stat_value: &mut StatValuePair,
213            _: Querier<Self::Qualifier>,
214        ) {
215            match_stat! {
216                stat_value => {
217                    (IntStat::A, value) => {
218                        value.add(1);
219                    },
220                    (IntStat::B, value) => {
221                        value.add(2);
222                    },
223                    (v @ IntStat, value) => {
224                        value.add(v as i32);
225                    },
226                    (FlagsStat::E, value) => {
227                        value.or(1);
228                    },
229                    (v @ FlagsStat, value) => {
230                        value.or(v as i32);
231                    },
232                }
233            }
234        }
235    }
236}