moonshine_kind/lib.rs
1#![doc = include_str!("../README.md")]
2
3use bevy_ecs::{prelude::*, query::QueryFilter};
4
5pub mod prelude {
6 pub use crate::{kind, Kind, OfKind};
7 pub use crate::{GetInstanceCommands, InstanceCommands};
8 pub use crate::{Instance, InstanceMut, InstanceRef};
9 pub use crate::{KindBundle, SpawnInstance, SpawnInstanceWorld};
10}
11
12/// A type which represents the kind of an [`Entity`].
13///
14/// An entity is of kind `T` if it matches [`Query<Entity, <T as Kind>::Filter>`][`Query`].
15///
16/// By default, an entity with a [`Component`] of type `T` is also of kind `T`.
17///
18/// # Examples
19/// ```
20/// # use bevy::prelude::*;
21/// # use moonshine_kind::prelude::*;
22///
23/// #[derive(Component)]
24/// struct Apple;
25///
26/// #[derive(Component)]
27/// struct Orange;
28///
29/// struct Fruit;
30///
31/// impl Kind for Fruit {
32/// type Filter = Or<(With<Apple>, With<Orange>)>;
33/// }
34///
35/// fn fruits(query: Query<Instance<Fruit>>) {
36/// for fruit in query.iter() {
37/// println!("{fruit:?} is a fruit!");
38/// }
39/// }
40///
41/// # bevy_ecs::system::assert_is_system(fruits);
42/// ```
43pub trait Kind: 'static + Send + Sized + Sync {
44 type Filter: QueryFilter;
45
46 /// Returns the debug name of this kind.
47 ///
48 /// By default, this is the short type name (without path) of this kind.
49 fn debug_name() -> String {
50 moonshine_util::get_short_name(std::any::type_name::<Self>())
51 }
52}
53
54impl<T: Component> Kind for T {
55 type Filter = With<T>;
56}
57
58/// Represents the kind of any [`Entity`].
59///
60/// See [`Instance<Any>`] for more information on usage.
61pub struct Any;
62
63impl Kind for Any {
64 type Filter = ();
65}
66
67mod instance;
68
69pub use instance::*;
70
71/// A trait which allows safe casting from one [`Kind`] to another.
72///
73/// # Usage
74/// Prefer to use the [`kind`] macro to implement this trait.
75pub trait CastInto<T: Kind>: Kind {
76 fn cast_into(instance: Instance<Self>) -> Instance<T>;
77}
78
79impl<T: Kind> CastInto<T> for T {
80 fn cast_into(instance: Instance<Self>) -> Instance<Self> {
81 instance
82 }
83}
84
85/// A macro to safely implement [`CastInto`] for a pair of related [`Kind`]s.
86///
87/// See [`CastInto`] for more information.
88///
89/// # Usage
90/// ```
91/// # use bevy::prelude::*;
92/// # use moonshine_kind::prelude::*;
93///
94/// struct Fruit;
95///
96/// impl Kind for Fruit {
97/// type Filter = With<Apple>;
98/// }
99///
100/// #[derive(Component)]
101/// struct Apple;
102///
103/// // We can guarantee all entities with an `Apple` component are of kind `Fruit`:
104/// kind!(Apple is Fruit);
105///
106/// fn eat_apple(apple: Instance<Apple>) {
107/// println!("Crunch!");
108/// // SAFE: Because we said so.
109/// eat_fruit(apple.cast_into());
110/// }
111///
112/// fn eat_fruit(fruit: Instance<Fruit>) {
113/// println!("Yum!");
114/// }
115/// ```
116#[macro_export]
117macro_rules! kind {
118 ($T:ident is $U:ty) => {
119 impl $crate::CastInto<$U> for $T {
120 fn cast_into(instance: $crate::Instance<Self>) -> $crate::Instance<$U> {
121 // SAFE: Because we said so!
122 unsafe { instance.cast_into_unchecked() }
123 }
124 }
125 };
126}
127
128/// A short alias for using a [`Kind`] as a [`QueryFilter`].
129///
130/// # Example
131/// ```
132/// # use bevy::prelude::*;
133/// # use moonshine_kind::prelude::*;
134///
135/// #[derive(Component)]
136/// struct Apple;
137///
138/// fn count_apples(query: Query<(), OfKind<Apple>>) -> usize {
139/// query.iter().count()
140/// }
141///
142/// # bevy_ecs::system::assert_is_system(count_apples);
143/// ```
144pub type OfKind<T> = <T as Kind>::Filter;
145
146/// A [`Bundle`] which represents a [`Kind`].
147///
148/// # Usage
149/// This trait is used to allow spawning an [`Instance<T>`] where `T` is [`<Self as KindBundle>::Kind`][`KindBundle::Kind`].
150///
151/// Any [`Component`] is automatically a kind bundle of its own kind.
152///
153/// See [`SpawnInstance`] for more information.
154pub trait KindBundle: Bundle {
155 /// The [`Kind`] represented by this [`Bundle`].
156 type Kind: Kind;
157}
158
159impl<T: Kind + Bundle> KindBundle for T {
160 type Kind = T;
161}
162
163/// Extension trait to safely spawn an [`Instance<T>`] using [`Commands`] where `T` associated with a [`KindBundle`].
164pub trait SpawnInstance {
165 /// Spawns a new [`Instance<T>`] using its associated [`KindBundle`].
166 ///
167 /// # Example
168 /// ```
169 /// # use bevy::prelude::*;
170 /// # use moonshine_kind::prelude::*;
171 ///
172 /// #[derive(Component)]
173 /// struct Apple;
174 ///
175 /// fn spawn_apple(mut commands: Commands) {
176 /// let apple: Instance<Apple> = commands.spawn_instance(Apple).instance();
177 /// println!("Spawned {apple:?}!");
178 /// }
179 ///
180 /// # bevy_ecs::system::assert_is_system(spawn_apple);
181 fn spawn_instance<T: KindBundle>(&mut self, _: T) -> InstanceCommands<'_, T::Kind>;
182}
183
184impl SpawnInstance for Commands<'_, '_> {
185 fn spawn_instance<T: KindBundle>(&mut self, bundle: T) -> InstanceCommands<'_, T::Kind> {
186 let entity = self.spawn(bundle).id();
187 // SAFE: `entity` must be a valid instance of `T::Kind`.
188 unsafe { InstanceCommands::from_entity_unchecked(self.entity(entity)) }
189 }
190}
191
192/// Extension trait to safely spawn an [`Instance<T>`] using [`World`] where `T` associated with a [`KindBundle`].
193pub trait SpawnInstanceWorld {
194 /// Spawns a new [`Instance<T>`] using its associated [`KindBundle`].
195 ///
196 /// # Example
197 /// ```
198 /// # use bevy::prelude::*;
199 /// # use moonshine_kind::prelude::*;
200 ///
201 /// #[derive(Component)]
202 /// struct Apple;
203 ///
204 /// fn spawn_apple(world: &mut World) {
205 /// let apple: Instance<Apple> = world.spawn_instance(Apple).instance();
206 /// println!("Spawned {apple:?}!");
207 /// }
208 fn spawn_instance<T: KindBundle>(&mut self, _: T) -> InstanceMutItem<'_, T::Kind>
209 where
210 T::Kind: Component;
211}
212
213impl SpawnInstanceWorld for World {
214 fn spawn_instance<T: KindBundle>(&mut self, bundle: T) -> InstanceMutItem<'_, T::Kind>
215 where
216 T::Kind: Component,
217 {
218 let entity = self.spawn(bundle).id();
219 // SAFE: `entity` must be a valid instance of kind `T`.
220 InstanceMutItem::from_entity(self, entity).unwrap()
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use bevy_ecs::system::RunSystemOnce;
228
229 fn count<T: Kind>(query: Query<Instance<T>>) -> usize {
230 query.iter().count()
231 }
232
233 #[test]
234 fn kind_with() {
235 #[derive(Component)]
236 struct Foo;
237
238 let mut world = World::new();
239 world.spawn(Foo);
240 assert_eq!(world.run_system_once(count::<Foo>).unwrap(), 1);
241 }
242
243 #[test]
244 fn kind_without() {
245 #[derive(Component)]
246 struct Foo;
247
248 struct NotFoo;
249
250 impl Kind for NotFoo {
251 type Filter = Without<Foo>;
252 }
253
254 let mut world = World::new();
255 world.spawn(Foo);
256 assert_eq!(world.run_system_once(count::<NotFoo>).unwrap(), 0);
257 }
258
259 #[test]
260 fn kind_multi() {
261 #[derive(Component)]
262 struct Foo;
263
264 #[derive(Component)]
265 struct Bar;
266
267 let mut world = World::new();
268 world.spawn((Foo, Bar));
269 assert_eq!(world.run_system_once(count::<Foo>).unwrap(), 1);
270 assert_eq!(world.run_system_once(count::<Bar>).unwrap(), 1);
271 }
272
273 #[test]
274 fn kind_cast() {
275 #[derive(Component)]
276 struct Foo;
277
278 #[derive(Component)]
279 struct Bar;
280
281 kind!(Foo is Bar);
282
283 let any = Instance::<Any>::PLACEHOLDER;
284 let foo = Instance::<Foo>::PLACEHOLDER;
285 let bar = foo.cast_into::<Bar>();
286 assert!(foo.cast_into_any() == any);
287 assert!(bar.cast_into_any() == any);
288 // assert!(any.cast_into::<Foo>() == foo); // <-- Must not compile!
289 // assert!(bar.cast_into::<Foo>() == foo); // <-- Must not compile!
290 assert!(bar.entity() == foo.entity());
291 }
292}