diff --git a/core/item/Cargo.toml b/core/item/Cargo.toml index 727aaf7..b47fcf5 100644 --- a/core/item/Cargo.toml +++ b/core/item/Cargo.toml @@ -17,6 +17,7 @@ rimecraft-fmt = { path = "../../util/fmt" } rimecraft-freezer = { path = "../../util/freezer" } rimecraft-attachment = { path = "../../util/attachment" } rimecraft-serde-update = { path = "../../util/serde-update", optional = true } +rimecraft-serde-humanreadctl = { path = "../../util/serde-humanreadctl", optional = true } fastnbt = "2.4" rimecraft-nbt-ext = { path = "../../util/nbt-ext" } serde = { version = "1.0", optional = true, features = ["derive"] } @@ -28,6 +29,7 @@ serde = [ "rimecraft-registry/serde", "rimecraft-attachment/serde", "dep:rimecraft-serde-update", + "dep:rimecraft-serde-humanreadctl", ] [lints] diff --git a/core/item/src/lib.rs b/core/item/src/lib.rs index 153b381..893c5c0 100644 --- a/core/item/src/lib.rs +++ b/core/item/src/lib.rs @@ -5,7 +5,7 @@ use std::{marker::PhantomData, num::NonZeroU32}; use rimecraft_fmt::Formatting; use rimecraft_registry::{ProvideRegistry, Reg}; -mod stack; +pub mod stack; pub use stack::ItemStack; diff --git a/core/item/src/stack.rs b/core/item/src/stack.rs index fc3ac4c..b801bb4 100644 --- a/core/item/src/stack.rs +++ b/core/item/src/stack.rs @@ -1,3 +1,5 @@ +//! Item stack related types and traits. + use rimecraft_attachment::Attachments; use rimecraft_nbt_ext::Compound; @@ -21,7 +23,11 @@ use crate::{Item, ToItem}; )) )] pub struct ItemStack<'r, K, P> { - #[cfg_attr(feature = "serde", serde(rename = "id"))] + #[cfg_attr( + feature = "serde", + serde(rename = "id"), + serde(with = "serde_helper::item_serde") + )] item: Item<'r, K, P>, #[cfg_attr(feature = "serde", serde(rename = "Count"))] @@ -33,45 +39,91 @@ pub struct ItemStack<'r, K, P> { #[cfg_attr( feature = "serde", - serde(skip_serializing_if = "should_skip_attachment_ser"), - serde(default), - serde(serialize_with = "ser_attachments"), - serde(deserialize_with = "deser_attachments") + serde(skip_serializing_if = "serde_helper::should_skip_attachment_ser"), + serde(default = "serde_helper::default_attachments"), + serde(serialize_with = "serde_helper::ser_attachments"), + serde(deserialize_with = "serde_helper::deser_attachments") )] attachments: (Attachments, PhantomData

), } #[cfg(feature = "serde")] -fn should_skip_attachment_ser(attachments: &(Attachments, PhantomData

)) -> bool { - attachments.0.is_persistent_data_empty() -} +mod serde_helper { + use super::*; -#[cfg(feature = "serde")] -fn ser_attachments( - attachments: &(Attachments, PhantomData

), - serializer: S, -) -> Result -where - S: serde::Serializer, - K: serde::Serialize + Hash + Eq, -{ - serde::Serialize::serialize(&attachments.0, serializer) -} + #[inline] + pub fn default_attachments() -> (Attachments, PhantomData

) + where + P: InitAttachments, + { + let mut att = Attachments::new(); + P::init_attachments(&mut att); + (att, PhantomData) + } -#[cfg(feature = "serde")] -fn deser_attachments<'de, K, P, D>( - deserializer: D, -) -> Result<(Attachments, PhantomData

), >::Error> -where - D: serde::Deserializer<'de>, - P: InitAttachments, - K: serde::Deserialize<'de> + rimecraft_serde_update::Update<'de> + Hash + Eq, -{ - use rimecraft_serde_update::Update; - let mut attachments = Attachments::new(); - P::init_attachments(&mut attachments); - attachments.update(deserializer)?; - Ok((attachments, PhantomData)) + #[inline] + pub fn should_skip_attachment_ser( + attachments: &(Attachments, PhantomData

), + ) -> bool { + attachments.0.is_persistent_data_empty() + } + + #[inline] + pub fn ser_attachments( + attachments: &(Attachments, PhantomData

), + serializer: S, + ) -> Result + where + S: serde::Serializer, + K: serde::Serialize + Hash + Eq, + { + serde::Serialize::serialize(&attachments.0, serializer) + } + + pub fn deser_attachments<'de, K, P, D>( + deserializer: D, + ) -> Result<(Attachments, PhantomData

), >::Error> + where + D: serde::Deserializer<'de>, + P: InitAttachments, + K: serde::Deserialize<'de> + rimecraft_serde_update::Update<'de> + Hash + Eq, + { + use rimecraft_serde_update::Update; + let mut attachments = Attachments::new(); + P::init_attachments(&mut attachments); + attachments.update(deserializer)?; + Ok((attachments, PhantomData)) + } + + pub mod item_serde { + use rimecraft_registry::ProvideRegistry; + use rimecraft_serde_humanreadctl::HumanReadableControlled; + use serde::{Deserialize, Serialize}; + + use crate::RawItem; + + use super::*; + + #[inline] + pub fn serialize(item: &Item<'_, K, P>, serializer: S) -> Result + where + S: serde::Serializer, + K: Serialize + Hash + Eq, + { + item.serialize(HumanReadableControlled::new(serializer, true)) + } + + #[inline] + pub fn deserialize<'rr, 'd, K, P, D>(deserializer: D) -> Result, D::Error> + where + 'rr: 'd, + D: serde::Deserializer<'d>, + K: Deserialize<'d> + Hash + Eq + std::fmt::Debug + 'rr, + P: InitAttachments + ProvideRegistry<'rr, K, RawItem

