use std::{any::TypeId, cmp::Ordering, ptr::NonNull};
use atomic_refcell::AtomicRefCell;
use crate::{borrow::ContextBorrow, Error, IntoAccess, Result};
use hecs::Component;
pub struct Context<'a> {
data: &'a dyn Data,
}
unsafe impl Send for Context<'_> {}
unsafe impl Sync for Context<'_> {}
mod erased_cell;
use erased_cell::*;
impl<'a> Context<'a> {
pub fn new(data: &'a dyn Data) -> Context {
Self { data }
}
pub fn borrow<T>(&'a self) -> Result<T::Target>
where
T: ContextBorrow<'a>,
{
T::borrow(self)
}
pub fn cell<T: IntoAccess>(&'a self) -> Result<&AtomicRefCell<NonNull<u8>>> {
let access = T::access();
self.data
.get(access.id())
.ok_or_else(|| Error::MissingData(access.name()))
}
}
pub trait Data {
fn get(&self, ty: TypeId) -> Option<&AtomicRefCell<NonNull<u8>>>;
}
pub trait IntoData<With>: Send + Sync {
type Target: Data;
unsafe fn into_data(self, with: &mut With) -> Self::Target;
}
impl<With: Component> IntoData<With> for () {
type Target = [ErasedCell; 1];
unsafe fn into_data(self, with: &mut With) -> Self::Target {
[ErasedCell::from_ref(with)]
}
}
macro_rules! tuple_impl {
() => {};
($([$idx: tt => $name: ident]),*) => {
impl<$( $name ), *, With> IntoData<With> for ($(&mut $name,) *)
where
With: Component,
$($name: Component), *
{
type Target = [ErasedCell; 1 + count!($($name )*)];
unsafe fn into_data(self, with: &mut With) -> Self::Target {
let mut val = [
$( ErasedCell::from_ref::<$name>(self.$idx),)*
ErasedCell::from_ref(with),
];
val.sort_unstable();
val
}
}
};
}
impl_for_tuples_idx!(tuple_impl);
impl<const C: usize> Data for [ErasedCell; C] {
fn get(&self, ty: TypeId) -> Option<&AtomicRefCell<NonNull<u8>>> {
let mut low = 0;
let mut high = C - 1;
while low <= high {
let mid = (high - low) / 2 + low;
let val = &self[mid];
match val.cmp_id(ty) {
Ordering::Less => low = mid + 1,
Ordering::Equal => return Some(&val.cell),
Ordering::Greater if mid == 0 => break,
Ordering::Greater => high = mid - 1,
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::{Context, IntoData};
#[test]
fn context() {
let mut a = 64_i32;
let mut b = "Hello, World";
let data = unsafe { (&mut a, &mut b).into_data(&mut ()) };
let context = Context::new(&data);
{
let a = context.borrow::<&i32>().unwrap();
let mut b = context.borrow::<&mut &str>().unwrap();
assert_eq!(*b, "Hello, World");
*b = "Foo Fighters";
drop(b);
let b = context.borrow::<&&str>().unwrap();
assert_eq!(*a, 64);
assert_eq!(*b, "Foo Fighters");
let c = context.borrow::<&f32>();
assert!(c.is_err());
}
}
}