veloren_common_state/plugin/
memory_manager.rs

1use atomic_refcell::AtomicRefCell;
2use common::{
3    comp::{Health, Player},
4    uid::{IdMaps, Uid},
5};
6use core::ptr::NonNull;
7use specs::{
8    Component, Entities, Entity, Read, ReadStorage, WriteStorage, storage::GenericReadStorage,
9};
10use tracing::error;
11
12pub struct EcsWorld<'a, 'b> {
13    pub entities: &'b Entities<'a>,
14    pub health: EcsComponentAccess<'a, 'b, Health>,
15    pub uid: EcsComponentAccess<'a, 'b, Uid>,
16    pub player: EcsComponentAccess<'a, 'b, Player>,
17    pub id_maps: &'b Read<'a, IdMaps>,
18}
19
20pub enum EcsComponentAccess<'a, 'b, T: Component> {
21    Read(&'b ReadStorage<'a, T>),
22    ReadOwned(ReadStorage<'a, T>),
23    Write(&'b WriteStorage<'a, T>),
24    WriteOwned(WriteStorage<'a, T>),
25}
26
27impl<T: Component> EcsComponentAccess<'_, '_, T> {
28    pub fn get(&self, entity: Entity) -> Option<&T> {
29        match self {
30            EcsComponentAccess::Read(e) => e.get(entity),
31            EcsComponentAccess::Write(e) => e.get(entity),
32            EcsComponentAccess::ReadOwned(e) => e.get(entity),
33            EcsComponentAccess::WriteOwned(e) => e.get(entity),
34        }
35    }
36}
37
38impl<'a, 'b, T: Component> From<&'b ReadStorage<'a, T>> for EcsComponentAccess<'a, 'b, T> {
39    fn from(a: &'b ReadStorage<'a, T>) -> Self { Self::Read(a) }
40}
41
42impl<'a, T: Component> From<ReadStorage<'a, T>> for EcsComponentAccess<'a, '_, T> {
43    fn from(a: ReadStorage<'a, T>) -> Self { Self::ReadOwned(a) }
44}
45
46impl<'a, 'b, T: Component> From<&'b WriteStorage<'a, T>> for EcsComponentAccess<'a, 'b, T> {
47    fn from(a: &'b WriteStorage<'a, T>) -> Self { Self::Write(a) }
48}
49
50impl<'a, T: Component> From<WriteStorage<'a, T>> for EcsComponentAccess<'a, '_, T> {
51    fn from(a: WriteStorage<'a, T>) -> Self { Self::WriteOwned(a) }
52}
53
54/// This structure wraps the ECS pointer to ensure safety
55pub struct EcsAccessManager {
56    ecs_pointer: AtomicRefCell<Option<NonNull<EcsWorld<'static, 'static>>>>,
57}
58
59// SAFETY: Synchronization is handled via `AtomicRefCell`. These impls are
60// bounded on the Send/Sync properties of the EcsWorld reference.
61unsafe impl Send for EcsAccessManager where for<'a, 'b, 'c> &'a EcsWorld<'b, 'c>: Send {}
62unsafe impl Sync for EcsAccessManager where for<'a, 'b, 'c> &'a EcsWorld<'b, 'c>: Sync {}
63
64impl Default for EcsAccessManager {
65    fn default() -> Self {
66        Self {
67            ecs_pointer: AtomicRefCell::new(None),
68        }
69    }
70}
71
72impl EcsAccessManager {
73    // This function take a World reference and a function to execute ensuring the
74    // pointer will never be corrupted during the execution of the function!
75    pub fn execute_with<T>(&self, world: &EcsWorld, func: impl FnOnce() -> T) -> T {
76        let _guard = scopeguard::guard((), |_| {
77            // ensure the pointer is cleared in any case
78            if let Ok(mut ptr) = self.ecs_pointer.try_borrow_mut() {
79                *ptr = None;
80            } else {
81                error!("EcsWorld reference still in use when `func` finished, aborting");
82                std::process::abort();
83            }
84        });
85        *self.ecs_pointer.borrow_mut() =
86            Some(NonNull::from(world).cast::<EcsWorld<'static, 'static>>());
87        func()
88    }
89
90    /// Calls the provided closure with a reference to the ecs world.
91    ///
92    /// # Aborts
93    ///
94    /// Aborts if `execute_with` returns while calling this. For example, this
95    /// can happen if this is called from a separate thread and there isn't
96    /// anything preventing `execute_with` from returning.
97    pub fn with<F, R>(&self, f: F) -> R
98    where
99        F: for<'a, 'b, 'c> FnOnce(Option<&'a EcsWorld<'b, 'c>>) -> R,
100    {
101        let ptr = self.ecs_pointer.borrow();
102        let ecs_world = ptr.map(|ptr| {
103            // SAFETY: If this is Some, we are inside an `execute_with` call and this is
104            // a valid reference at least until `execute_with` returns.
105            //
106            // We hold a shared borrow guard while the reference is in use here and abort
107            // if `execute_with` finishes while this is still held.
108            //
109            // The called closure can't escape the reference because it must be callable for
110            // any set of lifetimes. Variance of the lifetime parameters in EcsWorld are
111            // not an issue for the same reason:
112            // https://discord.com/channels/273534239310479360/592856094527848449/1111018259815342202
113            unsafe { ptr.as_ref() }
114        });
115        f(ecs_world)
116    }
117}