Skip to content

Commit

Permalink
Merge pull request 'Local context building blocks' (#64) from local-c…
Browse files Browse the repository at this point in the history
…x into main

Reviewed-on: https://codeberg.org/DM-Earth/rimecraft/pulls/64
Reviewed-by: C191239 <[email protected]>
  • Loading branch information
C191239 committed Sep 9, 2024
2 parents c2053f4 + 77f5344 commit abf385a
Show file tree
Hide file tree
Showing 11 changed files with 669 additions and 0 deletions.
29 changes: 29 additions & 0 deletions crates/core/local-cx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "rimecraft-local-cx"
version = "0.1.0"
edition = "2021"
authors = ["JieningYu <[email protected]>"]
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
154 changes: 154 additions & 0 deletions crates/core/local-cx/src/dyn_cx.rs
Original file line number Diff line number Diff line change
@@ -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<LocalCx> {
map: AHashMap<TypeId, fn(LocalCx, &mut (dyn FnMut(*const ()) + '_))>,
}

impl<Cx> ContextTable<Cx> {
/// Creates a new context table.
#[inline]
pub fn new() -> Self {
Self {
map: AHashMap::new(),
}
}

/// Enables dynamic fetching of a type.
pub fn enable<T>(&mut self)
where
Cx: LocalContext<T>,
{
let ty = typeid::of::<T>();
self.map.insert(ty, |cx, f| {
let val = <Cx as LocalContext<T>>::acquire(cx);
f(std::ptr::from_ref(&val).cast::<()>())
});
}
}

impl<Cx> Default for ContextTable<Cx> {
#[inline]
fn default() -> Self {
Self::new()
}
}

impl<Cx> Clone for ContextTable<Cx> {
#[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<LocalCx>>,
}

impl<'a, Cx> DynamicContext<'a, Cx> {
/// Creates a new dynamic context with owned context table.
#[inline]
pub fn new(cx: Cx, table: ContextTable<Cx>) -> 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<Cx>) -> Self {
Self {
cx,
table: Cow::Borrowed(table),
}
}
}

impl<Cx> 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<Cx> BaseLocalContext for &DynamicContext<'_, Cx> {}

impl<Cx, T> LocalContext<T> for &DynamicContext<'_, Cx>
where
Cx: LocalContext<T>,
{
#[inline]
fn acquire(self) -> T {
self.cx.acquire()
}
}

trait ErasedDynCx {
fn erased_acquire(&self, ty: TypeId, f: &mut (dyn FnMut(*const ()) + '_));
}

impl<Cx> 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<T> LocalContext<T> for UnsafeDynamicContext<'_>
where
T: Copy,
{
fn acquire(self) -> T {
let mut val = None;
self.0.erased_acquire(typeid::of::<T>(), &mut |obj| {
val = Some(unsafe { *obj.cast::<T>() })
});
val.unwrap_or_else(|| {
panic!(
"type {} not found for dynamic context",
std::any::type_name::<T>()
)
})
}
}

impl Debug for UnsafeDynamicContext<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("UnsafeDynamicContext").finish()
}
}
68 changes: 68 additions & 0 deletions crates/core/local-cx/src/edcode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#![cfg(feature = "edcode")]

use edcode2::{Buf, BufMut};

use crate::WithLocalCx;

impl<T, Cx> Buf for WithLocalCx<T, Cx>
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<T, Cx> BufMut for WithLocalCx<T, Cx>
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<T1: Buf>(&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)
}
}
63 changes: 63 additions & 0 deletions crates/core/local-cx/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<T>: 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<T, LocalCx> {
/// The data.
pub inner: T,
/// The local context.
pub local_cx: LocalCx,
}

impl<T, Cx> Debug for WithLocalCx<T, Cx>
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<T>(self, inner: T) -> WithLocalCx<T, Self>
where
Self: Sized,
{
WithLocalCx {
inner,
local_cx: self,
}
}
}

impl<Cx> LocalContextExt for Cx where Cx: BaseLocalContext {}

mod tests;
Loading

0 comments on commit abf385a

Please sign in to comment.