1#![allow(deprecated)] #![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5pub mod prelude {
6 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
36pub 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_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#[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 pub const fn new(source: &str) -> Self {
115 Self::from_hash(const_fnv1a_hash::fnv1a_hash_str_64(source))
116 }
117
118 pub const fn from_hash(hash: u64) -> Self {
120 Self(hash)
121 }
122
123 pub const fn hash(&self) -> u64 {
125 self.0
126 }
127
128 pub fn pretty_hash(&self) -> String {
134 base31::encode(self.0)
135 }
136
137 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 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 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Reflect)]
275pub struct TagMeta {
276 pub tag: Tag,
278 pub name: &'static str,
280}
281
282impl TagMeta {
283 pub fn iter() -> impl Iterator<Item = &'static TagMeta> {
285 inventory::iter::<TagMeta>()
286 }
287}
288
289inventory::collect!(TagMeta);
290
291#[derive(Resource, Default)]
303pub struct TagNames(HashMap<Tag, Cow<'static, str>>);
304
305impl TagNames {
306 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 pub fn global() -> &'static Self {
336 static GLOBAL: Lazy<TagNames> = Lazy::new(TagNames::generate);
337 &GLOBAL
338 }
339
340 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 pub fn get_cached(&self, tag: Tag) -> Option<Cow<'static, str>> {
350 self.0.get(&tag).cloned()
351 }
352
353 pub fn iter(&self) -> impl Iterator<Item = (&Tag, &Cow<'static, str>)> {
355 self.0.iter()
356 }
357}
358
359#[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 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 pub fn new() -> Self {
424 Self::default()
425 }
426
427 pub fn len(&self) -> usize {
429 self.0.len()
430 }
431
432 pub fn is_empty(&self) -> bool {
434 self.0.is_empty()
435 }
436
437 pub fn contains(&self, tag: Tag) -> bool {
439 self.0.contains(&tag)
440 }
441
442 pub fn insert(&mut self, tag: Tag) -> bool {
444 self.0.insert(tag)
445 }
446
447 pub fn remove(&mut self, tag: Tag) -> bool {
449 self.0.remove(&tag)
450 }
451
452 pub fn retain(&mut self, f: impl FnMut(&Tag) -> bool) {
454 self.0.retain(f)
455 }
456
457 pub fn iter(&self) -> impl Iterator<Item = Tag> + '_ {
459 self.0.iter().copied()
460 }
461
462 #[must_use]
464 pub fn union(mut self, other: impl Into<Tags>) -> Self {
465 self.extend(other.into());
466 self
467 }
468
469 pub fn extend(&mut self, other: impl Into<Tags>) {
471 self.0.extend(other.into());
472 }
473
474 pub fn is_disjoint(&self, tags: &Tags) -> bool {
476 self.0.is_disjoint(&tags.0)
477 }
478
479 pub fn is_subset(&self, tags: &Tags) -> bool {
481 self.0.is_subset(&tags.0)
482 }
483
484 pub fn is_superset(&self, tags: &Tags) -> bool {
486 self.0.is_superset(&tags.0)
487 }
488
489 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 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#[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#[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)] 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}