Skip to main content

pebble/ecs/
resources.rs

1use std::cell::{Cell, RefCell, RefMut};
2
3thread_local! {
4    /// Name of the system currently executing on this thread, set by
5    /// [`crate::app::App::run_stage_once`] around each system's `run` call.
6    /// Used to enrich the panic message when a system fetches a resource
7    /// that isn't present, so the trace names the offending system instead
8    /// of just the missing type — the pre-flight check in
9    /// [`App::validate_stage_resources`](crate::app::App::validate_stage_resources)
10    /// only covers bare `Res`/`ResMut` in non-convergent stages, so this is
11    /// the fallback that also covers convergent stages and any other path
12    /// that reaches `get_resource`/`get_resource_mut` directly.
13    static CURRENT_SYSTEM: Cell<Option<&'static str>> = Cell::new(None);
14}
15
16/// Set the name of the system about to run on this thread. Returns a guard
17/// that restores the previous value on drop, so nested/re-entrant calls
18/// (e.g. convergence passes) behave correctly.
19pub(crate) struct CurrentSystemGuard(Option<&'static str>);
20
21impl Drop for CurrentSystemGuard {
22    fn drop(&mut self) {
23        CURRENT_SYSTEM.with(|c| c.set(self.0));
24    }
25}
26
27pub(crate) fn set_current_system(name: &'static str) -> CurrentSystemGuard {
28    let previous = CURRENT_SYSTEM.with(|c| c.replace(Some(name)));
29    CurrentSystemGuard(previous)
30}
31
32fn current_system_suffix() -> String {
33    CURRENT_SYSTEM.with(|c| match c.get() {
34        Some(name) => format!(" (while running system `{name}`)"),
35        None => String::new(),
36    })
37}
38
39/// Container for singleton resources stored inside the ECS world.
40///
41/// All resources live on a single hidden entity so they participate in the
42/// same borrow-checking rules as regular components. [`Resources`] is passed
43/// to every system alongside the [`hecs::World`].
44pub struct Resources {
45    pub(crate) resource_entity: hecs::Entity,
46    cmds: RefCell<hecs::CommandBuffer>,
47    generation: Cell<u64>,
48}
49
50impl Resources {
51    /// Create a new `Resources` container, spawning the internal resource entity.
52    pub fn new(world: &mut hecs::World) -> Self {
53        Self {
54            resource_entity: world.spawn(()),
55            cmds: RefCell::new(hecs::CommandBuffer::default()),
56            generation: Cell::new(0),
57        }
58    }
59
60    /// Insert or replace a resource of type `T`.
61    pub fn insert_resource<T>(&mut self, world: &mut hecs::World, res: T)
62    where
63        T: hecs::Component,
64    {
65        world.insert_one(self.resource_entity, res).ok();
66        self.generation.set(self.generation.get() + 1);
67    }
68
69    /// Borrow resource `T`, panicking if it is not present.
70    pub fn get_resource<'a, T>(&self, world: &'a hecs::World) -> hecs::Ref<'a, T>
71    where
72        T: hecs::Component,
73    {
74        world.get::<&T>(self.resource_entity).unwrap_or_else(|_| {
75            panic!(
76                "Resource not found: {}{}",
77                std::any::type_name::<T>(),
78                current_system_suffix()
79            )
80        })
81    }
82
83    /// Mutably borrow resource `T`, panicking if it is not present.
84    pub fn get_resource_mut<'a, T>(&self, world: &'a hecs::World) -> hecs::RefMut<'a, T>
85    where
86        T: hecs::Component,
87    {
88        world.get::<&mut T>(self.resource_entity).unwrap_or_else(|_| {
89            panic!(
90                "Resource not found: {}{}",
91                std::any::type_name::<T>(),
92                current_system_suffix()
93            )
94        })
95    }
96
97    /// Returns `true` if resource `T` is currently present.
98    pub fn has_resource<T>(&self, world: &hecs::World) -> bool
99    where
100        T: hecs::Component,
101    {
102        if let Ok(_) = world.get::<&T>(self.resource_entity) {
103            return true;
104        }
105
106        false
107    }
108
109    /// Borrow the shared command buffer used to defer world mutations.
110    pub fn get_command_buffer<'a>(&'a self) -> RefMut<'a, hecs::CommandBuffer> {
111        self.cmds.borrow_mut()
112    }
113
114    /// Insert resource `T` only if it is not already present.
115    ///
116    /// Returns `true` if the resource was inserted, `false` if it already existed.
117    pub fn try_insert<T>(&mut self, world: &mut hecs::World, res: T) -> bool
118    where
119        T: hecs::Component,
120    {
121        if self.has_resource::<T>(world) {
122            return false;
123        }
124        world.insert_one(self.resource_entity, res).ok();
125        self.generation.set(self.generation.get() + 1);
126        true
127    }
128
129    pub fn generation(&self) -> u64 {
130        self.generation.get()
131    }
132
133    /// Manually bump the generation counter.
134    ///
135    /// Called by [`App`](crate::app::App) after flushing the command buffer when
136    /// it detects that new resources were inserted via [`Commands`](crate::ecs::system::Commands)
137    /// (which bypasses the normal [`insert_resource`](Self::insert_resource) path).
138    pub(crate) fn bump_generation(&self) {
139        self.generation.set(self.generation.get() + 1);
140    }
141}