diff --git a/core/Cargo.toml b/core/Cargo.toml index bce594a..a7a4d15 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -42,3 +42,4 @@ bimap = "0.6" thiserror = "1.0" rsa = "0.9" enumn = "0.1" +erased-serde = "0.3" diff --git a/core/src/text/mod.rs b/core/src/text/mod.rs index 0b4c8c0..b11ddbd 100644 --- a/core/src/text/mod.rs +++ b/core/src/text/mod.rs @@ -13,7 +13,9 @@ use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use rimecraft_event::Event; -use rimecraft_primitives::{id, ErasedSerDeUpdate, Id}; +use rimecraft_primitives::{id, ErasedSerDeUpdate, Id, SerDeUpdate}; + +use crate::Rgb; use super::formatting::Formatting; @@ -27,6 +29,8 @@ pub enum Error { ParseInt(std::num::ParseIntError), #[error("formatting error: {0}")] Formatting(super::formatting::Error), + #[error("invalid name: {0}")] + InvalidName(String), } /// TODO: Implement net.minecraft.text.Text @@ -39,7 +43,7 @@ pub trait Text { /// The style of a [`Text`].\ /// A style is immutable. -#[derive(PartialEq, Eq)] +#[derive(PartialEq)] pub struct Style { color: Option, bold: Option, @@ -130,39 +134,40 @@ impl Style { /// # MCJE Reference /// /// This type represents `net.minecraft.text.TextColor` (yarn). -#[derive(Debug, Hash)] +#[derive(Debug, Hash, Eq)] pub struct Color { /// A 24-bit color. - rgb: u32, - name: Option>, + rgb: Rgb, + name: Option<&'static str>, } impl Color { const RGB_PREFIX: &str = "#"; #[inline] - pub fn from_rgb(rgb: u32) -> Self { - Self { rgb, name: None } + pub fn rgb(&self) -> Rgb { + self.rgb } #[inline] - pub fn new(rgb: u32, name: Cow<'static, str>) -> Self { - Self { - rgb, - name: Some(name), - } - } - - #[inline] - fn as_hex_str(&self) -> String { + fn to_hex_code(&self) -> String { format!("{}{:06X}", Self::RGB_PREFIX, self.rgb) } - #[inline] - pub fn name(&self) -> Cow<'_, str> { + pub fn name(&self) -> Cow<'static, str> { self.name - .clone() - .unwrap_or_else(|| Cow::Owned(self.as_hex_str())) + .map(|e| Cow::Borrowed(e)) + .unwrap_or_else(|| Cow::Owned(self.to_hex_code())) + } +} + +impl Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(name) = self.name { + f.write_str(name) + } else { + f.write_str(&self.to_hex_code()) + } } } @@ -171,54 +176,48 @@ impl FromStr for Color { fn from_str(s: &str) -> Result { if let Some(value) = s.strip_prefix(Self::RGB_PREFIX) { - Ok(Self::from_rgb(value.parse().map_err(Error::ParseInt)?)) - } else { - let f: Formatting = s.parse().map_err(Error::Formatting)?; Ok(Self { - rgb: f.color_value().ok_or(Error::ColorValueNotFound)?, - name: Some(Cow::Borrowed(f.name())), + rgb: value.parse().map_err(Error::ParseInt)?, + name: None, }) + } else { + s.parse::() + .map_err(Error::Formatting)? + .try_into() } } } -impl TryFrom<&'static Formatting> for Color { +impl TryFrom for Color { type Error = Error; - fn try_from(value: &'static Formatting) -> Result { - if value.is_color() { - Ok(Self { - rgb: value.color_value().unwrap(), - name: Some(Cow::Borrowed(value.name())), - }) - } else { - Err(Error::InvalidColor) - } + fn try_from(value: Formatting) -> Result { + Ok(Self { + rgb: value.color_value().ok_or(Error::ColorValueNotFound)?, + name: Some(value.name()), + }) } } impl PartialEq for Color { + #[inline] fn eq(&self, other: &Self) -> bool { self.rgb == other.rgb } } -impl Eq for Color {} - -impl Display for Color { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name = self.name(); - write!(f, "{name}") - } -} - -#[derive(Hash, PartialEq, Eq)] +#[derive(Hash, PartialEq, Eq, Debug)] pub struct ClickEvent { action: ClickEventAction, value: String, } impl ClickEvent { + #[inline] + pub fn new(action: ClickEventAction, value: String) -> Self { + Self { action, value } + } + #[inline] pub fn action(&self) -> ClickEventAction { self.action @@ -228,20 +227,6 @@ impl ClickEvent { pub fn value(&self) -> &str { &self.value } - - #[inline] - pub fn new(action: ClickEventAction, value: String) -> Self { - Self { action, value } - } -} - -impl Debug for ClickEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let action = self.action().name(); - let value = self.value(); - - write!(f, "ClickEvent{{action={action}, value='{value}'}}") - } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -280,9 +265,12 @@ impl ClickEventAction { pub fn is_user_definable(self) -> bool { !matches!(self, Self::OpenFile) } +} - #[inline] - pub fn from_name(name: &str) -> Option { +impl FromStr for ClickEventAction { + type Err = Error; + + fn from_str(s: &str) -> Result { static MAP: Lazy> = Lazy::new(|| { ClickEventAction::VALUES .into_iter() @@ -290,13 +278,18 @@ impl ClickEventAction { .collect() }); - MAP.get(name).copied() + MAP.get(s) + .copied() + .ok_or_else(|| Error::InvalidName(s.to_owned())) } } trait UpdDebug: ErasedSerDeUpdate + Debug {} impl UpdDebug for T where T: ?Sized + ErasedSerDeUpdate + Debug {} +erased_serde::serialize_trait_object!(UpdDebug); +rimecraft_primitives::update_trait_object!(UpdDebug); + pub struct HoverEvent { contents: (Arc, TypeId), action: &'static HoverEventAction, @@ -334,15 +327,6 @@ impl HoverEvent { } } -impl Serialize for HoverEvent { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - todo!() - } -} - impl Hash for HoverEvent { #[inline] fn hash(&self, state: &mut H) { @@ -368,7 +352,33 @@ impl PartialEq for HoverEvent { } } -impl Eq for HoverEvent {} +impl Serialize for HoverEvent { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + #[derive(Serialize)] + struct Struct<'a> { + action: &'static HoverEventAction, + contents: &'a (dyn UpdDebug + Send + Sync), + } + + Struct { + action: self.action, + contents: &*self.contents.0, + }.serialize(serializer) + } +} + +impl SerDeUpdate for HoverEvent { + fn update<'de, D>( + &'de mut self, + deserializer: D, + ) -> Result<(), >::Error> + where + D: serde::Deserializer<'de> { + todo!() + } +} #[derive(PartialEq, Eq, Clone, Hash)] pub struct HoverEventAction { @@ -436,11 +446,12 @@ impl HoverEventAction { impl Debug for HoverEventAction { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "", self.name) + f.debug_struct("HoverEventAction").field("name", &self.name).finish() } } impl Serialize for HoverEventAction { + #[inline] fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -456,6 +467,7 @@ impl<'de> Deserialize<'de> for &'static HoverEventAction { { static VARIANTS: Lazy> = Lazy::new(|| HE_MAPPING.iter().map(|v| v.0.as_str()).collect()); + let value = String::deserialize(deserializer)?; use serde::de::Error; diff --git a/core/src/util/formatting.rs b/core/src/util/formatting.rs index 0d1a8d3..cec562c 100644 --- a/core/src/util/formatting.rs +++ b/core/src/util/formatting.rs @@ -1,11 +1,41 @@ -use std::{borrow::Cow, collections::HashMap, ops::Deref, str::FromStr}; +use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr}; use once_cell::sync::Lazy; -use super::ColorIndex; +use super::Rgb; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct ColorIndex { + value: i8, +} + +impl ColorIndex { + #[inline] + pub fn new(value: Option) -> Self { + Self { + value: value.map(|e| e as i8).unwrap_or(-1), + } + } + + #[inline] + pub fn value(self) -> Option { + if self.value == -1 { + None + } else { + Some(self.value as u8) + } + } +} + +impl Default for ColorIndex { + #[inline] + fn default() -> Self { + Self { value: -1 } + } +} macro_rules! formattings { - ($( $i:ident => $n:expr, $c:expr, $m:expr, $ci:expr, $cv:expr ),+,) => { + ($( $i:ident => $n:literal, $c:literal, $m:expr, $ci:literal, $cv:expr ),+,) => { /// An enum holding formattings. /// /// There are two types of formattings, color and modifier. Color formattings @@ -22,8 +52,6 @@ macro_rules! formattings { } impl Formatting { - const CODE_PREFIX: char = '§'; - #[inline] fn name_raw(self) -> &'static str { match self { @@ -67,10 +95,10 @@ macro_rules! formattings { /// Returns the color of the formatted text, or /// `None` if the formatting has no associated color. #[inline] - pub fn color_value(self) -> Option { + pub fn color_value(self) -> Option { match self { $( - Formatting::$i => $cv, + Formatting::$i => $cv.map(Rgb::new), )* } } @@ -131,6 +159,8 @@ pub enum Error { } impl Formatting { + const CODE_PREFIX: char = '§'; + /// Returns `true` if the formatting is associated with /// a color, `false` otherwise. #[inline] @@ -188,6 +218,16 @@ impl FromStr for Formatting { } } +impl Display for Formatting { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write; + + f.write_char(Self::CODE_PREFIX)?; + f.write_char(self.code()) + } +} + impl TryFrom for Formatting { type Error = Error; diff --git a/core/src/util/math.rs b/core/src/util/math/mod.rs similarity index 98% rename from core/src/util/math.rs rename to core/src/util/math/mod.rs index 95b75cb..86b33d3 100644 --- a/core/src/util/math.rs +++ b/core/src/util/math/mod.rs @@ -1,9 +1,11 @@ +pub mod random; + use std::ops::Deref; /// A box with double-valued coords. /// The box is axis-aligned and the coords are minimum inclusive and maximum exclusive. #[derive(Clone, Copy, PartialEq)] -pub struct BoundingBox { +pub struct BoundBox { pub min_x: f64, pub min_y: f64, pub min_z: f64, @@ -12,7 +14,7 @@ pub struct BoundingBox { pub max_z: f64, } -impl BoundingBox { +impl BoundBox { /// Creates a box of the given positions (in (x, y, z)) as corners. #[inline] pub fn new>(pos1: T, pos2: T) -> Self { @@ -172,7 +174,7 @@ impl BoundingBox { } } -impl std::hash::Hash for BoundingBox { +impl std::hash::Hash for BoundBox { #[inline] fn hash(&self, state: &mut H) { state.write_u64(self.min_x.to_bits()); @@ -184,9 +186,9 @@ impl std::hash::Hash for BoundingBox { } } -impl Eq for BoundingBox {} +impl Eq for BoundBox {} -impl From for BoundingBox { +impl From for BoundBox { #[inline] fn from(value: glam::DVec3) -> Self { Self { @@ -200,7 +202,7 @@ impl From for BoundingBox { } } -impl std::fmt::Display for BoundingBox { +impl std::fmt::Display for BoundBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("Box[")?; diff --git a/core/src/util/math/random.rs b/core/src/util/math/random.rs new file mode 100644 index 0000000..8a89f43 --- /dev/null +++ b/core/src/util/math/random.rs @@ -0,0 +1,2 @@ +pub trait Random { +} \ No newline at end of file diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs index 53e60dd..a7ccd67 100644 --- a/core/src/util/mod.rs +++ b/core/src/util/mod.rs @@ -1,8 +1,10 @@ -use formatting::Formatting; - pub mod formatting; pub mod math; +use formatting::Formatting; + +use std::{fmt::UpperHex, str::FromStr}; + #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[repr(u8)] pub enum Rarity { @@ -23,3 +25,44 @@ impl From for Formatting { } } } + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +#[repr(u8)] +pub enum Hand { + Main, + Off, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[repr(transparent)] +pub struct Rgb { + value: u32, +} + +impl Rgb { + #[inline] + pub fn new(value: u32) -> Self { + Self { value } + } + + #[inline] + pub fn value(self) -> u32 { + self.value + } +} + +impl UpperHex for Rgb { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:06X}", self.value) + } +} + +impl FromStr for Rgb { + type Err = std::num::ParseIntError; + + #[inline] + fn from_str(s: &str) -> Result { + s.parse().map(Rgb::new) + } +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index a11e527..e6aabe5 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -2,7 +2,7 @@ pub mod identifier; pub mod reference; #[cfg(feature = "serde")] -mod serde_update; +pub mod serde_update; #[cfg(test)] mod tests; diff --git a/primitives/src/serde_update.rs b/primitives/src/serde_update.rs index faa1cda..9e272f6 100644 --- a/primitives/src/serde_update.rs +++ b/primitives/src/serde_update.rs @@ -36,6 +36,9 @@ pub trait ErasedUpdate: erased_serde::Serialize { ) -> Result<(), erased_serde::Error>; } +erased_serde::serialize_trait_object!(ErasedUpdate); +crate::update_trait_object!(ErasedUpdate); + impl ErasedUpdate for T where T: ?Sized + Update, @@ -47,3 +50,64 @@ where self.update(deserializer) } } + +/// Implement [`Update`] for the target trait object types +/// that implements [`ErasedUpdate`]. +#[macro_export] +macro_rules! update_trait_object { + ($t:path) => { + impl $crate::serde_update::Update for dyn $t { + $crate::__impl_update_from_erased!(); + } + + impl $crate::serde_update::Update for dyn $t + Send { + $crate::__impl_update_from_erased!(); + } + + impl $crate::serde_update::Update for dyn $t + Sync { + $crate::__impl_update_from_erased!(); + } + + impl $crate::serde_update::Update for dyn $t + Send + Sync { + $crate::__impl_update_from_erased!(); + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_update_from_erased { + () => { + fn update<'de, D>( + &'de mut self, + deserializer: D, + ) -> Result<(), >::Error> + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + self.erased_update(&mut ::erase(deserializer)) + .map_err(D::Error::custom) + } + }; +} + +#[derive(serde::Serialize)] +struct ErasedWrapper<'a> { + value: &'a mut dyn ErasedUpdate, +} + +impl Update for ErasedWrapper<'_> { + fn update<'de, D>( + &'de mut self, + deserializer: D, + ) -> Result<(), >::Error> + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + self.value + .erased_update(&mut ::erase(deserializer)) + .map_err(D::Error::custom) + } +}