#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "entity")]
pub struct Entity(pub u32);
impl Entity {
pub(crate) const NONE: Self = Self(0);
#[must_use]
pub(crate) const fn is_none(self) -> bool {
self.0 == 0
}
#[must_use]
pub(crate) const fn from_raw(raw: u32) -> Option<Self> {
if raw == 0 { None } else { Some(Self(raw)) }
}
}
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "item_id")]
pub struct ItemId(pub u32);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "player_key")]
pub struct PlayerKey(pub u64);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
bytemuck::Zeroable,
bytemuck::Pod,
crate::Shape,
crate::Script,
)]
#[serde(transparent)]
#[shape(api = "recipe_id")]
pub struct RecipeId(pub u32);
macro_rules! id_conversions {
($($id:ident($int:ty)),* $(,)?) => {$(
impl $id {
/// The id of the game's number `id`, in a `const` too.
///
/// # Examples
///
#[doc = concat!("See [`", stringify!($id), "`].")]
#[must_use]
pub const fn new(id: $int) -> Self {
Self(id)
}
#[doc = concat!("See [`", stringify!($id), "`].")]
#[must_use]
pub const fn get(self) -> $int {
self.0
}
}
impl From<$int> for $id {
fn from(id: $int) -> Self {
Self(id)
}
}
impl From<$id> for $int {
fn from(id: $id) -> Self {
id.0
}
}
impl core::fmt::LowerHex for $id {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::LowerHex::fmt(&self.0, f)
}
}
impl core::fmt::UpperHex for $id {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::UpperHex::fmt(&self.0, f)
}
}
)*};
}
id_conversions!(Entity(u32), ItemId(u32), PlayerKey(u64), RecipeId(u32));
const _: () = {
assert!(size_of::<Entity>() == size_of::<u32>() && align_of::<Entity>() == align_of::<u32>());
assert!(size_of::<ItemId>() == size_of::<u32>() && align_of::<ItemId>() == align_of::<u32>());
assert!(
size_of::<PlayerKey>() == size_of::<u64>() && align_of::<PlayerKey>() == align_of::<u64>()
);
assert!(
size_of::<RecipeId>() == size_of::<u32>() && align_of::<RecipeId>() == align_of::<u32>()
);
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_item_id_converts_to_and_from_its_number() {
let item = ItemId::from(1_042_u32);
assert_eq!(u32::from(item), 1_042);
assert_eq!(item, ItemId::new(1_042));
assert_eq!(item.get(), 1_042);
}
#[test]
fn a_player_key_converts_to_and_from_its_number() {
let key = PlayerKey::from(u64::MAX - 1);
assert_eq!(u64::from(key), u64::MAX - 1);
assert_eq!(key, PlayerKey::new(u64::MAX - 1));
assert_eq!(key.get(), u64::MAX - 1);
}
#[test]
fn a_recipe_id_converts_to_and_from_its_number() {
let recipe = RecipeId::from(77_u32);
assert_eq!(u32::from(recipe), 77);
assert_eq!(recipe, RecipeId::new(77));
assert_eq!(recipe.get(), 77);
}
#[test]
fn an_id_prints_in_hex_as_its_number_does() {
assert_eq!(format!("{:#x}", ItemId::new(0x5e01)), "0x5e01");
assert_eq!(format!("{:016X}", PlayerKey::new(0xab)), "00000000000000AB");
assert_eq!(format!("{:x}", RecipeId::new(255)), "ff");
}
#[test]
fn an_entity_of_zero_is_no_entity() {
assert_eq!(Entity::from_raw(0), None);
assert_eq!(Entity::from_raw(9), Some(Entity(9)));
assert!(Entity::NONE.is_none());
assert!(!Entity(9).is_none());
}
fn bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
postcard::to_allocvec(value).unwrap()
}
fn read<T: serde::de::DeserializeOwned, N: serde::Serialize>(number: N) -> T {
postcard::from_bytes(&bytes(&number)).unwrap()
}
#[test]
fn every_id_serializes_as_its_bare_number() {
assert_eq!(bytes(&Entity(1210)), bytes(&1210_u32));
assert_eq!(bytes(&ItemId::new(1_042)), bytes(&1_042_u32));
assert_eq!(bytes(&PlayerKey::new(u64::MAX)), bytes(&u64::MAX));
assert_eq!(bytes(&RecipeId::new(77)), bytes(&77_u32));
}
#[test]
fn every_id_deserializes_from_its_bare_number() {
assert_eq!(read::<Entity, _>(1210_u32), Entity(1210));
assert_eq!(read::<ItemId, _>(9_u32), ItemId::new(9));
assert_eq!(read::<PlayerKey, _>(u64::MAX), PlayerKey::new(u64::MAX));
assert_eq!(read::<RecipeId, _>(77_u32), RecipeId::new(77));
}
}