Skip to main content

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