Skip to main content

nexus_rt/
adapt.rs

1//! Event-type adapters for handlers.
2//!
3//! Each adapter wraps a single handler and transforms its event interface.
4//!
5//! - [`Adapt`] — decodes a wire event into a domain type, skipping
6//!   dispatch on `None`.
7//! - [`ByRef`] — wraps a `Handler<&E>` to implement `Handler<E>`.
8//!   The event is borrowed before dispatch.
9//! - [`Cloned`] — wraps a `Handler<E>` to implement `Handler<&E>`.
10//!   The event is cloned before dispatch. Explicit opt-in to the
11//!   clone cost.
12//! - [`Owned`] — wraps a `Handler<E::Owned>` to implement `Handler<&E>`
13//!   via [`ToOwned`]. More general than `Cloned`: handles `&str → String`,
14//!   `&[u8] → Vec<u8>`, etc.
15
16use crate::Handler;
17use crate::world::World;
18
19/// Lightweight adapter that decodes a wire-format event into a domain type
20/// before dispatching to an inner handler.
21///
22/// Implements [`Handler<Wire>`] by calling `decode(Wire) -> Option<T>`,
23/// then forwarding `T` to the inner [`Handler<T>`]. Skips dispatch when
24/// decode returns `None` (wrong template, decode error, filtered, etc.).
25///
26/// The decode function takes `Wire` by value. For reference types like
27/// SBE flyweight decoders (`ReadBuf<'a>`), this is already a borrow -
28/// no double indirection.
29///
30/// Both the decode function and inner handler are concrete types —
31/// monomorphizes to a direct call chain with no vtable overhead.
32///
33/// # Examples
34///
35/// ```
36/// use nexus_rt::{WorldBuilder, ResMut, IntoHandler, Handler, Resource};
37/// use nexus_rt::Adapt;
38///
39/// #[derive(Resource)]
40/// struct Counter(u64);
41///
42/// // Wire event — in practice this would be a decoder/buffer type.
43/// struct WireMsg(u32);
44///
45/// // Decode takes Wire by value. For reference-type wire events
46/// // (e.g. SBE flyweight decoders like MessageHeaderDecoder<ReadBuf<'a>>),
47/// // this is already a borrow — no double indirection.
48/// fn decode_wire(wire: &WireMsg) -> Option<u64> {
49///     Some(wire.0 as u64)
50/// }
51///
52/// fn accumulate(mut counter: ResMut<Counter>, event: u64) {
53///     counter.0 += event;
54/// }
55///
56/// let mut builder = WorldBuilder::new();
57/// builder.register(Counter(0));
58/// let mut world = builder.build();
59///
60/// let handler = accumulate.into_handler(world.registry());
61/// let mut adapted: Adapt<_, _> = Adapt::new(decode_wire, handler);
62///
63/// // Wire type is &WireMsg — reference type taken by value.
64/// adapted.run(&mut world, &WireMsg(10));
65/// adapted.run(&mut world, &WireMsg(5));
66/// assert_eq!(world.resource::<Counter>().0, 15);
67/// ```
68pub struct Adapt<F, H> {
69    decode: F,
70    inner: H,
71}
72
73impl<F, H> Adapt<F, H> {
74    /// Create a new adapter from a decode function and an inner handler.
75    pub fn new(decode: F, inner: H) -> Self {
76        Self { decode, inner }
77    }
78}
79
80impl<Wire, T, F, H> Handler<Wire> for Adapt<F, H>
81where
82    F: FnMut(Wire) -> Option<T> + Send,
83    H: Handler<T>,
84{
85    fn run(&mut self, world: &mut World, event: Wire) {
86        if let Some(decoded) = (self.decode)(event) {
87            self.inner.run(world, decoded);
88        }
89    }
90
91    fn name(&self) -> &'static str {
92        self.inner.name()
93    }
94}
95
96// =============================================================================
97// ByRef — owned-to-reference adapter
98// =============================================================================
99
100/// Owned-to-reference adapter. Wraps a [`Handler<&E>`](Handler) and
101/// implements `Handler<E>` — the event is borrowed before dispatch.
102///
103/// Use when a handler written for `&E` needs to slot into a position
104/// that provides owned `E`. This is the natural adapter for handlers
105/// inside [`FanOut`](crate::FanOut) or [`Broadcast`](crate::Broadcast)
106/// that were originally written for owned events.
107///
108/// # Examples
109///
110/// ```
111/// use nexus_rt::{WorldBuilder, ResMut, IntoHandler, Handler, ByRef, Resource};
112///
113/// #[derive(Resource)]
114/// struct Counter(u64);
115///
116/// fn process(mut counter: ResMut<Counter>, event: &u32) {
117///     counter.0 += *event as u64;
118/// }
119///
120/// let mut builder = WorldBuilder::new();
121/// builder.register(Counter(0));
122/// let mut world = builder.build();
123///
124/// let h = process.into_handler(world.registry());
125/// let mut adapted = ByRef(h);
126/// adapted.run(&mut world, 5u32);
127/// assert_eq!(world.resource::<Counter>().0, 5);
128/// ```
129pub struct ByRef<H>(pub H);
130
131impl<E, H> Handler<E> for ByRef<H>
132where
133    H: for<'e> Handler<&'e E> + Send,
134{
135    fn run(&mut self, world: &mut World, event: E) {
136        self.0.run(world, &event);
137    }
138
139    fn name(&self) -> &'static str {
140        self.0.name()
141    }
142}
143
144// =============================================================================
145// Cloned — reference-to-owned adapter
146// =============================================================================
147
148/// Reference-to-owned adapter. Wraps a [`Handler<E>`](Handler) and
149/// implements `Handler<&E>` — the event is cloned before dispatch.
150///
151/// Explicit opt-in to the clone cost. For `E: Copy` the compiler
152/// elides the clone entirely.
153///
154/// Use when an owned-event handler needs to participate in a
155/// reference-based dispatch context (e.g. inside a
156/// [`FanOut`](crate::FanOut)).
157///
158/// # Examples
159///
160/// ```
161/// use nexus_rt::{WorldBuilder, ResMut, IntoHandler, Handler, Cloned, Resource};
162///
163/// #[derive(Resource)]
164/// struct Counter(u64);
165///
166/// fn process(mut counter: ResMut<Counter>, event: u32) {
167///     counter.0 += event as u64;
168/// }
169///
170/// let mut builder = WorldBuilder::new();
171/// builder.register(Counter(0));
172/// let mut world = builder.build();
173///
174/// let h = process.into_handler(world.registry());
175/// let mut adapted = Cloned(h);
176/// adapted.run(&mut world, &5u32);
177/// assert_eq!(world.resource::<Counter>().0, 5);
178/// ```
179pub struct Cloned<H>(pub H);
180
181impl<'e, E: Clone + 'e, H: Handler<E> + Send> Handler<&'e E> for Cloned<H> {
182    fn run(&mut self, world: &mut World, event: &'e E) {
183        self.0.run(world, event.clone());
184    }
185
186    fn name(&self) -> &'static str {
187        self.0.name()
188    }
189}
190
191// =============================================================================
192// Owned — reference-to-owned adapter via ToOwned
193// =============================================================================
194
195/// Reference-to-owned adapter via [`ToOwned`]. Wraps a
196/// [`Handler<E::Owned>`](Handler) and implements `Handler<&E>` — the
197/// event is converted via [`to_owned()`](ToOwned::to_owned) before
198/// dispatch.
199///
200/// More general than [`Cloned`]: handles `&str → String`,
201/// `&[u8] → Vec<u8>`, and any other [`ToOwned`] impl where the owned
202/// type differs from the reference target. For `T: Clone`, `ToOwned`
203/// is blanket-implemented with `Owned = T`, so this adapter also
204/// works as a drop-in replacement for `Cloned` in those cases.
205///
206/// `E` must be named explicitly because the `ToOwned` mapping is not
207/// invertible — given `Handler<String>`, the compiler cannot infer
208/// that `E = str`. Use [`Owned::new`] with turbofish when needed.
209/// For simple `Clone` types where `E = E::Owned`, prefer [`Cloned`].
210///
211/// # Examples
212///
213/// ```
214/// use nexus_rt::{WorldBuilder, ResMut, IntoHandler, Handler, Owned, Resource};
215///
216/// #[derive(Resource)]
217/// struct Buffer(String);
218///
219/// fn process(mut buf: ResMut<Buffer>, event: String) {
220///     buf.0.push_str(&event);
221/// }
222///
223/// let mut builder = WorldBuilder::new();
224/// builder.register(Buffer(String::new()));
225/// let mut world = builder.build();
226///
227/// let h = process.into_handler(world.registry());
228/// let mut adapted = Owned::<_, str>::new(h);
229/// adapted.run(&mut world, "hello");
230/// assert_eq!(world.resource::<Buffer>().0.as_str(), "hello");
231/// ```
232pub struct Owned<H, E: ?Sized> {
233    handler: H,
234    _event: std::marker::PhantomData<fn(&E)>,
235}
236
237impl<H, E: ?Sized> Owned<H, E> {
238    /// Create a new `Owned` adapter.
239    ///
240    /// When `E` cannot be inferred, use turbofish:
241    /// `Owned::<_, str>::new(handler)`.
242    pub fn new(handler: H) -> Self {
243        Self {
244            handler,
245            _event: std::marker::PhantomData,
246        }
247    }
248}
249
250impl<'e, E, H> Handler<&'e E> for Owned<H, E>
251where
252    E: ToOwned + 'e + ?Sized,
253    H: Handler<E::Owned> + Send,
254{
255    fn run(&mut self, world: &mut World, event: &'e E) {
256        self.handler.run(world, event.to_owned());
257    }
258
259    fn name(&self) -> &'static str {
260        self.handler.name()
261    }
262}
263
264// =============================================================================
265// Tests
266// =============================================================================
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::{IntoHandler, ResMut, WorldBuilder};
272
273    struct WireMsg(u32);
274
275    // Wire type is &WireMsg — taken by value (already a reference).
276    #[allow(clippy::unnecessary_wraps)]
277    fn decode_wire(wire: &WireMsg) -> Option<u64> {
278        Some(wire.0 as u64)
279    }
280
281    fn decode_filter(wire: &WireMsg) -> Option<u64> {
282        if wire.0 > 0 {
283            Some(wire.0 as u64)
284        } else {
285            None
286        }
287    }
288
289    fn accumulate(mut counter: ResMut<u64>, event: u64) {
290        *counter += event;
291    }
292
293    #[test]
294    fn dispatch_decodes_and_forwards() {
295        let mut builder = WorldBuilder::new();
296        builder.register::<u64>(0);
297        let mut world = builder.build();
298
299        let handler = accumulate.into_handler(world.registry());
300        let mut adapted = Adapt::new(decode_wire, handler);
301
302        adapted.run(&mut world, &WireMsg(10));
303        adapted.run(&mut world, &WireMsg(5));
304        assert_eq!(*world.resource::<u64>(), 15);
305    }
306
307    #[test]
308    fn none_skips_dispatch() {
309        let mut builder = WorldBuilder::new();
310        builder.register::<u64>(0);
311        let mut world = builder.build();
312
313        let handler = accumulate.into_handler(world.registry());
314        let mut adapted = Adapt::new(decode_filter, handler);
315
316        adapted.run(&mut world, &WireMsg(10));
317        adapted.run(&mut world, &WireMsg(0)); // filtered
318        adapted.run(&mut world, &WireMsg(3));
319        assert_eq!(*world.resource::<u64>(), 13);
320    }
321
322    #[test]
323    fn delegates_name() {
324        let mut builder = WorldBuilder::new();
325        builder.register::<u64>(0);
326        let world = builder.build();
327
328        let handler = accumulate.into_handler(world.registry());
329        let expected = handler.name();
330        let adapted = Adapt::new(decode_wire, handler);
331
332        assert_eq!(adapted.name(), expected);
333    }
334
335    // -- ByRef ----------------------------------------------------------------
336
337    fn ref_accumulate(mut counter: ResMut<u64>, event: &u64) {
338        *counter += *event;
339    }
340
341    #[test]
342    fn by_ref_dispatch() {
343        let mut builder = WorldBuilder::new();
344        builder.register::<u64>(0);
345        let mut world = builder.build();
346
347        let h = ref_accumulate.into_handler(world.registry());
348        let mut adapted = ByRef(h);
349        adapted.run(&mut world, 10u64);
350        adapted.run(&mut world, 5u64);
351        assert_eq!(*world.resource::<u64>(), 15);
352    }
353
354    #[test]
355    fn by_ref_delegates_name() {
356        let mut builder = WorldBuilder::new();
357        builder.register::<u64>(0);
358        let world = builder.build();
359
360        let handler = ref_accumulate.into_handler(world.registry());
361        let expected = handler.name();
362        let adapted = ByRef(handler);
363        assert_eq!(adapted.name(), expected);
364    }
365
366    // -- Cloned ---------------------------------------------------------------
367
368    #[test]
369    fn cloned_dispatch() {
370        let mut builder = WorldBuilder::new();
371        builder.register::<u64>(0);
372        let mut world = builder.build();
373
374        let h = accumulate.into_handler(world.registry());
375        let mut adapted = Cloned(h);
376        adapted.run(&mut world, &10u64);
377        adapted.run(&mut world, &5u64);
378        assert_eq!(*world.resource::<u64>(), 15);
379    }
380
381    #[test]
382    fn cloned_delegates_name() {
383        let mut builder = WorldBuilder::new();
384        builder.register::<u64>(0);
385        let world = builder.build();
386
387        let handler = accumulate.into_handler(world.registry());
388        let expected = handler.name();
389        let adapted = Cloned(handler);
390        assert_eq!(adapted.name(), expected);
391    }
392
393    #[test]
394    fn cloned_copy_type() {
395        let mut builder = WorldBuilder::new();
396        builder.register::<u64>(0);
397        let mut world = builder.build();
398
399        // u32 is Copy — clone is free
400        fn add_u32(mut counter: ResMut<u64>, event: u32) {
401            *counter += event as u64;
402        }
403
404        let h = add_u32.into_handler(world.registry());
405        let mut adapted = Cloned(h);
406        adapted.run(&mut world, &42u32);
407        assert_eq!(*world.resource::<u64>(), 42);
408    }
409
410    // -- Owned ----------------------------------------------------------------
411
412    fn append_string(mut buf: ResMut<String>, event: String) {
413        buf.push_str(&event);
414    }
415
416    #[test]
417    fn owned_str_to_string() {
418        let mut builder = WorldBuilder::new();
419        builder.register::<String>(String::new());
420        let mut world = builder.build();
421
422        let h = append_string.into_handler(world.registry());
423        let mut adapted = Owned::<_, str>::new(h);
424        // &str → String via ToOwned
425        adapted.run(&mut world, "hello");
426        adapted.run(&mut world, " world");
427        assert_eq!(world.resource::<String>().as_str(), "hello world");
428    }
429
430    #[test]
431    fn owned_delegates_name() {
432        let mut builder = WorldBuilder::new();
433        builder.register::<String>(String::new());
434        let world = builder.build();
435
436        let handler = append_string.into_handler(world.registry());
437        let expected = handler.name();
438        let adapted = Owned::<_, str>::new(handler);
439        assert_eq!(adapted.name(), expected);
440    }
441
442    #[test]
443    fn owned_clone_type() {
444        let mut builder = WorldBuilder::new();
445        builder.register::<u64>(0);
446        let mut world = builder.build();
447
448        // u64: Clone, so ToOwned blanket impl gives Owned = u64
449        let h = accumulate.into_handler(world.registry());
450        let mut adapted = Owned::<_, u64>::new(h);
451        adapted.run(&mut world, &10u64);
452        adapted.run(&mut world, &5u64);
453        assert_eq!(*world.resource::<u64>(), 15);
454    }
455}