> + 'rr, + { + Item::deserialize(HumanReadableControlled::new(deserializer, true)) + } + } } impl<'r, K, P> ItemStack<'r, K, P> diff --git a/util/registry/src/lib.rs b/util/registry/src/lib.rs index ba25315..1658a5f 100644 --- a/util/registry/src/lib.rs +++ b/util/registry/src/lib.rs @@ -37,6 +37,19 @@ pub struct Registry { } /// Reference of a registration. +/// +/// # Serialization and Deserialization +/// +/// This type can be serialized and deserialized using `serde` and `edcode`. +/// (with `serde` feature and `edcode` feature respectively) +/// +/// ## Serde +/// +/// When serializing this reference with `serde`, it will serialize the ID +/// of the entry, if the serializer is **human readable**. Otherwise, it will +/// serialize the **raw ID** of the entry. +/// +/// This corresponds to the `compressed` option in *Mojang Serialization*. pub struct Reg<'a, K, T> { raw: usize, registry: &'a Registry, @@ -518,7 +531,7 @@ where } #[cfg(feature = "serde")] -pub mod serde { +mod serde { //! Helper module for `serde` support. use std::hash::Hash; @@ -529,13 +542,16 @@ pub mod serde { where K: serde::Serialize, { - #[inline] fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - let entry: &RefEntry<_, _> = self.as_ref(); - entry.key.value().serialize(serializer) + if serializer.is_human_readable() { + let entry: &RefEntry<_, _> = self.as_ref(); + entry.key.value().serialize(serializer) + } else { + serializer.serialize_i32(self.raw as i32) + } } } @@ -549,58 +565,17 @@ pub mod serde { where D: serde::Deserializer<'de>, { - let key = K::deserialize(deserializer)?; - T::registry() - .get(&key) - .ok_or_else(|| serde::de::Error::custom(format!("key {key:?} not found"))) - } - } - - /// Wrapper for serializing a compressed entry. - #[derive(Debug, Clone, Copy)] - pub struct Compressed(pub T); - - impl serde::Serialize for Compressed<&Reg<'_, K, T>> - where - K: serde::Serialize, - { - #[inline] - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_i32(self.0.raw as i32) - } - } - - impl serde::Serialize for Compressed> - where - K: serde::Serialize, - { - #[inline] - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - Compressed(&self.0).serialize(serializer) - } - } - - impl<'a, 'r, 'de, K, T> serde::Deserialize<'de> for Compressed> - where - 'r: 'a, - T: ProvideRegistry<'r, K, T> + 'r, - K: serde::Deserialize<'de> + Hash + Eq + std::fmt::Debug + 'r, - { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let raw = i32::deserialize(deserializer)? as usize; - T::registry() - .of_raw(raw) - .map(Compressed) - .ok_or_else(|| serde::de::Error::custom(format!("raw id {raw} not found"))) + if deserializer.is_human_readable() { + let key = K::deserialize(deserializer)?; + T::registry() + .get(&key) + .ok_or_else(|| serde::de::Error::custom(format!("key {key:?} not found"))) + } else { + let raw = i32::deserialize(deserializer)? as usize; + T::registry() + .of_raw(raw) + .ok_or_else(|| serde::de::Error::custom(format!("raw id {raw} not found"))) + } } } } diff --git a/util/serde-humanreadctl/Cargo.toml b/util/serde-humanreadctl/Cargo.toml new file mode 100644 index 0000000..f58ab23 --- /dev/null +++ b/util/serde-humanreadctl/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rimecraft-serde-humanreadctl" +version = "0.1.0" +edition = "2021" +authors = ["JieningYu "] +description = "Serde human-readable controlling layers" +repository = "https://github.com/rimecraft-rs/rimecraft/" +license = "AGPL-3.0-or-later" +categories = ["rust-patterns"] + +[badges] +maintenance = { status = "passively-maintained" } + +[dependencies] +serde = "1.0" + +[features] + +[lints] +workspace = true diff --git a/util/serde-humanreadctl/src/lib.rs b/util/serde-humanreadctl/src/lib.rs new file mode 100644 index 0000000..1e54c5f --- /dev/null +++ b/util/serde-humanreadctl/src/lib.rs @@ -0,0 +1,355 @@ +//! Serde human-readable controlling layers. + +use serde::{Deserializer, Serializer}; + +/// Wrapper to a [`Serializer`] or [`Deserializer`] that supports +/// configuring `is_human_readable` manually. +#[derive(Debug)] +pub struct HumanReadableControlled { + inner: T, + human_readable: bool, +} + +impl HumanReadableControlled { + /// Creates a new `HumanReadableControlled` with the given inner value and human-readable flag. + #[inline] + pub const fn new(inner: T, human_readable: bool) -> Self { + Self { + inner, + human_readable, + } + } + + /// Returns the inner value. + #[inline] + pub fn into_inner(self) -> T { + self.inner + } +} + +macro_rules! ser { + ($($f:ident, $t:ty),*$(,)?) => { + $( + #[inline] + fn $f(self, v: $t) -> Result { + self.inner.$f(v) + } + )* + }; +} + +macro_rules! ser_gat { + ($($t:ident),*$(,)?) => { + $(type $t = ::$t;)* + }; +} + +impl Serializer for HumanReadableControlled +where + S: Serializer, +{ + ser_gat! { + Ok, Error, + SerializeSeq, + SerializeTuple, + SerializeTupleStruct, + SerializeTupleVariant, + SerializeMap, + SerializeStruct, + SerializeStructVariant, + } + + ser! { + serialize_bool, bool, + serialize_i8, i8, + serialize_i16, i16, + serialize_i32, i32, + serialize_i64, i64, + serialize_i128, i128, + serialize_u8, u8, + serialize_u16, u16, + serialize_u32, u32, + serialize_u64, u64, + serialize_u128, u128, + serialize_f32, f32, + serialize_f64, f64, + serialize_char, char, + serialize_str, &str, + serialize_bytes, &[u8], + } + + #[inline] + fn serialize_none(self) -> Result { + self.inner.serialize_none() + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: serde::Serialize, + { + self.inner.serialize_some(value) + } + + #[inline] + fn serialize_unit(self) -> Result { + self.inner.serialize_unit() + } + + #[inline] + fn serialize_unit_struct(self, name: &'static str) -> Result { + self.inner.serialize_unit_struct(name) + } + + #[inline] + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + self.inner + .serialize_unit_variant(name, variant_index, variant) + } + + #[inline] + fn serialize_newtype_struct( + self, + name: &'static str, + value: &T, + ) -> Result + where + T: serde::Serialize, + { + self.inner.serialize_newtype_struct(name, value) + } + + #[inline] + fn serialize_newtype_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: serde::Serialize, + { + self.inner + .serialize_newtype_variant(name, variant_index, variant, value) + } + + #[inline] + fn serialize_seq(self, len: Option) -> Result { + self.inner.serialize_seq(len) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + self.inner.serialize_tuple(len) + } + + #[inline] + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + self.inner.serialize_tuple_struct(name, len) + } + + #[inline] + fn serialize_tuple_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.inner + .serialize_tuple_variant(name, variant_index, variant, len) + } + + #[inline] + fn serialize_map(self, len: Option) -> Result { + self.inner.serialize_map(len) + } + + #[inline] + fn serialize_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + self.inner.serialize_struct(name, len) + } + + #[inline] + fn serialize_struct_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.inner + .serialize_struct_variant(name, variant_index, variant, len) + } + + #[inline] + fn collect_seq(self, iter: I) -> Result + where + I: IntoIterator, + ::Item: serde::Serialize, + { + self.inner.collect_seq(iter) + } + + #[inline] + fn collect_map(self, iter: I) -> Result + where + K: serde::Serialize, + V: serde::Serialize, + I: IntoIterator, + { + self.inner.collect_map(iter) + } + + #[inline] + fn collect_str(self, value: &T) -> Result + where + T: std::fmt::Display, + { + self.inner.collect_str(value) + } + + #[inline] + fn is_human_readable(&self) -> bool { + self.human_readable + } +} + +macro_rules! deser { + ($($t:ident),*$(,)?) => { + $( + #[inline] + fn $t(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.inner.$t(visitor) + } + )* + }; +} + +impl<'de, S> Deserializer<'de> for HumanReadableControlled +where + S: Deserializer<'de>, +{ + type Error = >::Error; + + deser! { + deserialize_any, + deserialize_bool, + deserialize_i8, + deserialize_i16, + deserialize_i32, + deserialize_i64, + deserialize_i128, + deserialize_u8, + deserialize_u16, + deserialize_u32, + deserialize_u64, + deserialize_u128, + deserialize_f32, + deserialize_f64, + deserialize_char, + deserialize_str, + deserialize_string, + deserialize_bytes, + deserialize_byte_buf, + deserialize_option, + deserialize_unit, + deserialize_seq, + deserialize_map, + deserialize_identifier, + deserialize_ignored_any, + } + + #[inline] + fn deserialize_unit_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + self.inner.deserialize_unit_struct(name, visitor) + } + + #[inline] + fn deserialize_newtype_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + self.inner.deserialize_newtype_struct(name, visitor) + } + + #[inline] + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.inner.deserialize_tuple(len, visitor) + } + + #[inline] + fn deserialize_tuple_struct( + self, + name: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + self.inner.deserialize_tuple_struct(name, len, visitor) + } + + #[inline] + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + self.inner.deserialize_struct(name, fields, visitor) + } + + #[inline] + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + self.inner.deserialize_enum(name, variants, visitor) + } + + #[inline] + fn is_human_readable(&self) -> bool { + self.human_readable + } +}