nexus_rt/world.rs
1//! Type-erased singleton resource storage.
2//!
3//! [`World`] is a unified store where each resource type gets a direct pointer
4//! ([`ResourceId`]) for O(1) dispatch-time access. Registration happens through
5//! [`WorldBuilder`], which freezes into an immutable [`World`] container via
6//! [`build()`](WorldBuilder::build).
7//!
8//! The type [`Registry`] maps types to [`ResourceId`] pointers. It is shared
9//! between [`WorldBuilder`] and [`World`], and is passed to [`Param::init`] and
10//! [`IntoHandler::into_handler`](crate::IntoHandler::into_handler) so that handlers can resolve their parameter
11//! state during driver setup — before or after `build()`.
12//!
13//! # Lifecycle
14//!
15//! ```text
16//! let mut builder = WorldBuilder::new();
17//! builder.register::<PriceCache>(value);
18//! builder.register::<TimerDriver>(value);
19//!
20//! // Drivers can resolve handlers against builder.registry()
21//! // before World is built.
22//!
23//! let world = builder.build(); // → World (frozen)
24//! ```
25//!
26//! After `build()`, the container is frozen — no inserts, no removes. All
27//! [`ResourceId`] values are valid for the lifetime of the [`World`] container.
28
29use std::any::{TypeId, type_name};
30use std::cell::Cell;
31#[cfg(debug_assertions)]
32use std::cell::UnsafeCell;
33use std::marker::PhantomData;
34use std::ptr::NonNull;
35use std::sync::Arc;
36use std::sync::atomic::AtomicBool;
37
38use rustc_hash::FxHashMap;
39
40// =============================================================================
41// Debug-mode aliasing detection
42// =============================================================================
43
44/// Tracks resource accesses within a single Param::fetch phase to detect
45/// aliasing violations at runtime in debug builds.
46///
47/// Dispatch macros call [`World::clear_borrows`] then
48/// [`World::track_borrow`] for each resource fetched. If the same resource
49/// is fetched twice within a phase, we panic with a diagnostic. This catches
50/// framework bugs where two params in the same handler resolve to the same
51/// resource — something [`Registry::check_access`] catches at construction
52/// time, but this catches dynamically for dispatch paths (like [`Opaque`](crate::Opaque)
53/// closures) that bypass static analysis.
54///
55/// Only active during the narrow `Param::fetch` window — safe API methods
56/// are not tracked.
57///
58/// Completely compiled out in release builds — zero bytes, zero branches.
59#[cfg(debug_assertions)]
60pub(crate) struct BorrowTracker {
61 /// Pointer addresses accessed in current phase.
62 /// Uses `UnsafeCell` for interior mutability because `Param::fetch` /
63 /// `World::track_borrow` operate on `&World`. Single-threaded,
64 /// non-reentrant access only.
65 accessed: UnsafeCell<Vec<NonNull<u8>>>,
66}
67
68#[cfg(debug_assertions)]
69impl BorrowTracker {
70 fn new() -> Self {
71 Self {
72 accessed: UnsafeCell::new(Vec::new()),
73 }
74 }
75
76 /// Reset all tracking state. Called before each `Param::fetch` phase.
77 fn clear(&self) {
78 // SAFETY: single-threaded, non-reentrant. No other reference to
79 // the inner Vec exists during this call.
80 let ptrs = unsafe { &mut *self.accessed.get() };
81 ptrs.clear();
82 }
83
84 /// Record an access. Panics if already accessed in this phase.
85 fn track(&self, id: ResourceId) {
86 // SAFETY: single-threaded, non-reentrant. No other reference to
87 // the inner Vec exists during this call.
88 let ptrs = unsafe { &mut *self.accessed.get() };
89 assert!(
90 !ptrs.contains(&id.0),
91 "conflicting access: resource {id} was accessed by more than one parameter \
92 in the same dispatch phase",
93 );
94 ptrs.push(id.0);
95 }
96}
97
98// =============================================================================
99// Core types
100// =============================================================================
101
102/// Direct pointer identifying a resource within a [`World`] container.
103///
104/// Points to a heap-allocated `ResourceCell<T>`. Dispatch-time access is
105/// a single deref — no index lookup, no Vec indirection.
106///
107/// Obtained from [`WorldBuilder::register`], [`WorldBuilder::ensure`],
108/// [`Registry::id`], [`World::id`], or their `try_` / `_default` variants.
109#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
110pub struct ResourceId(NonNull<u8>);
111
112impl ResourceId {
113 fn as_ptr(self) -> *mut u8 {
114 self.0.as_ptr()
115 }
116}
117
118// SAFETY: `ResourceId` is a thin, copyable handle to a `ResourceCell<T>`
119// allocated and pinned for the lifetime of its `World`:
120// - every `ResourceCell<T>` is registered with `T: Send`, so the pointee is
121// safe to send between threads,
122// - the underlying pointer is stable for the `World`'s entire lifetime, and
123// - a `ResourceId` cannot be dereferenced without going through `World`,
124// which enforces the single-threaded dispatch / aliasing invariants.
125unsafe impl Send for ResourceId {}
126
127impl std::fmt::Display for ResourceId {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 write!(f, "{:p}", self.0)
130 }
131}
132
133/// Monotonic event sequence number for event ordering.
134///
135/// Each event processed by a driver is assigned a unique sequence number
136/// via [`World::next_sequence`]. Handlers can read the current sequence
137/// via [`Seq`](crate::Seq) or advance it via [`SeqMut`](crate::SeqMut).
138///
139/// Uses `i64` for wire-format compatibility (FIX/SBE, Protobuf, Avro
140/// all have native signed 64-bit; unsigned is awkward or absent) and to
141/// support sentinel values ([`NULL`](Self::NULL),
142/// [`UNINITIALIZED`](Self::UNINITIALIZED)) without `Option` overhead.
143///
144/// Wrapping is harmless — at one increment per event, the positive `i64`
145/// space takes ~292 years at 1 GHz to exhaust.
146///
147/// # Sentinels
148///
149/// | Constant | Value | Meaning |
150/// |----------|-------|---------|
151/// | [`NULL`](Self::NULL) | `i64::MIN` | No sequence exists (SBE null convention) |
152/// | [`UNINITIALIZED`](Self::UNINITIALIZED) | `-1` | Not yet assigned |
153/// | [`ZERO`](Self::ZERO) | `0` | Starting point before any events |
154///
155/// # Examples
156///
157/// ```
158/// use nexus_rt::Sequence;
159///
160/// let a = Sequence::ZERO;
161/// let b = a.next();
162///
163/// assert!(b > a);
164/// assert_eq!(b.as_i64(), 1);
165/// assert_eq!(b.elapsed_since(a), 1);
166///
167/// assert!(Sequence::NULL.is_null());
168/// assert!(!Sequence::ZERO.is_null());
169/// ```
170#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
171pub struct Sequence(pub(crate) i64);
172
173impl Sequence {
174 /// SBE-compatible null — `i64::MIN`. Indicates no sequence exists.
175 ///
176 /// Maps directly to the SBE int64 null sentinel on the wire.
177 pub const NULL: Self = Self(i64::MIN);
178
179 /// Uninitialized sentinel — `-1`. Indicates a sequence has not yet
180 /// been assigned.
181 pub const UNINITIALIZED: Self = Self(-1);
182
183 /// The zero sequence — the starting point before any events.
184 pub const ZERO: Self = Self(0);
185
186 /// Create a sequence from a raw `i64` value.
187 ///
188 /// Use for construction in tests, deserialization, or replay.
189 pub const fn new(value: i64) -> Self {
190 Self(value)
191 }
192
193 /// Create a sequence from a raw `i64` value.
194 ///
195 /// Symmetric with [`as_i64`](Self::as_i64). Use for wire protocol
196 /// deserialization.
197 pub const fn from_i64(value: i64) -> Self {
198 Self(value)
199 }
200
201 /// Returns the raw `i64` value.
202 ///
203 /// Use for logging, metrics, serialization, or passing to external
204 /// systems.
205 pub const fn as_i64(self) -> i64 {
206 self.0
207 }
208
209 /// Returns `true` if this is the [`NULL`](Self::NULL) sentinel.
210 pub const fn is_null(self) -> bool {
211 self.0 == i64::MIN
212 }
213
214 /// Returns `true` if this is the [`UNINITIALIZED`](Self::UNINITIALIZED) sentinel.
215 pub const fn is_uninitialized(self) -> bool {
216 self.0 == -1
217 }
218
219 /// Returns the next sequence number (wrapping).
220 ///
221 /// This is a pure computation — it does not advance any world state.
222 /// Use [`World::next_sequence`] to actually advance the world's
223 /// current sequence.
224 pub const fn next(self) -> Self {
225 Self(self.0.wrapping_add(1))
226 }
227
228 /// Returns the number of events between `earlier` and `self`.
229 ///
230 /// Wrapping-aware: if `self` has wrapped past `earlier`, the result
231 /// is the wrapping distance. Returns 0 if `self == earlier`.
232 pub const fn elapsed_since(self, earlier: Self) -> i64 {
233 self.0.wrapping_sub(earlier.0)
234 }
235}
236
237impl std::fmt::Display for Sequence {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 self.0.fmt(f)
240 }
241}
242
243/// Heap-allocated wrapper for a single resource value.
244///
245/// Resources are individually heap-allocated via `Box::new(ResourceCell { value })`.
246/// The `ResourceId` is the raw pointer to this cell — a single deref to reach
247/// the value with zero framework overhead.
248pub(crate) struct ResourceCell<T> {
249 pub(crate) value: T,
250}
251
252/// Reconstruct and drop a `Box<ResourceCell<T>>` from a raw pointer.
253///
254/// # Safety
255///
256/// `ptr` must have been produced by `Box::into_raw(Box::new(ResourceCell { .. }))`
257/// where the value field is `T`. Must only be called once per pointer.
258unsafe fn drop_resource<T>(ptr: *mut u8) {
259 // SAFETY: ptr was produced by Box::into_raw(Box::new(ResourceCell { .. }))
260 // where value: T. Called exactly once in Storage::drop.
261 unsafe {
262 let _ = Box::from_raw(ptr as *mut ResourceCell<T>);
263 }
264}
265
266// =============================================================================
267// Registry — type-to-pointer mapping
268// =============================================================================
269
270/// Type-to-pointer mapping shared between [`WorldBuilder`] and [`World`].
271///
272/// Contains only the type registry — no storage backing. Passed to
273/// [`IntoHandler::into_handler`](crate::IntoHandler::into_handler) and
274/// [`Param::init`](crate::Param::init) so handlers can resolve
275/// [`ResourceId`]s during driver setup.
276///
277/// Obtained via [`WorldBuilder::registry()`] or [`World::registry()`].
278pub struct Registry {
279 indices: FxHashMap<TypeId, ResourceId>,
280}
281
282impl Registry {
283 pub(crate) fn new() -> Self {
284 Self {
285 indices: FxHashMap::default(),
286 }
287 }
288
289 /// Resolve the [`ResourceId`] for a type. Cold path — uses HashMap lookup.
290 ///
291 /// # Panics
292 ///
293 /// Panics if the resource type was not registered.
294 pub fn id<T: Resource>(&self) -> ResourceId {
295 *self
296 .indices
297 .get(&TypeId::of::<T>())
298 .unwrap_or_else(|| {
299 panic!(
300 "resource `{}` not registered — call WorldBuilder::register::<{}>(initial_value) during setup",
301 type_name::<T>(),
302 type_name::<T>()
303 )
304 })
305 }
306
307 /// Try to resolve the [`ResourceId`] for a type. Returns `None` if the
308 /// type was not registered.
309 pub fn try_id<T: Resource>(&self) -> Option<ResourceId> {
310 self.indices.get(&TypeId::of::<T>()).copied()
311 }
312
313 /// Returns `true` if a resource of type `T` has been registered.
314 pub fn contains<T: Resource>(&self) -> bool {
315 self.indices.contains_key(&TypeId::of::<T>())
316 }
317
318 /// Returns the number of registered resources.
319 pub fn len(&self) -> usize {
320 self.indices.len()
321 }
322
323 /// Returns `true` if no resources have been registered.
324 pub fn is_empty(&self) -> bool {
325 self.indices.is_empty()
326 }
327
328 /// Validate that a set of parameter accesses don't conflict.
329 ///
330 /// Two accesses conflict when they target the same ResourceId (same
331 /// pointer). O(n²) pairwise comparison — handler arity is 1-8, so
332 /// this is trivially fast at build time.
333 ///
334 /// # Panics
335 ///
336 /// Panics if any resource is accessed by more than one parameter.
337 #[cold]
338 pub fn check_access(&self, accesses: &[(Option<ResourceId>, &str)]) {
339 for i in 0..accesses.len() {
340 let Some(id_i) = accesses[i].0 else { continue };
341 for j in (i + 1)..accesses.len() {
342 let Some(id_j) = accesses[j].0 else { continue };
343 assert!(
344 id_i != id_j,
345 "conflicting access: resource borrowed by `{}` conflicts with \
346 resource borrowed by `{}` in the same handler",
347 accesses[j].1,
348 accesses[i].1,
349 );
350 }
351 }
352 }
353}
354
355// =============================================================================
356// Storage — shared backing between builder and frozen container
357// =============================================================================
358
359/// Pointer + type-erased drop function for a single resource.
360struct DropEntry {
361 ptr: *mut u8,
362 drop_fn: unsafe fn(*mut u8),
363}
364
365/// Internal storage for type-erased resource cleanup.
366///
367/// Only walked during [`World::drop`] — no dispatch-time role. The actual
368/// resource data lives in individually heap-allocated `ResourceCell<T>`
369/// values, pointed to by [`ResourceId`].
370pub(crate) struct Storage {
371 drop_entries: Vec<DropEntry>,
372}
373
374impl Storage {
375 pub(crate) fn new() -> Self {
376 Self {
377 drop_entries: Vec::new(),
378 }
379 }
380
381 pub(crate) fn len(&self) -> usize {
382 self.drop_entries.len()
383 }
384
385 pub(crate) fn is_empty(&self) -> bool {
386 self.drop_entries.is_empty()
387 }
388}
389
390// SAFETY: All values stored in Storage were registered via `register<T: Send + 'static>`,
391// so every concrete type behind the raw pointers is Send. Storage exclusively owns
392// these heap allocations — they are not aliased or shared. Transferring ownership
393// to another thread is safe.
394#[allow(clippy::non_send_fields_in_send_ty)]
395unsafe impl Send for Storage {}
396
397impl Drop for Storage {
398 fn drop(&mut self) {
399 for entry in &self.drop_entries {
400 // SAFETY: each DropEntry was created in WorldBuilder::register().
401 // drop_fn is the monomorphized destructor for the concrete
402 // ResourceCell<T> behind ptr. Called exactly once here.
403 unsafe {
404 (entry.drop_fn)(entry.ptr);
405 }
406 }
407 }
408}
409
410// =============================================================================
411// WorldBuilder
412// =============================================================================
413
414/// Builder for registering resources before freezing into a [`World`] container.
415///
416/// Each resource type can only be registered once. Registration assigns a
417/// direct [`ResourceId`] pointer.
418///
419/// The [`registry()`](Self::registry) method exposes the type-to-pointer mapping
420/// so that drivers can resolve handlers against the builder before `build()`.
421///
422/// # Examples
423///
424/// ```
425/// use nexus_rt::{WorldBuilder, Resource};
426///
427/// #[derive(Resource)]
428/// struct Counter(u64);
429/// #[derive(Resource)]
430/// struct Flag(bool);
431///
432/// let mut builder = WorldBuilder::new();
433/// let id = builder.register(Counter(42));
434/// builder.register(Flag(true));
435/// let world = builder.build();
436///
437/// unsafe {
438/// assert_eq!(world.get::<Counter>(id).0, 42);
439/// }
440/// ```
441pub struct WorldBuilder {
442 registry: Registry,
443 storage: Storage,
444}
445
446/// Marker trait for types that can be stored in a [`World`].
447///
448/// Requires `Send + 'static`. Use `#[derive(Resource)]` to implement,
449/// or [`new_resource!`](crate::new_resource) for newtype wrappers.
450///
451/// ```
452/// use nexus_rt::Resource;
453///
454/// #[derive(Resource)]
455/// struct OrderBook {
456/// bids: Vec<(f64, f64)>,
457/// asks: Vec<(f64, f64)>,
458/// }
459/// ```
460///
461/// # Why not just `Send + 'static`?
462///
463/// Without the marker trait, two modules can independently register
464/// `u64` and silently collide. The `Resource` bound forces a newtype,
465/// making collisions a compile error.
466#[diagnostic::on_unimplemented(
467 message = "this type cannot be stored as a resource in the World",
468 note = "add `#[derive(Resource)]` to your type, or use `new_resource!` for a newtype wrapper"
469)]
470pub trait Resource: Send + 'static {}
471
472// Test-only impls for primitive types used in unit tests within this crate.
473// NOT available to external crates — they should use #[derive(Resource)] or new_resource!.
474#[cfg(test)]
475mod resource_test_impls {
476 use super::Resource;
477 impl Resource for bool {}
478 impl Resource for u32 {}
479 impl Resource for u64 {}
480 impl Resource for i64 {}
481 impl Resource for f64 {}
482 impl Resource for String {}
483 impl<T: Send + 'static> Resource for Vec<T> {}
484 impl<T: Send + Sync + 'static> Resource for std::sync::Arc<T> {}
485}
486
487impl WorldBuilder {
488 /// Create an empty builder.
489 pub fn new() -> Self {
490 Self {
491 registry: Registry::new(),
492 storage: Storage::new(),
493 }
494 }
495
496 /// Register a resource and return its [`ResourceId`].
497 ///
498 /// The value is heap-allocated inside a `ResourceCell<T>` and ownership
499 /// is transferred to the container. The pointer is stable for the
500 /// lifetime of the resulting [`World`].
501 ///
502 /// # Panics
503 ///
504 /// Panics if a resource of the same type is already registered.
505 #[cold]
506 pub fn register<T: Resource>(&mut self, value: T) -> ResourceId {
507 let type_id = TypeId::of::<T>();
508 assert!(
509 !self.registry.indices.contains_key(&type_id),
510 "resource `{}` already registered",
511 type_name::<T>(),
512 );
513
514 let cell = Box::new(ResourceCell { value });
515 let raw = Box::into_raw(cell) as *mut u8;
516 // SAFETY: Box::into_raw never returns null.
517 let ptr = unsafe { NonNull::new_unchecked(raw) };
518 let id = ResourceId(ptr);
519 self.registry.indices.insert(type_id, id);
520 self.storage.drop_entries.push(DropEntry {
521 ptr: raw,
522 drop_fn: drop_resource::<T>,
523 });
524 id
525 }
526
527 /// Register a resource using its [`Default`] value and return its
528 /// [`ResourceId`].
529 ///
530 /// Equivalent to `self.register::<T>(T::default())`.
531 #[cold]
532 pub fn register_default<T: Default + Resource>(&mut self) -> ResourceId {
533 self.register(T::default())
534 }
535
536 /// Ensure a resource is registered, returning its [`ResourceId`].
537 ///
538 /// If the type is already registered, returns the existing ID and
539 /// drops `value`. If not, registers it and returns the new ID.
540 ///
541 /// Use [`register`](Self::register) when duplicate registration is a
542 /// bug that should panic. Use `ensure` when multiple plugins or
543 /// drivers may independently need the same resource type.
544 #[cold]
545 pub fn ensure<T: Resource>(&mut self, value: T) -> ResourceId {
546 if let Some(id) = self.registry.try_id::<T>() {
547 return id;
548 }
549 self.register(value)
550 }
551
552 /// Ensure a resource is registered using its [`Default`] value,
553 /// returning its [`ResourceId`].
554 ///
555 /// If the type is already registered, returns the existing ID.
556 /// If not, registers `T::default()` and returns the new ID.
557 #[cold]
558 pub fn ensure_default<T: Default + Resource>(&mut self) -> ResourceId {
559 if let Some(id) = self.registry.try_id::<T>() {
560 return id;
561 }
562 self.register(T::default())
563 }
564
565 /// Returns a shared reference to the type registry.
566 ///
567 /// Use this for construction-time calls like
568 /// [`into_handler`](crate::IntoHandler::into_handler),
569 /// [`into_callback`](crate::IntoCallback::into_callback), and
570 /// [`into_step`](crate::pipeline::IntoStep::into_step).
571 pub fn registry(&self) -> &Registry {
572 &self.registry
573 }
574
575 /// Returns a mutable reference to the type registry.
576 ///
577 /// Rarely needed — [`registry()`](Self::registry) suffices for
578 /// construction-time calls. Exists for direct mutation of the
579 /// registry if needed.
580 #[allow(dead_code)]
581 pub(crate) fn registry_mut(&mut self) -> &mut Registry {
582 &mut self.registry
583 }
584
585 /// Returns the number of registered resources.
586 pub fn len(&self) -> usize {
587 self.storage.len()
588 }
589
590 /// Returns `true` if no resources have been registered.
591 pub fn is_empty(&self) -> bool {
592 self.storage.is_empty()
593 }
594
595 /// Returns `true` if a resource of type `T` has been registered.
596 pub fn contains<T: Resource>(&self) -> bool {
597 self.registry.contains::<T>()
598 }
599
600 /// Install a plugin. The plugin is consumed and registers its
601 /// resources into this builder.
602 pub fn install_plugin(&mut self, plugin: impl crate::plugin::Plugin) -> &mut Self {
603 plugin.build(self);
604 self
605 }
606
607 /// Install a driver. The installer is consumed, registers its resources
608 /// into this builder, and returns a concrete poller for dispatch-time
609 /// polling.
610 pub fn install_driver<D: crate::driver::Installer>(&mut self, driver: D) -> D::Poller {
611 driver.install(self)
612 }
613
614 /// Freeze the builder into an immutable [`World`] container.
615 ///
616 /// After this call, no more resources can be registered. All
617 /// [`ResourceId`] values remain valid for the lifetime of the
618 /// returned [`World`].
619 ///
620 /// When the `reactors` feature is enabled, `ReactorNotify`,
621 /// `DeferredRemovals`, and
622 /// `SourceRegistry` are automatically registered
623 /// if not already present.
624 #[allow(unused_mut)]
625 pub fn build(mut self) -> World {
626 #[cfg(feature = "reactors")]
627 let (reactor_notify_id, reactor_removals_id) = {
628 self.ensure(crate::reactor::ReactorNotify::new(16, 64));
629 self.ensure(crate::reactor::DeferredRemovals::default());
630 self.ensure(crate::reactor::SourceRegistry::new());
631 (
632 self.registry.id::<crate::reactor::ReactorNotify>(),
633 self.registry.id::<crate::reactor::DeferredRemovals>(),
634 )
635 };
636
637 World {
638 registry: self.registry,
639 storage: self.storage,
640 current_sequence: Cell::new(Sequence(0)),
641 shutdown: Arc::new(AtomicBool::new(false)),
642 _not_sync: PhantomData,
643 #[cfg(feature = "reactors")]
644 reactor_notify_id,
645 #[cfg(feature = "reactors")]
646 reactor_removals_id,
647 #[cfg(feature = "reactors")]
648 reactor_events: Some(nexus_notify::Events::with_capacity(256)),
649 #[cfg(debug_assertions)]
650 borrow_tracker: BorrowTracker::new(),
651 }
652 }
653}
654
655impl Default for WorldBuilder {
656 fn default() -> Self {
657 Self::new()
658 }
659}
660
661// =============================================================================
662// World — frozen container
663// =============================================================================
664
665/// Frozen singleton resource storage.
666///
667/// Analogous to Bevy's `World`, but restricted to singleton resources
668/// (no entities, no components, no archetypes).
669///
670/// Created by [`WorldBuilder::build()`]. Resources are accessed via
671/// [`ResourceId`] direct pointers for O(1) dispatch-time access — a single
672/// pointer deref per fetch, zero framework overhead.
673///
674/// # Safe API
675///
676/// - [`resource`](Self::resource) / [`resource_mut`](Self::resource_mut) —
677/// cold-path access via HashMap lookup.
678///
679/// # Unsafe API (framework internals)
680///
681/// The low-level `get` / `get_mut` methods are `unsafe` — used by
682/// [`Param::fetch`](crate::Param) for zero-overhead dispatch.
683/// The caller must ensure no mutable aliasing.
684pub struct World {
685 /// Type-to-pointer mapping. Same registry used during build.
686 registry: Registry,
687 /// Type-erased pointer storage. Drop handled by `Storage`.
688 storage: Storage,
689 /// Current sequence number. `Cell` so handlers can advance it
690 /// through `&World` via [`SeqMut`](crate::SeqMut).
691 current_sequence: Cell<Sequence>,
692 /// Cooperative shutdown flag. Shared with [`ShutdownHandle`](crate::ShutdownHandle)
693 /// via `Arc`. Handlers access it through the [`Shutdown`](crate::Shutdown) Param.
694 shutdown: Arc<AtomicBool>,
695 /// World must not be shared across threads — it holds interior-mutable
696 /// `Cell<Sequence>` values accessed through `&self`. `!Sync` enforced by
697 /// `PhantomData<Cell<()>>`.
698 _not_sync: PhantomData<Cell<()>>,
699 /// Pre-resolved pointer to ReactorNotify for O(1) reactor operations.
700 #[cfg(feature = "reactors")]
701 reactor_notify_id: ResourceId,
702 /// Pre-resolved pointer to DeferredRemovals (avoids HashMap lookup per frame).
703 #[cfg(feature = "reactors")]
704 reactor_removals_id: ResourceId,
705 /// Pre-allocated events buffer for dispatch (avoids allocation per frame).
706 #[cfg(feature = "reactors")]
707 reactor_events: Option<nexus_notify::Events>,
708 /// Debug-only aliasing tracker. Detects duplicate resource access within
709 /// a single dispatch phase. Compiled out entirely in release builds.
710 #[cfg(debug_assertions)]
711 borrow_tracker: BorrowTracker,
712}
713
714impl World {
715 /// Convenience constructor — returns a new [`WorldBuilder`].
716 pub fn builder() -> WorldBuilder {
717 WorldBuilder::new()
718 }
719
720 /// Returns a shared reference to the type registry.
721 ///
722 /// Use this for read-only queries (e.g. [`id`](Registry::id),
723 /// [`contains`](Registry::contains)) and construction-time calls
724 /// like [`into_handler`](crate::IntoHandler::into_handler).
725 pub fn registry(&self) -> &Registry {
726 &self.registry
727 }
728
729 /// Returns a mutable reference to the type registry.
730 ///
731 /// Rarely needed — [`registry()`](Self::registry) suffices for
732 /// construction-time calls. Exists for direct mutation of the
733 /// registry if needed.
734 #[allow(dead_code)]
735 pub(crate) fn registry_mut(&mut self) -> &mut Registry {
736 &mut self.registry
737 }
738
739 /// Resolve the [`ResourceId`] for a type. Cold path — uses HashMap lookup.
740 ///
741 /// # Panics
742 ///
743 /// Panics if the resource type was not registered.
744 pub fn id<T: Resource>(&self) -> ResourceId {
745 self.registry.id::<T>()
746 }
747
748 /// Try to resolve the [`ResourceId`] for a type. Returns `None` if the
749 /// type was not registered.
750 pub fn try_id<T: Resource>(&self) -> Option<ResourceId> {
751 self.registry.try_id::<T>()
752 }
753
754 /// Returns the number of registered resources.
755 pub fn len(&self) -> usize {
756 self.storage.len()
757 }
758
759 /// Returns `true` if no resources are stored.
760 pub fn is_empty(&self) -> bool {
761 self.storage.is_empty()
762 }
763
764 /// Returns `true` if a resource of type `T` is stored.
765 pub fn contains<T: Resource>(&self) -> bool {
766 self.registry.contains::<T>()
767 }
768
769 // =========================================================================
770 // Safe resource access (cold path — HashMap lookup per call)
771 // =========================================================================
772
773 /// Safe shared access to a resource. Cold path — resolves via HashMap.
774 ///
775 /// Takes `&self` — multiple shared references can coexist. The borrow
776 /// checker prevents mixing with [`resource_mut`](Self::resource_mut)
777 /// (which takes `&mut self`).
778 ///
779 /// # Panics
780 ///
781 /// Panics if the resource type was not registered.
782 pub fn resource<T: Resource>(&self) -> &T {
783 let id = self.registry.id::<T>();
784 // SAFETY: id resolved from our own registry. &self prevents mutable
785 // aliases — resource_mut takes &mut self.
786 unsafe { self.get(id) }
787 }
788
789 /// Safe exclusive access to a resource. Cold path — resolves via HashMap.
790 ///
791 /// # Panics
792 ///
793 /// Panics if the resource type was not registered.
794 pub fn resource_mut<T: Resource>(&mut self) -> &mut T {
795 let id = self.registry.id::<T>();
796 // SAFETY: id resolved from our own registry. &mut self ensures
797 // exclusive access — no other references can exist.
798 unsafe { self.get_mut(id) }
799 }
800
801 // =========================================================================
802 // One-shot dispatch
803 // =========================================================================
804
805 /// Run a system once with full Param resolution.
806 ///
807 /// Intended for one-shot initialization after [`build()`](WorldBuilder::build).
808 /// Accepts both void-returning (`fn(params...)`) and bool-returning
809 /// (`fn(params...) -> bool`) functions via [`IntoSystem`](crate::IntoSystem).
810 /// The return value is always ignored — startup has no DAG to
811 /// propagate through. Named functions only (same closure limitation
812 /// as [`IntoHandler`](crate::IntoHandler)).
813 ///
814 /// Can be called multiple times for phased initialization.
815 ///
816 /// # Examples
817 ///
818 /// ```ignore
819 /// fn startup(
820 /// mut driver: ResMut<MioDriver>,
821 /// mut listener: ResMut<Listener>,
822 /// ) {
823 /// // wire drivers to IO sources...
824 /// }
825 ///
826 /// let mut world = wb.build();
827 /// world.run_startup(startup);
828 /// ```
829 pub fn run_startup<F, Params, M>(&mut self, f: F)
830 where
831 F: crate::IntoSystem<Params, M>,
832 {
833 use crate::System;
834 let mut sys = f.into_system(&self.registry);
835 sys.run(self);
836 }
837
838 // =========================================================================
839 // Shutdown
840 // =========================================================================
841
842 /// Returns a [`ShutdownHandle`](crate::shutdown::ShutdownHandle)
843 /// sharing the same flag as the world's shutdown state.
844 ///
845 /// The handle is owned by the event loop and checked each iteration.
846 /// Handlers trigger shutdown via the [`Shutdown`](crate::Shutdown) Param.
847 pub fn shutdown_handle(&self) -> crate::shutdown::ShutdownHandle {
848 crate::shutdown::ShutdownHandle::new(Arc::clone(&self.shutdown))
849 }
850
851 /// Returns a reference to the shutdown flag.
852 ///
853 /// Used by the [`Shutdown`](crate::Shutdown) Param for direct access.
854 pub(crate) fn shutdown_flag(&self) -> &AtomicBool {
855 &self.shutdown
856 }
857
858 /// Run the event loop until shutdown is triggered.
859 ///
860 /// The closure receives `&mut World` and defines one iteration of
861 /// the poll loop — which drivers to poll, in what order, with what
862 /// timeout. The loop exits when a handler calls
863 /// [`Shutdown::trigger`](crate::Shutdown::trigger) or
864 /// an external signal flips the flag (see
865 /// `ShutdownHandle::enable_signals` (requires `signals` feature)).
866 ///
867 /// # Examples
868 ///
869 /// ```ignore
870 /// let mut world = wb.build();
871 /// world.run_startup(startup);
872 ///
873 /// world.run(|world| {
874 /// let now = Instant::now();
875 /// let timeout = timer.next_deadline(world)
876 /// .map(|d| d.saturating_duration_since(now));
877 /// mio.poll(world, timeout).expect("mio poll");
878 /// timer.poll(world, now);
879 /// });
880 /// ```
881 pub fn run(&mut self, mut f: impl FnMut(&mut World)) {
882 while !self.shutdown.load(std::sync::atomic::Ordering::Relaxed) {
883 f(self);
884 }
885 }
886
887 // =========================================================================
888 // Sequence
889 // =========================================================================
890
891 /// Returns the current event sequence number.
892 pub fn current_sequence(&self) -> Sequence {
893 self.current_sequence.get()
894 }
895
896 /// Advance to the next event sequence number and return it.
897 ///
898 /// Drivers call this before dispatching each event. The returned
899 /// sequence number identifies the event being processed.
900 pub fn next_sequence(&mut self) -> Sequence {
901 let next = Sequence(self.current_sequence.get().0.wrapping_add(1));
902 self.current_sequence.set(next);
903 next
904 }
905
906 /// Returns a reference to the sequence `Cell`.
907 ///
908 /// Used by [`SeqMut`](crate::SeqMut) Param for direct access.
909 pub(crate) fn sequence_cell(&self) -> &Cell<Sequence> {
910 &self.current_sequence
911 }
912
913 /// Set the current sequence number directly.
914 ///
915 /// Use for recovery / replay — restores the sequence to a known
916 /// checkpoint so that subsequent `next_sequence` calls continue
917 /// from the right point.
918 pub fn set_sequence(&mut self, seq: Sequence) {
919 self.current_sequence.set(seq);
920 }
921
922 // =========================================================================
923 // Unsafe resource access (hot path — pre-resolved ResourceId)
924 // =========================================================================
925
926 /// Fetch a shared reference to a resource by direct pointer.
927 ///
928 /// # Safety
929 ///
930 /// - `id` must have been returned by [`WorldBuilder::register`] for
931 /// the same builder that produced this container.
932 /// - `T` must be the same type that was registered at this `id`.
933 /// - The caller must ensure no mutable reference to this resource exists.
934 #[inline(always)]
935 pub unsafe fn get<T: 'static>(&self, id: ResourceId) -> &T {
936 // SAFETY: caller guarantees id was returned by register() on the
937 // builder that produced this container. T matches the registered type.
938 // No mutable alias exists. ResourceId points to a valid ResourceCell<T>.
939 unsafe { &(*(id.as_ptr() as *const ResourceCell<T>)).value }
940 }
941
942 /// Fetch a mutable reference to a resource by direct pointer.
943 ///
944 /// Takes `&self` — the container structure is frozen, but individual
945 /// resources have interior mutability via raw pointers. Sound because
946 /// callers (single-threaded sequential dispatch) uphold no-aliasing.
947 ///
948 /// # Safety
949 ///
950 /// - `id` must have been returned by [`WorldBuilder::register`] for
951 /// the same builder that produced this container.
952 /// - `T` must be the same type that was registered at this `id`.
953 /// - The caller must ensure no other reference (shared or mutable) to this
954 /// resource exists.
955 #[inline(always)]
956 #[allow(clippy::mut_from_ref)]
957 pub unsafe fn get_mut<T: 'static>(&self, id: ResourceId) -> &mut T {
958 // SAFETY: caller guarantees id was returned by register() on the
959 // builder that produced this container. T matches the registered type.
960 // No aliases exist. ResourceId points to a valid ResourceCell<T>.
961 unsafe { &mut (*(id.as_ptr() as *mut ResourceCell<T>)).value }
962 }
963
964 /// Reset borrow tracking for a new dispatch phase.
965 ///
966 /// Called before each [`Param::fetch`](crate::Param::fetch) in dispatch
967 /// macros. Only exists in debug builds.
968 #[cfg(debug_assertions)]
969 pub(crate) fn clear_borrows(&self) {
970 self.borrow_tracker.clear();
971 }
972
973 /// Record a resource access in the debug borrow tracker.
974 ///
975 /// Called by [`Param::fetch`](crate::Param::fetch) impls for each
976 /// resource parameter. Panics if the resource was already accessed
977 /// in the current phase (since the last [`clear_borrows`](Self::clear_borrows)).
978 /// Only exists in debug builds.
979 #[cfg(debug_assertions)]
980 pub(crate) fn track_borrow(&self, id: ResourceId) {
981 self.borrow_tracker.track(id);
982 }
983
984 // =========================================================================
985 // Reactor convenience methods (behind `reactors` feature)
986 // =========================================================================
987
988 /// Register a new data source for reactor subscriptions.
989 ///
990 /// Convenience for `world.resource_mut::<ReactorNotify>().register_source()`.
991 #[cfg(feature = "reactors")]
992 pub fn register_source(&mut self) -> crate::reactor::DataSource {
993 self.resource_mut::<crate::reactor::ReactorNotify>()
994 .register_source()
995 }
996
997 /// Create and register a reactor from a step function + context factory.
998 ///
999 /// The closure receives the assigned [`Token`](nexus_notify::Token) so
1000 /// the reactor can store it for wire routing or self-deregistration.
1001 /// Params are resolved from the internal registry — single pointer
1002 /// deref at dispatch time.
1003 ///
1004 /// This is the primary registration API. It handles the borrow
1005 /// juggling internally: allocates the token, resolves params from
1006 /// the registry, and inserts the reactor — all in one call.
1007 ///
1008 /// # Example
1009 ///
1010 /// ```ignore
1011 /// let src = world.register_source();
1012 /// world.spawn_reactor(
1013 /// |id| QuotingCtx { reactor_id: id, instrument: BTC, layer: 1 },
1014 /// quoting_step,
1015 /// ).subscribe(src);
1016 /// ```
1017 #[cfg(feature = "reactors")]
1018 pub fn spawn_reactor<C, Params, F: crate::reactor::IntoReactor<C, Params>>(
1019 &mut self,
1020 ctx_fn: impl FnOnce(nexus_notify::Token) -> C,
1021 step: F,
1022 ) -> crate::reactor::ReactorRegistration<'_> {
1023 // SAFETY: reactor_notify_id was resolved during build() from the same
1024 // WorldBuilder. ReactorNotify is heap-allocated via ResourceCell —
1025 // the pointer is stable for World's lifetime. We access it via raw
1026 // pointer to simultaneously read the registry (disjoint: registry is
1027 // an inline field, ReactorNotify is on the heap). &mut self guarantees
1028 // no other references to any World resource exist.
1029 let notify_ptr: *mut crate::reactor::ReactorNotify =
1030 unsafe { self.get_mut::<crate::reactor::ReactorNotify>(self.reactor_notify_id) };
1031 // SAFETY: notify_ptr is stable (heap-allocated ResourceCell).
1032 // No other references exist — previous &mut was not retained.
1033 let token = unsafe { &mut *notify_ptr }.create_reactor();
1034 let ctx = ctx_fn(token);
1035 let reactor = step.into_reactor(ctx, &self.registry);
1036 // SAFETY: notify_ptr is stable (heap-allocated ResourceCell).
1037 // No other references exist — ctx_fn and into_reactor don't alias.
1038 let notify = unsafe { &mut *notify_ptr };
1039 notify.insert_reactor(token, reactor)
1040 }
1041
1042 /// Register a pre-built reactor in one step.
1043 ///
1044 /// For reactors that don't need their token in the context, or for
1045 /// [`PipelineReactor`](crate::PipelineReactor) instances.
1046 #[cfg(feature = "reactors")]
1047 pub fn spawn_built_reactor(
1048 &mut self,
1049 reactor: impl crate::reactor::Reactor + 'static,
1050 ) -> crate::reactor::ReactorRegistration<'_> {
1051 // SAFETY: reactor_notify_id was resolved during build() from the
1052 // same WorldBuilder. &mut self guarantees exclusive access.
1053 let notify =
1054 unsafe { self.get_mut::<crate::reactor::ReactorNotify>(self.reactor_notify_id) };
1055 notify.register_built(reactor)
1056 }
1057
1058 /// Dispatch all woken reactors and process deferred removals.
1059 ///
1060 /// Call this post-frame after event handlers have called
1061 /// [`ReactorNotify::mark`](crate::ReactorNotify::mark).
1062 /// Returns `true` if any reactor ran.
1063 #[cfg(feature = "reactors")]
1064 pub fn dispatch_reactors(&mut self) -> bool {
1065 // SAFETY: reactor_notify_id was resolved during build() from the
1066 // same WorldBuilder. &mut self guarantees exclusive access. We hold
1067 // a raw pointer to allow scoped re-borrows across reactor dispatch.
1068 let notify_ptr: *mut crate::reactor::ReactorNotify =
1069 unsafe { self.get_mut::<crate::reactor::ReactorNotify>(self.reactor_notify_id) };
1070
1071 // Poll — scoped &mut, dropped before reactor dispatch.
1072 let mut events = self
1073 .reactor_events
1074 .take()
1075 .unwrap_or_else(|| nexus_notify::Events::with_capacity(256));
1076 {
1077 // SAFETY: notify_ptr is stable (heap-allocated ResourceCell).
1078 // Scoped — &mut is dropped before any other access.
1079 let notify = unsafe { &mut *notify_ptr };
1080 notify.poll(&mut events);
1081 }
1082 let ran = !events.is_empty();
1083
1084 // Dispatch — each reactor is moved out before run(), put back after.
1085 // &mut ReactorNotify is scoped tightly to avoid aliasing during run().
1086 for token in events.iter() {
1087 let idx = token.index();
1088 let reactor = {
1089 // SAFETY: notify_ptr is stable. Scoped — &mut is dropped
1090 // before reactor.run(self) which may re-borrow World.
1091 let notify = unsafe { &mut *notify_ptr };
1092 notify.take_reactor(idx)
1093 }; // &mut dropped here — safe to call run()
1094 if let Some(mut reactor) = reactor {
1095 reactor.run(self);
1096 // SAFETY: notify_ptr is stable. reactor.run() is complete,
1097 // so no World borrows remain. Safe to re-borrow notify.
1098 let notify = unsafe { &mut *notify_ptr };
1099 notify.put_reactor(idx, reactor);
1100 }
1101 }
1102
1103 // Deferred removals — swap the inner Vec out to avoid holding
1104 // &mut DeferredRemovals and &mut ReactorNotify simultaneously.
1105 // Zero allocation: the Vec is swapped back and reused next frame.
1106 // SAFETY: reactor_removals_id was resolved during build() from
1107 // the same WorldBuilder. No other references to this resource.
1108 let removals =
1109 unsafe { self.get_mut::<crate::reactor::DeferredRemovals>(self.reactor_removals_id) };
1110 let mut pending = removals.take();
1111 if !pending.is_empty() {
1112 // SAFETY: notify_ptr is stable. removals &mut was dropped
1113 // (pending now owns the data). No aliasing.
1114 let notify = unsafe { &mut *notify_ptr };
1115 while let Some(token) = pending.pop() {
1116 notify.remove_reactor(token);
1117 }
1118 }
1119 // Put the (now empty) Vec back for reuse.
1120 // SAFETY: same removals_id, no other references.
1121 let removals =
1122 unsafe { self.get_mut::<crate::reactor::DeferredRemovals>(self.reactor_removals_id) };
1123 removals.put(pending);
1124
1125 // Return events buffer for reuse next frame.
1126 self.reactor_events = Some(events);
1127
1128 ran
1129 }
1130
1131 /// Number of registered reactors.
1132 #[cfg(feature = "reactors")]
1133 pub fn reactor_count(&self) -> usize {
1134 self.resource::<crate::reactor::ReactorNotify>()
1135 .reactor_count()
1136 }
1137}
1138
1139// SAFETY: All resources are `T: Send` (enforced by `register`). World owns all
1140// heap-allocated data exclusively — the raw pointers are not aliased or shared.
1141// Transferring ownership to another thread is safe; the new thread becomes the
1142// sole accessor.
1143#[allow(clippy::non_send_fields_in_send_ty)]
1144unsafe impl Send for World {}
1145
1146// =============================================================================
1147// Tests
1148// =============================================================================
1149
1150#[cfg(test)]
1151mod tests {
1152 use super::*;
1153 use std::sync::{Arc, Weak};
1154
1155 struct Price {
1156 value: f64,
1157 }
1158 impl Resource for Price {}
1159
1160 struct Venue {
1161 name: &'static str,
1162 }
1163 impl Resource for Venue {}
1164
1165 struct Config {
1166 max_orders: usize,
1167 }
1168 impl Resource for Config {}
1169
1170 #[test]
1171 fn register_and_build() {
1172 let mut builder = WorldBuilder::new();
1173 builder.register::<Price>(Price { value: 100.0 });
1174 builder.register::<Venue>(Venue { name: "test" });
1175 let world = builder.build();
1176 #[cfg(not(feature = "reactors"))]
1177 assert_eq!(world.len(), 2);
1178 #[cfg(feature = "reactors")]
1179 assert_eq!(world.len(), 5); // + ReactorNotify + DeferredRemovals + SourceRegistry
1180 }
1181
1182 #[test]
1183 #[allow(clippy::float_cmp)]
1184 fn resource_ids_are_distinct() {
1185 let mut builder = WorldBuilder::new();
1186 let id0 = builder.register::<Price>(Price { value: 0.0 });
1187 let id1 = builder.register::<Venue>(Venue { name: "" });
1188 let id2 = builder.register::<Config>(Config { max_orders: 0 });
1189 assert_ne!(id0, id1);
1190 assert_ne!(id1, id2);
1191 assert_ne!(id0, id2);
1192
1193 let world = builder.build();
1194 unsafe {
1195 assert_eq!(world.get::<Price>(id0).value, 0.0);
1196 assert_eq!(world.get::<Venue>(id1).name, "");
1197 assert_eq!(world.get::<Config>(id2).max_orders, 0);
1198 }
1199 }
1200
1201 #[test]
1202 #[allow(clippy::float_cmp)]
1203 fn get_returns_registered_value() {
1204 let mut builder = WorldBuilder::new();
1205 builder.register::<Price>(Price { value: 42.5 });
1206 let world = builder.build();
1207
1208 let id = world.id::<Price>();
1209 // SAFETY: id resolved from this container, type matches, no aliasing.
1210 let price = unsafe { world.get::<Price>(id) };
1211 assert_eq!(price.value, 42.5);
1212 }
1213
1214 #[test]
1215 #[allow(clippy::float_cmp)]
1216 fn get_mut_modifies_value() {
1217 let mut builder = WorldBuilder::new();
1218 builder.register::<Price>(Price { value: 1.0 });
1219 let world = builder.build();
1220
1221 let id = world.id::<Price>();
1222 // SAFETY: id resolved from this container, type matches, no aliasing.
1223 unsafe {
1224 world.get_mut::<Price>(id).value = 99.0;
1225 assert_eq!(world.get::<Price>(id).value, 99.0);
1226 }
1227 }
1228
1229 #[test]
1230 fn try_id_returns_none_for_unregistered() {
1231 let world = WorldBuilder::new().build();
1232 assert!(world.try_id::<Price>().is_none());
1233 }
1234
1235 #[test]
1236 fn try_id_returns_some_for_registered() {
1237 let mut builder = WorldBuilder::new();
1238 builder.register::<Price>(Price { value: 0.0 });
1239 let world = builder.build();
1240
1241 assert!(world.try_id::<Price>().is_some());
1242 }
1243
1244 #[test]
1245 #[should_panic(expected = "already registered")]
1246 fn panics_on_duplicate_registration() {
1247 let mut builder = WorldBuilder::new();
1248 builder.register::<Price>(Price { value: 1.0 });
1249 builder.register::<Price>(Price { value: 2.0 });
1250 }
1251
1252 #[test]
1253 #[should_panic(expected = "not registered")]
1254 fn panics_on_unregistered_id() {
1255 let world = WorldBuilder::new().build();
1256 world.id::<Price>();
1257 }
1258
1259 #[test]
1260 fn empty_builder_builds_empty_world() {
1261 let world = WorldBuilder::new().build();
1262 // With the `reactors` feature, ReactorNotify + DeferredRemovals +
1263 // SourceRegistry are auto-registered. Without it, the world is truly empty.
1264 #[cfg(not(feature = "reactors"))]
1265 assert_eq!(world.len(), 0);
1266 #[cfg(feature = "reactors")]
1267 assert_eq!(world.len(), 3);
1268 }
1269
1270 #[test]
1271 fn drop_runs_destructors() {
1272 let arc = Arc::new(42u32);
1273 let weak: Weak<u32> = Arc::downgrade(&arc);
1274
1275 {
1276 let mut builder = WorldBuilder::new();
1277 builder.register::<Arc<u32>>(arc);
1278 let _world = builder.build();
1279 // Arc still alive — held by World
1280 assert!(weak.upgrade().is_some());
1281 }
1282 // World dropped — Arc should be deallocated
1283 assert!(weak.upgrade().is_none());
1284 }
1285
1286 #[test]
1287 fn builder_drop_cleans_up_without_build() {
1288 let arc = Arc::new(99u32);
1289 let weak: Weak<u32> = Arc::downgrade(&arc);
1290
1291 {
1292 let mut builder = WorldBuilder::new();
1293 builder.register::<Arc<u32>>(arc);
1294 }
1295 // Builder dropped without build() — Storage::drop cleans up
1296 assert!(weak.upgrade().is_none());
1297 }
1298
1299 #[test]
1300 #[allow(clippy::float_cmp)]
1301 fn multiple_types_independent() {
1302 let mut builder = WorldBuilder::new();
1303 let price_id = builder.register::<Price>(Price { value: 10.0 });
1304 let venue_id = builder.register::<Venue>(Venue { name: "CB" });
1305 let config_id = builder.register::<Config>(Config { max_orders: 500 });
1306 let world = builder.build();
1307
1308 unsafe {
1309 assert_eq!(world.get::<Price>(price_id).value, 10.0);
1310 assert_eq!(world.get::<Venue>(venue_id).name, "CB");
1311 assert_eq!(world.get::<Config>(config_id).max_orders, 500);
1312 }
1313 }
1314
1315 #[test]
1316 fn contains_reflects_registration() {
1317 let mut builder = WorldBuilder::new();
1318 assert!(!builder.contains::<Price>());
1319
1320 builder.register::<Price>(Price { value: 0.0 });
1321 assert!(builder.contains::<Price>());
1322 assert!(!builder.contains::<Venue>());
1323
1324 let world = builder.build();
1325 assert!(world.contains::<Price>());
1326 assert!(!world.contains::<Venue>());
1327 }
1328
1329 #[test]
1330 #[allow(clippy::float_cmp)]
1331 fn send_to_another_thread() {
1332 let mut builder = WorldBuilder::new();
1333 builder.register::<Price>(Price { value: 55.5 });
1334 let world = builder.build();
1335
1336 let handle = std::thread::spawn(move || {
1337 let id = world.id::<Price>();
1338 // SAFETY: sole owner on this thread, no aliasing.
1339 unsafe { world.get::<Price>(id).value }
1340 });
1341 assert_eq!(handle.join().unwrap(), 55.5);
1342 }
1343
1344 #[test]
1345 fn registry_accessible_from_builder() {
1346 let mut builder = WorldBuilder::new();
1347 let registered_id = builder.register::<u64>(42);
1348
1349 let registry = builder.registry();
1350 assert!(registry.contains::<u64>());
1351 assert!(!registry.contains::<bool>());
1352
1353 let id = registry.id::<u64>();
1354 assert_eq!(id, registered_id);
1355 }
1356
1357 #[test]
1358 fn registry_accessible_from_world() {
1359 let mut builder = WorldBuilder::new();
1360 builder.register::<u64>(42);
1361 let world = builder.build();
1362
1363 let registry = world.registry();
1364 assert!(registry.contains::<u64>());
1365
1366 // Registry from world and world.id() agree.
1367 assert_eq!(registry.id::<u64>(), world.id::<u64>());
1368 }
1369
1370 // -- Safe accessor tests --------------------------------------------------
1371
1372 #[test]
1373 #[allow(clippy::float_cmp)]
1374 fn resource_reads_value() {
1375 let mut builder = WorldBuilder::new();
1376 builder.register::<Price>(Price { value: 42.5 });
1377 let world = builder.build();
1378
1379 assert_eq!(world.resource::<Price>().value, 42.5);
1380 }
1381
1382 #[test]
1383 fn resource_mut_modifies_value() {
1384 let mut builder = WorldBuilder::new();
1385 builder.register::<u64>(0);
1386 let mut world = builder.build();
1387
1388 *world.resource_mut::<u64>() = 99;
1389 assert_eq!(*world.resource::<u64>(), 99);
1390 }
1391
1392 #[test]
1393 fn register_default_works() {
1394 let mut builder = WorldBuilder::new();
1395 let id = builder.register_default::<Vec<u32>>();
1396 let world = builder.build();
1397
1398 assert_eq!(id, world.id::<Vec<u32>>());
1399 let v = world.resource::<Vec<u32>>();
1400 assert!(v.is_empty());
1401 }
1402
1403 #[test]
1404 fn ensure_registers_new_type() {
1405 let mut builder = WorldBuilder::new();
1406 let id = builder.ensure::<u64>(42);
1407 let world = builder.build();
1408
1409 assert_eq!(id, world.id::<u64>());
1410 assert_eq!(*world.resource::<u64>(), 42);
1411 }
1412
1413 #[test]
1414 fn ensure_returns_existing_id() {
1415 let mut builder = WorldBuilder::new();
1416 let id1 = builder.register::<u64>(42);
1417 let id2 = builder.ensure::<u64>(99);
1418 assert_eq!(id1, id2);
1419
1420 // Original value preserved, new value dropped.
1421 let world = builder.build();
1422 assert_eq!(*world.resource::<u64>(), 42);
1423 }
1424
1425 #[test]
1426 fn ensure_default_registers_new_type() {
1427 let mut builder = WorldBuilder::new();
1428 let id = builder.ensure_default::<Vec<u32>>();
1429 let world = builder.build();
1430
1431 assert_eq!(id, world.id::<Vec<u32>>());
1432 assert!(world.resource::<Vec<u32>>().is_empty());
1433 }
1434
1435 #[test]
1436 fn ensure_default_returns_existing_id() {
1437 let mut builder = WorldBuilder::new();
1438 builder.register::<Vec<u32>>(vec![1, 2, 3]);
1439 let id = builder.ensure_default::<Vec<u32>>();
1440 let world = builder.build();
1441
1442 assert_eq!(id, world.id::<Vec<u32>>());
1443 // Original value preserved.
1444 assert_eq!(*world.resource::<Vec<u32>>(), vec![1, 2, 3]);
1445 }
1446
1447 // -- Sequence tests -----------------------------------------------------------
1448
1449 #[test]
1450 fn sequence_default_is_zero() {
1451 assert_eq!(Sequence::default(), Sequence(0));
1452 }
1453
1454 #[test]
1455 fn next_sequence_increments() {
1456 let mut world = WorldBuilder::new().build();
1457 assert_eq!(world.current_sequence(), Sequence(0));
1458 world.next_sequence();
1459 assert_eq!(world.current_sequence(), Sequence(1));
1460 world.next_sequence();
1461 assert_eq!(world.current_sequence(), Sequence(2));
1462 }
1463
1464 // -- run_startup tests ----------------------------------------------------
1465
1466 #[test]
1467 fn run_startup_dispatches_handler() {
1468 use crate::ResMut;
1469
1470 let mut builder = WorldBuilder::new();
1471 builder.register::<u64>(0);
1472 builder.register::<bool>(false);
1473 let mut world = builder.build();
1474
1475 fn init(mut counter: ResMut<u64>, mut flag: ResMut<bool>) {
1476 *counter = 42;
1477 *flag = true;
1478 }
1479
1480 world.run_startup(init);
1481
1482 assert_eq!(*world.resource::<u64>(), 42);
1483 assert!(*world.resource::<bool>());
1484 }
1485
1486 #[test]
1487 fn run_startup_multiple_phases() {
1488 use crate::ResMut;
1489
1490 let mut builder = WorldBuilder::new();
1491 builder.register::<u64>(0);
1492 let mut world = builder.build();
1493
1494 fn phase1(mut counter: ResMut<u64>) {
1495 *counter += 10;
1496 }
1497
1498 fn phase2(mut counter: ResMut<u64>) {
1499 *counter += 5;
1500 }
1501
1502 world.run_startup(phase1);
1503 world.run_startup(phase2);
1504
1505 assert_eq!(*world.resource::<u64>(), 15);
1506 }
1507
1508 // -- Plugin / Driver tests ------------------------------------------------
1509
1510 #[test]
1511 fn plugin_registers_resources() {
1512 struct TestPlugin;
1513
1514 impl crate::plugin::Plugin for TestPlugin {
1515 fn build(self, world: &mut WorldBuilder) {
1516 world.register::<u64>(42);
1517 world.register::<bool>(true);
1518 }
1519 }
1520
1521 let mut builder = WorldBuilder::new();
1522 builder.install_plugin(TestPlugin);
1523 let world = builder.build();
1524
1525 assert_eq!(*world.resource::<u64>(), 42);
1526 assert!(*world.resource::<bool>());
1527 }
1528
1529 #[test]
1530 fn driver_installs_and_returns_handle() {
1531 struct TestInstaller;
1532 struct TestHandle {
1533 counter_id: ResourceId,
1534 }
1535
1536 impl crate::driver::Installer for TestInstaller {
1537 type Poller = TestHandle;
1538
1539 fn install(self, world: &mut WorldBuilder) -> TestHandle {
1540 let counter_id = world.register::<u64>(0);
1541 TestHandle { counter_id }
1542 }
1543 }
1544
1545 let mut builder = WorldBuilder::new();
1546 let handle = builder.install_driver(TestInstaller);
1547 let world = builder.build();
1548
1549 // Handle's pre-resolved ID can access the resource.
1550 unsafe {
1551 assert_eq!(*world.get::<u64>(handle.counter_id), 0);
1552 }
1553 }
1554
1555 // -- check_access conflict detection ----------------------------------------
1556
1557 #[test]
1558 fn check_access_no_conflict() {
1559 let mut builder = WorldBuilder::new();
1560 let id_a = builder.register::<u64>(0);
1561 let id_b = builder.register::<u32>(0);
1562 builder
1563 .registry()
1564 .check_access(&[(Some(id_a), "a"), (Some(id_b), "b")]);
1565 }
1566
1567 #[test]
1568 #[should_panic(expected = "conflicting access")]
1569 fn check_access_detects_conflict() {
1570 let mut builder = WorldBuilder::new();
1571 let id = builder.register::<u64>(0);
1572 builder
1573 .registry()
1574 .check_access(&[(Some(id), "a"), (Some(id), "b")]);
1575 }
1576
1577 #[test]
1578 fn sequence_wrapping() {
1579 let builder = WorldBuilder::new();
1580 let mut world = builder.build();
1581
1582 // Advance to MAX.
1583 world.current_sequence.set(Sequence(i64::MAX));
1584 assert_eq!(world.current_sequence(), Sequence(i64::MAX));
1585
1586 // Wrap to MIN (which is the NULL sentinel, but wrapping is
1587 // purely mechanical — it doesn't assign semantic meaning).
1588 let seq = world.next_sequence();
1589 assert_eq!(seq, Sequence(i64::MIN));
1590 assert_eq!(world.current_sequence(), Sequence(i64::MIN));
1591 }
1592
1593 // -- BorrowTracker tests (debug builds only) ------------------------------
1594
1595 #[cfg(debug_assertions)]
1596 #[test]
1597 #[should_panic(expected = "conflicting access")]
1598 fn borrow_tracker_catches_double_access() {
1599 let mut builder = WorldBuilder::new();
1600 let id = builder.register::<u64>(42);
1601 let world = builder.build();
1602 world.clear_borrows();
1603 world.track_borrow(id);
1604 world.track_borrow(id); // same resource, same phase
1605 }
1606
1607 #[cfg(debug_assertions)]
1608 #[test]
1609 fn borrow_tracker_allows_after_clear() {
1610 let mut builder = WorldBuilder::new();
1611 let id = builder.register::<u64>(42);
1612 let world = builder.build();
1613 world.clear_borrows();
1614 world.track_borrow(id);
1615 world.clear_borrows();
1616 world.track_borrow(id); // new phase, no conflict
1617 }
1618
1619 #[cfg(debug_assertions)]
1620 #[test]
1621 fn borrow_tracker_different_resources_ok() {
1622 let mut builder = WorldBuilder::new();
1623 let id_a = builder.register::<u64>(1);
1624 let id_b = builder.register::<u32>(2);
1625 let world = builder.build();
1626 world.clear_borrows();
1627 world.track_borrow(id_a);
1628 world.track_borrow(id_b); // different resources, no conflict
1629 }
1630}