diff --git a/crates/core/block-entity/Cargo.toml b/crates/core/block-entity/Cargo.toml index 09e22131..fedbe91b 100644 --- a/crates/core/block-entity/Cargo.toml +++ b/crates/core/block-entity/Cargo.toml @@ -16,11 +16,15 @@ rimecraft-global-cx = { path = "../global-cx" } rimecraft-registry = { path = "../registry" } rimecraft-block = { path = "../block" } rimecraft-voxel-math = { path = "../../util/voxel-math" } -rimecraft-downcast = { path = "../../util/downcast" } +typeid = "1.0" ahash = "0.8" serde = "1.0" erased-serde = "0.4" -rimecraft-serde-update = { path = "../../util/serde-update", features = ["erased"] } +rimecraft-serde-update = { path = "../../util/serde-update", features = [ + "erased", +] } +component = { path = "../component", package = "rimecraft-component" } +bitflags = "2.6" [features] diff --git a/crates/core/block-entity/src/components_util.rs b/crates/core/block-entity/src/components_util.rs new file mode 100644 index 00000000..eb73365c --- /dev/null +++ b/crates/core/block-entity/src/components_util.rs @@ -0,0 +1,50 @@ +use std::fmt::Debug; + +use ahash::AHashSet; +use component::{map::ComponentMap, ComponentType, RawErasedComponentType}; +use rimecraft_global_cx::ProvideIdTy; + +/// Access to components of a block entity. +pub struct ComponentsAccess<'env, 'a, Cx> +where + Cx: ProvideIdTy, +{ + pub(crate) set: &'env mut AHashSet>, + pub(crate) map: &'env ComponentMap<'a, Cx>, +} + +impl<'a, Cx> ComponentsAccess<'_, 'a, Cx> +where + Cx: ProvideIdTy, +{ + /// Gets a component of the given type. + /// + /// # Safety + /// + /// This function could not guarantee lifetime of type `T` is sound. + /// The type `T`'s lifetime parameters should not overlap lifetime `'a`. + pub unsafe fn get(&mut self, ty: &ComponentType<'a, T>) -> Option<&T> { + self.set.insert(RawErasedComponentType::from(ty)); + self.map.get(ty) + } + + /// Reborrow this access. + pub fn reborrow(&mut self) -> ComponentsAccess<'_, 'a, Cx> { + ComponentsAccess { + set: self.set, + map: self.map, + } + } +} + +impl Debug for ComponentsAccess<'_, '_, Cx> +where + Cx: ProvideIdTy + Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ComponentsAccess") + .field("set", &self.set) + .field("map", &self.map) + .finish() + } +} diff --git a/crates/core/block-entity/src/deser_nbt.rs b/crates/core/block-entity/src/deser_nbt.rs deleted file mode 100644 index 4a3feaa7..00000000 --- a/crates/core/block-entity/src/deser_nbt.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! Helper module for deserializing NBT data into block entities. - -use std::{fmt::Debug, hash::Hash, marker::PhantomData}; - -use rimecraft_block::{BlockState, ProvideBlockStateExtTy}; -use rimecraft_global_cx::ProvideNbtTy; -use rimecraft_registry::ProvideRegistry; -use rimecraft_voxel_math::BlockPos; -use serde::{de::DeserializeSeed, Deserialize, Deserializer}; - -use crate::{BlockEntity, ProvideBlockEntity, RawBlockEntityTypeDyn}; - -/// The dummy value for block entity types. -pub const DUMMY: &str = "DUMMY"; - -/// A [`DeserializeSeed`] for [`BlockEntity`]. -/// -/// This deserializes the id of the block entity type and its data. -pub struct CreateFromNbt<'w, Cx> -where - Cx: ProvideBlockStateExtTy, -{ - /// The position of the block entity. - pub pos: BlockPos, - /// The state of the [`Block`] the block entity belongs to. - pub state: BlockState<'w, Cx>, - - /// Whether to respect the [`DUMMY`] value. - pub respect_dummy: bool, -} - -impl Debug for CreateFromNbt<'_, Cx> -where - Cx: ProvideBlockStateExtTy + Debug, - Cx::BlockStateExt: Debug, - Cx::Id: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CreateFromNbt") - .field("pos", &self.pos) - .field("state", &self.state) - .finish() - } -} - -impl<'w, 'de, Cx> DeserializeSeed<'de> for CreateFromNbt<'w, Cx> -where - Cx: ProvideBlockStateExtTy - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>> - + ProvideNbtTy, - Cx::Id: Deserialize<'de> + Hash + Eq, - Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, -{ - type Value = Option>>; - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct Visitor<'w, Cx>(CreateFromNbt<'w, Cx>) - where - Cx: ProvideBlockStateExtTy; - - impl<'de, 'w, Cx> serde::de::Visitor<'de> for Visitor<'w, Cx> - where - Cx: ProvideBlockStateExtTy - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>> - + ProvideNbtTy, - Cx::Id: Deserialize<'de> + Hash + Eq, - Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, - { - type Value = Option>>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("a block entity") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - use serde::__private::de::Content; - - let mut id: Option = None; - let mut is_dummy = false; - let mut collect: Vec, Content<'de>)>> = - Vec::with_capacity(map.size_hint().map_or(0, |i| i - 1)); - - enum Field<'de> { - Id, - Other(Content<'de>), - } - - impl<'de> Deserialize<'de> for Field<'de> { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - struct FieldVisitor; - - impl<'de> serde::de::Visitor<'de> for FieldVisitor { - type Value = Field<'de>; - - fn expecting( - &self, - formatter: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - formatter.write_str("a field") - } - - fn visit_str(self, v: &str) -> Result, E> - where - E: serde::de::Error, - { - match v { - "id" => Ok(Field::Id), - _ => Ok(Field::Other(Content::String(v.into()))), - } - } - } - - deserializer.deserialize_identifier(FieldVisitor) - } - } - - #[derive(Deserialize)] - #[serde(untagged)] - enum MaybeDummy { - Dummy(Dummy), - Value(T), - } - - pub struct Dummy; - - impl<'de> Deserialize<'de> for Dummy { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct DummyVisitor; - - impl serde::de::Visitor<'_> for DummyVisitor { - type Value = Dummy; - - fn expecting( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - write!(f, "a '{}' value", DUMMY) - } - - #[inline] - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - if v == DUMMY { - Ok(Dummy) - } else { - Err(serde::de::Error::custom("expected 'DUMMY'")) - } - } - } - - deserializer.deserialize_str(DummyVisitor) - } - } - - while let Some(key) = map.next_key()? { - match key { - Field::Id => { - if id.is_some() { - return Err(serde::de::Error::duplicate_field("id")); - } - - if self.0.respect_dummy { - let dummy: MaybeDummy = map.next_value()?; - match dummy { - MaybeDummy::Dummy(Dummy) => { - is_dummy = true; - } - MaybeDummy::Value(i) => { - id = Some(i); - } - } - } else { - id = Some(map.next_value()?); - } - } - Field::Other(content) => { - collect.push(Some((content, map.next_value()?))); - } - } - } - - if is_dummy { - let state = self.0.state.state; - let res = if let Some(constructor) = - ProvideBlockEntity::block_entity_constructor(state.data()) - { - Ok(Some(constructor(self.0.pos))) - } else { - Ok(None) - }; - res - } else { - let id = id.ok_or_else(|| serde::de::Error::missing_field("id"))?; - let registry: &rimecraft_registry::Registry<_, RawBlockEntityTypeDyn<'w, Cx>> = - Cx::registry(); - let ty = registry.get(&id).ok_or_else(|| { - serde::de::Error::custom(format!("unknown block entity type: {}", id)) - })?; - - let mut be = ty.instantiate(self.0.pos, self.0.state); - // Update the block entity data. - if let Some(be) = &mut be { - rimecraft_serde_update::Update::update( - &mut be.data, - serde::__private::de::FlatMapDeserializer(&mut collect, PhantomData), - )?; - } - Ok(be) - } - } - } - - deserializer.deserialize_map(Visitor(self)) - } -} diff --git a/crates/core/block-entity/src/lib.rs b/crates/core/block-entity/src/lib.rs index 0bd8491d..232fde6c 100644 --- a/crates/core/block-entity/src/lib.rs +++ b/crates/core/block-entity/src/lib.rs @@ -2,18 +2,33 @@ use std::{any::TypeId, fmt::Debug}; +use ::component::{ + changes::ComponentChanges, map::ComponentMap, ErasedComponentType, RawErasedComponentType, +}; +use ahash::AHashSet; use erased_serde::{serialize_trait_object, Serialize as ErasedSerialize}; use rimecraft_block::{BlockState, ProvideBlockStateExtTy}; use rimecraft_global_cx::ProvideIdTy; use rimecraft_registry::{entry::RefEntry, Reg}; -use rimecraft_serde_update::{erased::ErasedUpdate, update_trait_object}; +use rimecraft_serde_update::erased::ErasedUpdate; use rimecraft_voxel_math::BlockPos; -use serde::{Deserializer, Serialize}; -pub use rimecraft_downcast::ToStatic; +mod components_util; +pub mod serde; -pub mod deser_nbt; +pub use components_util::ComponentsAccess; + +/// Re-export of `rimecraft-component` +pub mod component { + pub use ::component::*; +} + +/// A trait for providing fundamental built-in component types. +pub trait ProvideBuiltInComponentTypes<'r>: ProvideIdTy { + /// The type of block entity data. + fn block_entity_data() -> ErasedComponentType<'r, Self>; +} /// A type of [`BlockEntity`]. pub trait RawBlockEntityType: Debug @@ -32,10 +47,10 @@ where } /// A type of [`BlockEntity`] that can be used in a type erased context. -pub type RawBlockEntityTypeDyn<'r, Cx> = Box + Send + Sync + 'r>; +pub type DynRawBlockEntityType<'r, Cx> = Box + Send + Sync + 'r>; /// A type of [`BlockEntity`]. -pub type BlockEntityType<'r, Cx> = Reg<'r, ::Id, RawBlockEntityTypeDyn<'r, Cx>>; +pub type BlockEntityType<'r, Cx> = Reg<'r, ::Id, DynRawBlockEntityType<'r, Cx>>; /// An object holding extra data about a block in a world. pub struct RawBlockEntity<'a, T: ?Sized, Cx> @@ -46,6 +61,7 @@ where pos: BlockPos, removed: bool, cached_state: BlockState<'a, Cx>, + components: ComponentMap<'a, Cx>, data: T, } @@ -67,11 +83,12 @@ where removed: false, cached_state: state, data, + components: ComponentMap::EMPTY, } } } -impl RawBlockEntity<'_, T, Cx> +impl<'a, T: ?Sized, Cx> RawBlockEntity<'a, T, Cx> where Cx: ProvideBlockStateExtTy, { @@ -93,6 +110,18 @@ where self.pos } + /// Gets the component map of this block entity. + #[inline] + pub fn components(&self) -> &ComponentMap<'a, Cx> { + &self.components + } + + /// Gets the mutable component map of this block entity. + #[inline] + pub fn components_mut(&mut self) -> &mut ComponentMap<'a, Cx> { + &mut self.components + } + /// Marks this block entity as not removed. #[inline] pub fn cancel_removal(&mut self) { @@ -112,6 +141,49 @@ where } } +impl<'a, T: ?Sized, Cx> RawBlockEntity<'a, T, Cx> +where + Cx: ProvideBlockStateExtTy + ProvideBuiltInComponentTypes<'a>, + T: Data<'a, Cx>, +{ + /// Reads components from given pair of default and changed components. + pub fn read_components( + &mut self, + default: &'a ComponentMap<'a, Cx>, + changes: ComponentChanges<'a, '_, Cx>, + ) { + let mut set: AHashSet> = [*Cx::block_entity_data()].into(); + let mut map = ComponentMap::with_changes(default, changes); + self.data.read_components(ComponentsAccess { + set: &mut set, + map: &mut map, + }); + + let Some(changes) = map.changes() else { + unreachable!() + }; + let (added, _) = changes + .retain(|ty| !set.contains(&*ty)) + .into_added_removed_pair(); + self.components = added; + } +} + +impl<'a, T: ?Sized, Cx> RawBlockEntity<'a, T, Cx> +where + Cx: ProvideBlockStateExtTy, + T: Data<'a, Cx>, +{ + /// Creates a component map from the data and inner components + /// of this block entity. + pub fn create_components(&self) -> ComponentMap<'a, Cx> { + let mut builder = ComponentMap::builder(); + builder.extend(self.components.iter()); + self.data.insert_components(&mut builder); + builder.build() + } +} + impl Debug for RawBlockEntity<'_, T, Cx> where Cx: ProvideBlockStateExtTy + Debug, @@ -130,17 +202,75 @@ where } } +/// A trait for generic block entity data types. +pub trait Data<'a, Cx> +where + Cx: ProvideIdTy, +{ + /// Reads components from the given accessor. + #[inline] + fn read_components(&mut self, accessor: ComponentsAccess<'_, 'a, Cx>) { + let _ = accessor; + } + + /// Writes components to the given builder. + #[inline] + fn insert_components(&self, builder: &mut component::map::Builder<'a, Cx>) { + let _ = builder; + } +} + /// Type erased block entity data. -pub trait ErasedData +/// +/// See [`Data`]. +pub trait ErasedData<'a, Cx> where - Self: ErasedSerialize + for<'de> ErasedUpdate<'de> + Send + Sync + Debug + sealed::Sealed, + Self: ErasedSerialize + + for<'de> ErasedUpdate<'de> + + Data<'a, Cx> + + Send + + Sync + + Debug + + sealed::Sealed, + Cx: ProvideIdTy, { /// The [`TypeId`] of this data. - fn type_id(&self) -> TypeId; + fn type_id(&self) -> TypeId { + typeid::of::() + } +} + +// Emit a warning +#[allow(single_use_lifetimes)] +mod ser_dyn_obj { + use super::*; + serialize_trait_object!(<'a, Cx> ErasedData<'a, Cx> where Cx: ProvideIdTy); } -serialize_trait_object! { ErasedData } -update_trait_object! { ErasedData } +impl<'de, Cx> rimecraft_serde_update::Update<'de> for dyn ErasedData<'_, Cx> + '_ +where + Cx: ProvideIdTy, +{ + rimecraft_serde_update::__internal_update_from_erased!(); +} +impl<'de, Cx> rimecraft_serde_update::Update<'de> for dyn ErasedData<'_, Cx> + Send + '_ +where + Cx: ProvideIdTy, +{ + rimecraft_serde_update::__internal_update_from_erased!(); +} +impl<'de, Cx> rimecraft_serde_update::Update<'de> for dyn ErasedData<'_, Cx> + Sync + '_ +where + Cx: ProvideIdTy, +{ + rimecraft_serde_update::__internal_update_from_erased!(); +} +impl<'de, Cx> rimecraft_serde_update::Update<'de> for dyn ErasedData<'_, Cx> + Send + Sync + '_ +where + Cx: ProvideIdTy, +{ + rimecraft_serde_update::__internal_update_from_erased!(); +} mod sealed { pub trait Sealed {} @@ -148,18 +278,19 @@ mod sealed { impl sealed::Sealed for T where T: ErasedSerialize + for<'de> ErasedUpdate<'de> + Send + Sync {} -impl ErasedData for T +impl<'a, T, Cx> ErasedData<'a, Cx> for T where - T: ErasedSerialize + for<'de> ErasedUpdate<'de> + ToStatic + Debug + Send + Sync, + T: ErasedSerialize + for<'de> ErasedUpdate<'de> + Data<'a, Cx> + Debug + Send + Sync, + Cx: ProvideIdTy, { #[inline] fn type_id(&self) -> TypeId { - TypeId::of::<::StaticRepr>() + typeid::of::() } } /// A type-erased variant of [`RawBlockEntity`]. -pub type BlockEntity<'w, Cx> = RawBlockEntity<'w, dyn ErasedData + 'w, Cx>; +pub type BlockEntity<'w, Cx> = RawBlockEntity<'w, dyn ErasedData<'w, Cx> + 'w, Cx>; impl<'w, Cx> BlockEntity<'w, Cx> where @@ -168,7 +299,12 @@ where /// Downcasts this type erased block entity into block entity with a concrete data type. /// /// This function returns an immutable reference if the type matches. - pub fn downcast_ref(&self) -> Option<&RawBlockEntity<'w, T, Cx>> { + /// + /// # Safety + /// + /// This function could not guarantee lifetime of type `T` is sound. + /// The type `T`'s lifetime parameters should not overlap lifetime `'w`. + pub unsafe fn downcast_ref(&self) -> Option<&RawBlockEntity<'w, T, Cx>> { if self.matches_type::() { unsafe { Some(&*(self as *const BlockEntity<'w, Cx> as *const RawBlockEntity<'w, T, Cx>)) @@ -181,7 +317,12 @@ where /// Downcasts this type erased block entity into block entity with a concrete data type. /// /// This function returns a mutable reference if the type matches. - pub fn downcast_mut(&mut self) -> Option<&mut RawBlockEntity<'w, T, Cx>> { + /// + /// # Safety + /// + /// This function could not guarantee lifetime of type `T` is sound. + /// The type `T`'s lifetime parameters should not overlap lifetime `'w`. + pub unsafe fn downcast_mut(&mut self) -> Option<&mut RawBlockEntity<'w, T, Cx>> { if self.matches_type::() { unsafe { Some(&mut *(self as *mut BlockEntity<'w, Cx> as *mut RawBlockEntity<'w, T, Cx>)) @@ -191,45 +332,11 @@ where } } - /// Whether the type of data in this block entity can be safely downcasted + /// Whether the type of data in this block entity can be safely downcast /// into the target type. #[inline] - pub fn matches_type(&self) -> bool { - self.data.type_id() == TypeId::of::() - } -} - -impl Serialize for RawBlockEntity<'_, T, Cx> -where - Cx: ProvideBlockStateExtTy, - T: ?Sized + Serialize, - Cx::Id: Serialize, -{ - /// Serializes this block entity's data and type id. - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeMap; - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("id", &<&RefEntry<_, _>>::from(self.ty))?; - self.data - .serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; - map.end() - } -} - -impl<'de, T, Cx> rimecraft_serde_update::Update<'de> for RawBlockEntity<'_, T, Cx> -where - Cx: ProvideBlockStateExtTy, - T: rimecraft_serde_update::Update<'de> + ?Sized, -{ - #[inline] - fn update(&mut self, deserializer: D) -> Result<(), >::Error> - where - D: Deserializer<'de>, - { - self.data.update(deserializer) + pub fn matches_type(&self) -> bool { + self.data.type_id() == typeid::of::() } } diff --git a/crates/core/block-entity/src/serde.rs b/crates/core/block-entity/src/serde.rs new file mode 100644 index 00000000..1e5ffabd --- /dev/null +++ b/crates/core/block-entity/src/serde.rs @@ -0,0 +1,297 @@ +//! Serialization and deserialization of block entities. + +use std::{fmt::Debug, marker::PhantomData}; + +use bitflags::bitflags; +use component::{map::ComponentMap, RawErasedComponentType}; +use rimecraft_block::{BlockState, ProvideBlockStateExtTy}; +use rimecraft_registry::ProvideRegistry; +use rimecraft_voxel_math::BlockPos; +use serde::{de::DeserializeSeed, Deserialize, Serialize}; + +use crate::{BlockEntity, DynRawBlockEntityType, RawBlockEntity}; + +bitflags! { + /// Essential flags for serializing a block entity. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct Flags: u8 { + /// Serializes the registration identifier. + const ID = 1u8 << 0; + /// Serializes the position. + const POS = 1u8 << 1; + /// Serializes the component map. + const COMPONENTS = 1u8 << 2; + } +} + +impl Flags { + /// Serializes the identifier and position. + #[inline(always)] + pub fn identifying_data() -> Self { + Self::POS | Self::ID + } +} + +impl Default for Flags { + #[inline] + fn default() -> Self { + Self::COMPONENTS + } +} + +/// Data flagged by [`SerializeFlags`], for serialization. +#[derive(Debug)] +pub struct Flagged(pub T, pub Flags); + +impl Serialize for Flagged<&RawBlockEntity<'_, T, Cx>> +where + Cx: ProvideBlockStateExtTy, + T: ?Sized + Serialize, + Cx::Id: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(None)?; + for flag in self.1.iter() { + match flag { + Flags::COMPONENTS => { + if !self.0.components.is_empty() { + map.serialize_entry(&Field::Components, &self.0.components)? + } + } + Flags::ID => { + map.serialize_entry(&Field::Id, &self.0.ty)?; + } + Flags::POS => { + map.serialize_entry(&Field::X, &self.0.pos.x())?; + map.serialize_entry(&Field::Y, &self.0.pos.y())?; + map.serialize_entry(&Field::Z, &self.0.pos.z())?; + } + _ => {} + } + } + self.0 + .data + .serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; + map.end() + } +} + +impl Serialize for Flagged> +where + Cx: ProvideBlockStateExtTy, + T: Serialize, + Cx::Id: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + Flagged(&self.0, self.1).serialize(serializer) + } +} + +enum Field<'de> { + Id, + Components, + + X, + Y, + Z, + + Other(serde::__private::de::Content<'de>), +} + +impl Serialize for Field<'_> { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(match self { + Field::Id => "id", + Field::Components => "components", + Field::X => "x", + Field::Y => "y", + Field::Z => "z", + Field::Other(_) => unimplemented!(), + }) + } +} + +impl<'de> Deserialize<'de> for Field<'de> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor<'de>(PhantomData<&'de ()>); + + impl<'de> serde::de::Visitor<'de> for Visitor<'de> { + type Value = Field<'de>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "a field") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v { + "id" => Ok(Field::Id), + "components" => Ok(Field::Components), + "x" => Ok(Field::X), + "y" => Ok(Field::Y), + "z" => Ok(Field::Z), + other => Ok(Field::Other(serde::__private::de::Content::String( + other.to_owned(), + ))), + } + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: serde::de::Error, + { + match v { + "id" => Ok(Field::Id), + "components" => Ok(Field::Components), + "x" => Ok(Field::X), + "y" => Ok(Field::Y), + "z" => Ok(Field::Z), + other => Ok(Field::Other(serde::__private::de::Content::Str(other))), + } + } + } + + deserializer.deserialize_identifier(Visitor(PhantomData)) + } +} + +/// This serializes the block entity using default value of [`SerializeFlags`]. +impl Serialize for RawBlockEntity<'_, T, Cx> +where + Cx: ProvideBlockStateExtTy, + T: ?Sized + Serialize, + Cx::Id: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + Flagged(self, Flags::default()).serialize(serializer) + } +} + +/// Seed for deserializing a block state. +pub struct Seed<'a, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + /// Position of the block state. + pub pos: BlockPos, + /// State of the block. + pub state: BlockState<'a, Cx>, +} + +impl<'a, 'de, Cx> DeserializeSeed<'de> for Seed<'a, Cx> +where + Cx: ProvideBlockStateExtTy> + + ProvideRegistry<'a, Cx::Id, RawErasedComponentType<'a, Cx>> + + ProvideRegistry<'a, Cx::Id, DynRawBlockEntityType<'a, Cx>>, +{ + type Value = Box>; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor<'a, Cx>(BlockPos, BlockState<'a, Cx>) + where + Cx: ProvideBlockStateExtTy; + + impl<'a, 'de, Cx> serde::de::Visitor<'de> for Visitor<'a, Cx> + where + Cx: ProvideBlockStateExtTy> + + ProvideRegistry<'a, Cx::Id, RawErasedComponentType<'a, Cx>> + + ProvideRegistry<'a, Cx::Id, DynRawBlockEntityType<'a, Cx>>, + { + type Value = Box>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "a block entity") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut id: Option = None; + let mut components: Option> = None; + use serde::__private::de::Content; + let mut collect: Vec, Content<'de>)>> = + Vec::with_capacity(map.size_hint().map_or(0, |i| i - 1)); + + while let Some(field) = map.next_key::>()? { + match field { + Field::Id => id = Some(map.next_value()?), + Field::Components => components = Some(map.next_value()?), + // Skip position information + Field::X | Field::Y | Field::Z => {} + Field::Other(c) => collect.push(Some((c, map.next_value()?))), + } + } + + let id = id.ok_or_else(|| serde::de::Error::missing_field("id"))?; + let components = components.unwrap_or(ComponentMap::EMPTY); + + let ty = >>::registry() + .get(&id) + .ok_or_else(|| { + serde::de::Error::custom(format!("unknown block entity type {}", id)) + })?; + let mut be = ty + .instantiate(self.0, self.1) + .ok_or_else(|| serde::de::Error::custom("failed to create block entity"))?; + rimecraft_serde_update::Update::update( + &mut *be, + serde::__private::de::FlatMapDeserializer(&mut collect, PhantomData), + )?; + be.components = components; + + Ok(be) + } + } + + deserializer.deserialize_map(Visitor(self.pos, self.state.clone())) + } +} + +impl<'de, T, Cx> rimecraft_serde_update::Update<'de> for RawBlockEntity<'_, T, Cx> +where + Cx: ProvideBlockStateExtTy, + T: ?Sized + rimecraft_serde_update::Update<'de>, +{ + #[inline] + fn update(&mut self, deserializer: D) -> Result<(), >::Error> + where + D: serde::Deserializer<'de>, + { + self.data.update(deserializer) + } +} + +impl Debug for Seed<'_, Cx> +where + Cx: ProvideBlockStateExtTy + Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DeserializeSeed") + .field("pos", &self.pos) + .field("state", &self.state) + .finish() + } +} diff --git a/crates/core/component/Cargo.toml b/crates/core/component/Cargo.toml index 9f106eba..ad254a2a 100644 --- a/crates/core/component/Cargo.toml +++ b/crates/core/component/Cargo.toml @@ -17,7 +17,7 @@ erased-serde = "0.4" bytes = "1.6" ahash = "0.8" edcode2 = { path = "../../util/edcode2", package = "rimecraft-edcode2" } -rimecraft-registry = { path = "../registry", features = ["serde"] } +rimecraft-registry = { path = "../registry", features = ["serde", "edcode"] } rimecraft-global-cx = { path = "../global-cx", features = ["nbt", "std"] } rimecraft-maybe = { path = "../../util/maybe" } typeid = "1.0" diff --git a/crates/core/component/src/changes.rs b/crates/core/component/src/changes.rs index ff2b14f7..7b6ee89d 100644 --- a/crates/core/component/src/changes.rs +++ b/crates/core/component/src/changes.rs @@ -2,7 +2,7 @@ use std::{cell::UnsafeCell, fmt::Debug, marker::PhantomData, str::FromStr, sync::OnceLock}; -use ahash::AHashMap; +use ahash::{AHashMap, AHashSet}; use bytes::{Buf, BufMut}; use edcode2::{BufExt as _, BufMutExt as _, Decode, Encode}; use rimecraft_global_cx::ProvideIdTy; @@ -11,8 +11,9 @@ use rimecraft_registry::{ProvideRegistry, Reg}; use serde::{de::DeserializeSeed, ser::SerializeMap, Deserialize, Serialize}; use crate::{ - map::CompTyCell, ComponentType, ErasedComponentType, Object, RawErasedComponentType, - UnsafeDebugIter, UnsafeSerdeCodec, + map::{CompTyCell, ComponentMap}, + ComponentType, ErasedComponentType, Object, RawErasedComponentType, UnsafeDebugIter, + UnsafeSerdeCodec, }; /// Changes of components. @@ -68,6 +69,57 @@ where pub fn is_empty(&self) -> bool { self.changed.is_empty() } + + /// Retains only the components specified by the predicate. + pub fn retain<'cow, F>(self, mut f: F) -> ComponentChanges<'a, 'cow, Cx> + where + F: FnMut(ErasedComponentType<'a, Cx>) -> bool, + { + let mut this = self.into_owned(); + let Maybe::Owned(SimpleOwned(map)) = &mut this.changed else { + unreachable!() + }; + map.retain(|k, _| f(k.0)); + this + } + + /// Converts the changes into owned version. + pub fn into_owned<'cow>(self) -> ComponentChanges<'a, 'cow, Cx> { + ComponentChanges { + changed: match self.changed { + Maybe::Borrowed(borrowed) => Maybe::Owned(SimpleOwned( + borrowed + .iter() + .map(|(CompTyCell(k), v)| { + (CompTyCell(*k), v.as_deref().map(k.f.util.clone)) + }) + .collect(), + )), + Maybe::Owned(owned) => Maybe::Owned(owned), + }, + ser_count: self.ser_count, + } + } + + /// Converts the changes into a pair of added components and removed component types. + pub fn into_added_removed_pair( + self, + ) -> (ComponentMap<'a, Cx>, AHashSet>) { + if self.is_empty() { + (ComponentMap::EMPTY, AHashSet::new()) + } else { + let mut builder = ComponentMap::builder(); + let mut set = AHashSet::new(); + for (CompTyCell(ty), obj) in self.changed.iter() { + if let Some(obj) = obj { + builder.insert_raw(*ty, (ty.f.util.clone)(&**obj)); + } else { + set.insert(*ty); + } + } + (builder.build(), set) + } + } } impl Serialize for ComponentChanges<'_, '_, Cx> @@ -221,7 +273,7 @@ where } } -impl<'a: 'de, 'de, Cx, B> Decode<'de, B> for ComponentChanges<'a, '_, Cx> +impl<'a, 'de, Cx, B> Decode<'de, B> for ComponentChanges<'a, '_, Cx> where Cx: ProvideIdTy Decode<'de, &'b mut B>> + ProvideRegistry<'a, Cx::Id, RawErasedComponentType<'a, Cx>>, diff --git a/crates/core/component/src/lib.rs b/crates/core/component/src/lib.rs index 4ed654dc..684d00d4 100644 --- a/crates/core/component/src/lib.rs +++ b/crates/core/component/src/lib.rs @@ -20,6 +20,8 @@ mod dyn_any; use dyn_any::Any; +pub use ahash::{AHashMap, AHashSet}; + /// Type of a component data. /// /// The type `T` should be unique for each component, as it's used to identify the component. @@ -41,22 +43,42 @@ impl ComponentType<'_, T> { } } +impl<'a, T> ComponentType<'a, T> { + /// Creates a builder of component type. + #[inline] + pub const fn builder() -> TypeBuilder<'a, T, Cx> { + TypeBuilder { + serde_codec: None, + packet_codec: None, + _marker: PhantomData, + } + } +} + impl<'a, T> ComponentType<'a, T> where - T: Clone + Eq + Send + Sync + 'a, + T: Clone + Eq + Hash + Send + Sync + 'a, { const UTIL: DynUtil<'a> = DynUtil { clone: |obj| Box::new(unsafe { &*(obj as *const Object<'_> as *const T) }.clone()), eq: |a, b| unsafe { *(a as *const Object<'_> as *const T) == *(b as *const Object<'_> as *const T) }, + hash: |obj, mut state| { + let obj = unsafe { &*(obj as *const Object<'_> as *const T) }; + obj.hash(&mut state); + }, }; } /// Creates a new [`PacketCodec`] by encoding and decoding through `edcode2`. pub const fn packet_codec_edcode<'a, T>() -> PacketCodec<'a, T> where - T: for<'b> Encode<&'b mut dyn BufMut> + for<'b> Decode<'a, &'b mut dyn Buf> + Send + Sync + 'a, + T: for<'b> Encode<&'b mut dyn BufMut> + + for<'b> Decode<'static, &'b mut dyn Buf> + + Send + + Sync + + 'a, { PacketCodec { codec: UnsafePacketCodec { @@ -111,7 +133,10 @@ where { SerdeCodec { codec: UnsafeSerdeCodec { - ser: |obj| unsafe { &*(obj as *const Object<'_> as *const T) }, + ser: |obj| unsafe { + &*(obj as *const Object<'_> as *const T + as *const (dyn erased_serde::Serialize + 'a)) + }, de: |deserializer| { erased_serde::deserialize::(deserializer).map(|v| { let v: Box> = Box::new(v); @@ -156,9 +181,13 @@ impl<'a, T, Cx> TypeBuilder<'a, T, Cx> { impl<'a, T, Cx> TypeBuilder<'a, T, Cx> where - T: Clone + Eq + Send + Sync + 'a, + T: Clone + Eq + Hash + Send + Sync + 'a, { /// Builds a new [`ComponentType`] with the given codecs. + /// + /// # Panics + /// + /// Panics if the packet codec is not set. pub const fn build(self) -> ComponentType<'a, T> { ComponentType { f: Funcs { @@ -217,7 +246,7 @@ pub struct SerdeCodec<'a, T> { #[derive(Debug, Clone, Copy)] #[allow(dead_code)] struct UnsafeSerdeCodec<'a> { - ser: for<'s> fn(&'s Object<'a>) -> &'s dyn erased_serde::Serialize, + ser: for<'s> fn(&'s Object<'a>) -> &'s (dyn erased_serde::Serialize + 'a), de: fn(&mut dyn erased_serde::Deserializer<'_>) -> erased_serde::Result>>, upd: fn(&mut Object<'a>, &mut dyn erased_serde::Deserializer<'a>) -> erased_serde::Result<()>, } @@ -233,14 +262,15 @@ pub struct PacketCodec<'a, T> { #[allow(dead_code)] struct UnsafePacketCodec<'a> { encode: fn(&'_ Object<'a>, &'_ mut dyn BufMut) -> Result<(), edcode2::BoxedError<'static>>, - decode: fn(&'_ mut dyn Buf) -> Result>, edcode2::BoxedError<'a>>, + decode: fn(&'_ mut dyn Buf) -> Result>, edcode2::BoxedError<'static>>, upd: fn(&'_ mut Object<'a>, &'_ mut dyn Buf) -> Result<(), edcode2::BoxedError<'a>>, } #[derive(Debug, Clone, Copy)] struct DynUtil<'a> { clone: fn(&Object<'a>) -> Box>, - eq: fn(&Object<'a>, &Object<'a>) -> bool, + eq: fn(&'_ Object<'a>, &'_ Object<'a>) -> bool, + hash: fn(&'_ Object<'a>, &'_ mut dyn std::hash::Hasher), } #[derive(Debug, Clone, Copy)] @@ -327,6 +357,13 @@ where } } +impl Copy for RawErasedComponentType<'_, Cx> {} +impl Clone for RawErasedComponentType<'_, Cx> { + fn clone(&self) -> Self { + *self + } +} + /// Registration wrapper of [`RawErasedComponentType`]. pub type ErasedComponentType<'a, Cx> = Reg<'a, ::Id, RawErasedComponentType<'a, Cx>>; diff --git a/crates/core/component/src/map.rs b/crates/core/component/src/map.rs index 10a8d6cc..a6ca1452 100644 --- a/crates/core/component/src/map.rs +++ b/crates/core/component/src/map.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use crate::{ changes::ComponentChanges, dyn_any, ComponentType, ErasedComponentType, Object, - RawErasedComponentType, UnsafeSerdeCodec, UnsafeDebugIter, + RawErasedComponentType, UnsafeDebugIter, UnsafeSerdeCodec, }; #[repr(transparent)] @@ -43,7 +43,7 @@ where { #[inline] fn default() -> Self { - Self::empty() + Self::EMPTY } } @@ -51,10 +51,14 @@ impl<'a, Cx> ComponentMap<'a, Cx> where Cx: ProvideIdTy, { + /// An empty component map. + pub const EMPTY: Self = Self(MapInner::Empty); + /// Creates an empty component map. + #[deprecated = "use `ComponentMap::EMPTY` instead"] #[inline] pub const fn empty() -> Self { - Self(MapInner::Empty) + Self::EMPTY } /// Creates a **patched** component map with given base map. @@ -67,6 +71,24 @@ where }) } + /// Creates a **patched** component map with given base map and changes. + pub fn with_changes( + base: &'a ComponentMap<'a, Cx>, + changes: ComponentChanges<'a, '_, Cx>, + ) -> Self { + Self(MapInner::Patched { + base, + changes_count: changes.changed.values().map(|v| v.is_some() as isize).sum(), + changes: match changes.changed { + Maybe::Borrowed(c) => c + .iter() + .map(|(k, v)| (CompTyCell(k.0), v.as_deref().map(k.0.f.util.clone))) + .collect(), + Maybe::Owned(c) => c.0, + }, + }) + } + /// Returns a builder for creating a simple component map. #[inline] pub fn builder() -> Builder<'a, Cx> { @@ -75,6 +97,14 @@ where } } + /// Returns a builder for creating a simple component map with given capacity. + #[inline] + pub fn builder_with_capacity(capacity: usize) -> Builder<'a, Cx> { + Builder { + map: AHashMap::with_capacity(capacity), + } + } + /// Gets the component with given type. /// /// # Safety @@ -86,8 +116,10 @@ where .and_then(|val| unsafe { val.downcast_ref() }) } - #[inline] - fn get_raw(&self, ty: &RawErasedComponentType<'a, Cx>) -> Option<&Object<'_>> { + /// Gets the component with given type. + /// + /// This function is similar to `get`, but it returns the raw object instead of the reference. + pub fn get_raw(&self, ty: &RawErasedComponentType<'a, Cx>) -> Option<&Object<'a>> { match &self.0 { MapInner::Empty => None, MapInner::Patched { base, changes, .. } => changes @@ -112,8 +144,10 @@ where .and_then(|(k, v)| v.downcast_ref().map(|v| (k, v))) } - #[inline] - fn get_key_value_raw( + /// Gets the component and its type registration with given type. + /// + /// This function is similar to `get_key_value`, but it returns the raw object instead of the reference. + pub fn get_key_value_raw( &self, ty: &RawErasedComponentType<'a, Cx>, ) -> Option<(ErasedComponentType<'a, Cx>, &Object<'a>)> { @@ -132,8 +166,10 @@ where self.contains_raw(&RawErasedComponentType::from(ty)) } - #[inline] - fn contains_raw(&self, ty: &RawErasedComponentType<'a, Cx>) -> bool { + /// Returns whether a component with given type exist. + /// + /// This function is similar to `contains`, but it receives the raw component type instead of the typed one. + pub fn contains_raw(&self, ty: &RawErasedComponentType<'a, Cx>) -> bool { match &self.0 { MapInner::Empty => false, MapInner::Patched { base, changes, .. } => changes @@ -155,11 +191,10 @@ where .and_then(|val| unsafe { val.downcast_mut() }) } - #[inline] - unsafe fn get_mut_raw( - &mut self, - ty: &RawErasedComponentType<'a, Cx>, - ) -> Option<&mut Object<'a>> { + /// Gets the component with given type, with mutable access. + /// + /// This function is similar to `get_mut`, but it returns the raw object instead of the reference. + pub fn get_mut_raw(&mut self, ty: &RawErasedComponentType<'a, Cx>) -> Option<&mut Object<'a>> { match &mut self.0 { MapInner::Empty => None, MapInner::Patched { base, changes, .. } => { @@ -333,8 +368,8 @@ where Some(ComponentChanges { changed: Maybe::Borrowed(changes), ser_count: changes - .iter() - .filter(|(cell, _)| cell.0.is_serializable()) + .keys() + .filter(|cell| cell.0.is_serializable()) .count(), }) } else { @@ -448,6 +483,65 @@ where } } +impl PartialEq for ComponentMap<'_, Cx> +where + Cx: ProvideIdTy, +{ + fn eq(&self, other: &Self) -> bool { + if self.len() != other.len() { + return false; + } + + self.iter().all(move |(ty, obj)| { + other + .get_raw(&*ty) + .map_or(false, |o| (ty.f.util.eq)(obj, o)) + }) + } +} + +impl Eq for ComponentMap<'_, Cx> where Cx: ProvideIdTy {} + +impl Hash for ComponentMap<'_, Cx> +where + Cx: ProvideIdTy, +{ + fn hash(&self, state: &mut H) { + for (ty, obj) in self.iter() { + ty.hash(state); + (ty.f.util.hash)(obj, state); + } + } +} + +impl Clone for ComponentMap<'_, Cx> +where + Cx: ProvideIdTy, +{ + fn clone(&self) -> Self { + match &self.0 { + MapInner::Empty => Self::EMPTY, + MapInner::Patched { + base, + changes, + changes_count, + } => Self(MapInner::Patched { + base, + changes: changes + .iter() + .map(|(k, v)| (CompTyCell(k.0), v.as_deref().map(k.0.f.util.clone))) + .collect(), + changes_count: *changes_count, + }), + MapInner::Simple(map) => Self(MapInner::Simple( + map.iter() + .map(|(k, v)| (CompTyCell(k.0), (k.0.f.util.clone)(&**v))) + .collect(), + )), + } + } +} + /// A builder for creating a simple component map. pub struct Builder<'a, Cx> where @@ -466,8 +560,7 @@ where /// /// This function panics when the given component type's type information does not match with /// the given static type. - #[inline] - pub fn insert(mut self, ty: ErasedComponentType<'a, Cx>, val: T) -> Self + pub fn insert(&mut self, ty: ErasedComponentType<'a, Cx>, val: T) where T: Send + Sync + 'a, { @@ -477,7 +570,24 @@ where "the component type should matches the type of given value" ); self.map.insert(CompTyCell(ty), Box::new(val)); - self + } + + /// Inserts a component into this map. + /// + /// This function is similar to `insert`, but it receives the raw component type instead of the typed one. + /// + /// # Panics + /// + /// This function panics when the given component type's type information does not match with + /// the type of given value. + #[inline] + pub fn insert_raw(&mut self, ty: ErasedComponentType<'a, Cx>, val: Box>) { + assert_eq!( + ty.ty, + (*val).type_id(), + "the component type should matches the type of given value" + ); + self.map.insert(CompTyCell(ty), val); } /// Builds the component map. @@ -490,6 +600,35 @@ where } } +impl<'a, Cx> Extend<(ErasedComponentType<'a, Cx>, Box>)> for Builder<'a, Cx> +where + Cx: ProvideIdTy, +{ + fn extend, Box>)>>( + &mut self, + iter: T, + ) { + self.map.extend( + iter.into_iter() + .filter(|(k, v)| k.ty == (**v).type_id()) + .map(|(k, v)| (CompTyCell(k), v)), + ); + } +} + +impl<'a, 's, Cx> Extend<(ErasedComponentType<'a, Cx>, &'s Object<'a>)> for Builder<'a, Cx> +where + Cx: ProvideIdTy, +{ + #[inline] + fn extend, &'s Object<'a>)>>( + &mut self, + iter: T, + ) { + self.extend(iter.into_iter().map(|(k, v)| (k, (k.f.util.clone)(v)))); + } +} + impl<'a, Cx> From> for ComponentMap<'a, Cx> where Cx: ProvideIdTy, diff --git a/crates/core/global-cx/src/lib.rs b/crates/core/global-cx/src/lib.rs index 65b0eed1..ae50027c 100644 --- a/crates/core/global-cx/src/lib.rs +++ b/crates/core/global-cx/src/lib.rs @@ -30,6 +30,12 @@ pub trait ProvideIdTy: GlobalContext { type Id: Display + Hash + Eq; } +/// Marker trait for global contexts that provide a version type. +pub trait ProvideVersionTy: GlobalContext { + /// Version type. + type Version: Display + Hash + Eq; +} + /// Marker trait for global contexts that provide a `NbtCompound` type and friends. #[cfg(feature = "nbt")] pub trait ProvideNbtTy: GlobalContext { diff --git a/crates/core/item/Cargo.toml b/crates/core/item/Cargo.toml index 6c6daa11..0c49bc9d 100644 --- a/crates/core/item/Cargo.toml +++ b/crates/core/item/Cargo.toml @@ -20,6 +20,7 @@ rimecraft-registry = { path = "../registry" } rimecraft-fmt = { path = "../../util/fmt" } serde = { version = "1.0", optional = true } edcode2 = { path = "../../util/edcode2", package = "rimecraft-edcode2", optional = true } +component = { path = "../component", package = "rimecraft-component" } [features] default = ["serde", "edcode"] diff --git a/crates/core/item/src/edcode.rs b/crates/core/item/src/edcode.rs index 25b085b5..fb344451 100644 --- a/crates/core/item/src/edcode.rs +++ b/crates/core/item/src/edcode.rs @@ -1,43 +1,50 @@ +use component::{changes::ComponentChanges, map::ComponentMap, RawErasedComponentType}; use edcode2::{Buf, BufExt, BufMut, BufMutExt, Decode, Encode}; -use rimecraft_global_cx::nbt_edcode::{ReadNbt, WriteNbt}; use rimecraft_registry::{ProvideRegistry, Reg}; -use crate::{stack::ItemStackCx, ItemStack, RawItem}; +use crate::{stack::ItemStackCx, Item, ItemSettings, ItemStack, RawItem}; -impl Encode for ItemStack<'_, Cx> +impl<'r, Cx, B> Encode for ItemStack<'r, Cx> where - Cx: ItemStackCx + for<'a> WriteNbt>, + Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem<'r, Cx>>, B: BufMut, { fn encode(&self, mut buf: B) -> Result<(), edcode2::BoxedError<'static>> { - if self.count() == 0 { - buf.put_bool(false); + if self.is_empty() { + buf.put_variable(0u32); + Ok(()) } else { - buf.put_bool(true); + buf.put_variable(self.count()); let item = self.item(); item.encode(&mut buf)?; self.count().encode(&mut buf)?; - if item.settings().max_damage.is_some() || item.settings().sync_nbt { - Cx::write_nbt(self.nbt(), buf.writer())? - } + self.components() + .changes() + .ok_or("components not patched")? + .encode(buf) } - Ok(()) } } impl<'r, 'de, Cx, B> Decode<'de, B> for ItemStack<'r, Cx> where - Cx: ItemStackCx + ReadNbt> + ProvideRegistry<'r, Cx::Id, RawItem>, + Cx: ItemStackCx Decode<'de, &'b mut B>> + + ProvideRegistry<'r, Cx::Id, RawItem<'r, Cx>> + + ProvideRegistry<'r, Cx::Id, RawErasedComponentType<'r, Cx>>, B: Buf, { fn decode(mut buf: B) -> Result> { - if buf.get_bool() { - let item = Reg::<'r, Cx::Id, RawItem>::decode(&mut buf)?; - let count = buf.get_u8(); - let nbt = Cx::read_nbt(buf.reader())?; - Ok(ItemStack::with_nbt(item, count, nbt)) - } else { + let count: u32 = buf.get_variable(); + if count == 0 { Ok(ItemStack::empty()) + } else { + let item = Item::<'r, Cx>::decode(&mut buf)?; + let changes = ComponentChanges::<'r, 'r, Cx>::decode(buf)?; + Ok(ItemStack::with_component( + item, + count, + ComponentMap::with_changes(Reg::into_inner(item).settings().components(), changes), + )) } } } diff --git a/crates/core/item/src/lib.rs b/crates/core/item/src/lib.rs index c991a598..60314fef 100644 --- a/crates/core/item/src/lib.rs +++ b/crates/core/item/src/lib.rs @@ -1,7 +1,8 @@ //! Minecraft item primitives. -use std::{marker::PhantomData, num::NonZeroU32}; +use std::marker::PhantomData; +use component::map::ComponentMap; use rimecraft_fmt::Formatting; use rimecraft_global_cx::ProvideIdTy; use rimecraft_registry::{ProvideRegistry, Reg}; @@ -12,17 +13,38 @@ pub mod stack; pub use stack::ItemStack; +/// Provides settings type for items. +pub trait ProvideSettingsTy: ProvideIdTy { + /// Settings type of an item. + type Settings<'a>: ItemSettings<'a, Self>; +} + +/// Settings of an item. +pub trait ItemSettings<'a, Cx> +where + Cx: ProvideIdTy, +{ + /// Returns components of the item. + fn components(&self) -> &ComponentMap<'a, Cx>; +} + /// Item containing settings. #[derive(Debug)] -pub struct RawItem { - settings: Settings, +pub struct RawItem<'a, Cx> +where + Cx: ProvideSettingsTy, +{ + settings: Cx::Settings<'a>, _marker: PhantomData, } -impl RawItem { +impl<'a, Cx> RawItem<'a, Cx> +where + Cx: ProvideSettingsTy, +{ /// Creates a new `Item` with the given settings. #[inline] - pub const fn new(settings: Settings) -> Self { + pub const fn new(settings: Cx::Settings<'a>) -> Self { Self { settings, _marker: PhantomData, @@ -31,21 +53,14 @@ impl RawItem { /// Returns the settings of the item. #[inline] - pub fn settings(&self) -> &Settings { + pub fn settings(&self) -> &Cx::Settings<'a> { &self.settings } } -impl From for RawItem { - #[inline] - fn from(settings: Settings) -> Self { - Self::new(settings) - } -} - -impl<'r, K, Cx> ProvideRegistry<'r, K, Self> for RawItem +impl<'r, K, Cx> ProvideRegistry<'r, K, Self> for RawItem<'r, Cx> where - Cx: ProvideRegistry<'r, K, Self>, + Cx: ProvideRegistry<'r, K, Self> + ProvideSettingsTy, { #[inline] fn registry() -> &'r rimecraft_registry::Registry { @@ -54,43 +69,10 @@ where } /// An item usable by players and other entities. -pub type Item<'r, Cx> = Reg<'r, ::Id, RawItem>; +pub type Item<'r, Cx> = Reg<'r, ::Id, RawItem<'r, Cx>>; /// The max item count of an `ItemStack`. -pub const MAX_STACK_COUNT: u32 = 64; - -/// Settings of an [`Item`]. -/// -/// A setting configure behaviors common to all items, such as the -/// stack's max count. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Settings { - /// The maximum count of the item that can be stacked in a single slot. - pub max_count: NonZeroU32, - /// The maximum amount of damage the item can take. - pub max_damage: Option, - - /// The rarity of the item. - pub rarity: Rarity, - - /// Whether an item should have its NBT data sent to the client. - pub sync_nbt: bool, -} - -impl Default for Settings { - #[inline] - fn default() -> Self { - Self { - max_count: NonZeroU32::new(MAX_STACK_COUNT).unwrap(), - max_damage: None, - rarity: Default::default(), - sync_nbt: true, - } - } -} - -#[doc(alias = "ItemProperties")] -pub use Settings as ItemSettings; +pub const MAX_STACK_COUNT: u32 = 64u32; /// Rarity of an item. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] diff --git a/crates/core/item/src/stack.rs b/crates/core/item/src/stack.rs index de0a6fdb..644d376b 100644 --- a/crates/core/item/src/stack.rs +++ b/crates/core/item/src/stack.rs @@ -1,16 +1,17 @@ //! Item stack related types and traits. -use rimecraft_global_cx::{ProvideIdTy, ProvideNbtTy}; -use rimecraft_registry::ProvideRegistry; +use component::map::ComponentMap; +use rimecraft_global_cx::ProvideIdTy; +use rimecraft_registry::{ProvideRegistry, Reg}; use std::{fmt::Debug, marker::PhantomData}; -use crate::{Item, RawItem}; +use crate::{Item, ItemSettings, ProvideSettingsTy, RawItem}; /// Global context used for item stacks. -pub trait ItemStackCx: ProvideIdTy + ProvideNbtTy {} +pub trait ItemStackCx: ProvideIdTy + ProvideSettingsTy {} -impl ItemStackCx for T where T: ProvideIdTy + ProvideNbtTy {} +impl ItemStackCx for T where T: ProvideIdTy + ProvideSettingsTy {} /// A stack of items. /// @@ -20,10 +21,9 @@ where Cx: ItemStackCx, { item: Item<'r, Cx>, - count: u8, + count: u32, - /// Item stack's custom NBT. - nbt: Option, + components: ComponentMap<'r, Cx>, } impl<'r, Cx> ItemStack<'r, Cx> @@ -32,31 +32,38 @@ where { /// Creates a new item stack with the given item and count. #[inline] - pub fn new(item: Item<'r, Cx>, count: u8) -> Self { - Self::with_nbt(item, count, None) + pub fn new(item: Item<'r, Cx>, count: u32) -> Self { + Self::with_component( + item, + count, + ComponentMap::new(Reg::into_inner(item).settings().components()), + ) } /// Creates a new item stack with the given item, count, and custom NBT tag. - pub fn with_nbt(item: Item<'r, Cx>, count: u8, nbt: Option) -> Self { - Self { item, count, nbt } + pub fn with_component( + item: Item<'r, Cx>, + count: u32, + components: ComponentMap<'r, Cx>, + ) -> Self { + Self { + item, + count, + components, + } } } impl<'r, Cx> ItemStack<'r, Cx> where - Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem> + 'r, + Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem<'r, Cx>>, { /// Creates an empty item stack. #[inline] pub fn empty() -> Self { Self::new(Item::default(), 0) } -} -impl<'r, Cx> ItemStack<'r, Cx> -where - Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem> + 'r, -{ /// Returns whether the stack is empty. #[inline] pub fn is_empty(&self) -> bool { @@ -76,50 +83,32 @@ where /// Returns the count of the stack. #[inline] - pub fn count(&self) -> u8 { + pub fn count(&self) -> u32 { self.count } - /// Returns the custom NBT of the stack. + /// Returns the components of the stack. #[inline] - pub fn nbt(&self) -> Option<&Cx::Compound> { - self.nbt.as_ref() + pub fn components(&self) -> &ComponentMap<'r, Cx> { + &self.components } - /// Returns a mutable reference to the custom NBT of the stack. + /// Returns a mutable reference to the components of the stack. #[inline] - pub fn nbt_mut(&mut self) -> Option<&mut Cx::Compound> { - self.nbt.as_mut() + pub fn components_mut(&mut self) -> &mut ComponentMap<'r, Cx> { + &mut self.components } /// Sets the count of the stack. #[inline] - pub fn set_count(&mut self, count: u8) { + pub fn set_count(&mut self, count: u32) { self.count = count; } - - /// Sets the custom NBT of the stack. - #[inline] - pub fn set_nbt(&mut self, nbt: Option) { - self.nbt = nbt; - } -} - -impl ItemStack<'_, Cx> -where - Cx: ItemStackCx, - Cx::Compound: Default, -{ - /// Returns the custom NBT of the stack, create one if it does not exist. - #[inline] - pub fn get_or_create_nbt(&mut self) -> &mut Cx::Compound { - self.nbt.get_or_insert_with(Default::default) - } } impl<'r, Cx> Default for ItemStack<'r, Cx> where - Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem> + 'r, + Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem<'r, Cx>> + 'r, { #[inline] fn default() -> Self { @@ -150,212 +139,195 @@ where impl PartialEq for ItemStack<'_, Cx> where Cx: ItemStackCx, - Cx::Compound: PartialEq, { #[inline] fn eq(&self, other: &Self) -> bool { - self.item == other.item && self.count == other.count && self.nbt == other.nbt + self.item == other.item && self.count == other.count && self.components == other.components } } -impl Eq for ItemStack<'_, Cx> -where - Cx: ItemStackCx, - Cx::Compound: Eq, -{ -} +impl Eq for ItemStack<'_, Cx> where Cx: ItemStackCx {} impl Clone for ItemStack<'_, Cx> where Cx: ItemStackCx, - Cx::Compound: Clone, { #[inline] fn clone(&self) -> Self { - Self::with_nbt(self.item, self.count, self.nbt.clone()) + Self::with_component(self.item, self.count, self.components.clone()) } } -impl Debug for ItemStack<'_, Cx> +impl<'r, Cx> Debug for ItemStack<'r, Cx> where - Cx: ItemStackCx + Debug, - Cx::Id: Debug, - Cx::Compound: Debug, + Cx: ItemStackCx: Debug> + Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ItemStack") .field("item", &self.item) .field("count", &self.count) - .field("nbt", &self.nbt) + .field("components", &self.components) .finish() } } -#[cfg(feature = "serde")] -pub use _serde::{deserialize_vanilla, serialize_vanilla, DeserItemStack, SerItemStack}; - #[cfg(feature = "serde")] #[allow(clippy::missing_errors_doc)] mod _serde { - use std::hash::Hash; + use std::{hash::Hash, str::FromStr}; + use component::{changes::ComponentChanges, RawErasedComponentType}; use rimecraft_registry::entry::RefEntry; use serde::{Deserialize, Serialize}; use super::*; - /// Global context behavior for serializing item stacks. - pub trait SerItemStack<'r>: ItemStackCx { - /// Serializes the item stack. - fn serialize(serializer: S, stack: &ItemStack<'r, Self>) -> Result - where - S: serde::Serializer; - } - - /// Global context behavior for deserializing item stacks. - pub trait DeserItemStack<'r, 'de>: ItemStackCx { - /// Deserializes the item stack. - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>; - } - - impl<'r, Cx> Serialize for ItemStack<'r, Cx> + impl Serialize for ItemStack<'_, Cx> where - Cx: SerItemStack<'r>, + Cx: ItemStackCx, { - #[inline] fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - Cx::serialize(serializer, self) + use serde::ser::SerializeStruct; + + let mut state = serializer + .serialize_struct("ItemStack", 2 + self.components.is_empty() as usize)?; + let entry: &RefEntry<_, _> = self.item.into(); + state.serialize_field("id", entry)?; + state.serialize_field("count", &self.count)?; + state.serialize_field( + "components", + &self + .components + .changes() + .ok_or_else(|| serde::ser::Error::custom("components not patched"))?, + )?; + state.end() } } impl<'r, 'de, Cx> Deserialize<'de> for ItemStack<'r, Cx> where - Cx: DeserItemStack<'r, 'de>, + Cx: ItemStackCx + + ProvideRegistry<'r, Cx::Id, RawItem<'r, Cx>> + + ProvideRegistry<'r, Cx::Id, RawErasedComponentType<'r, Cx>>, + Cx::Id: Deserialize<'de> + FromStr + Hash + Eq, { #[inline] fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - Cx::deserialize(deserializer) - } - } - - /// Serializes the item stack in vanilla format. - /// - /// This is a helper function for implementing [`SerItemStack`] for a global context. - pub fn serialize_vanilla( - serializer: S, - stack: &ItemStack<'_, Cx>, - ) -> Result - where - S: serde::Serializer, - Cx: ItemStackCx, - Cx::Id: Serialize, - Cx::Compound: Serialize, - { - use serde::ser::SerializeStruct; - - let mut state = - serializer.serialize_struct("ItemStack", 2 + stack.nbt.is_some() as usize)?; - let entry: &RefEntry<_, _> = stack.item.into(); - state.serialize_field("id", entry)?; - state.serialize_field("Count", &stack.count)?; - if let Some(nbt) = &stack.nbt { - state.serialize_field("tag", nbt)?; - } - state.end() - } - - /// Deserializes the item stack in vanilla format. - /// - /// This is a helper function for implementing [`DeserItemStack`] for a global context. - pub fn deserialize_vanilla<'r, 'de, Cx, D>( - deserializer: D, - ) -> Result, D::Error> - where - 'r: 'de, - D: serde::Deserializer<'de>, - Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem>, - Cx::Id: Deserialize<'de> + Hash + Eq, - Cx::Compound: Deserialize<'de>, - { - struct Visitor<'r, Cx> { - _marker: PhantomData, - } - - impl<'r, 'de, Cx> serde::de::Visitor<'de> for Visitor<'r, Cx> - where - 'r: 'de, - Cx: ItemStackCx + ProvideRegistry<'r, Cx::Id, RawItem>, - Cx::Id: Deserialize<'de> + Hash + Eq, - Cx::Compound: Deserialize<'de>, - { - type Value = ItemStack<'r, Cx>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("a vanilla item stack structure") + struct Visitor<'r, Cx> { + _marker: PhantomData, } - fn visit_map(self, mut map: A) -> Result + impl<'r, 'de, Cx> serde::de::Visitor<'de> for Visitor<'r, Cx> where - A: serde::de::MapAccess<'de>, + Cx: ItemStackCx + + ProvideRegistry<'r, Cx::Id, RawItem<'r, Cx>> + + ProvideRegistry<'r, Cx::Id, RawErasedComponentType<'r, Cx>>, + Cx::Id: Deserialize<'de> + FromStr + Hash + Eq, { - let mut id = None; - let mut count = 0u8; - let mut tag = None; - - #[derive(Deserialize)] - #[serde(field_identifier)] - enum Field { - #[serde(rename = "id")] - Id, - #[serde(rename = "Count")] - Count, - #[serde(rename = "tag")] - Tag, + type Value = ItemStack<'r, Cx>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a vanilla item stack structure") } - while let Some(key) = map.next_key()? { - match key { - Field::Id => { - if id.is_some() { - return Err(serde::de::Error::duplicate_field("id")); + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut id = None; + let mut count = 0u32; + let mut components: Option> = None; + + enum Field { + Id, + Count, + Tag, + } + + impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct FieldVisitor; + + impl serde::de::Visitor<'_> for FieldVisitor { + type Value = Field; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + formatter.write_str("`id`, `Count`, or `tag`") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "id" => Ok(Field::Id), + "count" => Ok(Field::Count), + "components" => Ok(Field::Tag), + _ => Err(serde::de::Error::unknown_field( + value, + &["id", "count", "components"], + )), + } + } } - let entry: &RefEntry> = map.next_value()?; - id = Some(Cx::registry().of_raw(entry.raw_id()).unwrap()); + deserializer.deserialize_identifier(FieldVisitor) } - Field::Count => { - count = map.next_value::()?; - } - Field::Tag => { - if tag.is_some() { - return Err(serde::de::Error::duplicate_field("tag")); + } + + while let Some(key) = map.next_key()? { + match key { + Field::Id => { + if id.is_some() { + return Err(serde::de::Error::duplicate_field("id")); + } + let entry: &RefEntry> = map.next_value()?; + id = Some(Cx::registry().of_raw(entry.raw_id()).unwrap()); + } + Field::Count => { + count = map.next_value::()?; + } + Field::Tag => { + if components.is_some() { + return Err(serde::de::Error::duplicate_field("components")); + } + components = Some(map.next_value()?); } - tag = Some(map.next_value()?); } } - } - Ok(ItemStack { - item: id.ok_or_else(|| serde::de::Error::missing_field("id"))?, - count, - nbt: tag.ok_or_else(|| serde::de::Error::missing_field("tag"))?, - }) + let item = id.ok_or_else(|| serde::de::Error::missing_field("id"))?; + Ok(ItemStack { + item, + count, + components: ComponentMap::with_changes( + Reg::into_inner(item).settings().components(), + components + .ok_or_else(|| serde::de::Error::missing_field("components"))?, + ), + }) + } } - } - deserializer.deserialize_struct( - "ItemStack", - &["id", "Count", "tag"], - Visitor { - _marker: PhantomData, - }, - ) + deserializer.deserialize_struct( + "ItemStack", + &["id", "count", "components"], + Visitor { + _marker: PhantomData, + }, + ) + } } } diff --git a/crates/core/registry/src/lib.rs b/crates/core/registry/src/lib.rs index 0693c009..c3567199 100644 --- a/crates/core/registry/src/lib.rs +++ b/crates/core/registry/src/lib.rs @@ -581,12 +581,8 @@ mod serde { where S: serde::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) - } + let entry: &RefEntry<_, _> = self.as_ref(); + entry.serialize(serializer) } } @@ -599,17 +595,10 @@ mod serde { where D: serde::Deserializer<'de>, { - if deserializer.is_human_readable() { - let key = K::deserialize(deserializer)?; - T::registry() - .get(&key) - .ok_or_else(|| serde::de::Error::custom("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 {} not found", raw))) - } + let key = K::deserialize(deserializer)?; + T::registry() + .get(&key) + .ok_or_else(|| serde::de::Error::custom("key not found")) } } } diff --git a/crates/core/world/src/chunk/world_chunk.rs b/crates/core/world/src/chunk/world_chunk.rs index dd79585c..0e48e0d4 100644 --- a/crates/core/world/src/chunk/world_chunk.rs +++ b/crates/core/world/src/chunk/world_chunk.rs @@ -3,7 +3,7 @@ use parking_lot::RwLock; use rimecraft_block::BlockState; use rimecraft_block_entity::{ - deser_nbt::CreateFromNbt, BlockEntity, ProvideBlockEntity, RawBlockEntityTypeDyn, + component::RawErasedComponentType, BlockEntity, DynRawBlockEntityType, ProvideBlockEntity, }; use rimecraft_chunk_palette::{Maybe, SimpleOwned}; use rimecraft_fluid::{BsToFs, FluidState}; @@ -83,7 +83,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { @@ -164,15 +165,13 @@ where nbt: Cx::Compound, ) -> Option> { let be = DeserializeSeed::deserialize( - CreateFromNbt { + rimecraft_block_entity::serde::Seed { pos, state: self.peek_block_state_lf(pos, BlockState::clone).unwrap(), - respect_dummy: true, }, Cx::compound_to_deserializer(&nbt), ) - .ok() - .flatten(); + .ok(); if let Some(be) = be { self.add_block_entity(be); @@ -195,15 +194,13 @@ where nbt: Cx::Compound, ) -> Option> { let be = DeserializeSeed::deserialize( - CreateFromNbt { + rimecraft_block_entity::serde::Seed { pos, state: self.peek_block_state(pos, BlockState::clone).unwrap(), - respect_dummy: true, }, Cx::compound_to_deserializer(&nbt), ) - .ok() - .flatten(); + .ok(); if let Some(be) = be { self.add_block_entity_locked(be); @@ -262,7 +259,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { @@ -320,7 +318,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { @@ -378,7 +377,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { @@ -496,7 +496,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { @@ -532,7 +533,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { @@ -546,7 +548,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { @@ -557,7 +560,8 @@ where Cx: ChunkCx<'w> + ComputeIndex> + BsToFs<'w> - + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + + ProvideRegistry<'w, Cx::Id, DynRawBlockEntityType<'w, Cx>> + + ProvideRegistry<'w, Cx::Id, RawErasedComponentType<'w, Cx>>, Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, Cx::Id: for<'de> Deserialize<'de>, { diff --git a/crates/util/serde-update/src/erased.rs b/crates/util/serde-update/src/erased.rs index c9c16be2..317bfa07 100644 --- a/crates/util/serde-update/src/erased.rs +++ b/crates/util/serde-update/src/erased.rs @@ -4,6 +4,7 @@ use crate::*; +// Part of the public API. #[doc(hidden)] #[macro_export] macro_rules! __internal_update_from_erased {