diff --git a/crates/core/local-cx/Cargo.toml b/crates/core/local-cx/Cargo.toml new file mode 100644 index 0000000..ed85985 --- /dev/null +++ b/crates/core/local-cx/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "rimecraft-local-cx" +version = "0.1.0" +edition = "2021" +authors = ["JieningYu "] +description = "Rimecraft local context traits" +repository = "https://github.com/rimecraft-rs/rimecraft/" +license = "AGPL-3.0-or-later" +categories = [] + +[badges] +maintenance = { status = "passively-maintained" } + +[dependencies] +global-cx = { path = "../global-cx", package = "rimecraft-global-cx" } +serde = { version = "1.0", default-features = false, optional = true } +erased-serde = { version = "0.4", optional = true } +edcode2 = { path = "../../util/edcode2", package = "rimecraft-edcode2", optional = true } +typeid = { version = "1.0", optional = true } +ahash = { version = "0.8", optional = true } + +[features] +serde = ["dep:serde"] +erased-serde = ["dep:erased-serde"] +edcode = ["dep:edcode2"] +dyn-cx = ["dep:typeid", "dep:ahash"] + +[lints] +workspace = true diff --git a/crates/core/local-cx/src/dyn_cx.rs b/crates/core/local-cx/src/dyn_cx.rs new file mode 100644 index 0000000..36516d3 --- /dev/null +++ b/crates/core/local-cx/src/dyn_cx.rs @@ -0,0 +1,154 @@ +//! Dynamic context providers. + +#![cfg(feature = "dyn-cx")] + +use std::{any::TypeId, borrow::Cow, fmt::Debug}; + +use ahash::AHashMap; + +use crate::{BaseLocalContext, LocalContext}; + +/// Function table for getting contexts. +#[derive(Debug)] +pub struct ContextTable { + map: AHashMap, +} + +impl ContextTable { + /// Creates a new context table. + #[inline] + pub fn new() -> Self { + Self { + map: AHashMap::new(), + } + } + + /// Enables dynamic fetching of a type. + pub fn enable(&mut self) + where + Cx: LocalContext, + { + let ty = typeid::of::(); + self.map.insert(ty, |cx, f| { + let val = >::acquire(cx); + f(std::ptr::from_ref(&val).cast::<()>()) + }); + } +} + +impl Default for ContextTable { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Clone for ContextTable { + #[inline] + fn clone(&self) -> Self { + Self { + map: self.map.clone(), + } + } +} + +/// A dynamic context provider. +#[derive(Debug)] +pub struct DynamicContext<'a, LocalCx> { + cx: LocalCx, + table: Cow<'a, ContextTable>, +} + +impl<'a, Cx> DynamicContext<'a, Cx> { + /// Creates a new dynamic context with owned context table. + #[inline] + pub fn new(cx: Cx, table: ContextTable) -> Self { + Self { + cx, + table: Cow::Owned(table), + } + } + + /// Creates a new dynamic context with borrowed context table. + #[inline] + pub fn from_borrowed_table(cx: Cx, table: &'a ContextTable) -> Self { + Self { + cx, + table: Cow::Borrowed(table), + } + } +} + +impl DynamicContext<'_, Cx> +where + Cx: BaseLocalContext, +{ + /// Turns this context into an [`UnsafeDynamicContext`]. + /// + /// # Safety + /// + /// The returned type is not safe enough to exist. + #[inline(always)] + pub unsafe fn as_unsafe_cx(&self) -> UnsafeDynamicContext<'_> { + UnsafeDynamicContext(self) + } +} + +impl BaseLocalContext for &DynamicContext<'_, Cx> {} + +impl LocalContext for &DynamicContext<'_, Cx> +where + Cx: LocalContext, +{ + #[inline] + fn acquire(self) -> T { + self.cx.acquire() + } +} + +trait ErasedDynCx { + fn erased_acquire(&self, ty: TypeId, f: &mut (dyn FnMut(*const ()) + '_)); +} + +impl ErasedDynCx for DynamicContext<'_, Cx> +where + Cx: BaseLocalContext, +{ + #[inline] + fn erased_acquire(&self, ty: TypeId, f: &mut (dyn FnMut(*const ()) + '_)) { + if let Some(g) = self.table.map.get(&ty) { + g(self.cx, f) + } + } +} + +/// A dynamic context provider that is **unsafe to exist**. +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct UnsafeDynamicContext<'a>(&'a (dyn ErasedDynCx + 'a)); + +impl BaseLocalContext for UnsafeDynamicContext<'_> {} + +impl LocalContext for UnsafeDynamicContext<'_> +where + T: Copy, +{ + fn acquire(self) -> T { + let mut val = None; + self.0.erased_acquire(typeid::of::(), &mut |obj| { + val = Some(unsafe { *obj.cast::() }) + }); + val.unwrap_or_else(|| { + panic!( + "type {} not found for dynamic context", + std::any::type_name::() + ) + }) + } +} + +impl Debug for UnsafeDynamicContext<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("UnsafeDynamicContext").finish() + } +} diff --git a/crates/core/local-cx/src/edcode.rs b/crates/core/local-cx/src/edcode.rs new file mode 100644 index 0000000..ff59ce5 --- /dev/null +++ b/crates/core/local-cx/src/edcode.rs @@ -0,0 +1,68 @@ +#![cfg(feature = "edcode")] + +use edcode2::{Buf, BufMut}; + +use crate::WithLocalCx; + +impl Buf for WithLocalCx +where + T: Buf, +{ + #[inline(always)] + fn remaining(&self) -> usize { + self.inner.remaining() + } + + #[inline(always)] + fn chunk(&self) -> &[u8] { + self.inner.chunk() + } + + #[inline(always)] + fn advance(&mut self, cnt: usize) { + self.inner.advance(cnt) + } + + #[inline(always)] + fn chunks_vectored<'a>(&'a self, dst: &mut [std::io::IoSlice<'a>]) -> usize { + self.inner.chunks_vectored(dst) + } +} + +unsafe impl BufMut for WithLocalCx +where + T: BufMut, +{ + #[inline(always)] + fn remaining_mut(&self) -> usize { + self.inner.remaining_mut() + } + + #[inline(always)] + unsafe fn advance_mut(&mut self, cnt: usize) { + self.inner.advance_mut(cnt) + } + + #[inline(always)] + fn chunk_mut(&mut self) -> &mut edcode2::UninitSlice { + self.inner.chunk_mut() + } + + #[inline(always)] + fn put(&mut self, src: T1) + where + Self: Sized, + { + self.inner.put(src) + } + + #[inline(always)] + fn put_slice(&mut self, src: &[u8]) { + self.inner.put_slice(src) + } + + #[inline(always)] + fn put_bytes(&mut self, val: u8, cnt: usize) { + self.inner.put_bytes(val, cnt) + } +} diff --git a/crates/core/local-cx/src/lib.rs b/crates/core/local-cx/src/lib.rs new file mode 100644 index 0000000..20939c9 --- /dev/null +++ b/crates/core/local-cx/src/lib.rs @@ -0,0 +1,63 @@ +//! Local context traits. + +use std::fmt::Debug; + +use global_cx::GlobalContext; + +pub mod dyn_cx; + +mod edcode; +pub mod serde; + +/// A base local context. +pub trait BaseLocalContext: Sized + Copy {} + +/// A local context provides data to the global context. +pub trait LocalContext: BaseLocalContext { + /// Acquire the data from the local context. + fn acquire(self) -> T; +} + +/// Global context types that provides implicit local context type. +pub trait ProvideLocalCxTy: GlobalContext { + /// The local context type. + type Context<'cx>: BaseLocalContext; +} + +/// A type that carries a local context. +/// +/// This type is used to carry a local context along with the data. +pub struct WithLocalCx { + /// The data. + pub inner: T, + /// The local context. + pub local_cx: LocalCx, +} + +impl Debug for WithLocalCx +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &self.inner) + } +} + +/// Extension trait for local context. +pub trait LocalContextExt { + /// Create a `WithLocalCx` with the given inner data. + #[inline] + fn with(self, inner: T) -> WithLocalCx + where + Self: Sized, + { + WithLocalCx { + inner, + local_cx: self, + } + } +} + +impl LocalContextExt for Cx where Cx: BaseLocalContext {} + +mod tests; diff --git a/crates/core/local-cx/src/serde.rs b/crates/core/local-cx/src/serde.rs new file mode 100644 index 0000000..17ec01f --- /dev/null +++ b/crates/core/local-cx/src/serde.rs @@ -0,0 +1,168 @@ +//! Serde support for local context. + +#![cfg(feature = "serde")] +#![allow(clippy::missing_errors_doc)] + +use std::marker::PhantomData; + +use crate::WithLocalCx; + +/// Serialize the value with a local context. +pub trait SerializeWithCx { + /// Serialize the value with the given serializer and the local context. + fn serialize_with_cx(&self, serializer: WithLocalCx) -> Result + where + S: serde::Serializer; +} + +/// Deserialize the value with a local context. +pub trait DeserializeWithCx<'de, LocalCx>: Sized { + /// Deserialize the value with the given deserializer and the local context. + fn deserialize_with_cx(deserializer: WithLocalCx) -> Result + where + D: serde::Deserializer<'de>; + + /// Deserialize the value in place with the given deserializer and the local context. + #[inline] + fn deserialize_in_place_with_cx( + this: &mut Self, + deserializer: WithLocalCx, + ) -> Result<(), D::Error> + where + D: serde::Deserializer<'de>, + { + *this = Self::deserialize_with_cx(deserializer)?; + Ok(()) + } +} + +impl SerializeWithCx for T +where + T: serde::Serialize, +{ + #[inline] + fn serialize_with_cx(&self, serializer: WithLocalCx) -> Result + where + S: serde::Serializer, + { + serde::Serialize::serialize(self, serializer.inner) + } +} + +impl<'de, Cx, T> DeserializeWithCx<'de, Cx> for T +where + T: serde::Deserialize<'de>, +{ + #[inline] + fn deserialize_with_cx(deserializer: WithLocalCx) -> Result + where + D: serde::Deserializer<'de>, + { + serde::Deserialize::deserialize(deserializer.inner) + } + + #[inline] + fn deserialize_in_place_with_cx( + this: &mut Self, + deserializer: WithLocalCx, + ) -> Result<(), D::Error> + where + D: serde::Deserializer<'de>, + { + serde::Deserialize::deserialize_in_place(deserializer.inner, this) + } +} + +impl serde::Serialize for WithLocalCx +where + T: SerializeWithCx, +{ + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.inner.serialize_with_cx(WithLocalCx { + inner: serializer, + local_cx: &self.local_cx, + }) + } +} + +impl<'de, T, Cx> serde::de::DeserializeSeed<'de> for WithLocalCx, Cx> +where + T: DeserializeWithCx<'de, Cx>, +{ + type Value = T; + + #[inline] + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + T::deserialize_with_cx(WithLocalCx { + inner: deserializer, + local_cx: &self.local_cx, + }) + } +} + +#[cfg(feature = "erased-serde")] +mod erased { + use std::{cell::Cell, marker::PhantomData}; + + use crate::WithLocalCx; + + /// Serialize the value with a local context, with serializer type-erased. + pub trait ErasedSerializeWithCx { + /// Serialize the value with the given serializer and the local context. + fn erased_serialize_with_cx( + &self, + serializer: WithLocalCx<&mut dyn erased_serde::Serializer, &LocalCx>, + ) -> Result<(), erased_serde::Error>; + } + + impl ErasedSerializeWithCx for T + where + T: super::SerializeWithCx + ?Sized, + { + fn erased_serialize_with_cx( + &self, + serializer: WithLocalCx<&mut dyn erased_serde::Serializer, &Cx>, + ) -> Result<(), erased_serde::Error> { + thread_local! { + static CONTEXT: Cell<*const ()> = const{ Cell::new(std::ptr::null()) }; + } + + struct SerHelper<'a, T: ?Sized, Cx>(&'a T, PhantomData); + + impl serde::Serialize for SerHelper<'_, T, Cx> + where + T: super::SerializeWithCx + ?Sized, + { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let ptr: *const Cx = CONTEXT.get().cast(); + let context = unsafe { &*ptr }; + self.0.serialize_with_cx(WithLocalCx { + inner: serializer, + local_cx: context, + }) + } + } + + let ptr = std::ptr::from_ref(serializer.local_cx); + CONTEXT.set(ptr.cast()); + erased_serde::Serialize::erased_serialize( + &SerHelper(self, PhantomData::), + serializer.inner, + ) + } + } +} + +#[cfg(feature = "erased-serde")] +pub use erased::ErasedSerializeWithCx; diff --git a/crates/core/local-cx/src/tests.rs b/crates/core/local-cx/src/tests.rs new file mode 100644 index 0000000..2151435 --- /dev/null +++ b/crates/core/local-cx/src/tests.rs @@ -0,0 +1,53 @@ +#![cfg(test)] + +use crate::{BaseLocalContext, LocalContext}; + +struct DummyCx { + msg: u32, + info: String, +} + +impl BaseLocalContext for &DummyCx {} + +impl LocalContext for &DummyCx { + fn acquire(self) -> u32 { + self.msg + } +} + +impl<'a> LocalContext<&'a str> for &'a DummyCx { + fn acquire(self) -> &'a str { + &self.info + } +} + +#[test] +#[cfg(feature = "dyn-cx")] +fn dyn_context() { + use crate::dyn_cx::{ContextTable, DynamicContext}; + + let context_raw = DummyCx { + msg: 114, + info: "hello".to_owned(), + }; + + let mut table = ContextTable::new(); + table.enable::(); + table.enable::<&str>(); + let table: ContextTable<&DummyCx> = table; + + let dyn_cx = DynamicContext::new(&context_raw, table); + let erased = unsafe { dyn_cx.as_unsafe_cx() }; + + assert_eq!( + LocalContext::::acquire(&context_raw), + LocalContext::::acquire(erased), + "msg value mismatch" + ); + + assert_eq!( + LocalContext::<&str>::acquire(&context_raw), + LocalContext::<&str>::acquire(erased), + "info value mismatch" + ); +} diff --git a/crates/core/registry/Cargo.toml b/crates/core/registry/Cargo.toml index 7c00b81..92ff9b4 100644 --- a/crates/core/registry/Cargo.toml +++ b/crates/core/registry/Cargo.toml @@ -15,6 +15,8 @@ maintenance = { status = "passively-maintained" } parking_lot = "0.12" serde = { version = "1.0", optional = true } edcode2 = { path = "../../util/edcode2", package = "rimecraft-edcode2", optional = true } +ahash = "0.8.11" +typeid = "1.0" [features] serde = ["dep:serde"] diff --git a/crates/core/registry/src/dyn_manager.rs b/crates/core/registry/src/dyn_manager.rs new file mode 100644 index 0000000..eaadfa3 --- /dev/null +++ b/crates/core/registry/src/dyn_manager.rs @@ -0,0 +1,109 @@ +use std::{any::TypeId, borrow::Borrow, fmt::Debug, hash::Hash, sync::Arc}; + +use ahash::AHashSet; + +use crate::{key::Key, Registry}; + +type RegistryObj<'a, K> = dyn DynRegistry + Send + Sync + 'a; + +/// Object-safe registry marker trait. +#[doc(hidden)] +pub trait DynRegistry: sealed::DynRegistrySealed {} + +mod sealed { + use super::*; + + pub trait DynRegistrySealed { + fn erased_key(&self) -> &Key; + + fn type_id(&self) -> TypeId; + } +} + +impl sealed::DynRegistrySealed for Registry { + #[inline] + fn erased_key(&self) -> &Key { + self.key.cast_ref() + } + + #[inline] + fn type_id(&self) -> TypeId { + typeid::of::() + } +} + +struct RegCell<'a, K>(Arc>); + +impl Debug for RegCell<'_, K> +where + K: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0.erased_key()) + } +} + +impl Borrow> for RegCell<'_, K> { + #[inline] + fn borrow(&self) -> &Key { + self.0.erased_key() + } +} + +impl Hash for RegCell<'_, K> +where + K: Hash, +{ + #[inline] + fn hash(&self, state: &mut H) { + self.0.erased_key().hash(state) + } +} + +impl PartialEq for RegCell<'_, K> +where + K: PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0.erased_key() == other.0.erased_key() + } +} + +impl Eq for RegCell<'_, K> where K: Eq {} + +/// A dynamic static registry manager. +#[doc(alias = "DynamicRegistryManager")] +#[derive(Debug)] +pub struct DynRegistries<'a, K> { + map: AHashSet>, +} + +impl DynRegistries<'_, K> +where + K: Hash + Eq, +{ + /// Obtains a registry from the manager. + /// + /// # 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(&self, key: &Key>) -> Option<&Registry> { + self.map + .get(key.cast_ref::<()>()) + .filter(|reg| (*reg.0).type_id() == typeid::of::()) + .map(|reg| unsafe { &*std::ptr::from_ref(&*reg.0).cast::>() }) + } +} + +impl<'a, K> FromIterator>> for DynRegistries<'a, K> +where + K: Hash + Eq, +{ + fn from_iter>>>(iter: T) -> Self { + Self { + map: iter.into_iter().map(|reg| RegCell(reg)).collect(), + } + } +} diff --git a/crates/core/registry/src/key.rs b/crates/core/registry/src/key.rs index f7fd75e..271aa4a 100644 --- a/crates/core/registry/src/key.rs +++ b/crates/core/registry/src/key.rs @@ -36,6 +36,22 @@ impl Key { pub fn registry(&self) -> &K { &self.registry } + + #[doc(hidden)] + #[inline] + pub fn cast(self) -> Key { + Key { + registry: self.registry, + value: self.value, + _marker: PhantomData, + } + } + + #[doc(hidden)] + #[inline] + pub fn cast_ref(&self) -> &Key { + unsafe { &*std::ptr::from_ref(self).cast::>() } + } } impl Key diff --git a/crates/core/registry/src/lib.rs b/crates/core/registry/src/lib.rs index e0e0e1b..7cbb4a8 100644 --- a/crates/core/registry/src/lib.rs +++ b/crates/core/registry/src/lib.rs @@ -17,6 +17,7 @@ use key::Key; use parking_lot::RwLock; use tag::Tags; +mod dyn_manager; pub mod entry; pub mod key; pub mod tag; @@ -27,6 +28,8 @@ pub use entry::Entry as RegistryEntry; pub use key::Key as RegistryKey; pub use tag::TagKey; +pub use dyn_manager::*; + /// Immutable registry of various in-game components. #[derive(Debug)] pub struct Registry { @@ -523,6 +526,7 @@ where } /// Trait for providing a registry. +#[deprecated = "use local-cx to obtain registry instead"] pub trait ProvideRegistry<'r, K, T> { /// Gets the registry. fn registry() -> &'r Registry; diff --git a/crates/util/edcode2/src/lib.rs b/crates/util/edcode2/src/lib.rs index aa7fbc0..10a5586 100644 --- a/crates/util/edcode2/src/lib.rs +++ b/crates/util/edcode2/src/lib.rs @@ -7,6 +7,9 @@ pub use codecs::Variable; pub mod codecs; +#[doc(hidden)] +pub use bytes::buf::UninitSlice; + #[cfg(feature = "derive")] pub use rimecraft_edcode2_derive::{Decode, Encode};