diff --git a/.gitignore b/.gitignore index a00f1eb..7ffeb5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ */target /target -/.tuffous/config_gui.json /.vscode /.idea /.VSCodeCounter diff --git a/crates/core/block-entity/Cargo.toml b/crates/core/block-entity/Cargo.toml new file mode 100644 index 0000000..e2ded30 --- /dev/null +++ b/crates/core/block-entity/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "rimecraft-block-entity" +version = "0.1.0" +edition = "2021" +authors = ["JieningYu "] +description = "Minecraft block entity primitives" +repository = "https://github.com/rimecraft-rs/rimecraft/" +license = "AGPL-3.0-or-later" +categories = [] + +[badges] +maintenance = { status = "passively-maintained" } + +[dependencies] +rimecraft-global-cx = { path = "../global-cx" } +rimecraft-registry = { path = "../../util/registry" } +rimecraft-block = { path = "../block" } +rimecraft-voxel-math = { path = "../../util/voxel-math" } +rimecraft-downcast = { path = "../../util/downcast" } +ahash = "0.8" +serde = "1.0" +erased-serde = "0.4" +rimecraft-serde-update = { path = "../../util/serde-update", features = ["erased"] } + +[features] + +[lints] +workspace = true diff --git a/crates/core/block-entity/src/deser_nbt.rs b/crates/core/block-entity/src/deser_nbt.rs new file mode 100644 index 0000000..4a3feaa --- /dev/null +++ b/crates/core/block-entity/src/deser_nbt.rs @@ -0,0 +1,229 @@ +//! 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 new file mode 100644 index 0000000..0bd8491 --- /dev/null +++ b/crates/core/block-entity/src/lib.rs @@ -0,0 +1,253 @@ +//! Rimecraft block entity primitives. + +use std::{any::TypeId, fmt::Debug}; + +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_voxel_math::BlockPos; +use serde::{Deserializer, Serialize}; + +pub use rimecraft_downcast::ToStatic; + +pub mod deser_nbt; + +/// A type of [`BlockEntity`]. +pub trait RawBlockEntityType: Debug +where + Cx: ProvideBlockStateExtTy, +{ + /// Whether the block entity supports the given state. + fn supports(&self, state: &BlockState<'_, Cx>) -> bool; + + /// Creates a new instance of the block entity. + fn instantiate<'w>( + &self, + pos: BlockPos, + state: BlockState<'w, Cx>, + ) -> Option>>; +} + +/// A type of [`BlockEntity`] that can be used in a type erased context. +pub type RawBlockEntityTypeDyn<'r, Cx> = Box + Send + Sync + 'r>; + +/// A type of [`BlockEntity`]. +pub type BlockEntityType<'r, Cx> = Reg<'r, ::Id, RawBlockEntityTypeDyn<'r, Cx>>; + +/// An object holding extra data about a block in a world. +pub struct RawBlockEntity<'a, T: ?Sized, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + ty: BlockEntityType<'a, Cx>, + pos: BlockPos, + removed: bool, + cached_state: BlockState<'a, Cx>, + + data: T, +} + +impl<'a, T, Cx> RawBlockEntity<'a, T, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + /// Creates a new block entity. + pub fn new( + ty: BlockEntityType<'a, Cx>, + pos: BlockPos, + state: BlockState<'a, Cx>, + data: T, + ) -> Self { + Self { + ty, + pos, + removed: false, + cached_state: state, + data, + } + } +} + +impl RawBlockEntity<'_, T, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + /// Gets the immutable inner data of this block entity. + #[inline] + pub fn data(&self) -> &T { + &self.data + } + + /// Gets the mutable inner data of this block entity. + #[inline] + pub fn data_mut(&mut self) -> &mut T { + &mut self.data + } + + /// Gets the position of this block entity. + #[inline] + pub fn pos(&self) -> BlockPos { + self.pos + } + + /// Marks this block entity as not removed. + #[inline] + pub fn cancel_removal(&mut self) { + self.removed = false; + } + + /// Marks this block entity as removed. + #[inline] + pub fn mark_removed(&mut self) { + self.removed = true; + } + + /// Whether this block entity is marked as removed. + #[inline] + pub fn is_removed(&self) -> bool { + self.removed + } +} + +impl Debug for RawBlockEntity<'_, T, Cx> +where + Cx: ProvideBlockStateExtTy + Debug, + Cx::BlockStateExt: Debug, + Cx::Id: Debug, + T: Debug + ?Sized, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BlockEntity") + .field("type", &<&RefEntry<_, _>>::from(self.ty).key().value()) + .field("pos", &self.pos) + .field("removed", &self.removed) + .field("cached_state", &self.cached_state) + .field("data", &&self.data) + .finish() + } +} + +/// Type erased block entity data. +pub trait ErasedData +where + Self: ErasedSerialize + for<'de> ErasedUpdate<'de> + Send + Sync + Debug + sealed::Sealed, +{ + /// The [`TypeId`] of this data. + fn type_id(&self) -> TypeId; +} + +serialize_trait_object! { ErasedData } +update_trait_object! { ErasedData } + +mod sealed { + pub trait Sealed {} +} + +impl sealed::Sealed for T where T: ErasedSerialize + for<'de> ErasedUpdate<'de> + Send + Sync {} + +impl ErasedData for T +where + T: ErasedSerialize + for<'de> ErasedUpdate<'de> + ToStatic + Debug + Send + Sync, +{ + #[inline] + fn type_id(&self) -> TypeId { + TypeId::of::<::StaticRepr>() + } +} + +/// A type-erased variant of [`RawBlockEntity`]. +pub type BlockEntity<'w, Cx> = RawBlockEntity<'w, dyn ErasedData + 'w, Cx>; + +impl<'w, Cx> BlockEntity<'w, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + /// 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>> { + if self.matches_type::() { + unsafe { + Some(&*(self as *const BlockEntity<'w, Cx> as *const RawBlockEntity<'w, T, Cx>)) + } + } else { + None + } + } + + /// 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>> { + if self.matches_type::() { + unsafe { + Some(&mut *(self as *mut BlockEntity<'w, Cx> as *mut RawBlockEntity<'w, T, Cx>)) + } + } else { + None + } + } + + /// Whether the type of data in this block entity can be safely downcasted + /// 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) + } +} + +/// A trait for providing block entities. +/// +/// This should be implemented for [`ProvideBlockStateExtTy::BlockStateExt`]s. +pub trait ProvideBlockEntity<'w, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + /// Whether this block has a block entity. + #[inline] + fn has_block_entity(&self) -> bool { + self.block_entity_constructor().is_some() + } + + /// Gets the block entity constructor of this block. + fn block_entity_constructor<'s>( + &'s self, + ) -> Option Box> + 's>; +} diff --git a/crates/core/block/src/behave.rs b/crates/core/block/src/behave.rs new file mode 100644 index 0000000..663e30d --- /dev/null +++ b/crates/core/block/src/behave.rs @@ -0,0 +1,10 @@ +//! Common behaviors of block state extensions. + +use rimecraft_state::State; + +/// Block state extensions that could returns a luminance value from +/// the given state of the block. +pub trait ProvideLuminance: Sized { + /// The luminance. + fn luminance(&self, state: &State<'_, Self>) -> u32; +} diff --git a/crates/core/block/src/lib.rs b/crates/core/block/src/lib.rs index b2aa766..c16af43 100644 --- a/crates/core/block/src/lib.rs +++ b/crates/core/block/src/lib.rs @@ -1,10 +1,13 @@ //! Minecraft block primitives. +use behave::ProvideLuminance; use rimecraft_global_cx::{GlobalContext, ProvideIdTy}; use rimecraft_registry::{ProvideRegistry, Reg}; -use rimecraft_state::{States, StatesMut}; +use rimecraft_state::{State, States, StatesMut}; -use std::marker::PhantomData; +use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc}; + +pub mod behave; pub use rimecraft_state as state; @@ -104,7 +107,82 @@ pub trait ProvideStateIds: GlobalContext { } /// Global contexts providing block state extensions. -pub trait ProvideBlockStateExtTy: GlobalContext { +pub trait ProvideBlockStateExtTy: ProvideIdTy { /// The type of the block state extension. type BlockStateExt; } + +/// The `BlockState` type. +/// +/// This contains the block registration and the [`State`]. +pub struct BlockState<'w, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + /// The block. + pub block: Block<'w, Cx>, + + /// The state. + pub state: Arc>, +} + +impl BlockState<'_, Cx> +where + Cx: ProvideBlockStateExtTy, + Cx::BlockStateExt: ProvideLuminance, +{ + /// Returns the luminance level of this block state. + #[inline] + pub fn luminance(&self) -> u32 { + self.state.data().luminance(&self.state) + } +} + +impl Debug for BlockState<'_, Cx> +where + Cx: ProvideBlockStateExtTy + Debug, + Cx::Id: Debug, + Cx::BlockStateExt: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IBlockState") + .field("block", &self.block) + .field("state", &self.state) + .finish() + } +} + +impl Clone for BlockState<'_, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + #[inline] + fn clone(&self) -> Self { + Self { + block: self.block, + state: self.state.clone(), + } + } +} + +impl PartialEq for BlockState<'_, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.block == other.block && Arc::ptr_eq(&self.state, &other.state) + } +} + +impl Eq for BlockState<'_, Cx> where Cx: ProvideBlockStateExtTy {} + +impl Hash for BlockState<'_, Cx> +where + Cx: ProvideBlockStateExtTy, +{ + fn hash(&self, state: &mut H) { + self.block.hash(state); + Arc::as_ptr(&self.state).hash(state); + } +} diff --git a/crates/core/fluid/Cargo.toml b/crates/core/fluid/Cargo.toml index f97028f..32e3373 100644 --- a/crates/core/fluid/Cargo.toml +++ b/crates/core/fluid/Cargo.toml @@ -15,6 +15,8 @@ maintenance = { status = "passively-maintained" } rimecraft-global-cx = { path = "../global-cx" } rimecraft-registry = { path = "../../util/registry" } rimecraft-state = { path = "../state" } +rimecraft-maybe = { path = "../../util/maybe" } +rimecraft-block = { path = "../block" } [features] diff --git a/crates/core/fluid/src/lib.rs b/crates/core/fluid/src/lib.rs index 9357d56..9718ea1 100644 --- a/crates/core/fluid/src/lib.rs +++ b/crates/core/fluid/src/lib.rs @@ -1,10 +1,12 @@ -//! Minecraft Block primitives. +//! Minecraft Fluid primitives. -use rimecraft_global_cx::{GlobalContext, ProvideIdTy}; +use rimecraft_block::{BlockState, ProvideBlockStateExtTy}; +use rimecraft_global_cx::ProvideIdTy; +use rimecraft_maybe::Maybe; use rimecraft_registry::{ProvideRegistry, Reg}; -use rimecraft_state::States; +use rimecraft_state::{State, States}; -use std::marker::PhantomData; +use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc}; pub use rimecraft_state as state; @@ -69,7 +71,110 @@ pub struct Settings { pub type Fluid<'a, Cx> = Reg<'a, ::Id, RawFluid<'a, Cx>>; /// Global contexts providing fluid state extensions. -pub trait ProvideFluidStateExtTy: GlobalContext { +pub trait ProvideFluidStateExtTy: ProvideIdTy { /// The type of the fluid state extension. type FluidStateExt; } + +/// The `FluidState` type. +/// +/// This contains the fluid registration and the [`State`]. +pub struct FluidState<'w, Cx> +where + Cx: ProvideFluidStateExtTy, +{ + /// The fluid. + pub fluid: Fluid<'w, Cx>, + /// The state. + pub state: Arc>, +} + +impl Debug for FluidState<'_, Cx> +where + Cx: ProvideFluidStateExtTy + Debug, + Cx::Id: Debug, + Cx::FluidStateExt: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IFluidState") + .field("fluid", &self.fluid) + .field("state", &self.state) + .finish() + } +} + +impl Clone for FluidState<'_, Cx> +where + Cx: ProvideFluidStateExtTy, +{ + #[inline] + fn clone(&self) -> Self { + Self { + fluid: self.fluid, + state: self.state.clone(), + } + } +} + +impl PartialEq for FluidState<'_, Cx> +where + Cx: ProvideFluidStateExtTy, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.fluid == other.fluid && Arc::ptr_eq(&self.state, &other.state) + } +} + +impl Eq for FluidState<'_, Cx> where Cx: ProvideFluidStateExtTy {} + +impl Hash for FluidState<'_, Cx> +where + Cx: ProvideFluidStateExtTy, +{ + #[inline] + fn hash(&self, state: &mut H) { + self.fluid.hash(state); + Arc::as_ptr(&self.state).hash(state); + } +} + +/// Global Contexts that is able to convert [`BlockState`] to [`FluidState`] instances. +pub trait BsToFs<'w> +where + Self: ProvideFluidStateExtTy + ProvideBlockStateExtTy, +{ + /// Converts a block state to a fluid state. + fn block_to_fluid_state<'a>( + bs: Maybe<'a, BlockState<'w, Self>>, + ) -> Maybe<'a, FluidState<'w, Self>>; +} + +/// Extenstions to the `Maybe<'_, IBlockState<'_, _>>`. +pub trait BlockStateExt<'a, 'w, Cx> +where + Cx: ProvideFluidStateExtTy, +{ + /// Converts this block state to fluid state. + fn to_fluid_state(self) -> Maybe<'a, FluidState<'w, Cx>>; +} + +impl<'a, 'w, Cx> BlockStateExt<'a, 'w, Cx> for Maybe<'a, BlockState<'w, Cx>> +where + Cx: BsToFs<'w>, +{ + #[inline] + fn to_fluid_state(self) -> Maybe<'a, FluidState<'w, Cx>> { + Cx::block_to_fluid_state(self) + } +} + +impl<'a, 'w, Cx> BlockStateExt<'a, 'w, Cx> for &'a BlockState<'w, Cx> +where + Cx: BsToFs<'w>, +{ + #[inline] + fn to_fluid_state(self) -> Maybe<'a, FluidState<'w, Cx>> { + Cx::block_to_fluid_state(Maybe::Borrowed(self)) + } +} diff --git a/crates/core/global-cx/Cargo.toml b/crates/core/global-cx/Cargo.toml index fbc1788..0a96a41 100644 --- a/crates/core/global-cx/Cargo.toml +++ b/crates/core/global-cx/Cargo.toml @@ -12,10 +12,13 @@ categories = [] maintenance = { status = "passively-maintained" } [dependencies] +serde = { version = "1.0", default-features = false, optional = true } [features] default = ["std"] std = [] +serde = ["dep:serde"] +nbt = ["serde"] [lints] workspace = true diff --git a/crates/core/global-cx/src/lib.rs b/crates/core/global-cx/src/lib.rs index bbffd98..6064e08 100644 --- a/crates/core/global-cx/src/lib.rs +++ b/crates/core/global-cx/src/lib.rs @@ -8,27 +8,39 @@ #![no_std] +extern crate alloc; + #[cfg(feature = "std")] extern crate std; +use core::{fmt::Display, hash::Hash}; + +use alloc::boxed::Box; +use serde::Deserializer; + /// Marker trait for global contexts. pub trait GlobalContext: Sized + 'static {} /// Marker trait for global contexts that provide an identifier type. pub trait ProvideIdTy: GlobalContext { /// Identifier type. - type Id; + type Id: Display + Hash + Eq; } /// Marker trait for global contexts that provide a `NbtCompound` type and friends. +#[cfg(feature = "nbt")] pub trait ProvideNbtTy: GlobalContext { /// NBT compound type. type Compound; /// [`i32`] array type. - type IntArray; + type IntArray: Into> + From>; + /// [`i64`] array type. - type LongArray; + type LongArray: Into> + From>; + + /// Function that converts a `Compound` to a [`Deserializer`]. + fn compound_to_deserializer(compound: &Self::Compound) -> impl Deserializer<'_>; } #[cfg(feature = "std")] diff --git a/crates/core/item/Cargo.toml b/crates/core/item/Cargo.toml index 58bced8..8c85a90 100644 --- a/crates/core/item/Cargo.toml +++ b/crates/core/item/Cargo.toml @@ -15,7 +15,7 @@ categories = [] maintenance = { status = "passively-maintained" } [dependencies] -rimecraft-global-cx = { path = "../global-cx" } +rimecraft-global-cx = { path = "../global-cx", features = ["nbt"] } rimecraft-registry = { path = "../../util/registry" } rimecraft-fmt = { path = "../../util/fmt" } serde = { version = "1.0", optional = true } diff --git a/crates/core/item/src/stack.rs b/crates/core/item/src/stack.rs index ced8d56..de0a6fd 100644 --- a/crates/core/item/src/stack.rs +++ b/crates/core/item/src/stack.rs @@ -310,25 +310,35 @@ mod _serde { 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, + } + while let Some(key) = map.next_key()? { match key { - "id" => { + 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()); } - "Count" => { + Field::Count => { count = map.next_value::()?; } - "tag" => { + Field::Tag => { if tag.is_some() { return Err(serde::de::Error::duplicate_field("tag")); } tag = Some(map.next_value()?); } - _ => {} } } diff --git a/crates/core/palette/src/container.rs b/crates/core/palette/src/container.rs index 2c968f4..b622582 100644 --- a/crates/core/palette/src/container.rs +++ b/crates/core/palette/src/container.rs @@ -1,7 +1,8 @@ //! Paletted containers. -use std::{collections::HashMap, hash::Hash, marker::PhantomData}; +use std::{hash::Hash, marker::PhantomData}; +use ahash::AHashMap; use rimecraft_maybe::Maybe; use rimecraft_packed_int_array::PackedIntArray; @@ -133,9 +134,9 @@ where .then(|| self.data.palette.get(0)) .flatten() { - counter(&val, self.data.storage.len()); + counter(&*val, self.data.storage.len()); } else { - let mut map = HashMap::new(); + let mut map = AHashMap::new(); if let Some(array) = self.data.storage.as_array() { array.iter().for_each(|i| { if let Some(val) = map.get_mut(&i) { @@ -151,7 +152,7 @@ where .into_iter() .filter_map(|(i, c)| self.data.palette.get(i as usize).map(|i| (i, c))) { - counter(&obj, c); + counter(&*obj, c); } } } @@ -285,7 +286,7 @@ where .as_array() .and_then(|array| array.get(i)) .and_then(|i| palette.get(i as usize)) - .and_then(|obj| self.palette.index(&obj)) + .and_then(|obj| self.palette.index(&*obj)) { self.storage.as_array_mut().unwrap().swap(i, raw as u32); } diff --git a/crates/core/palette/src/lib.rs b/crates/core/palette/src/lib.rs index ff90e14..6faeda8 100644 --- a/crates/core/palette/src/lib.rs +++ b/crates/core/palette/src/lib.rs @@ -9,7 +9,7 @@ use ahash::AHashMap; pub use iter::Iter; use iter::IterImpl; -pub use rimecraft_maybe::Maybe; +pub use rimecraft_maybe::{Maybe, SimpleOwned}; /// A palette maps object from and to small integer IDs that uses less number of bits /// to make storage smaller. @@ -448,5 +448,3 @@ impl std::fmt::Display for Error { } impl std::error::Error for Error {} - -//TODO: tests diff --git a/crates/core/state/src/lib.rs b/crates/core/state/src/lib.rs index 9d50ca7..c7941b8 100644 --- a/crates/core/state/src/lib.rs +++ b/crates/core/state/src/lib.rs @@ -391,10 +391,9 @@ impl Display for Error { impl std::error::Error for Error {} +/// Serde support for `State`s. #[cfg(feature = "serde")] pub mod serde { - //! Serde support for state. - use std::sync::Arc; use rimecraft_serde_update::Update; diff --git a/crates/core/world/Cargo.toml b/crates/core/world/Cargo.toml index 3bd963a..881a78e 100644 --- a/crates/core/world/Cargo.toml +++ b/crates/core/world/Cargo.toml @@ -12,18 +12,23 @@ categories = [] maintenance = { status = "passively-maintained" } [dependencies] -rimecraft-global-cx = { path = "../global-cx" } +# Rimecraft crates +rimecraft-global-cx = { path = "../global-cx", features = ["nbt"] } rimecraft-chunk-palette = { path = "../palette" } rimecraft-registry = { path = "../../util/registry", features = ["serde"] } rimecraft-state = { path = "../state" } rimecraft-block = { path = "../block" } rimecraft-fluid = { path = "../fluid" } +rimecraft-block-entity = { path = "../block-entity" } rimecraft-voxel-math = { path = "../../util/voxel-math" } +rimecraft-packed-int-array = { path = "../../util/packed-int-array" } +# External utils serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1" rimecraft-edcode = { path = "../../util/edcode", optional = true } parking_lot = "0.12" fastnbt = "2.5" +ahash = "0.8" [features] default = ["edcode"] diff --git a/crates/core/world/src/chunk.rs b/crates/core/world/src/chunk.rs index b3b40a2..f12e427 100644 --- a/crates/core/world/src/chunk.rs +++ b/crates/core/world/src/chunk.rs @@ -1,110 +1,296 @@ -//! Types and traits for working with chunks of blocks in a world. +//! Types and traits for working with chunks in a world. +//! +//! A chunk represents a scoped, mutable view of `Biome`s, [`BlockState`]s, [`FluidState`]s and [`BlockEntity`]s. + +use std::{fmt::Debug, hash::Hash}; + +use ahash::AHashMap; +use parking_lot::{Mutex, RwLock}; +use rimecraft_block::{BlockState, ProvideBlockStateExtTy, ProvideStateIds, RawBlock}; +use rimecraft_chunk_palette::{ + container::ProvidePalette, IndexFromRaw as PalIndexFromRaw, IndexToRaw as PalIndexToRaw, Maybe, +}; +use rimecraft_fluid::ProvideFluidStateExtTy; +use rimecraft_global_cx::{ProvideIdTy, ProvideNbtTy}; +use rimecraft_registry::{ProvideRegistry, Registry}; +use rimecraft_voxel_math::BlockPos; + +use crate::{ + heightmap::{self, Heightmap}, + view::{ + block::{BlockLuminanceView, BlockView, LockedBlockViewMut}, + HeightLimit, + }, + BlockEntityCell, Sealed, +}; mod internal_types; + +mod be_tick; +pub mod light; mod section; mod upgrade; -use std::fmt::Debug; +pub mod world_chunk; -pub use internal_types::*; -use rimecraft_block::ProvideBlockStateExtTy; -use rimecraft_fluid::ProvideFluidStateExtTy; -use rimecraft_global_cx::ProvideIdTy; pub use rimecraft_voxel_math::ChunkPos; + pub use section::ChunkSection; pub use upgrade::UpgradeData; +pub use world_chunk::WorldChunk; + +pub use internal_types::*; -use crate::view::HeightLimit; +/// The length of the border of a chunk. +pub const BORDER_LEN: u32 = 16; /// Types associated with a `Chunk`. /// /// # Generics /// /// - `'w`: The world lifetime. See the crate document for more information. -pub trait ChunkTy<'w>: ProvideBlockStateExtTy + ProvideFluidStateExtTy + ProvideIdTy { +pub trait ChunkCx<'w> +where + Self: ProvideBlockStateExtTy + ProvideFluidStateExtTy + ProvideIdTy + ProvideNbtTy, +{ /// The type of block state id list. - type BlockStateList; + type BlockStateList: for<'s> PalIndexFromRaw<'s, Maybe<'s, BlockState<'w, Self>>> + + for<'a> PalIndexToRaw<&'a BlockState<'w, Self>> + + Clone; /// The type of biomes. type Biome: 'w; /// The type of biome id list. type BiomeList; + + /// The `Heightmap.Type` type of heightmaps. + type HeightmapType: heightmap::Type<'w, Self> + Hash + Eq; } -/// A scoped, mutable view of biomes, block states, fluid states and -/// block entities. -/// -/// # Generics -/// -/// - `'w`: The world lifetime. See the crate document for more information. -/// - `T`: The chunk implementation data type. It provides functionalities like `WorldChunk` and `ProtoChunk`. -/// - `K`: The `Identifier` type. -/// - `Cx`: The global context type, providing access to the static fields and logics of the game. -pub struct Chunk<'w, T, Cx> +/// A generic chunk data structure. +#[non_exhaustive] +pub struct BaseChunk<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w>, { - pos: ChunkPos, - udata: UpgradeData<'w, Cx>, - hlimit: HeightLimit, - section_array: Option>>, + /// Position of this chunk. + pub pos: ChunkPos, + /// Upgrade data of this chunk. + pub upgrade_data: UpgradeData<'w, Cx>, + /// Heightmaps of this chunk. + pub heightmaps: RwLock>>, + /// Height limit of this chunk. + pub height_limit: HeightLimit, + /// Map of block positions to block entities. + pub block_entities: RwLock>>, + /// Map of block positions to block entities. + pub block_entity_nbts: Mutex>, + /// The internal chunk sections. + pub section_array: Box<[RwLock>]>, + /// Increases for each tick a player spends with the chunk loaded. + /// This is a cumulative measure of time. + pub inhabited_time: u64, + /// Whether this chunk needs saving. + pub needs_saving: bool, +} - vdata: T, +impl<'w, Cx> Debug for BaseChunk<'w, Cx> +where + Cx: ChunkCx<'w> + Debug, + Cx::Id: Debug, + Cx::BlockStateExt: Debug, + Cx::BlockStateList: Debug, + Cx::FluidStateExt: Debug, + Cx::Biome: Debug, + Cx::BiomeList: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Chunk") + .field("pos", &self.pos) + .field("upgrade_data", &self.upgrade_data) + .field("height_limit", &self.height_limit) + .field("section_array", &self.section_array) + .field("inhabited_time", &self.inhabited_time) + .field("needs_saving", &self.needs_saving) + .finish_non_exhaustive() + } } -impl<'w, T, Cx> Chunk<'w, T, Cx> +impl<'w, Cx> BaseChunk<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w> + + ProvideStateIds + + ProvidePalette> + + ProvidePalette> + + ProvideRegistry<'w, Cx::Id, RawBlock<'w, Cx>>, + + Cx::BlockStateList: for<'a> PalIndexToRaw<&'a IBlockState<'w, Cx>> + + for<'s> PalIndexFromRaw<'s, Maybe<'s, IBlockState<'w, Cx>>> + + Clone, + + &'w Registry: Into, + Cx::BiomeList: for<'a> PalIndexToRaw<&'a IBiome<'w, Cx>> + + for<'s> PalIndexFromRaw<'s, Maybe<'s, IBiome<'w, Cx>>> + + Clone, { /// Creates a new chunk from scratch. /// /// # Panics /// - /// This method panics if the length of the section array does not match the - /// vertical section count of the height limit. See [`HeightLimit::count_vertical_sections`]. - pub fn new( + /// Panics if the given section array length is not the vertical count of + /// chunk sections of given `height_limit`. + /// + /// See [`HeightLimit::count_vertical_sections`]. + pub fn new( pos: ChunkPos, upgrade_data: UpgradeData<'w, Cx>, height_limit: HeightLimit, - section_array: Option>>, - vdata: T, - ) -> Self { - if let Some(ref array) = section_array { - assert_eq! { - array.len() as i32, - height_limit.count_vertical_sections(), - "the section array must have the same length as the vertical section count of the height limit" - } - } - + biome_registry: &'w Registry, + inhabited_time: u64, + section_array: Option, + ) -> Self + where + I: Iterator>> + ExactSizeIterator, + { Self { pos, - udata: upgrade_data, - hlimit: height_limit, - section_array, - vdata, + needs_saving: false, + inhabited_time, + upgrade_data, + height_limit, + heightmaps: RwLock::new(AHashMap::new()), + block_entities: RwLock::new(AHashMap::new()), + block_entity_nbts: Mutex::new(AHashMap::new()), + section_array: { + let len = height_limit.count_vertical_sections() as usize; + if let Some(section_array) = section_array { + assert_eq!(section_array.len(), len, "length of given section array should be the count of vertical sections of the chunk"); + section_array + .map(|opt| { + RwLock::new(opt.unwrap_or_else(|| ChunkSection::from(biome_registry))) + }) + .collect() + } else { + (0..len) + .map(|_| RwLock::new(ChunkSection::from(biome_registry))) + .collect() + } + }, } } } -impl<'w, T, Cx> Debug for Chunk<'w, T, Cx> +/// Types that can represent an immutable [`BaseChunk`]. +pub trait AsBaseChunk<'w, Cx> where - T: Debug, - Cx: ChunkTy<'w> + Debug, - Cx::Id: Debug, - Cx::BlockStateExt: Debug, - Cx::BlockStateList: Debug, - Cx::FluidStateExt: Debug, - Cx::Biome: Debug, - Cx::BiomeList: Debug, + Cx: ChunkCx<'w>, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Chunk") - .field("pos", &self.pos) - .field("udata", &self.udata) - .field("hlimit", &self.hlimit) - .field("section_array", &self.section_array) - .field("vdata", &self.vdata) - .finish() + /// Returns a [`BaseChunk`]. + fn as_base_chunk(&self) -> Sealed<&BaseChunk<'w, Cx>>; +} + +/// Types that can represent a mutable [`BaseChunk`]. +pub trait AsBaseChunkMut<'w, Cx>: AsBaseChunk<'w, Cx> +where + Cx: ChunkCx<'w>, +{ + /// Returns a [`BaseChunk`]. + fn as_base_chunk_mut(&mut self) -> Sealed<&mut BaseChunk<'w, Cx>>; +} + +/// Immutable chunk behaviors. +pub trait Chunk<'w, Cx> +where + Self: AsBaseChunk<'w, Cx> + BlockView<'w, Cx> + BlockLuminanceView<'w, Cx>, + Cx: ChunkCx<'w>, +{ + /// Returns the array of chunk sections of this chunk. + #[inline] + fn sections(&self) -> &[RwLock>] { + &self.as_base_chunk().0.section_array + } + + /// Gets the [`ChunkSection`] at the given Y index of this chunk. + #[inline] + fn section(&self, index: usize) -> Option<&RwLock>> { + self.sections().get(index) + } + + /// Returns the [`HeightLimit`] of this chunk. + #[inline] + fn height_limit(&self) -> HeightLimit { + self.as_base_chunk().0.height_limit + } + + /// Returns the index of highest non-empty [`ChunkSection`] in this chunk. + /// + /// See [`ChunkSection::is_empty`]. + fn highest_non_empty_section(&self) -> Option { + self.sections().iter().rposition(|s| !s.read().is_empty()) + } + + /// Peeks the heightmaps of this chunk. + #[inline] + fn peek_heightmaps(&self, pk: F) -> T + where + F: for<'a> FnOnce(&'a AHashMap>) -> T, + { + let rg = self.as_base_chunk().0.heightmaps.read(); + pk(&rg) + } + + /// Peeks the heightmaps of this chunk. + #[inline] + fn peek_heightmaps_mut_locked(&self, pk: F) -> T + where + F: for<'a> FnOnce(&'a mut AHashMap>) -> T, + { + let mut rg = self.as_base_chunk().0.heightmaps.write(); + pk(&mut rg) + } + + /// Returns the position of this chunk. + #[inline] + fn pos(&self) -> ChunkPos { + self.as_base_chunk().0.pos + } +} + +/// Mutable chunk behaviors. +pub trait ChunkMut<'w, Cx> +where + Self: AsBaseChunkMut<'w, Cx> + Chunk<'w, Cx> + LockedBlockViewMut<'w, Cx>, + Cx: ChunkCx<'w>, +{ + /// Returns the array of chunk sections of this chunk. + #[inline] + fn sections_mut(&mut self) -> &mut [RwLock>] { + &mut self.as_base_chunk_mut().0.section_array + } + + /// Gets the [`ChunkSection`] at the given Y index of this chunk. + #[inline] + fn section_mut(&mut self, index: usize) -> Option<&mut RwLock>> { + self.sections_mut().get_mut(index) + } + + /// Peeks the heightmaps of this chunk. + #[inline] + fn peek_heightmaps_mut(&mut self, pk: F) -> T + where + F: for<'a> FnOnce(&'a mut AHashMap>) -> T, + { + pk(self.as_base_chunk_mut().0.heightmaps.get_mut()) + } + + /// Returns the index of highest non-empty [`ChunkSection`] in this chunk. + /// + /// This method is the same as [`Chunk::highest_non_empty_section`] but lock-free. + /// + /// See [`ChunkSection::is_empty`]. + fn highest_non_empty_section_lf(&mut self) -> Option { + self.sections_mut() + .iter_mut() + .rposition(|s| !s.get_mut().is_empty()) } } diff --git a/crates/core/world/src/chunk/be_tick.rs b/crates/core/world/src/chunk/be_tick.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/core/world/src/chunk/be_tick.rs @@ -0,0 +1 @@ + diff --git a/crates/core/world/src/chunk/internal_types.rs b/crates/core/world/src/chunk/internal_types.rs index fff20b9..45b735b 100644 --- a/crates/core/world/src/chunk/internal_types.rs +++ b/crates/core/world/src/chunk/internal_types.rs @@ -1,138 +1,10 @@ -use std::{fmt::Debug, hash::Hash, sync::Arc}; - -use rimecraft_block::Block; -use rimecraft_fluid::Fluid; use rimecraft_global_cx::ProvideIdTy; use rimecraft_registry::Reg; -use rimecraft_state::State; -use super::ChunkTy; +use super::ChunkCx; /// The internal-used `Biome` type. -pub type IBiome<'w, Cx> = Reg<'w, ::Id, >::Biome>; - -/// The internal-used `BlockState` type. -/// -/// This contains the block registration and the [`State`]. -pub struct IBlockState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - /// The block. - pub block: Block<'w, Cx>, - /// The state. - pub state: Arc>, -} - -impl<'w, Cx> Debug for IBlockState<'w, Cx> -where - Cx: ChunkTy<'w> + Debug, - Cx::Id: Debug, - Cx::BlockStateExt: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("IBlockState") - .field("block", &self.block) - .field("state", &self.state) - .finish() - } -} - -impl<'w, Cx> Clone for IBlockState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - #[inline] - fn clone(&self) -> Self { - Self { - block: self.block, - state: self.state.clone(), - } - } -} - -impl<'w, Cx> PartialEq for IBlockState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - #[inline] - fn eq(&self, other: &Self) -> bool { - self.block == other.block && Arc::ptr_eq(&self.state, &other.state) - } -} - -impl<'w, Cx> Eq for IBlockState<'w, Cx> where Cx: ChunkTy<'w> {} - -impl<'w, Cx> Hash for IBlockState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - #[inline] - fn hash(&self, state: &mut H) { - self.block.hash(state); - Arc::as_ptr(&self.state).hash(state); - } -} - -/// The internal-used `FluidState` type. -/// -/// This contains the fluid registration and the [`State`]. -pub struct IFluidState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - /// The fluid. - pub fluid: Fluid<'w, Cx>, - /// The state. - pub state: Arc>, -} - -impl<'w, Cx> Debug for IFluidState<'w, Cx> -where - Cx: ChunkTy<'w> + Debug, - Cx::Id: Debug, - Cx::FluidStateExt: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("IFluidState") - .field("fluid", &self.fluid) - .field("state", &self.state) - .finish() - } -} - -impl<'w, Cx> Clone for IFluidState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - #[inline] - fn clone(&self) -> Self { - Self { - fluid: self.fluid, - state: self.state.clone(), - } - } -} - -impl<'w, Cx> PartialEq for IFluidState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - #[inline] - fn eq(&self, other: &Self) -> bool { - self.fluid == other.fluid && Arc::ptr_eq(&self.state, &other.state) - } -} - -impl<'w, Cx> Eq for IFluidState<'w, Cx> where Cx: ChunkTy<'w> {} +pub type IBiome<'w, Cx> = Reg<'w, ::Id, >::Biome>; -impl<'w, Cx> Hash for IFluidState<'w, Cx> -where - Cx: ChunkTy<'w>, -{ - #[inline] - fn hash(&self, state: &mut H) { - self.fluid.hash(state); - Arc::as_ptr(&self.state).hash(state); - } -} +pub(crate) use rimecraft_block::BlockState as IBlockState; +pub(crate) use rimecraft_fluid::FluidState as IFluidState; diff --git a/crates/core/world/src/chunk/light.rs b/crates/core/world/src/chunk/light.rs new file mode 100644 index 0000000..198dee7 --- /dev/null +++ b/crates/core/world/src/chunk/light.rs @@ -0,0 +1,27 @@ +//! Chunk lighting related stuffs. + +use rimecraft_packed_int_array::PackedIntArray; + +use crate::view::HeightLimit; + +/// Bytes stores the maximum sky light that reaches each block, +/// regardless of current time. +#[derive(Debug)] +pub struct ChunkSkyLight { + pal: PackedIntArray, + min_y: i32, + // reusable block positions: unneeded in Rust +} + +impl ChunkSkyLight { + /// Creates a new chunk sky light. + #[allow(clippy::missing_panics_doc)] + pub fn new(height: HeightLimit) -> Self { + let min_y = height.bottom() - 1; + let j = usize::BITS - ((height.top() - min_y + 1) as usize).leading_zeros(); + Self { + pal: PackedIntArray::from_packed(j, 256, None).unwrap(), + min_y, + } + } +} diff --git a/crates/core/world/src/chunk/section.rs b/crates/core/world/src/chunk/section.rs index 05d6ef1..fa7f212 100644 --- a/crates/core/world/src/chunk/section.rs +++ b/crates/core/world/src/chunk/section.rs @@ -1,20 +1,21 @@ use std::fmt::Debug; -use rimecraft_block::ProvideStateIds; +use rimecraft_block::{Block, BlockState, ProvideStateIds, RawBlock}; use rimecraft_chunk_palette::{ container::{PalettedContainer, ProvidePalette}, IndexFromRaw as PalIndexFromRaw, IndexToRaw as PalIndexToRaw, Maybe, }; -use rimecraft_registry::Registry; +use rimecraft_fluid::{BlockStateExt as _, BsToFs}; +use rimecraft_registry::{ProvideRegistry, Registry}; -use super::{internal_types::*, ChunkTy}; +use super::{internal_types::*, ChunkCx}; /// Section on a `Chunk`. pub struct ChunkSection<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w>, { - bsc: PalettedContainer, Cx>, + bsc: PalettedContainer, Cx>, bic: PalettedContainer, Cx>, ne_block_c: u16, @@ -24,18 +25,16 @@ where impl<'w, Cx> ChunkSection<'w, Cx> where - Cx: ChunkTy<'w>, - Cx::BlockStateList: for<'s> PalIndexFromRaw<'s, Maybe<'s, IBlockState<'w, Cx>>>, + Cx: BsToFs<'w> + ChunkCx<'w>, + Cx::BlockStateList: for<'s> PalIndexFromRaw<'s, Maybe<'s, BlockState<'w, Cx>>>, for<'a> &'a Cx::BlockStateList: IntoIterator, for<'a> <&'a Cx::BlockStateList as IntoIterator>::IntoIter: ExactSizeIterator, - - for<'a> &'a Cx::BlockStateExt: Into>>, { /// Creates a new chunk section with the given containers. #[inline] pub fn new( - bs_container: PalettedContainer, Cx>, + bs_container: PalettedContainer, Cx>, bi_container: PalettedContainer, Cx>, ) -> Self { let mut this = Self { @@ -55,12 +54,12 @@ where let mut rt_block_c = 0; let mut ne_fluid_c = 0; - self.bsc.count(|IBlockState { block, state }, count| { - let fs: Maybe<'_, _> = state.data().into(); - if !block.settings().is_empty { + self.bsc.count(|bs, count| { + let fs = bs.to_fluid_state(); + if !bs.block.settings().is_empty { ne_block_c += count; } - if block.settings().random_ticks { + if bs.block.settings().random_ticks { rt_block_c += count; } if !fs.fluid.settings().is_empty { @@ -79,11 +78,11 @@ where impl<'w, Cx> ChunkSection<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w>, { /// Returns the block state container of the chunk section. #[inline] - pub fn bs_container(&self) -> &PalettedContainer, Cx> { + pub fn bs_container(&self) -> &PalettedContainer, Cx> { &self.bsc } @@ -91,7 +90,7 @@ where #[inline] pub fn bs_container_mut( &mut self, - ) -> &mut PalettedContainer, Cx> { + ) -> &mut PalettedContainer, Cx> { &mut self.bsc } @@ -136,19 +135,27 @@ where impl<'w, Cx> ChunkSection<'w, Cx> where - Cx: ChunkTy<'w> + ComputeIndex>, - Cx::BlockStateList: for<'s> PalIndexFromRaw<'s, Maybe<'s, IBlockState<'w, Cx>>>, + Cx: ChunkCx<'w> + ComputeIndex>, { /// Returns the block state at the given position. #[inline] - pub fn block_state(&self, x: u32, y: u32, z: u32) -> Option>> { + pub fn block_state(&self, x: u32, y: u32, z: u32) -> Option>> { self.bsc.get(Cx::compute_index(x, y, z)).map(From::from) } + + /// Returns the fluid state at the given position. + #[inline] + pub fn fluid_state(&self, x: u32, y: u32, z: u32) -> Option>> + where + Cx: BsToFs<'w>, + { + self.block_state(x, y, z).map(Cx::block_to_fluid_state) + } } impl<'w, Cx> ChunkSection<'w, Cx> where - Cx: ChunkTy<'w> + ComputeIndex>, + Cx: ChunkCx<'w> + ComputeIndex>, Cx::BiomeList: for<'s> PalIndexFromRaw<'s, Maybe<'s, IBiome<'w, Cx>>>, { /// Returns the biome at the given position. @@ -160,11 +167,10 @@ where impl<'w, Cx> ChunkSection<'w, Cx> where - Cx: ChunkTy<'w> + ComputeIndex>, - Cx::BlockStateList: for<'a> PalIndexToRaw<&'a IBlockState<'w, Cx>> - + for<'s> PalIndexFromRaw<'s, Maybe<'s, IBlockState<'w, Cx>>> + Cx: BsToFs<'w> + ChunkCx<'w> + ComputeIndex>, + Cx::BlockStateList: for<'a> PalIndexToRaw<&'a BlockState<'w, Cx>> + + for<'s> PalIndexFromRaw<'s, Maybe<'s, BlockState<'w, Cx>>> + Clone, - for<'a> &'a Cx::BlockStateExt: Into>>, { /// Sets the block state at the given position and returns the /// old one if present. @@ -174,18 +180,18 @@ where x: u32, y: u32, z: u32, - state: IBlockState<'w, Cx>, - ) -> Option>> { + state: BlockState<'w, Cx>, + ) -> Option>> { let bs_old = self.bsc.swap(Cx::compute_index(x, y, z), state.clone()); - if let Some(ref state_old) = bs_old { + if let Some(state_old) = bs_old.as_deref() { if !state_old.block.settings().is_empty { self.ne_block_c -= 1; if state_old.block.settings().random_ticks { self.rt_block_c -= 1; } } - let fs: Maybe<'_, IFluidState<'_, _>> = state_old.state.data().into(); + let fs = state_old.to_fluid_state(); if !fs.fluid.settings().is_empty { self.ne_fluid_c -= 1; } @@ -196,8 +202,7 @@ where self.rt_block_c += 1; } } - - let fs: Maybe<'_, IFluidState<'_, _>> = state.state.data().into(); + let fs = state.to_fluid_state(); if !fs.fluid.settings().is_empty { self.ne_fluid_c += 1; } @@ -209,23 +214,53 @@ where impl<'w, Cx> From<&'w Registry> for ChunkSection<'w, Cx> where - Cx: ChunkTy<'w> - + ProvideStateIds - + ProvidePalette> - + ProvidePalette>, - Cx::BlockStateList: for<'a> PalIndexToRaw<&'a IBlockState<'w, Cx>> - + for<'s> PalIndexFromRaw<'s, &'s IBlockState<'w, Cx>> + Cx: ChunkCx<'w> + + ProvideStateIds + + ProvidePalette> + + ProvidePalette> + + ProvideRegistry<'w, Cx::Id, RawBlock<'w, Cx>>, + + Cx::BlockStateList: for<'a> PalIndexToRaw<&'a BlockState<'w, Cx>> + + for<'s> PalIndexFromRaw<'s, Maybe<'s, BlockState<'w, Cx>>> + + Clone, + + &'w Registry: Into, + Cx::BiomeList: for<'a> PalIndexToRaw<&'a IBiome<'w, Cx>> + + for<'s> PalIndexFromRaw<'s, Maybe<'s, IBiome<'w, Cx>>> + Clone, { - fn from(_value: &'w Registry) -> Self { - unimplemented!() + /// Creates a [`ChunkSection`] for the given `Biome` registry/ + /// + /// # Panics + /// + /// Panics if the biome registry doesn't contains a default entry. + fn from(registry: &'w Registry) -> Self { + let default_block = Block::default(); + Self { + bsc: PalettedContainer::of_single( + Cx::state_ids(), + BlockState { + block: default_block, + state: default_block.states().default_state().clone(), + }, + ), + bic: PalettedContainer::of_single( + registry.into(), + registry + .default_entry() + .expect("biome registry should contains a default entry"), + ), + ne_block_c: 0, + rt_block_c: 0, + ne_fluid_c: 0, + } } } impl<'w, Cx> Debug for ChunkSection<'w, Cx> where + Cx: ChunkCx<'w> + Debug, Cx::Id: Debug, - Cx: ChunkTy<'w> + Debug, Cx::BlockStateExt: Debug, Cx::BlockStateList: Debug, Cx::Biome: Debug, @@ -246,7 +281,7 @@ where pub trait ComputeIndex: ProvidePalette { /// Computes the index of the given position. /// - /// The number type is unsigned because the index will overflow if it's negative. + /// The number type is unsigned because the index will overflow when it's negative. #[inline] fn compute_index(x: u32, y: u32, z: u32) -> usize { ((y << Self::EDGE_BITS | z) << Self::EDGE_BITS | x) as usize @@ -261,8 +296,8 @@ mod _edcode { impl<'w, Cx> Encode for ChunkSection<'w, Cx> where - Cx: ChunkTy<'w>, - Cx::BlockStateList: for<'a> PalIndexToRaw<&'a IBlockState<'w, Cx>>, + Cx: ChunkCx<'w>, + Cx::BlockStateList: for<'a> PalIndexToRaw<&'a BlockState<'w, Cx>>, Cx::BiomeList: for<'a> PalIndexToRaw<&'a IBiome<'w, Cx>>, { fn encode(&self, mut buf: B) -> Result<(), std::io::Error> @@ -277,15 +312,15 @@ mod _edcode { impl<'w, Cx> Update for ChunkSection<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w>, - Cx::BlockStateList: for<'s> PalIndexFromRaw<'s, IBlockState<'w, Cx>> + Clone, + Cx::BlockStateList: for<'s> PalIndexFromRaw<'s, BlockState<'w, Cx>> + Clone, Cx::BiomeList: for<'s> PalIndexFromRaw<'s, Maybe<'s, IBiome<'w, Cx>>> + for<'s> PalIndexFromRaw<'s, IBiome<'w, Cx>> + for<'a> PalIndexToRaw<&'a IBiome<'w, Cx>> + Clone, - Cx: ProvidePalette>, + Cx: ProvidePalette>, Cx: ProvidePalette>, { fn update(&mut self, mut buf: B) -> Result<(), std::io::Error> @@ -303,8 +338,8 @@ mod _edcode { impl<'w, Cx> ChunkSection<'w, Cx> where - Cx: ChunkTy<'w>, - Cx::BlockStateList: for<'a> PalIndexToRaw<&'a IBlockState<'w, Cx>>, + Cx: ChunkCx<'w>, + Cx::BlockStateList: for<'a> PalIndexToRaw<&'a BlockState<'w, Cx>>, Cx::BiomeList: for<'a> PalIndexToRaw<&'a IBiome<'w, Cx>>, { /// Returns the encoded length of the chunk section. @@ -315,7 +350,7 @@ mod _edcode { impl<'w, Cx> ChunkSection<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w>, Cx::BiomeList: for<'s> PalIndexFromRaw<'s, Maybe<'s, IBiome<'w, Cx>>> + for<'s> PalIndexFromRaw<'s, IBiome<'w, Cx>> + for<'a> PalIndexToRaw<&'a IBiome<'w, Cx>> diff --git a/crates/core/world/src/chunk/upgrade.rs b/crates/core/world/src/chunk/upgrade.rs index 70fe780..43da29e 100644 --- a/crates/core/world/src/chunk/upgrade.rs +++ b/crates/core/world/src/chunk/upgrade.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash}; -use fastnbt::IntArray; use rimecraft_block::RawBlock; use rimecraft_fluid::RawFluid; use rimecraft_global_cx::ProvideIdTy; @@ -10,12 +9,12 @@ use serde::Deserialize; use crate::view::HeightLimit; -use super::ChunkTy; +use super::ChunkCx; /// Upgrade data for a chunk. pub struct UpgradeData<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w>, { sides_to_upgrade: Vec, center_indices_upgrade: Box<[Box<[i32]>]>, @@ -28,11 +27,12 @@ type TickedBlock<'w, Cx> = TickedReg<'w, RawBlock<'w, Cx>, :: type TickedFluid<'w, Cx> = TickedReg<'w, RawFluid<'w, Cx>, ::Id>; #[derive(Debug)] +#[repr(transparent)] struct TickedReg<'r, T, K>(Reg<'r, K, T>); impl<'w, Cx> UpgradeData<'w, Cx> where - Cx: ChunkTy<'w> + Cx: ChunkCx<'w> + ProvideRegistry<'w, Cx::Id, RawBlock<'w, Cx>> + ProvideRegistry<'w, Cx::Id, RawFluid<'w, Cx>>, Cx::Id: Hash + Eq, @@ -51,21 +51,23 @@ where where D: serde::Deserializer<'de>, Cx::Id: Deserialize<'de>, + Cx::IntArray: Deserialize<'de>, { #[derive(Deserialize)] #[serde(bound(deserialize = r#" - Cx::Id: Deserialize<'de> + Hash + Eq, Cx: ProvideRegistry<'w, Cx::Id, RawBlock<'w, Cx>> + ProvideRegistry<'w, Cx::Id, RawFluid<'w, Cx>> - + ChunkTy<'w> + + ChunkCx<'w>, + Cx::Id: Deserialize<'de> + Hash + Eq, + Cx::IntArray: Deserialize<'de>, "#))] struct Serialized<'w, Cx> where - Cx: ChunkTy<'w>, + Cx: ChunkCx<'w>, { #[serde(rename = "Indices")] #[serde(default)] - indices: HashMap, + indices: HashMap, #[serde(rename = "Sides")] #[serde(default)] @@ -98,11 +100,11 @@ where let mut center_indices_upgrade: Box<[Box<[i32]>]> = vec![vec![].into_boxed_slice(); len].into_boxed_slice(); for (section, indices) in indices - .iter() + .into_iter() .filter_map(|(k, v)| k.parse::().ok().map(|k| (k, v))) .filter(|(k, _)| *k < len) { - center_indices_upgrade[section] = indices.iter().copied().collect(); + center_indices_upgrade[section] = indices.into(); } center_indices_upgrade }, @@ -114,7 +116,7 @@ where impl<'w, Cx> Debug for UpgradeData<'w, Cx> where - Cx: ChunkTy<'w> + Debug, + Cx: ChunkCx<'w> + Debug, Cx::Id: Debug, Cx::BlockStateExt: Debug, Cx::BlockStateList: Debug, diff --git a/crates/core/world/src/chunk/world_chunk.rs b/crates/core/world/src/chunk/world_chunk.rs new file mode 100644 index 0000000..dd79585 --- /dev/null +++ b/crates/core/world/src/chunk/world_chunk.rs @@ -0,0 +1,564 @@ +//! World chunks. + +use parking_lot::RwLock; +use rimecraft_block::BlockState; +use rimecraft_block_entity::{ + deser_nbt::CreateFromNbt, BlockEntity, ProvideBlockEntity, RawBlockEntityTypeDyn, +}; +use rimecraft_chunk_palette::{Maybe, SimpleOwned}; +use rimecraft_fluid::{BsToFs, FluidState}; +use rimecraft_registry::ProvideRegistry; +use rimecraft_voxel_math::{BlockPos, IVec3}; +use serde::{de::DeserializeSeed, Deserialize}; + +use crate::{ + heightmap, + view::block::{ + BlockLuminanceView, BlockView, BlockViewMut, LockFreeBlockView, LockedBlockViewMut, + }, + Sealed, +}; + +use super::{ + section::ComputeIndex, AsBaseChunk, AsBaseChunkMut, BaseChunk, BlockEntityCell, Chunk, ChunkCx, + ChunkMut, BORDER_LEN, +}; + +use std::{fmt::Debug, sync::Arc}; + +/// Chunk for worlds. +pub struct WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w>, +{ + /// The [`BaseChunk`]. + pub base: BaseChunk<'w, Cx>, + + is_client: bool, + loaded_to_world: bool, +} + +impl<'w, Cx> Debug for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + Debug, + Cx::Id: Debug, + Cx::BlockStateExt: Debug, + Cx::BlockStateList: Debug, + Cx::FluidStateExt: Debug, + Cx::Biome: Debug, + Cx::BiomeList: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WorldChunk") + .field("base", &self.base) + .finish_non_exhaustive() + } +} + +/// The type of `BlockEntity` creation in [`WorldChunk`]s. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum CreationType { + /// Immediate creation. + Immediate, + /// Queued creation. + Queued, + /// Checks if the block entity exists. + Check, +} + +impl<'w, Cx> WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w>, +{ + /// Whether this chunk can tick [`BlockEntity`]s. + #[inline(always)] + fn can_tick_be_glob(&self) -> bool { + self.loaded_to_world || self.is_client + } +} + +impl<'w, Cx> WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ + /// Peeks a [`BlockEntity`] at the target location, with given [`CreationType`]. + pub fn peek_block_entity_typed(&self, pos: BlockPos, pk: F, ty: CreationType) -> Option + where + F: for<'s> FnOnce(&'s BlockEntityCell<'w, Cx>) -> T, + { + let be = self.base.block_entities.read().get(&pos).cloned(); + if let Some(ref be) = be { + if be.read().is_removed() { + self.base.block_entities.write().remove(&pos); + return None; + } + } else { + if let Some(nbt) = self.base.block_entity_nbts.lock().remove(&pos) { + if let Some(be2) = self.load_block_entity_locked(pos, nbt) { + return Some(pk(&be2)); + } + } + if ty == CreationType::Immediate { + if let Some(be) = self.create_block_entity(pos) { + self.add_block_entity_locked(be) + } + } + } + + be.as_ref().map(pk) + } + + /// Peeks a [`BlockEntity`] at the target location, with given [`CreationType`]. + pub fn peek_block_entity_typed_lf( + &mut self, + pos: BlockPos, + pk: F, + ty: CreationType, + ) -> Option + where + F: for<'s> FnOnce(&'s BlockEntityCell<'w, Cx>) -> T, + { + let be = self.base.block_entities.get_mut().get(&pos).cloned(); + if let Some(ref be) = be { + if be.read().is_removed() { + self.base.block_entities.get_mut().remove(&pos); + return None; + } + } else { + if let Some(nbt) = self.base.block_entity_nbts.get_mut().remove(&pos) { + if let Some(be2) = self.load_block_entity(pos, nbt) { + return Some(pk(&be2)); + } + } + if ty == CreationType::Immediate { + if let Some(be) = self.create_block_entity_lf(pos) { + self.add_block_entity(be) + } + } + } + + be.as_ref().map(pk) + } + + /// Adds a block entity to this chunk. + pub fn add_block_entity(&mut self, block_entity: Box>) { + self.set_block_entity(block_entity); + //TODO: Update tickers and game event listeners + } + + /// Adds a block entity to this chunk. + pub fn add_block_entity_locked(&self, block_entity: Box>) { + self.set_block_entity_locked(block_entity); + //TODO: Update tickers and game event listeners + } + + fn load_block_entity( + &mut self, + pos: BlockPos, + nbt: Cx::Compound, + ) -> Option> { + let be = DeserializeSeed::deserialize( + CreateFromNbt { + pos, + state: self.peek_block_state_lf(pos, BlockState::clone).unwrap(), + respect_dummy: true, + }, + Cx::compound_to_deserializer(&nbt), + ) + .ok() + .flatten(); + + if let Some(be) = be { + self.add_block_entity(be); + Some( + self.base + .block_entities + .get_mut() + .get(&pos) + .expect("block entity should be inserted into this chunk") + .clone(), + ) + } else { + None + } + } + + fn load_block_entity_locked( + &self, + pos: BlockPos, + nbt: Cx::Compound, + ) -> Option> { + let be = DeserializeSeed::deserialize( + CreateFromNbt { + pos, + state: self.peek_block_state(pos, BlockState::clone).unwrap(), + respect_dummy: true, + }, + Cx::compound_to_deserializer(&nbt), + ) + .ok() + .flatten(); + + if let Some(be) = be { + self.add_block_entity_locked(be); + Some( + self.base + .block_entities + .read() + .get(&pos) + .expect("block entity should be inserted into this chunk") + .clone(), + ) + } else { + None + } + } + + #[inline] + fn create_block_entity(&self, pos: BlockPos) -> Option>> { + self.peek_block_state(pos, |be| { + be.state.data().block_entity_constructor().map(|f| f(pos)) + }) + .flatten() + } + + #[inline] + fn create_block_entity_lf(&mut self, pos: BlockPos) -> Option>> { + self.peek_block_state_lf(pos, |be| { + be.state.data().block_entity_constructor().map(|f| f(pos)) + }) + .flatten() + } +} + +impl<'w, Cx> AsBaseChunk<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w>, +{ + #[inline] + fn as_base_chunk(&self) -> Sealed<&BaseChunk<'w, Cx>> { + (&self.base).into() + } +} + +impl<'w, Cx> AsBaseChunkMut<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w>, +{ + #[inline] + fn as_base_chunk_mut(&mut self) -> Sealed<&mut BaseChunk<'w, Cx>> { + (&mut self.base).into() + } +} + +impl<'w, Cx> BlockView<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ + fn peek_block_state(&self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockState<'w, Cx>) -> T, + { + self.base + .section_array + .get(self.base.height_limit.section_index(pos.y())) + .and_then(|section| { + let rg = section.read(); + if rg.is_empty() { + None + } else { + let IVec3 { x, y, z } = pos.0 & (BORDER_LEN - 1) as i32; + rg.block_state(x as u32, y as u32, z as u32) + .as_deref() + .map(pk) + } + }) + } + + fn peek_fluid_state(&self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s FluidState<'w, Cx>) -> T, + { + self.base + .section_array + .get(self.base.height_limit.section_index(pos.y())) + .and_then(|section| { + let rg = section.read(); + if rg.is_empty() { + None + } else { + let IVec3 { x, y, z } = pos.0 & (BORDER_LEN - 1) as i32; + rg.fluid_state(x as u32, y as u32, z as u32) + .as_deref() + .map(pk) + } + }) + } + + #[inline(always)] + fn peek_block_entity(&self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockEntityCell<'w, Cx>) -> T, + { + self.peek_block_entity_typed(pos, pk, CreationType::Check) + } +} + +impl<'w, Cx> LockFreeBlockView<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ + fn peek_block_state_lf(&mut self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockState<'w, Cx>) -> T, + { + self.base + .section_array + .get_mut(self.base.height_limit.section_index(pos.y())) + .and_then(|section| { + let rg = section.get_mut(); + if rg.is_empty() { + None + } else { + let IVec3 { x, y, z } = pos.0 & (BORDER_LEN - 1) as i32; + rg.block_state(x as u32, y as u32, z as u32) + .as_deref() + .map(pk) + } + }) + } + + fn peek_fluid_state_lf(&mut self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s FluidState<'w, Cx>) -> T, + { + self.base + .section_array + .get_mut(self.base.height_limit.section_index(pos.y())) + .and_then(|section| { + let rg = section.get_mut(); + if rg.is_empty() { + None + } else { + let IVec3 { x, y, z } = pos.0 & (BORDER_LEN - 1) as i32; + rg.fluid_state(x as u32, y as u32, z as u32) + .as_deref() + .map(pk) + } + }) + } + + #[inline(always)] + fn peek_block_entity_lf(&mut self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockEntityCell<'w, Cx>) -> T, + { + self.peek_block_entity_typed_lf(pos, pk, CreationType::Check) + } +} + +impl<'w, Cx> BlockViewMut<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ + fn set_block_state( + &mut self, + pos: BlockPos, + state: BlockState<'w, Cx>, + moved: bool, + ) -> Option> { + let section = self + .section_mut(self.height_limit().section_index(pos.y()))? + .get_mut(); + let sec_is_empty = section.is_empty(); + if sec_is_empty && state.block.settings().is_empty { + return None; + } + + let bs; + let pos_alt = pos.0 & (BORDER_LEN as i32 - 1); + { + let IVec3 { x, y, z } = pos_alt; + bs = section + .set_block_state(x as u32, y as u32, z as u32, state.clone()) + .map(|maybe| match maybe { + Maybe::Borrowed(bs) => bs.clone(), + Maybe::Owned(SimpleOwned(bs)) => bs, + }); + } + + if bs + .as_ref() + .map_or(false, |s| Arc::ptr_eq(&s.state, &state.state)) + { + return None; + } + + { + let IVec3 { x, y, z } = IVec3 { + y: pos.y(), + ..pos_alt + }; + let this_ptr = self as *mut WorldChunk<'w, Cx>; + for ty in >::iter_block_update_types_wc() { + // SAFETY: This is safe because the `hms` is a valid pointer, and `peek_block_state_lf` does not interact with heightmaps. + unsafe { + if let Some(hm) = self.base.heightmaps.get_mut().get_mut(ty) { + hm.track_update(x, y, z, &state, |pos, pred| { + (*this_ptr) + .peek_block_state_lf(pos, |bs| pred(Some(bs))) + .unwrap_or_else(|| pred(None)) + }); + } + } + } + } + + //TODO: update lighting + //TODO: update profiler + + if let Some(ref bs) = bs { + let has_be = bs.state.data().has_block_entity(); + if !self.is_client { + //TODO: call `on_state_replaced`. + } else if bs.block != state.block && has_be { + self.remove_block_entity(pos); + } + } + + todo!() + } + + fn set_block_entity(&mut self, mut block_entity: Box>) { + if self + .peek_block_state_lf(block_entity.pos(), |bs| bs.state.data().has_block_entity()) + .unwrap_or_default() + { + //TODO: set world for block entity if necessary. + block_entity.cancel_removal(); + let mut be2 = self + .base + .block_entities + .get_mut() + .insert(block_entity.pos(), Arc::new(RwLock::new(block_entity))); + if let Some(be) = &mut be2 { + if let Some(be) = Arc::get_mut(be) { + be.get_mut().mark_removed(); + } else { + be.write().mark_removed(); + } + } + } + } + + fn remove_block_entity(&mut self, pos: BlockPos) -> Option> { + if self.can_tick_be_glob() { + let mut be = self.base.block_entities.get_mut().remove(&pos); + if let Some(be) = &mut be { + //TODO: remove game event listener + if let Some(raw) = Arc::get_mut(be) { + raw.get_mut().mark_removed(); + } else { + be.write().mark_removed(); + } + } + //TODO: remove ticker + be + } else { + None + } + } +} + +impl<'w, Cx> LockedBlockViewMut<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ + fn set_block_state_locked( + &self, + pos: BlockPos, + state: BlockState<'w, Cx>, + moved: bool, + ) -> Option> { + todo!() + } + + fn set_block_entity_locked(&self, mut block_entity: Box>) { + //TODO: set world for block entity if necessary. + block_entity.cancel_removal(); + let mut be2 = self + .base + .block_entities + .write() + .insert(block_entity.pos(), Arc::new(RwLock::new(block_entity))); + if let Some(be) = &mut be2 { + be.write().mark_removed(); + } + } + + fn remove_block_entity_locked(&self, pos: BlockPos) -> Option> { + todo!() + } +} + +impl<'w, Cx> BlockLuminanceView<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ + fn luminance(&self, pos: BlockPos) -> crate::view::StateOption { + todo!() + } +} + +impl<'w, Cx> Chunk<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ +} + +impl<'w, Cx> ChunkMut<'w, Cx> for WorldChunk<'w, Cx> +where + Cx: ChunkCx<'w> + + ComputeIndex> + + BsToFs<'w> + + ProvideRegistry<'w, Cx::Id, RawBlockEntityTypeDyn<'w, Cx>>, + Cx::BlockStateExt: ProvideBlockEntity<'w, Cx>, + Cx::Id: for<'de> Deserialize<'de>, +{ +} diff --git a/crates/core/world/src/event.rs b/crates/core/world/src/event.rs new file mode 100644 index 0000000..c4013c0 --- /dev/null +++ b/crates/core/world/src/event.rs @@ -0,0 +1 @@ +//! Event triggers. diff --git a/crates/core/world/src/heightmap.rs b/crates/core/world/src/heightmap.rs new file mode 100644 index 0000000..9f858cc --- /dev/null +++ b/crates/core/world/src/heightmap.rs @@ -0,0 +1,129 @@ +//! `Heightmap` implementation. + +use std::marker::PhantomData; + +use rimecraft_block::BlockState; +use rimecraft_packed_int_array::PackedIntArray; +use rimecraft_voxel_math::BlockPos; + +use crate::{chunk::ChunkCx, view::HeightLimit}; + +const STORAGE_LEN: usize = 256; + +/// Maps to store the Y-level of highest block at each horizontal coordinate. +#[derive(Debug)] +pub struct RawHeightmap<'w, P, Cx> { + storage: PackedIntArray, + predicate: P, + hlimit: HeightLimit, + _marker: PhantomData<&'w Cx>, +} + +impl<'w, P, Cx> RawHeightmap<'w, P, Cx> +where + Cx: ChunkCx<'w>, + Cx::HeightmapType: Type<'w, Cx, Predicate = P>, +{ + /// Creates a new heightmap. + /// + /// # Panics + /// + /// Panics when the given `HeightLimit`'s height is more than `2 ^ 256`. + pub fn new(height_limit: HeightLimit, ty: Cx::HeightmapType) -> Self { + let i = u32::BITS - (height_limit.height() + 1).leading_zeros(); + Self { + storage: PackedIntArray::from_packed(i, STORAGE_LEN, None) + .expect("the height should not more than 2 ^ 256"), + predicate: ty.predicate(), + hlimit: height_limit, + _marker: PhantomData, + } + } +} + +impl<'w, P, Cx> RawHeightmap<'w, P, Cx> +where + Cx: ChunkCx<'w>, + P: for<'s> FnMut(Option<&'s BlockState<'w, Cx>>) -> bool, +{ + /// Returns the highest block at the given coordinate. + #[inline] + pub fn get(&self, x: i32, z: i32) -> Option { + self.storage + .get(to_index(x, z)) + .map(|y| y as i32 + self.hlimit.bottom()) + } + + /// Sets the highest block at the given coordinate. + /// + /// # Panics + /// + /// Panics if the given (x, z) coordinate is out of bound. + #[inline] + pub fn set(&mut self, x: i32, z: i32, height: i32) { + self.storage + .set(to_index(x, z), (height - self.hlimit.bottom()) as u32) + } + + /// Updates this heightmap when the given [`BlockState`] at the location in this map is updated, + /// and returns whether there is an update to this heightmap. + pub fn track_update<'a, Pk>( + &'a mut self, + x: i32, + y: i32, + z: i32, + state: &BlockState<'w, Cx>, + mut peeker: Pk, + ) -> bool + where + Pk: for<'p> FnMut(BlockPos, &'p mut P) -> bool + 'a, + { + let Some(i) = self.get(x, z).filter(|i| y > *i - 2) else { + return false; + }; + + if (self.predicate)(Some(state)) { + if y >= i { + self.set(x, z, y + 1); + true + } else { + false + } + } else if y == i - 1 { + for j in (self.hlimit.bottom()..y).rev() { + if peeker(BlockPos::new(x, j, z), &mut self.predicate) { + self.set(x, z, j + 1); + return true; + } + } + + self.set(x, z, self.hlimit.bottom()); + true + } else { + false + } + } +} + +#[inline] +const fn to_index(x: i32, z: i32) -> usize { + (x + z * 16) as usize +} + +/// Several different heightmaps check and store highest block of different types, +/// and are used for different purposes. +pub trait Type<'w, Cx: ChunkCx<'w>>: 'w { + /// Predicate of block states. + type Predicate: for<'s> Fn(Option<&'s BlockState<'w, Cx>>) -> bool; + + /// Predicate of this type. + fn predicate(&self) -> Self::Predicate; + + /// Returns an [`Iterator`] of this type, containing all types that is required + /// to be updated on block state updates in `WorldChunk`. + fn iter_block_update_types_wc() -> impl Iterator; +} + +/// [`RawHeightmap`] with predicate type filled with [`Type::Predicate`]. +pub type Heightmap<'w, Cx> = + RawHeightmap<'w, <>::HeightmapType as Type<'w, Cx>>::Predicate, Cx>; diff --git a/crates/core/world/src/lib.rs b/crates/core/world/src/lib.rs index 5ff7eee..f046817 100644 --- a/crates/core/world/src/lib.rs +++ b/crates/core/world/src/lib.rs @@ -7,14 +7,34 @@ //! The world lifetime is `'w`, in common. It is the lifetime of the world itself, //! and `BlockState`s, `FluidState`s and the `Biome` registry should be bound to this lifetime. -use rimecraft_registry::Registry; - pub mod chunk; +pub mod event; +pub mod heightmap; pub mod tick; pub mod view; pub mod behave; -/// A wrapper for the `Registry` to adapt it to [`rimecraft_chunk_palette`] traits. -#[derive(Debug, Clone, Copy)] -pub struct RegPalWrapper<'a, K, T>(pub &'a Registry); +use std::sync::Arc; + +pub use ahash::{AHashMap, AHashSet}; +use parking_lot::RwLock; +use rimecraft_block_entity::BlockEntity; + +/// The default max light level of Minecraft. +pub const DEFAULT_MAX_LIGHT_LEVEL: u32 = 15; + +/// A sealed cell. +#[derive(Debug)] +#[repr(transparent)] +pub struct Sealed(pub(crate) T); + +impl From for Sealed { + #[inline(always)] + fn from(value: T) -> Self { + Self(value) + } +} + +/// Boxed block entity cell with internal mutability and reference-counting. +pub type BlockEntityCell<'w, Cx> = Arc>>>; diff --git a/crates/core/world/src/view.rs b/crates/core/world/src/view.rs index a86f005..a440386 100644 --- a/crates/core/world/src/view.rs +++ b/crates/core/world/src/view.rs @@ -2,6 +2,12 @@ use rimecraft_voxel_math::section_coord; +pub mod block; +pub mod light; +mod state_option; + +pub use state_option::StateOption; + /// Height limit specification. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct HeightLimit { @@ -21,54 +27,54 @@ impl HeightLimit { /// This is the number of blocks that can be modified in any vertical column /// within the view, or the vertical size, in blocks. #[inline] - pub const fn height(&self) -> u32 { + pub const fn height(self) -> u32 { self.height } /// Returns the bottom Y level, inclusive. #[inline] - pub const fn bottom(&self) -> i32 { + pub const fn bottom(self) -> i32 { self.bottom } /// Returns the top Y level, exclusive. #[inline] - pub const fn top(&self) -> i32 { + pub const fn top(self) -> i32 { self.bottom() + self.height() as i32 } /// Returns the bottom section coordinate, inclusive. - pub const fn bottom_section_coord(&self) -> i32 { + pub const fn bottom_section_coord(self) -> i32 { section_coord(self.bottom()) } /// Returns the top section coordinate, exclusive. - pub const fn top_section_coord(&self) -> i32 { + pub const fn top_section_coord(self) -> i32 { section_coord(self.top() - 1) + 1 } /// Returns the number of sections in the view, vertically. - pub const fn count_vertical_sections(&self) -> i32 { + pub const fn count_vertical_sections(self) -> i32 { self.top_section_coord() - self.bottom_section_coord() } /// Whether the given Y level is within the view's height limit. - pub const fn out_of_limit(&self, y: i32) -> bool { + pub const fn is_out_of_limit(self, y: i32) -> bool { y < self.bottom() || y >= self.top() } /// Returns a zero-based section index for the given Y level. - pub const fn section_index(&self, y: i32) -> i32 { - self.section_coord_to_index(section_coord(y)) + pub const fn section_index(self, y: i32) -> usize { + self.section_coord_to_index(section_coord(y)) as usize } /// Converts a section coordinate to a zero-based section index. - pub const fn section_coord_to_index(&self, coord: i32) -> i32 { + pub const fn section_coord_to_index(self, coord: i32) -> i32 { coord - self.bottom_section_coord() } /// Converts a zero-based section index to a section coordinate. - pub const fn section_index_to_coord(&self, index: i32) -> i32 { + pub const fn section_index_to_coord(self, index: i32) -> i32 { index + self.bottom_section_coord() } } diff --git a/crates/core/world/src/view/block.rs b/crates/core/world/src/view/block.rs new file mode 100644 index 0000000..b7f2a6e --- /dev/null +++ b/crates/core/world/src/view/block.rs @@ -0,0 +1,115 @@ +//! Block views. + +use std::sync::Arc; + +use rimecraft_block::{BlockState, ProvideBlockStateExtTy}; +use rimecraft_block_entity::BlockEntity; +use rimecraft_fluid::{FluidState, ProvideFluidStateExtTy}; +use rimecraft_voxel_math::BlockPos; + +use crate::{BlockEntityCell, DEFAULT_MAX_LIGHT_LEVEL}; + +use super::StateOption; + +/// A scoped, immutable view of [`BlockState`]s, [`FluidState`]s and [`BlockEntity`]s. +pub trait BlockView<'w, Cx> +where + Cx: ProvideBlockStateExtTy + ProvideFluidStateExtTy, +{ + /// Peeks the [`BlockState`] at the given position. + fn peek_block_state(&self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockState<'w, Cx>) -> T; + + /// Peeks the [`FluidState`] at the given position. + fn peek_fluid_state(&self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s FluidState<'w, Cx>) -> T; + + /// Peeks the [`BlockEntity`] at the given position. + fn peek_block_entity(&self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockEntityCell<'w, Cx>) -> T; +} + +/// View of block luminance source levels. +pub trait BlockLuminanceView<'w, Cx>: BlockView<'w, Cx> +where + Cx: ProvideBlockStateExtTy + ProvideFluidStateExtTy, +{ + /// Returns the luminance source level of the given position. + fn luminance(&self, pos: BlockPos) -> StateOption; + + /// Returns the max light level of this view. + /// + /// The default one is [`DEFAULT_MAX_LIGHT_LEVEL`]. + #[inline] + fn max_light_level(&self) -> u32 { + DEFAULT_MAX_LIGHT_LEVEL + } +} + +/// Lock-free variant of [`BlockView`]. +pub trait LockFreeBlockView<'w, Cx>: BlockView<'w, Cx> +where + Cx: ProvideBlockStateExtTy + ProvideFluidStateExtTy, +{ + /// Peeks the [`BlockState`] at the given position. + fn peek_block_state_lf(&mut self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockState<'w, Cx>) -> T; + + /// Peeks the [`FluidState`] at the given position. + fn peek_fluid_state_lf(&mut self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s FluidState<'w, Cx>) -> T; + + /// Peeks the [`BlockEntity`] at the given position. + fn peek_block_entity_lf(&mut self, pos: BlockPos, pk: F) -> Option + where + F: for<'s> FnOnce(&'s BlockEntityCell<'w, Cx>) -> T; +} + +/// Mutable variant of [`BlockView`], without internal mutability. +pub trait BlockViewMut<'w, Cx>: BlockView<'w, Cx> +where + Cx: ProvideBlockStateExtTy + ProvideFluidStateExtTy, +{ + /// Sets the block state at the given position. + /// + /// If the target block state is changed, the old block state is returned. + fn set_block_state( + &mut self, + pos: BlockPos, + state: BlockState<'w, Cx>, + moved: bool, + ) -> Option>; + + /// Adds a [`BlockEntity`] to this view. + fn set_block_entity(&mut self, block_entity: Box>); + + /// Removes a [`BlockEntity`] from this view, and returns it if presents. + fn remove_block_entity(&mut self, pos: BlockPos) -> Option>; +} + +/// [`BlockViewMut`] with internal mutability. +pub trait LockedBlockViewMut<'w, Cx>: BlockViewMut<'w, Cx> +where + Cx: ProvideBlockStateExtTy + ProvideFluidStateExtTy, +{ + /// Sets the block state at the given position. + /// + /// If the target block state is changed, the old block state is returned. + fn set_block_state_locked( + &self, + pos: BlockPos, + state: BlockState<'w, Cx>, + moved: bool, + ) -> Option>; + + /// Adds a [`BlockEntity`] to this view. + fn set_block_entity_locked(&self, block_entity: Box>); + + /// Removes a [`BlockEntity`] from this view, and returns it if presents. + fn remove_block_entity_locked(&self, pos: BlockPos) -> Option>; +} diff --git a/crates/core/world/src/view/light.rs b/crates/core/world/src/view/light.rs new file mode 100644 index 0000000..82bf5d0 --- /dev/null +++ b/crates/core/world/src/view/light.rs @@ -0,0 +1 @@ +//! Light view traits. diff --git a/crates/core/world/src/view/state_option.rs b/crates/core/world/src/view/state_option.rs new file mode 100644 index 0000000..b99332f --- /dev/null +++ b/crates/core/world/src/view/state_option.rs @@ -0,0 +1,41 @@ +/// Optional state result. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[allow(clippy::exhaustive_enums)] +pub enum StateOption { + /// A state. + Some(T), + /// The `void_air` variant of the state. + Void, + /// No state available. + None, +} + +macro_rules! fill_match { + ($s:expr, $e:expr) => { + match $s { + StateOption::Some(val) => StateOption::Some($e(val)), + StateOption::Void => StateOption::Void, + StateOption::None => StateOption::None, + } + }; +} + +impl StateOption { + /// Maps this optional state to another type. + pub fn map(self, mapper: F) -> StateOption + where + F: FnOnce(T) -> O, + { + fill_match!(self, mapper) + } +} + +impl From> for StateOption { + #[inline] + fn from(value: Option) -> Self { + match value { + Some(val) => StateOption::Some(val), + None => StateOption::None, + } + } +} diff --git a/crates/util/downcast/Cargo.toml b/crates/util/downcast/Cargo.toml new file mode 100644 index 0000000..dc781bc --- /dev/null +++ b/crates/util/downcast/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rimecraft-downcast" +version = "0.1.0" +edition = "2021" +authors = ["JieningYu "] +description = "Downcastable cells and non-static downcasting for Rust" +repository = "https://github.com/rimecraft-rs/rimecraft/" +license = "AGPL-3.0-or-later" +categories = ["rust-patterns"] + +[badges] +maintenance = { status = "passively-maintained" } + +[dependencies] + +[features] + +[lints] +workspace = true diff --git a/crates/util/downcast/src/lib.rs b/crates/util/downcast/src/lib.rs new file mode 100644 index 0000000..93ded4d --- /dev/null +++ b/crates/util/downcast/src/lib.rs @@ -0,0 +1,123 @@ +//! Downcastable cells and non-static downcasting for Rust. + +#![no_std] + +use core::{ + any::TypeId, + ops::{Deref, DerefMut}, +}; + +/// A cell that is able to downcast into a concrete type. +#[derive(Debug, Clone, Copy)] +pub struct Downcast { + ty: TypeId, + value: T, +} + +/// Trait for types that can be converted into a static variant. +/// +/// # Safety +/// +/// The repr type should be the static variant of the implemented type. +pub unsafe trait ToStatic { + /// The static variant of this type. + type StaticRepr: 'static; +} + +impl Downcast { + /// Creates a new `Downcast` cell. + #[inline] + pub fn new(value: T) -> Self { + Self { + ty: TypeId::of::(), + value, + } + } +} + +impl Downcast { + /// Creates a new `Downcast` cell with given [`TypeId`]. + /// + /// # Safety + /// + /// This function could not make sure the type id is correct. + #[inline] + pub const unsafe fn with_type_id(ty: TypeId, value: T) -> Self { + Self { ty, value } + } +} + +impl From for Downcast +where + T: ToStatic, +{ + #[inline] + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl Default for Downcast +where + T: Default + ToStatic, +{ + #[inline] + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl Downcast { + /// Downcasts the value into a concrete type, returning an immutable reference. + #[inline] + pub fn downcast_ref(&self) -> Option<&V> { + if self.is_safe::() { + unsafe { Some(&*(&self.value as *const T as *const V)) } + } else { + None + } + } + + /// Downcasts the value into a concrete type, returning a mutable reference. + #[inline] + pub fn downcast_mut(&mut self) -> Option<&mut V> { + if self.is_safe::() { + unsafe { Some(&mut *(&mut self.value as *mut T as *mut V)) } + } else { + None + } + } + + /// Whether it's safe to downcast into the given concrete type. + #[inline] + pub fn is_safe(&self) -> bool { + self.ty == TypeId::of::() + } +} + +impl Deref for Downcast { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl DerefMut for Downcast { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} + +/// Safe variant of [`ToStatic`] for implementing [`ToStatic`] on +/// static types without unsafe code. +pub trait ToStaticSafe: 'static {} + +unsafe impl ToStatic for T +where + T: ToStaticSafe, +{ + type StaticRepr = Self; +} diff --git a/crates/util/event/readme.md b/crates/util/event/readme.md index b995b4c..70f8d35 100644 --- a/crates/util/event/readme.md +++ b/crates/util/event/readme.md @@ -1,2 +1,3 @@ # Rimecraft Events + Rust implementation of the Fabric API Event system. diff --git a/crates/util/identifier/src/lib.rs b/crates/util/identifier/src/lib.rs index e4a6106..24cd538 100644 --- a/crates/util/identifier/src/lib.rs +++ b/crates/util/identifier/src/lib.rs @@ -45,8 +45,6 @@ impl Identifier { /// Namespace types that are able to separate with paths, or path types that are able to split by itself. pub trait Separate { - //TODO: use `Pattern` when it's stable - /// The separator used to separate namespace and path. const SEPARATOR: char; } diff --git a/crates/util/maybe/src/lib.rs b/crates/util/maybe/src/lib.rs index 6bcff59..ad82e56 100644 --- a/crates/util/maybe/src/lib.rs +++ b/crates/util/maybe/src/lib.rs @@ -22,12 +22,13 @@ pub enum Maybe<'a, T: ?Sized, Owned = SimpleOwned> { impl Maybe<'_, T, Owned> where - T: ToOwned, + T: ToOwned, + ::Owned: Into, { /// Converts the cell into an owned value. pub fn into_owned(self) -> Owned { match self { - Maybe::Borrowed(val) => val.to_owned(), + Maybe::Borrowed(val) => val.to_owned().into(), Maybe::Owned(owned) => owned, } } diff --git a/crates/util/registry/src/entry.rs b/crates/util/registry/src/entry.rs index 747653e..7b0e50c 100644 --- a/crates/util/registry/src/entry.rs +++ b/crates/util/registry/src/entry.rs @@ -118,8 +118,6 @@ impl std::fmt::Debug for TagsGuard<'_, K, T> { #[cfg(feature = "serde")] mod serde { - //! Helper module for `serde` support. - use std::hash::Hash; use crate::ProvideRegistry; @@ -161,8 +159,6 @@ mod serde { #[cfg(feature = "edcode")] mod edcode { - //! Helper module for `edcode` support. - use std::hash::Hash; use rimecraft_edcode::{Decode, Encode, VarI32}; diff --git a/crates/util/registry/src/key.rs b/crates/util/registry/src/key.rs index a8e6a5d..40f1f9b 100644 --- a/crates/util/registry/src/key.rs +++ b/crates/util/registry/src/key.rs @@ -136,10 +136,9 @@ mod serde { } } +/// Helper module for `edcode` support. #[cfg(feature = "edcode")] pub mod edcode { - //! Helper module for `edcode` support. - use rimecraft_edcode::{bytes, Decode, Encode}; use crate::ProvideRegistry; diff --git a/crates/util/registry/src/lib.rs b/crates/util/registry/src/lib.rs index c051084..a32bc1c 100644 --- a/crates/util/registry/src/lib.rs +++ b/crates/util/registry/src/lib.rs @@ -530,10 +530,9 @@ impl Registry where K: Hash + Eq + Clone, { - /// Binds given tags to entries, and - /// removes old tag bindings. - #[doc(alias = "populate_tags")] - pub fn bind_tags<'a, I>(&'a self, entries: I) + /// Binds given tags to entries, and removes old tag bindings. + #[doc(alias = "bind_tags")] + pub fn populate_tags<'a, I>(&'a self, entries: I) where I: IntoIterator, Vec<&'a RefEntry>)>, { @@ -564,8 +563,6 @@ where #[cfg(feature = "serde")] mod serde { - //! Helper module for `serde` support. - use std::hash::Hash; use crate::{entry::RefEntry, ProvideRegistry, Reg}; @@ -605,16 +602,14 @@ mod serde { 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"))) + .ok_or_else(|| serde::de::Error::custom(format!("raw id {} not found", raw))) } } } } #[cfg(feature = "edcode")] -pub mod edcode { - //! Helper module for `edcode` support. - +mod edcode { use rimecraft_edcode::{Decode, Encode, VarI32}; use crate::{ProvideRegistry, Reg}; diff --git a/crates/util/registry/src/tag.rs b/crates/util/registry/src/tag.rs index 10e0d53..d179290 100644 --- a/crates/util/registry/src/tag.rs +++ b/crates/util/registry/src/tag.rs @@ -109,10 +109,9 @@ impl<'a, K, T> Iterator for Iter<'a, K, T> { } } +/// Helper module for `serde` support. #[cfg(feature = "serde")] pub mod serde { - //! Helper module for `serde` support. - use std::str::FromStr; use crate::ProvideRegistry; diff --git a/crates/util/serde-update/src/erased.rs b/crates/util/serde-update/src/erased.rs index b048eda..6d5ee2f 100644 --- a/crates/util/serde-update/src/erased.rs +++ b/crates/util/serde-update/src/erased.rs @@ -16,11 +16,11 @@ macro_rules! __internal_update_from_erased { where D: ::serde::Deserializer<'de>, { - use ::serde::de::Error; - self.erased_update(&mut >::erase( - deserializer, - )) - .map_err(D::Error::custom) + $crate::erased::ErasedUpdate::erased_update( + self, + &mut >::erase(deserializer), + ) + .map_err(::serde::de::Error::custom) } }; } @@ -30,19 +30,19 @@ macro_rules! __internal_update_from_erased { #[macro_export] macro_rules! update_trait_object { ($($t:tt)*) => { - impl<'de> $crate::Update<'de> for dyn $($t)* { + impl<'a, 'de> $crate::Update<'de> for dyn $($t)* + 'a { $crate::__internal_update_from_erased!(); } - impl<'de> $crate::Update<'de> for dyn $($t)* + Send { + impl<'a, 'de> $crate::Update<'de> for dyn $($t)* + ::core::marker::Send + 'a { $crate::__internal_update_from_erased!(); } - impl<'de> $crate::Update<'de> for dyn $($t)* + Sync { + impl<'a, 'de> $crate::Update<'de> for dyn $($t)* + ::core::marker::Sync + 'a { $crate::__internal_update_from_erased!(); } - impl<'de> $crate::Update<'de> for dyn $($t)* + Send + Sync { + impl<'a, 'de> $crate::Update<'de> for dyn $($t)* + ::core::marker::Send + ::core::marker::Sync + 'a { $crate::__internal_update_from_erased!(); } }; diff --git a/crates/util/voxel-shape/src/set.rs b/crates/util/voxel-shape/src/set.rs index 914c379..1aec02f 100644 --- a/crates/util/voxel-shape/src/set.rs +++ b/crates/util/voxel-shape/src/set.rs @@ -118,7 +118,7 @@ impl<'a> VoxelSetSlice<'a> { /// Basic properties of a voxel. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(packed)] +// Don't use `repr(packed)`: it's discouraged by rustc. pub struct Props { /// Length of the set in the X direction. pub len_x: u32,