diff options
Diffstat (limited to 'engine-ecs/src/uid.rs')
| -rw-r--r-- | engine-ecs/src/uid.rs | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/engine-ecs/src/uid.rs b/engine-ecs/src/uid.rs new file mode 100644 index 0000000..26fbaee --- /dev/null +++ b/engine-ecs/src/uid.rs @@ -0,0 +1,207 @@ +use std::fmt::{Debug, Display, Formatter}; +use std::sync::atomic::{AtomicU32, Ordering}; + +use seq_macro::seq; + +use crate::component::Component; +use crate::util::{gen_mask_64, Array, BitMask, NumberExt}; + +static NEXT: AtomicU32 = AtomicU32::new(Uid::FIRST_UNIQUE_ID); + +static WILDCARD_ID: u32 = 1; + +const ID_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(32..=63)); +const RELATION_BITS: BitMask<u64> = BitMask::new(gen_mask_64!(6..=31)); +const IS_PAIR_BITS: BitMask<u64> = BitMask::new(1 << 0); + +/// A unique identifier. +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Uid +{ + inner: u64, +} + +impl Uid +{ + /// The id part of the first unique `Uid`. The ids `0..Uid::FIRST_UNIQUE_ID` are + /// reserved. + pub const FIRST_UNIQUE_ID: u32 = 5; + + /// Returns a new unique entity/component ID. + pub fn new_unique() -> Self + { + let id = NEXT.fetch_add(1, Ordering::Relaxed); + + Self { + inner: ID_BITS.field_prep(u64::from(id)) | IS_PAIR_BITS.field_prep(0), + } + } + + #[must_use] + pub fn wildcard() -> Self + { + Self { + inner: ID_BITS.field_prep(u64::from(WILDCARD_ID)) + | IS_PAIR_BITS.field_prep(0), + } + } + + /// Returns a new pair UID. + /// + /// # Panics + /// Will panic if either the given relation or target is a pair UID. + #[must_use] + pub fn new_pair(params: &PairParams) -> Self + { + assert!(!params.relation.is_pair(), "Pair relation cannot be a pair"); + assert!(!params.target.is_pair(), "Pair target cannot be a pair"); + + Self { + inner: ID_BITS.field_prep(u64::from(params.target.id())) + | RELATION_BITS.field_prep(u64::from(params.relation.id())) + | IS_PAIR_BITS.field_prep(1), + } + } + + #[must_use] + pub fn id(&self) -> u32 + { + let Ok(id) = u32::try_from(self.inner.field_get(ID_BITS)) else { + unreachable!("Uid id does not fit in u32"); + }; + + id + } + + #[must_use] + pub fn is_pair(&self) -> bool + { + let Ok(is_pair) = u8::try_from(self.inner.field_get(IS_PAIR_BITS)) else { + unreachable!(); + }; + + is_pair == 1 + } + + /// If this `Uid` is a pair, returns the relation `Uid`. + /// + /// # Panics + /// Will panic if this `Uid` is not a pair. + #[must_use] + pub fn relation(&self) -> Self + { + assert!(self.is_pair(), "Uid is not a pair"); + + let Ok(relation) = u32::try_from(self.inner.field_get(RELATION_BITS)) else { + unreachable!("Uid relation does not fit in u32"); + }; + + Self { + inner: ID_BITS.field_prep(u64::from(relation)) | IS_PAIR_BITS.field_prep(0), + } + } + + /// Compares the relation of this pair `Uid` with the relation of another pair `Uid`. + /// + /// # Panics + /// Will panic if any of the `Uid`s are not a pair. + #[must_use] + pub fn has_same_relation_as(&self, other: Self) -> bool + { + self.relation() == other.relation() + } + + /// If this `Uid` is a pair, returns the target `Uid`. + /// + /// # Panics + /// Will panic if this `Uid` is not a pair. + #[must_use] + pub fn target(&self) -> Self + { + assert!(self.is_pair(), "Uid is not a pair"); + + Self { + inner: ID_BITS.field_prep(u64::from(self.id())) | IS_PAIR_BITS.field_prep(0), + } + } +} + +impl Debug for Uid +{ + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result + { + let mut debug_struct = formatter.debug_struct("Uid"); + + if self.is_pair() { + return debug_struct + .field("relation", &self.relation()) + .field("target", &self.target()) + .finish(); + } + + debug_struct.field("id", &self.id()).finish() + } +} + +impl Display for Uid +{ + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result + { + if self.is_pair() { + return write!(formatter, "({}, {})", self.relation(), self.target()); + } + + if *self == Uid::wildcard() { + return write!(formatter, "*"); + } + + write!(formatter, "{}", self.id()) + } +} + +#[derive(Debug, Clone)] +pub struct PairParams +{ + pub relation: Uid, + pub target: Uid, +} + +pub trait With +{ + fn uid() -> Uid; +} + +impl<ComponentT: Component> With for ComponentT +{ + fn uid() -> Uid + { + Self::id() + } +} + +pub trait WithUidTuple +{ + type UidsArray: Array<Uid>; + + fn uids() -> Self::UidsArray; +} + +macro_rules! impl_with_uid_tuple { + ($c: tt) => { + seq!(I in 0..=$c { + impl<#(WithUid~I: With,)*> WithUidTuple for (#(WithUid~I,)*) + { + type UidsArray = [Uid; $c + 1]; + + fn uids() -> Self::UidsArray + { + [#(WithUid~I::uid(),)*] + } + } + }); + }; +} + +seq!(C in 0..=16 { + impl_with_uid_tuple!(C); +}); |
