Skip to main content

nexus_rt/
resource.rs

1//! Shared and mutable resource references for handler parameters.
2//!
3//! [`Res<T>`] and [`ResMut<T>`] appear in handler function signatures to
4//! declare read and write dependencies on [`World`](crate::World) resources.
5//! They are produced by [`Param::fetch`](crate::Param::fetch) during dispatch
6//! and deref to the inner `T` transparently.
7//!
8//! Both carry change-detection metadata: [`is_changed()`](Res::is_changed)
9//! compares the resource's `changed_at` stamp against the world's current
10//! sequence. [`ResMut`] stamps `changed_at` on [`DerefMut`] — the act of
11//! writing is the change signal, no manual marking needed.
12//!
13//! For optional dependencies, use [`Option<Res<T>>`] or
14//! [`Option<ResMut<T>>`] — these resolve to `None` if the type was not
15//! registered, rather than panicking at build time.
16//!
17//! # Examples
18//!
19//! ```
20//! use nexus_rt::{WorldBuilder, Res, ResMut, IntoHandler, Handler};
21//!
22//! fn process(config: Res<u64>, mut state: ResMut<bool>, _event: ()) {
23//!     if *config > 10 {
24//!         *state = true; // stamps changed_at
25//!     }
26//! }
27//!
28//! let mut builder = WorldBuilder::new();
29//! builder.register::<u64>(42);
30//! builder.register::<bool>(false);
31//! let mut world = builder.build();
32//!
33//! let mut handler = process.into_handler(world.registry());
34//! handler.run(&mut world, ());
35//!
36//! assert!(*world.resource::<bool>());
37//! ```
38
39use std::cell::Cell;
40use std::ops::{Deref, DerefMut};
41
42use crate::world::Sequence;
43
44/// Shared reference to a resource in [`World`](crate::World).
45///
46/// Analogous to Bevy's `Res<T>`.
47///
48/// Appears in handler function signatures to declare a read dependency.
49/// Derefs to the inner value transparently. Carries change-detection
50/// metadata — call [`is_changed`](Self::is_changed) to check.
51///
52/// For exclusive write access, use [`ResMut<T>`]. For optional read
53/// access (no panic if unregistered), use [`Option<Res<T>>`].
54///
55/// Construction is `pub(crate)` — only the dispatch layer creates these.
56pub struct Res<'w, T: 'static> {
57    value: &'w T,
58    changed_at: Sequence,
59    current_sequence: Sequence,
60}
61
62impl<'w, T: 'static> Res<'w, T> {
63    pub(crate) fn new(value: &'w T, changed_at: Sequence, current_sequence: Sequence) -> Self {
64        Self {
65            value,
66            changed_at,
67            current_sequence,
68        }
69    }
70
71    /// Returns `true` if the resource was modified during the current sequence.
72    pub fn is_changed(&self) -> bool {
73        self.changed_at == self.current_sequence
74    }
75
76    /// Returns `true` if the resource was modified after `since`.
77    ///
78    /// Unlike [`is_changed`](Self::is_changed) (equality check against
79    /// current sequence), this uses `>` — suitable for checking whether
80    /// any event since a prior checkpoint wrote this resource.
81    ///
82    /// Relies on numeric ordering of the underlying `u64` counter.
83    /// Not wrap-safe, but at one increment per event the `u64` sequence
84    /// space takes ~584 years at 1 GHz to exhaust.
85    pub fn changed_after(&self, since: Sequence) -> bool {
86        self.changed_at.0 > since.0
87    }
88}
89
90impl<T: std::fmt::Debug + 'static> std::fmt::Debug for Res<'_, T> {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        self.value.fmt(f)
93    }
94}
95
96impl<T: 'static> Deref for Res<'_, T> {
97    type Target = T;
98
99    #[inline(always)]
100    fn deref(&self) -> &T {
101        self.value
102    }
103}
104
105/// Mutable reference to a resource in [`World`](crate::World).
106///
107/// Analogous to Bevy's `ResMut<T>`.
108///
109/// Appears in handler function signatures to declare a write dependency.
110/// Derefs to the inner value transparently. Stamps the resource's
111/// `changed_at` sequence on [`DerefMut`] — the act of writing is the
112/// change signal.
113///
114/// For shared read access, use [`Res<T>`]. For optional write access
115/// (no panic if unregistered), use [`Option<ResMut<T>>`].
116///
117/// Construction is `pub(crate)` — only the dispatch layer creates these.
118pub struct ResMut<'w, T: 'static> {
119    value: &'w mut T,
120    changed_at: &'w Cell<Sequence>,
121    current_sequence: Sequence,
122}
123
124impl<'w, T: 'static> ResMut<'w, T> {
125    pub(crate) fn new(
126        value: &'w mut T,
127        changed_at: &'w Cell<Sequence>,
128        current_sequence: Sequence,
129    ) -> Self {
130        Self {
131            value,
132            changed_at,
133            current_sequence,
134        }
135    }
136
137    /// Returns `true` if the resource was modified during the current sequence.
138    pub fn is_changed(&self) -> bool {
139        self.changed_at.get() == self.current_sequence
140    }
141
142    /// Returns `true` if the resource was modified after `since`.
143    ///
144    /// Unlike [`is_changed`](Self::is_changed) (equality check against
145    /// current sequence), this uses `>` — suitable for checking whether
146    /// any event since a prior checkpoint wrote this resource.
147    ///
148    /// Relies on numeric ordering of the underlying `u64` counter.
149    /// Not wrap-safe, but at one increment per event the `u64` sequence
150    /// space takes ~584 years at 1 GHz to exhaust.
151    pub fn changed_after(&self, since: Sequence) -> bool {
152        self.changed_at.get().0 > since.0
153    }
154}
155
156impl<T: std::fmt::Debug + 'static> std::fmt::Debug for ResMut<'_, T> {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        self.value.fmt(f)
159    }
160}
161
162impl<T: 'static> Deref for ResMut<'_, T> {
163    type Target = T;
164
165    #[inline(always)]
166    fn deref(&self) -> &T {
167        self.value
168    }
169}
170
171impl<T: 'static> DerefMut for ResMut<'_, T> {
172    #[inline(always)]
173    fn deref_mut(&mut self) -> &mut T {
174        self.changed_at.set(self.current_sequence);
175        self.value
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn res_deref() {
185        let val = 42u64;
186        let res = Res::new(&val, Sequence::default(), Sequence::default());
187        assert_eq!(*res, 42);
188    }
189
190    #[test]
191    fn res_is_changed() {
192        let val = 42u64;
193        let tick = Sequence::default();
194        let res = Res::new(&val, tick, tick);
195        assert!(res.is_changed());
196    }
197
198    #[test]
199    fn res_not_changed() {
200        let val = 42u64;
201        // changed_at=0, current_sequence=1 → not changed
202        let res = Res::new(&val, Sequence::default(), Sequence(1));
203        assert!(!res.is_changed());
204    }
205
206    #[test]
207    fn res_mut_deref_mut() {
208        let mut val = 1u64;
209        let changed_at = Cell::new(Sequence::default());
210        let mut res = ResMut::new(&mut val, &changed_at, Sequence::default());
211        *res = 99;
212        assert_eq!(*res, 99);
213        drop(res);
214        assert_eq!(val, 99);
215    }
216
217    #[test]
218    fn res_mut_deref_mut_stamps() {
219        let mut val = 1u64;
220        let changed_at = Cell::new(Sequence(0));
221        let current = Sequence(5);
222        let mut res = ResMut::new(&mut val, &changed_at, current);
223
224        // Before DerefMut — changed_at is still 0
225        assert_eq!(changed_at.get(), Sequence(0));
226
227        *res = 99;
228
229        // After DerefMut — changed_at stamped to current_sequence
230        assert_eq!(changed_at.get(), Sequence(5));
231    }
232
233    #[test]
234    fn res_mut_deref_does_not_stamp() {
235        let mut val = 42u64;
236        let changed_at = Cell::new(Sequence(0));
237        let current = Sequence(5);
238        let res = ResMut::new(&mut val, &changed_at, current);
239
240        // Deref (shared) — read only, should not stamp
241        let _ = *res;
242        assert_eq!(changed_at.get(), Sequence(0));
243    }
244
245    #[test]
246    fn res_changed_after() {
247        let val = 42u64;
248        // changed_at=3, since=1 → changed after
249        let res = Res::new(&val, Sequence(3), Sequence(5));
250        assert!(res.changed_after(Sequence(1)));
251    }
252
253    #[test]
254    fn res_changed_after_equal_is_false() {
255        let val = 42u64;
256        // changed_at=3, since=3 → NOT changed after (equal, not greater)
257        let res = Res::new(&val, Sequence(3), Sequence(5));
258        assert!(!res.changed_after(Sequence(3)));
259    }
260
261    #[test]
262    fn res_mut_changed_after() {
263        let mut val = 1u64;
264        let changed_at = Cell::new(Sequence(5));
265        let res = ResMut::new(&mut val, &changed_at, Sequence(5));
266        assert!(res.changed_after(Sequence(2)));
267        assert!(!res.changed_after(Sequence(5)));
268        assert!(!res.changed_after(Sequence(7)));
269    }
270
271    #[test]
272    fn res_mut_is_changed() {
273        let mut val = 1u64;
274        let changed_at = Cell::new(Sequence(3));
275        let res = ResMut::new(&mut val, &changed_at, Sequence(3));
276        assert!(res.is_changed());
277
278        let res2 = ResMut::new(&mut val, &changed_at, Sequence(4));
279        assert!(!res2.is_changed());
280    }
281}