moonshine_tag/
lib.rs

1#![allow(deprecated)] // TODO: Remove
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5pub mod prelude {
6    //! Prelude module to import the most essential types and traits.
7
8    pub use crate::{tag_filter, tags, Tag, TagFilter, TagPlugin, Tags};
9
10    pub use crate::{ComponentTags, WithTags};
11}
12
13mod filter;
14
15pub extern crate inventory;
16
17use std::borrow::Cow;
18use std::fmt;
19use std::hash::{Hash, Hasher};
20use std::marker::PhantomData;
21
22use bevy_app::{App, Plugin};
23use bevy_ecs::lifecycle::HookContext;
24use bevy_ecs::prelude::*;
25use bevy_ecs::world::DeferredWorld;
26use bevy_platform::collections::{HashMap, HashSet};
27use bevy_reflect::prelude::*;
28use itertools::Itertools;
29use once_cell::sync::Lazy;
30use serde::{Deserialize, Serialize};
31
32use moonshine_util::prelude::*;
33
34pub use self::filter::*;
35
36/// A [`Plugin`] required to register tag related types for reflection.
37pub struct TagPlugin;
38
39impl Plugin for TagPlugin {
40    fn build(&self, app: &mut App) {
41        app.init_resource::<TagNames>()
42            .register_type::<Tag>()
43            .register_type::<Tags>()
44            .register_type::<HashSet<Tag>>()
45            .register_type_data::<HashSet<Tag>, ReflectSerialize>()
46            .register_type_data::<HashSet<Tag>, ReflectDeserialize>()
47            .register_type::<TagFilter>()
48            .register_type::<Box<TagFilter>>()
49            .register_type_data::<Box<TagFilter>, ReflectSerialize>()
50            .register_type_data::<Box<TagFilter>, ReflectDeserialize>();
51    }
52}
53
54/// Macro used to define new tags.
55///
56/// See [`Tag`] for more information.
57#[macro_export]
58macro_rules! tags {
59    ($(#[$meta:meta])* $v:vis $name:ident $(,)?) => {
60        $(#[$meta])*
61        $v const $name: $crate::Tag = $crate::Tag::new(stringify!($name));
62        $crate::inventory::submit! {
63            $crate::TagMeta { tag: $name, name: stringify!($name) }
64        }
65    };
66
67    ($(#[$meta:meta])* $v0:vis $n0:ident, $($v:vis $n:ident),* $(,)?) => {
68        $(#[$meta])*
69        $v0 const $n0: $crate::Tag = $crate::Tag::new(stringify!($n0));
70        $crate::inventory::submit! {
71            $crate::TagMeta { tag: $n0, name: stringify!($n0) }
72        }
73        $crate::tags!($(#[$meta])* $($v $n),*);
74    };
75}
76
77/// A generic, cheap, and mostly unique identifier.
78///
79///
80/// # Usage
81///
82/// A tag is a hashed representation of some string key. Two tags are equal if their hash values are equal.
83///
84/// Tags are defined to be very cheap to create, copy, and compare. However, they do not guarantee uniqueness.
85/// In most application domains, the chance of tag collision within a single subsystem is very low.
86///
87/// Internally, tags are hashed using a 64-bit [FNV-1a](https://softwareengineering.stackexchange.com/a/145633) algorithm.
88///
89/// # Example
90/// ```rust
91/// use moonshine_tag::prelude::*;
92///
93/// tags! { A, pub B };
94///
95/// assert_eq!(A, A);
96/// assert_ne!(A, B);
97/// ```
98#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Reflect)]
99#[cfg_attr(not(feature = "pretty-serde"), derive(Serialize, Deserialize))]
100#[reflect(Hash, PartialEq)]
101pub struct Tag(u64);
102
103impl Tag {
104    /// Creates a new tag from a string key.
105    ///
106    /// # Example
107    /// ```rust
108    /// use moonshine_tag::prelude::*;
109    ///
110    /// tags! { A };
111    ///
112    /// assert_eq!(Tag::new("A"), A);
113    /// ```
114    pub const fn new(source: &str) -> Self {
115        Self::from_hash(const_fnv1a_hash::fnv1a_hash_str_64(source))
116    }
117
118    /// Creates a new tag from a hash value.
119    pub const fn from_hash(hash: u64) -> Self {
120        Self(hash)
121    }
122
123    /// Returns the hash value of this tag.
124    pub const fn hash(&self) -> u64 {
125        self.0
126    }
127
128    /// Returns a human-friendly representation of this tag's hash.
129    ///
130    /// This is a [base31](https://github.com/kmanley/base31)-encoded string representation of the tag's hash value.
131    ///
132    /// This is, on average, faster than [`resolve_name`](Tag::resolve_name) for human-friendly identification.
133    pub fn pretty_hash(&self) -> String {
134        base31::encode(self.0)
135    }
136
137    /// Returns `true` if this tag matches the given [`TagFilter`].
138    pub fn matches(&self, filter: &TagFilter) -> bool {
139        use TagFilter::*;
140        match filter {
141            Equal(a) => a.iter().exactly_one().is_ok_and(|tag| tag == *self),
142            AllOf(a) => a.is_empty() || a.iter().exactly_one().is_ok_and(|tag| tag == *self),
143            AnyOf(a) => a.contains(*self),
144            And(a, b) => self.matches(a) && self.matches(b),
145            Or(a, b) => self.matches(a) || self.matches(b),
146            Not(a) => !self.matches(a),
147        }
148    }
149
150    /// Resolves the name of this tag if it has been defined using the `tags!` macro.
151    ///
152    /// Note that this function is very slow and should not be used in performance-critical code.
153    /// It performs a linear search over all registered tags to find a match and it is mainly designed for debugging and editor purposes.
154    ///
155    /// If performance is a concern, consider using [`pretty_hash`](Tag::pretty_hash) or cache the result of this function.
156    /// See [`iter_names`](Tag::iter_names) for an iterator over all registered tag names.
157    ///
158    /// Tags must be defined using the [`tags!`] macro to be registered.
159    pub fn resolve_name(&self) -> Option<&'static str> {
160        TagMeta::iter()
161            .find(|meta| meta.tag == *self)
162            .map(|meta| meta.name)
163    }
164
165    /// Returns the name of this tag if it has been defined using the `tags!` macro.
166    /// Otherwise, it returns the [`pretty_hash`](Tag::pretty_hash) as a fallback.
167    ///
168    /// Use this when you just want to display the most human-friendly format of the tag and
169    /// do not care for handling any errors due to unregistered tags.
170    ///
171    /// See:
172    /// - [`Tag::resolve_name`]
173    /// - [`Tag::pretty_hash`]
174    pub fn pretty_name(&self) -> Cow<'static, str> {
175        self.resolve_name()
176            .map(|name| name.into())
177            .unwrap_or_else(|| self.pretty_hash().into())
178    }
179
180    /// Returns an iterator over all registered tag names.
181    ///
182    /// Tags must be defined using the [`tags!`] macro to be registered.
183    pub fn iter_names() -> impl Iterator<Item = &'static str> {
184        TagMeta::iter().map(|meta| meta.name)
185    }
186}
187
188impl fmt::Debug for Tag {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        write!(f, "Tag({})", self.0)
191    }
192}
193
194impl Hash for Tag {
195    fn hash<H: Hasher>(&self, state: &mut H) {
196        state.write_u64(self.0)
197    }
198}
199
200impl FromWorld for Tag {
201    fn from_world(_: &mut World) -> Self {
202        Self(u64::MAX)
203    }
204}
205
206impl IntoIterator for Tag {
207    type Item = Tag;
208
209    type IntoIter = std::iter::Once<Tag>;
210
211    fn into_iter(self) -> Self::IntoIter {
212        std::iter::once(self)
213    }
214}
215
216#[cfg(feature = "pretty-serde")]
217impl Serialize for Tag {
218    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
219    where
220        S: serde::Serializer,
221    {
222        if let Some(name) = TagNames::global().get_cached(*self) {
223            serializer.serialize_str(&name)
224        } else {
225            serializer.serialize_u64(self.0)
226        }
227    }
228}
229
230#[cfg(feature = "pretty-serde")]
231impl<'de> Deserialize<'de> for Tag {
232    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233    where
234        D: serde::Deserializer<'de>,
235    {
236        deserializer.deserialize_any(TagVisitor)
237    }
238}
239
240#[cfg(feature = "pretty-serde")]
241struct TagVisitor;
242
243#[cfg(feature = "pretty-serde")]
244impl<'de> serde::de::Visitor<'de> for TagVisitor {
245    type Value = Tag;
246
247    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
248        write!(formatter, "string or numeric hash")
249    }
250
251    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
252    where
253        E: serde::de::Error,
254    {
255        Ok(Tag::new(v))
256    }
257
258    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
259    where
260        E: serde::de::Error,
261    {
262        Ok(Tag::new(&v))
263    }
264
265    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
266    where
267        E: serde::de::Error,
268    {
269        Ok(Tag::from_hash(v))
270    }
271}
272
273/// The metadata associated with a [`Tag`].
274#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Reflect)]
275pub struct TagMeta {
276    /// The tag itself.
277    pub tag: Tag,
278    /// The name of the tag.
279    pub name: &'static str,
280}
281
282impl TagMeta {
283    /// Iterates over all registered tag metadata.
284    pub fn iter() -> impl Iterator<Item = &'static TagMeta> {
285        inventory::iter::<TagMeta>()
286    }
287}
288
289inventory::collect!(TagMeta);
290
291/// A [`Resource`] which may be used to cache [`Tag`] names.
292///
293/// # Usage
294///
295/// When managing tags, it is often required to represent a [`Tag`] in a human-friendly way.
296/// While [`Tag::pretty_name`] exists, it is not cheap to use because it requires a linear search
297/// over all registered tags.
298///
299/// This is fine for small projects, but this method does not scale well as the numebr of tags increases.
300///
301/// One solution around this problem is to cache the tag names.
302#[derive(Resource, Default)]
303pub struct TagNames(HashMap<Tag, Cow<'static, str>>);
304
305impl TagNames {
306    /// Creates a new [`TagNames`] and preloads it with all existing tag names.
307    ///
308    /// The cost of this function increases linearly relative to the number of tags in your project.
309    /// This function is useful if you are willing to spend the initial cost of caching all tag names to
310    /// ensure no cache misses during lookup.
311    ///
312    /// # Example
313    /// ```rust
314    /// use bevy::prelude::*;
315    /// use moonshine_tag::{prelude::*, TagNames};
316    ///
317    /// let mut app = App::new();
318    ///
319    /// app.add_plugins((MinimalPlugins, TagPlugin));
320    /// app.insert_resource(TagNames::generate());
321    /// ```
322    pub fn generate() -> Self {
323        let mut inner = HashMap::new();
324        for &TagMeta { tag, name } in TagMeta::iter() {
325            inner.insert(tag, name.into());
326        }
327
328        Self(inner)
329    }
330
331    /// Returns a global instance of [`TagNames`].
332    ///
333    /// This global instance is lazily initialized and internally calls [`generate`](TagNames::generate)
334    /// on first execution which can be expensive depending on the number of registered tags.
335    pub fn global() -> &'static Self {
336        static GLOBAL: Lazy<TagNames> = Lazy::new(TagNames::generate);
337        &GLOBAL
338    }
339
340    /// Returns the name of the given [`Tag`] and stores a copy of it to speed up future calls.
341    pub fn get(&mut self, tag: Tag) -> Cow<'static, str> {
342        self.0
343            .entry(tag)
344            .or_insert_with(|| tag.pretty_name())
345            .clone()
346    }
347
348    /// Returns the name of the given [`Tag`] if it exists in the cache.
349    pub fn get_cached(&self, tag: Tag) -> Option<Cow<'static, str>> {
350        self.0.get(&tag).cloned()
351    }
352
353    /// Iterates over all [`Tag`]/name pairs saved in this cache.
354    pub fn iter(&self) -> impl Iterator<Item = (&Tag, &Cow<'static, str>)> {
355        self.0.iter()
356    }
357}
358
359/// A collection of tags.
360///
361/// # Examples
362///
363/// ```rust
364/// use moonshine_tag::prelude::*;
365///
366/// tags! { A, B };
367///
368/// let tags: Tags = [A, B].into();
369///
370/// assert_eq!(tags, [B, A]);
371/// ```
372///
373/// You may use this type on its own, or as a [`Component`]:
374///
375/// ```rust
376/// use bevy::prelude::*;
377/// use moonshine_tag::prelude::*;
378///
379/// tags! { A, B };
380///
381/// let mut world = World::new();
382/// let entity = world.spawn(Tags::from([A, B])).id();
383/// assert_eq!(world.get::<Tags>(entity).unwrap(), [A, B]);
384/// ```
385#[derive(Component, Default, Debug, Clone, PartialEq, Eq, Reflect)]
386#[cfg_attr(not(feature = "pretty-serde"), derive(Serialize, Deserialize))]
387#[reflect(Component)]
388pub struct Tags(HashSet<Tag>);
389
390impl Tags {
391    /// A static empty set of [`Tags`].
392    ///
393    /// # Usage
394    ///
395    /// This is convenient for cases when the absence of `Tags` implies "no tags", for example:
396    ///
397    /// ```
398    /// use bevy::prelude::*;
399    /// use moonshine_tag::prelude::*;
400    ///
401    /// let mut world = World::new();
402    ///
403    /// // Spawn an entity with no tags:
404    /// let entity = world.spawn_empty().id();
405    ///
406    /// // Get tags, or just use the global empty set.
407    /// let tags = world.get::<Tags>(entity).unwrap_or(Tags::empty());
408    ///
409    /// assert!(tags.is_empty());
410    /// ```
411    pub fn static_empty() -> &'static Tags {
412        static EMPTY: Lazy<Tags> = Lazy::new(Tags::new);
413        &EMPTY
414    }
415
416    #[doc(hidden)]
417    #[deprecated(since = "0.3.0", note = "use `static_empty` instead")]
418    pub fn empty() -> &'static Tags {
419        Self::static_empty()
420    }
421
422    /// Creates a new empty set of tags.
423    pub fn new() -> Self {
424        Self::default()
425    }
426
427    /// Returns the number of tags in this set.
428    pub fn len(&self) -> usize {
429        self.0.len()
430    }
431
432    /// Returns `true` if this set is empty.
433    pub fn is_empty(&self) -> bool {
434        self.0.is_empty()
435    }
436
437    /// Returns `true` if this set contains the given tag.
438    pub fn contains(&self, tag: Tag) -> bool {
439        self.0.contains(&tag)
440    }
441
442    /// Inserts a tag into this set.
443    pub fn insert(&mut self, tag: Tag) -> bool {
444        self.0.insert(tag)
445    }
446
447    /// Removes a tag from this set.
448    pub fn remove(&mut self, tag: Tag) -> bool {
449        self.0.remove(&tag)
450    }
451
452    /// See [`HashSet::retain`].
453    pub fn retain(&mut self, f: impl FnMut(&Tag) -> bool) {
454        self.0.retain(f)
455    }
456
457    /// Returns an iterator over the tags in this set.
458    pub fn iter(&self) -> impl Iterator<Item = Tag> + '_ {
459        self.0.iter().copied()
460    }
461
462    /// Returns the union of this and another set of tags.
463    #[must_use]
464    pub fn union(mut self, other: impl Into<Tags>) -> Self {
465        self.extend(other.into());
466        self
467    }
468
469    /// Adds all tags from another set to this one.
470    pub fn extend(&mut self, other: impl Into<Tags>) {
471        self.0.extend(other.into());
472    }
473
474    /// Returns `true` if this set has no tags in common with another set.
475    pub fn is_disjoint(&self, tags: &Tags) -> bool {
476        self.0.is_disjoint(&tags.0)
477    }
478
479    /// Returns `true` if the tags in this set are all present in another set.
480    pub fn is_subset(&self, tags: &Tags) -> bool {
481        self.0.is_subset(&tags.0)
482    }
483
484    /// Returns `true` if the tags in another set are all present in this set.
485    pub fn is_superset(&self, tags: &Tags) -> bool {
486        self.0.is_superset(&tags.0)
487    }
488
489    /// Returns `true` if this set matches the given filter.
490    pub fn matches(&self, filter: &TagFilter) -> bool {
491        use TagFilter::*;
492        match filter {
493            Equal(a) => a == self,
494            AllOf(a) => a.is_subset(self),
495            AnyOf(a) => !a.is_disjoint(self),
496            And(a, b) => self.matches(a) && self.matches(b),
497            Or(a, b) => self.matches(a) || self.matches(b),
498            Not(a) => !self.matches(a),
499        }
500    }
501
502    /// Returns a string representation of this set of tags.
503    ///
504    /// Note that this function is slow and should not be used in performance-critical code.
505    /// It is mainly designed for debugging and editor purposes.
506    pub fn to_pretty_string(&self) -> String {
507        self.0
508            .iter()
509            .map(|tag| {
510                tag.resolve_name()
511                    .map(|name| name.to_string())
512                    .unwrap_or_else(|| tag.pretty_hash())
513            })
514            .join(", ")
515    }
516}
517
518impl From<Tag> for Tags {
519    fn from(tag: Tag) -> Self {
520        let mut tags = Tags::new();
521        tags.insert(tag);
522        tags
523    }
524}
525
526impl FromIterator<Tag> for Tags {
527    fn from_iter<T: IntoIterator<Item = Tag>>(iter: T) -> Self {
528        Self(HashSet::from_iter(iter))
529    }
530}
531
532impl<const N: usize> From<[Tag; N]> for Tags {
533    fn from(tags: [Tag; N]) -> Self {
534        Tags::from_iter(tags)
535    }
536}
537
538impl<const N: usize> PartialEq<[Tag; N]> for Tags {
539    fn eq(&self, other: &[Tag; N]) -> bool {
540        self.0.len() == N && other.iter().all(|tag| self.0.contains(tag))
541    }
542}
543
544impl<const N: usize> PartialEq<[Tag; N]> for &Tags {
545    fn eq(&self, other: &[Tag; N]) -> bool {
546        self.0.len() == N && other.iter().all(|tag| self.0.contains(tag))
547    }
548}
549
550impl<const N: usize> PartialEq<Tags> for [Tag; N] {
551    fn eq(&self, other: &Tags) -> bool {
552        other == self
553    }
554}
555
556impl MergeComponent for Tags {
557    fn merge(&mut self, other: Self) {
558        self.0.extend(other);
559    }
560}
561
562impl IntoIterator for Tags {
563    type Item = Tag;
564    type IntoIter = bevy_platform::collections::hash_set::IntoIter<Self::Item>;
565
566    fn into_iter(self) -> Self::IntoIter {
567        self.0.into_iter()
568    }
569}
570
571#[cfg(feature = "pretty-serde")]
572impl Serialize for Tags {
573    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
574    where
575        S: serde::Serializer,
576    {
577        use serde::ser::SerializeSeq;
578        let mut seq = serializer.serialize_seq(Some(self.len()))?;
579        for tag in self.iter() {
580            seq.serialize_element(&tag)?;
581        }
582        seq.end()
583    }
584}
585
586#[cfg(feature = "pretty-serde")]
587impl<'de> Deserialize<'de> for Tags {
588    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
589    where
590        D: serde::Deserializer<'de>,
591    {
592        deserializer.deserialize_any(TagsVisitor)
593    }
594}
595
596#[cfg(feature = "pretty-serde")]
597struct TagsVisitor;
598
599#[cfg(feature = "pretty-serde")]
600impl<'de> serde::de::Visitor<'de> for TagsVisitor {
601    type Value = Tags;
602
603    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
604        write!(formatter, "string or numeric hash")
605    }
606
607    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
608    where
609        E: serde::de::Error,
610    {
611        Ok(Tag::new(v).into())
612    }
613
614    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
615    where
616        E: serde::de::Error,
617    {
618        Ok(Tag::new(&v).into())
619    }
620
621    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
622    where
623        E: serde::de::Error,
624    {
625        Ok(Tag::from_hash(v).into())
626    }
627
628    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
629    where
630        A: serde::de::SeqAccess<'de>,
631    {
632        let mut tags = Tags::new();
633        while let Ok(Some(tag)) = seq.next_element::<Tag>() {
634            tags.insert(tag);
635        }
636        Ok(tags)
637    }
638}
639
640/// A [`Component`] which merges a set of tags into single [`Tags`] component.
641///
642/// # Usage
643///
644/// This is useful for adding tags to entities which may or may not have a `Tags` component
645/// already without replacing the existing tags, especially when spawning bundles.
646///
647/// This implementation takes advantage of the unique type signature of Rust lambda functions
648/// to avoid conflicts when used in bundles. While more flexible, this usage is not possible
649/// as component requirements. For that, see [`ComponentTags`].
650///
651/// # Examples
652/// ```
653/// use bevy::prelude::*;
654/// use moonshine_tag::prelude::*;
655/// use moonshine_util::prelude::*;
656///
657/// tags! { A, B, C };
658///
659/// let mut world = World::new();
660/// let entity = world.spawn((
661///     Merge::<Tags>::with(|| [A, B].into()),
662///     Merge::<Tags>::with(|| [B, C].into()),
663/// ));
664/// assert_eq!(entity.get::<Tags>().unwrap(), [A, B, C]);
665/// ```
666#[derive(Component)]
667#[component(storage = "SparseSet")]
668#[component(on_add = Self::on_add)]
669#[deprecated(since = "0.3.0", note = "use `AddWith` from `moonshine-util` instead")]
670pub struct WithTags<I: IntoIterator<Item = Tag>, F: FnOnce() -> I>(pub F)
671where
672    F: 'static + Send + Sync,
673    I: 'static + Send + Sync;
674
675impl<I: IntoIterator<Item = Tag>, F: FnOnce() -> I> WithTags<I, F>
676where
677    F: 'static + Send + Sync,
678    I: 'static + Send + Sync,
679{
680    fn on_add(mut world: DeferredWorld, ctx: HookContext) {
681        let entity = ctx.entity;
682        world.commands().queue(move |world: &mut World| {
683            let mut entity = world.entity_mut(entity);
684            let this = entity.take::<Self>().unwrap();
685            let new_tags = Tags::from_iter(this.0());
686            if let Some(mut tags) = entity.get_mut::<Tags>() {
687                tags.extend(new_tags);
688            } else {
689                entity.insert(new_tags);
690            }
691        });
692    }
693}
694
695/// A [`Component`] which merges a set of tags into single [`Tags`] component.
696///
697/// # Usage
698///
699/// This is similar to [`WithTags`], except that it may be used as a component requirement.
700///
701/// Note that `T` can be anything. By convention, it is best to use `Self` to avoid requirement conflicts.
702///
703/// # Examples
704/// ```
705/// use bevy::prelude::*;
706/// use moonshine_tag::prelude::*;
707/// use moonshine_util::prelude::*;
708///
709/// tags! { A, B, C };
710///
711/// #[derive(Component)]
712/// #[require(MergeFrom<Self, Tags> = Tags::from(A))]
713/// struct Foo;
714///
715/// #[derive(Component)]
716/// #[require(MergeFrom<Self, Tags> = Tags::from([B, C]))]
717/// struct Bar;
718///
719/// let mut world = World::new();
720/// let entity = world.spawn((Foo, Bar));
721/// assert_eq!(entity.get::<Tags>().unwrap(), [A, B, C]);
722/// ```
723#[derive(Component)]
724#[component(storage = "SparseSet")]
725#[component(on_add = Self::on_add)]
726#[deprecated(since = "0.3.0", note = "use `AddFrom` from `moonshine-util` instead")]
727pub struct ComponentTags<T: Component>(Tags, PhantomData<T>);
728
729impl<T: Component> ComponentTags<T> {
730    fn on_add(mut world: DeferredWorld, ctx: HookContext) {
731        let entity = ctx.entity;
732        world.commands().queue(move |world: &mut World| {
733            let mut entity = world.entity_mut(entity);
734            let ComponentTags(new_tags, ..) = entity.take::<Self>().unwrap();
735            if let Some(mut tags) = entity.get_mut::<Tags>() {
736                tags.extend(new_tags);
737            } else {
738                entity.insert(new_tags);
739            }
740        });
741    }
742}
743
744impl<T: Component, I: Into<Tags>> From<I> for ComponentTags<T> {
745    fn from(tags: I) -> Self {
746        Self(tags.into(), PhantomData)
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    use super::*;
753
754    tags!(A, B, C);
755
756    #[test]
757    fn match_eq() {
758        assert_eq!(A, A);
759        assert_ne!(A, B);
760    }
761
762    pub fn matches(tags: impl Into<Tags>, filter: &TagFilter) -> bool {
763        tags.into().matches(filter)
764    }
765
766    #[test]
767    fn match_empty() {
768        assert!(matches([], &tag_filter!([])));
769        assert!(matches([], &tag_filter!([..])));
770        assert!(matches([], &tag_filter!([..] | [])));
771        assert!(matches([], &tag_filter!([..] & [])));
772
773        assert!(!matches([], &tag_filter!(![])));
774        assert!(!matches([], &tag_filter!([A])));
775        assert!(!matches([], &tag_filter!([A, ..])));
776        assert!(!matches([], &tag_filter!([A, B])));
777        assert!(!matches([], &tag_filter!([A | B])));
778    }
779
780    #[test]
781    fn match_tag() {
782        assert!(matches(A, &tag_filter!([..])));
783        assert!(matches(A, &tag_filter!([..] | [])));
784        assert!(matches(A, &tag_filter!(![])));
785        assert!(matches(A, &tag_filter!([A])));
786        assert!(matches(A, &tag_filter!([A | B])));
787        assert!(matches(A, &tag_filter!([B | A])));
788        assert!(matches(A, &tag_filter!(![B])));
789        assert!(matches(A, &tag_filter!([A, ..])));
790        assert!(matches(A, &tag_filter!([A, ..] | [B])));
791
792        assert!(A.matches(&tag_filter!([..])));
793        assert!(A.matches(&tag_filter!([..] | [])));
794        assert!(A.matches(&tag_filter!(![])));
795        assert!(A.matches(&tag_filter!([A])));
796        assert!(A.matches(&tag_filter!([A | B])));
797        assert!(A.matches(&tag_filter!([B | A])));
798        assert!(A.matches(&tag_filter!(![B])));
799        assert!(A.matches(&tag_filter!([A, ..])));
800        assert!(A.matches(&tag_filter!([A, ..] | [B])));
801
802        assert!(!matches(A, &tag_filter!([])));
803        assert!(!matches(A, &tag_filter!(![A])));
804        assert!(!matches(A, &tag_filter!([B])));
805        assert!(!matches(A, &tag_filter!([A, B, ..])));
806        assert!(!matches(A, &tag_filter!([B, A, ..])));
807        assert!(!matches(A, &tag_filter!([B | C])));
808        assert!(!matches(A, &tag_filter!([B, C, ..])));
809
810        assert!(!A.matches(&tag_filter!([])));
811        assert!(!A.matches(&tag_filter!(![A])));
812        assert!(!A.matches(&tag_filter!([B])));
813        assert!(!A.matches(&tag_filter!([A, B, ..])));
814        assert!(!A.matches(&tag_filter!([B, A, ..])));
815        assert!(!A.matches(&tag_filter!([B | C])));
816        assert!(!A.matches(&tag_filter!([B, C, ..])));
817    }
818
819    #[test]
820    fn match_tags() {
821        assert!(matches([A, B], &tag_filter!([..])));
822        assert!(matches([A, B], &tag_filter!([..] | [])));
823        assert!(matches([A, B], &tag_filter!(![])));
824        assert!(matches([A, B], &tag_filter!([A, B])));
825        assert!(matches([A, B], &tag_filter!([B, A])));
826        assert!(matches([A, B], &tag_filter!([A, ..])));
827        assert!(matches([A, B], &tag_filter!([A, B, ..])));
828        assert!(matches([A, B], &tag_filter!([A, B, ..] | [B, C])));
829
830        assert!(!matches([A, B], &tag_filter!([])));
831        assert!(!matches([A, B], &tag_filter!([A])));
832        assert!(!matches([A, B], &tag_filter!([A, B, C])));
833        assert!(!matches([A, B], &tag_filter!(![A, B])));
834        assert!(!matches([A, B], &tag_filter!([A, C])));
835        assert!(!matches([A, B], &tag_filter!([A, C, ..])));
836        assert!(!matches([A, B], &tag_filter!([A, B, C, ..])));
837        assert!(!matches([A, B], &tag_filter!([A, C, ..] | [B, C])));
838        assert!(!matches([A, B], &tag_filter!([C, ..])));
839    }
840
841    #[test]
842    fn with_tags() {
843        let mut world = World::new();
844        let entity = world
845            .spawn((WithTags(|| [A]), WithTags(|| [A, B]), WithTags(|| [B, C])))
846            .id();
847        world.flush();
848        assert_eq!(
849            world.get::<Tags>(entity).unwrap_or(Tags::static_empty()),
850            [A, B, C]
851        );
852    }
853
854    #[test]
855    fn with_tags_existing() {
856        let mut world = World::new();
857        let entity = world
858            .spawn((Tags::from(A), WithTags(|| [A, B]), WithTags(|| [B, C])))
859            .id();
860        world.flush();
861        assert_eq!(
862            world.get::<Tags>(entity).unwrap_or(Tags::static_empty()),
863            [A, B, C]
864        );
865    }
866
867    #[test]
868    fn tags_macro_usage() {
869        tags! {
870            pub N00,
871        }
872        tags! {
873            pub N01,
874            N02,
875        }
876        tags! {
877            pub N03,
878            pub N04,
879        }
880        tags! {
881            #[allow(non_upper_case_globals)]
882            pub n05,
883        }
884        tags! {
885            #[allow(non_upper_case_globals)]
886            n06,
887        }
888        tags! {
889            #[allow(non_upper_case_globals)] // Meta applies to all entries
890            n07,
891            n08,
892        }
893        tags! {
894            #[allow(non_upper_case_globals)]
895            pub n09,
896            n10,
897        }
898    }
899
900    #[cfg(feature = "pretty-serde")]
901    #[test]
902    fn test_pretty_serde() {
903        let ux = tag_filter![A, B, ..];
904        let sx = "AllOf([\"A\",\"B\"])";
905
906        let s = ron::to_string(&ux).unwrap();
907        assert_eq!(s, sx);
908
909        let u: TagFilter = ron::from_str(&s).unwrap();
910        assert_eq!(u, ux);
911    }
912}