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}