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}