Skip to main content

bevy_variable_property/
lib.rs

1#![doc = include_str!("../README.md")]
2pub mod interval_property;
3pub mod prop_rand;
4pub mod prop_range;
5pub mod variable_property;
6
7use bevy_math::*;
8use bevy_reflect::{Reflect, TypePath};
9use rand::{seq::SliceRandom, thread_rng};
10
11use std::ops::{Range, RangeInclusive};
12
13use crate::prop_rand::PropRand;
14use crate::prop_range::PropRange;
15
16use crate::variable_property::VariableProperty;
17
18/// Generic property that can be static, randomized within a range, randomly selected from a
19/// predetermined list, or entirely random on each read.
20///
21/// Implementation of Default provides `Static(T::default())`
22#[derive(Reflect, Clone)]
23pub enum Property<T> {
24    /// Produces the same value
25    Static(T),
26
27    /// Produces a random value within the given range
28    RandomRange(PropRange<T>),
29
30    /// Produces a randomly selected value from the given list
31    RandomChoice(Vec<T>),
32
33    /// Produces a completely random value
34    Random,
35}
36
37impl<T> VariableProperty for Property<T>
38where
39    T: PropRand + Clone + TypePath,
40{
41    type Output = T;
42    /// Gets a value based on the parameters of the Property
43    /// See [Property] for more information.
44    fn get_value(&self) -> T {
45        match self {
46            Property::Static(v) => v.clone(),
47            Property::RandomRange(range) => {
48                <T as PropRand>::gen_range(&mut thread_rng(), range.clone())
49            }
50            Property::RandomChoice(choices) => choices.choose(&mut thread_rng()).unwrap().clone(),
51            Property::Random => T::gen(&mut thread_rng()),
52        }
53    }
54}
55
56/// Provides `Static(T::default())`
57impl<T: Default> Default for Property<T> {
58    fn default() -> Self {
59        Property::Static(T::default())
60    }
61}
62
63/// Implements a variety of From implementations for the given type.
64///
65/// From<$type> -> Static, From<Range<$type>> -> RandomRange, From<RangeInclusve<$type>> -> RandomRange,
66/// From<Vec<$type>> -> RandomChoice, and From<&[$type]> -> RandomChoice,
67///
68/// The $from_type **must** implement Clone and PropRand to be able to utilize [Property::get_value].
69///
70/// If two types are provided, $from_type must implement Into for $into_prop_type
71macro_rules! prop_from_impl {
72    ($from_type:tt) => {
73        prop_from_impl!($from_type, $from_type);
74    };
75
76    ($from_type:tt, $into_prop_type:tt) => {
77        impl From<$from_type> for Property<$into_prop_type> {
78            fn from(v: $from_type) -> Self {
79                Property::Static(v.into())
80            }
81        }
82
83        impl From<Range<$from_type>> for Property<$into_prop_type> {
84            fn from(v: Range<$from_type>) -> Self {
85                Property::RandomRange(PropRange {
86                    start: v.start.into(),
87                    end: v.end.into(),
88                    inclusive: false,
89                })
90            }
91        }
92
93        impl From<RangeInclusive<$from_type>> for Property<$into_prop_type> {
94            fn from(v: RangeInclusive<$from_type>) -> Self {
95                Property::RandomRange(PropRange {
96                    start: v.start().clone().into(),
97                    end: v.end().clone().into(),
98                    inclusive: true,
99                })
100            }
101        }
102
103        impl From<Vec<$from_type>> for Property<$into_prop_type> {
104            fn from(v: Vec<$from_type>) -> Self {
105                Property::RandomChoice(v.into_iter().map(|x| x.into()).collect())
106            }
107        }
108
109        impl From<&[$from_type]> for Property<$into_prop_type> {
110            fn from(v: &[$from_type]) -> Self {
111                Property::RandomChoice(v.into_iter().cloned().map(|x| x.into()).collect())
112            }
113        }
114
115        impl<const N: usize> From<[$from_type; N]> for Property<$into_prop_type> {
116            fn from(v: [$from_type; N]) -> Self {
117                Property::RandomChoice(v.into())
118            }
119        }
120    };
121}
122
123/// Implements a variety of From implementations for the given type.
124///
125/// From<$type> -> Static, From<Range<$type>> -> RandomRange, From<RangeInclusve<$type>> -> RandomRange,
126/// From<Vec<$type>> -> RandomChoice, and From<&[$type]> -> RandomChoice,
127///
128/// The $from_type **must** implement Clone and PropRand to be able to utilize [Property::get_value].
129macro_rules! prop_from_impl_many {
130    ($($type:tt,)+) => {
131        $(
132            prop_from_impl!($type, $type);
133        )+
134    }
135}
136
137prop_from_impl_many!(
138    usize, isize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, Vec2, Vec3, Vec4,
139    UVec2, UVec3, UVec4, IVec2, IVec3, IVec4, DVec2, DVec3, DVec4,
140);
141
142impl<T, const N: usize> From<Range<[T; N]>> for Property<[T; N]> {
143    fn from(v: Range<[T; N]>) -> Self {
144        Self::RandomRange(PropRange {
145            start: v.start.into(),
146            end: v.end.into(),
147            inclusive: false,
148        })
149    }
150}
151
152impl<T: Clone, const N: usize> From<RangeInclusive<[T; N]>> for Property<[T; N]> {
153    fn from(v: RangeInclusive<[T; N]>) -> Self {
154        Self::RandomRange(PropRange {
155            start: v.start().clone(),
156            end: v.end().clone(),
157            inclusive: true,
158        })
159    }
160}
161
162impl<T, const N: usize> From<Vec<[T; N]>> for Property<[T; N]> {
163    fn from(v: Vec<[T; N]>) -> Self {
164        Property::RandomChoice(v.into_iter().map(|x| x.into()).collect())
165    }
166}
167
168impl<T: Clone, const N: usize> From<&[[T; N]]> for Property<[T; N]> {
169    fn from(v: &[[T; N]]) -> Self {
170        Property::RandomChoice(v.into_iter().cloned().collect())
171    }
172}
173
174impl<T, const N: usize, const M: usize> From<[[T; N]; M]> for Property<[T; N]> {
175    fn from(v: [[T; N]; M]) -> Self {
176        Property::RandomChoice(v.into_iter().collect())
177    }
178}
179
180pub mod prelude {
181    pub use crate::{
182        interval_property::IntervalProperty, prop_range::PropRange,
183        variable_property::VariableProperty, Property,
184    };
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    #[test]
191    fn range_generation() {
192        let ranges = (2.5..5.0, -10.0..0.0, 0.0..1.0);
193        let vec3_generator: (Property<f32>, Property<f32>, Property<f32>) = (
194            ranges.0.clone().into(),
195            ranges.1.clone().into(),
196            ranges.2.clone().into(),
197        );
198        let (x, y, z) = vec3_generator.get_value().into();
199        assert!(
200            ranges.0.contains(&x),
201            "{} was not in the range of ({}..{})",
202            x,
203            ranges.0.start,
204            ranges.0.end
205        );
206        assert!(
207            ranges.1.contains(&y),
208            "{} was not in the range of ({}..{})",
209            y,
210            ranges.1.start,
211            ranges.1.end
212        );
213        assert!(
214            ranges.2.contains(&z),
215            "{} was not in the range of ({}..{})",
216            z,
217            ranges.2.start,
218            ranges.2.end
219        );
220    }
221
222    #[test]
223    fn array_range_generation() {
224        let (start, end) = ([0usize, 25], [10, 50]);
225        let array_prop: Property<[usize; 2]> = (start..=end).into();
226        let [x, y] = array_prop.get_value();
227        assert!(
228            (start[0]..=end[0]).contains(&x),
229            "{} was not in the range of ({}..{})",
230            x,
231            start[0],
232            end[0]
233        );
234        assert!(
235            (start[1]..=end[1]).contains(&y),
236            "{} was not in the range of ({}..{})",
237            y,
238            start[1],
239            end[1]
240        );
241    }
242
243    #[test]
244    #[should_panic]
245    fn bad_range() {
246        let p = Property::from(10.0..1.0);
247        p.get_value();
248    }
249
250    #[test]
251    #[should_panic]
252    fn bad_array_range() {
253        let p = Property::from([0.0, 10.0]..[1.0, 5.0]);
254        p.get_value();
255    }
256
257    #[test]
258    fn tuples() {
259        let p = Property::Static((1.0, 5.0));
260        p.get_value();
261    }
262}