hecs_schedule/context/
mod.rs

1//! This module provides types and traits associated to accessing of borrowed
2//! values.
3use std::{any::TypeId, cmp::Ordering, ptr::NonNull};
4
5use atomic_refcell::AtomicRefCell;
6
7use crate::{borrow::ContextBorrow, Error, IntoAccess, Result};
8use hecs::Component;
9
10/// Holds all data necessary for the execution of the world.
11/// The data is held by references, and needs to outlive the context itself
12pub struct Context<'a> {
13    data: &'a dyn Data,
14}
15
16// Safe since Send + Sync is required for impl of IntoData
17unsafe impl Send for Context<'_> {}
18unsafe impl Sync for Context<'_> {}
19
20mod erased_cell;
21use erased_cell::*;
22
23impl<'a> Context<'a> {
24    /// Construct a new context from the tuple of references `data`
25    pub fn new(data: &'a dyn Data) -> Context {
26        Self { data }
27    }
28
29    /// Borrows data of type T from the context. Does not panic.
30    pub fn borrow<T>(&'a self) -> Result<T::Target>
31    where
32        T: ContextBorrow<'a>,
33    {
34        T::borrow(self)
35    }
36
37    /// Returns the cell associated to `T`.
38    /// **Note**: Types are erased, but casting is guaranteed to be correct.
39    pub fn cell<T: IntoAccess>(&'a self) -> Result<&AtomicRefCell<NonNull<u8>>> {
40        let access = T::access();
41        self.data
42            .get(access.id())
43            .ok_or_else(|| Error::MissingData(access.name()))
44    }
45}
46
47/// Dynamically accessed static collection of values
48pub trait Data {
49    /// Get the cell associated to the `TypeId`.
50    fn get(&self, ty: TypeId) -> Option<&AtomicRefCell<NonNull<u8>>>;
51}
52
53/// Convert a tuple or other type into [Data].
54pub trait IntoData<With>: Send + Sync {
55    /// The corresponding [Data] type.
56    type Target: Data;
57    /// Performs the conversion.
58    /// # Safety
59    /// Converts a tuple of references into NonNull. The lifetime is captures by
60    /// [Context]
61    unsafe fn into_data(self, with: &mut With) -> Self::Target;
62}
63
64impl<With: Component> IntoData<With> for () {
65    type Target = [ErasedCell; 1];
66
67    unsafe fn into_data(self, with: &mut With) -> Self::Target {
68        [ErasedCell::from_ref(with)]
69    }
70}
71
72macro_rules! tuple_impl {
73    ($([$idx: tt => $name: ident]),*) => {
74        impl<$( $name ), *, With> IntoData<With> for ($(&mut $name,) *)
75            where
76                With: Component,
77                $($name: Component), *
78        {
79            type Target = [ErasedCell; 1 + count!($($name )*)];
80
81            unsafe fn into_data(self, with: &mut With) -> Self::Target {
82
83                let mut val = [
84                    $( ErasedCell::from_ref::<$name>(self.$idx),)*
85                    ErasedCell::from_ref(with),
86                ];
87
88                val.sort_unstable();
89
90                val
91            }
92        }
93    };
94}
95
96impl_for_tuples_idx!(tuple_impl);
97
98impl<const C: usize> Data for [ErasedCell; C] {
99    fn get(&self, ty: TypeId) -> Option<&AtomicRefCell<NonNull<u8>>> {
100        let mut low = 0;
101        let mut high = C - 1;
102
103        while low <= high {
104            let mid = (high - low) / 2 + low;
105            let val = &self[mid];
106
107            match val.cmp_id(ty) {
108                Ordering::Less => low = mid + 1,
109                Ordering::Equal => return Some(&val.cell),
110                Ordering::Greater if mid == 0 => break,
111                Ordering::Greater => high = mid - 1,
112            }
113        }
114
115        None
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::{Context, IntoData};
122
123    #[test]
124    fn context() {
125        let mut a = 64_i32;
126        let mut b = "Hello, World";
127
128        let data = unsafe { (&mut a, &mut b).into_data(&mut ()) };
129
130        let context = Context::new(&data);
131
132        {
133            let a = context.borrow::<&i32>().unwrap();
134            let mut b = context.borrow::<&mut &str>().unwrap();
135
136            assert_eq!(*b, "Hello, World");
137            *b = "Foo Fighters";
138            drop(b);
139
140            let b = context.borrow::<&&str>().unwrap();
141            assert_eq!(*a, 64);
142            assert_eq!(*b, "Foo Fighters");
143
144            let c = context.borrow::<&f32>();
145            assert!(c.is_err());
146        }
147    }
148}