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 dense index
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 dense indices. It is shared between
9//! [`WorldBuilder`] and [`World`], and is passed to [`SystemParam::init`] and
10//! [`IntoSystem::into_system`] so that systems 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 systems 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;
31use std::marker::PhantomData;
32
33use rustc_hash::FxHashMap;
34
35// =============================================================================
36// Core types
37// =============================================================================
38
39/// Dense index identifying a resource type within a [`World`] container.
40///
41/// Assigned sequentially at registration (0, 1, 2, ...). Used as a direct
42/// index into internal storage at dispatch time — no hashing, no searching.
43///
44/// Only obtained from [`Registry::id`], [`World::id`], or their `try_` variants.
45#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
46pub struct ResourceId(usize);
47
48/// Monotonic event sequence number for change detection.
49///
50/// Each event processed by a driver is assigned a unique sequence number
51/// via [`World::next_sequence`]. Resources record the sequence at which
52/// they were last written. A resource is considered "changed" if its
53/// `changed_at` equals the world's `current_sequence`. Wrapping is
54/// harmless — only equality is checked.
55#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
56pub struct Sequence(pub(crate) u64);
57
58/// Type-erased drop function. Monomorphized at registration time so we
59/// can reconstruct and drop the original `Box<T>` from a `*mut u8`.
60pub(crate) type DropFn = unsafe fn(*mut u8);
61
62/// Reconstruct and drop a `Box<T>` from a raw pointer.
63///
64/// # Safety
65///
66/// `ptr` must have been produced by `Box::into_raw(Box::new(value))`
67/// where `value: T`. Must only be called once per pointer.
68pub(crate) unsafe fn drop_resource<T>(ptr: *mut u8) {
69    // SAFETY: ptr was produced by Box::into_raw(Box::new(value))
70    // where value: T. Called exactly once in Storage::drop.
71    unsafe {
72        let _ = Box::from_raw(ptr as *mut T);
73    }
74}
75
76// =============================================================================
77// Registry — type-to-index mapping
78// =============================================================================
79
80/// Type-to-index mapping shared between [`WorldBuilder`] and [`World`].
81///
82/// Contains only the type registry — no storage, no pointers. Passed to
83/// [`IntoSystem::into_system`](crate::IntoSystem::into_system) and
84/// [`SystemParam::init`](crate::SystemParam::init) so systems can resolve
85/// [`ResourceId`]s during driver setup.
86///
87/// Obtained via [`WorldBuilder::registry()`] or [`World::registry()`].
88pub struct Registry {
89    indices: FxHashMap<TypeId, ResourceId>,
90    /// Scratch bitset reused across [`check_access`](Self::check_access) calls.
91    /// Allocated once on the first call with >64 resources, then reused.
92    scratch: Vec<u64>,
93}
94
95impl Registry {
96    pub(crate) fn new() -> Self {
97        Self {
98            indices: FxHashMap::default(),
99            scratch: Vec::new(),
100        }
101    }
102
103    /// Resolve the [`ResourceId`] for a type. Cold path — uses HashMap lookup.
104    ///
105    /// # Panics
106    ///
107    /// Panics if the resource type was not registered.
108    pub fn id<T: 'static>(&self) -> ResourceId {
109        *self
110            .indices
111            .get(&TypeId::of::<T>())
112            .unwrap_or_else(|| panic!("resource `{}` not registered", type_name::<T>()))
113    }
114
115    /// Try to resolve the [`ResourceId`] for a type. Returns `None` if the
116    /// type was not registered.
117    pub fn try_id<T: 'static>(&self) -> Option<ResourceId> {
118        self.indices.get(&TypeId::of::<T>()).copied()
119    }
120
121    /// Returns `true` if a resource of type `T` has been registered.
122    pub fn contains<T: 'static>(&self) -> bool {
123        self.indices.contains_key(&TypeId::of::<T>())
124    }
125
126    /// Returns the number of registered resources.
127    pub fn len(&self) -> usize {
128        self.indices.len()
129    }
130
131    /// Returns `true` if no resources have been registered.
132    pub fn is_empty(&self) -> bool {
133        self.indices.is_empty()
134    }
135
136    /// Validate that a set of parameter accesses don't conflict.
137    ///
138    /// Two accesses conflict when they target the same ResourceId.
139    /// Called at construction time by `into_system`, `into_callback`,
140    /// and `into_stage`.
141    ///
142    /// Fast path (≤128 resources): single `u128` on the stack, zero heap.
143    /// Slow path (>128 resources): reusable `Vec<u64>` owned by Registry —
144    /// allocated once on first use, then cleared and reused.
145    ///
146    /// # Panics
147    ///
148    /// Panics if any resource is accessed by more than one parameter.
149    #[cold]
150    pub fn check_access(&mut self, accesses: &[(Option<ResourceId>, &str)]) {
151        let n = self.len();
152        if n == 0 {
153            return;
154        }
155
156        if n <= 128 {
157            // Fast path: single u128 on the stack.
158            let mut seen = 0u128;
159            for &(id, name) in accesses {
160                let Some(id) = id else { continue };
161                let bit = 1u128 << id.0;
162                assert!(
163                    seen & bit == 0,
164                    "conflicting access: resource borrowed by `{}` is already \
165                     borrowed by another parameter in the same system",
166                    name,
167                );
168                seen |= bit;
169            }
170        } else {
171            // Slow path: reusable heap buffer.
172            let words = n.div_ceil(64);
173            self.scratch.resize(words, 0);
174            self.scratch.fill(0);
175            for &(id, name) in accesses {
176                let Some(id) = id else { continue };
177                let word = id.0 / 64;
178                let bit = 1u64 << (id.0 % 64);
179                assert!(
180                    self.scratch[word] & bit == 0,
181                    "conflicting access: resource borrowed by `{}` is already \
182                     borrowed by another parameter in the same system",
183                    name,
184                );
185                self.scratch[word] |= bit;
186            }
187        }
188    }
189}
190
191// =============================================================================
192// Storage — shared backing between builder and frozen container
193// =============================================================================
194
195/// Interleaved pointer + change sequence for a single resource.
196/// 16 bytes — 4 slots per cache line.
197#[repr(C)]
198pub(crate) struct ResourceSlot {
199    pub(crate) ptr: *mut u8,
200    pub(crate) changed_at: Cell<Sequence>,
201}
202
203/// Internal storage for type-erased resource pointers and their destructors.
204///
205/// Owns the heap allocations and is responsible for cleanup. Shared between
206/// [`WorldBuilder`] and [`World`] via move — avoids duplicating Drop logic.
207pub(crate) struct Storage {
208    /// Dense array of interleaved pointer + change sequence pairs.
209    /// Each pointer was produced by `Box::into_raw`.
210    pub(crate) slots: Vec<ResourceSlot>,
211    /// Parallel array of drop functions. `drop_fns[i]` is the monomorphized
212    /// destructor for the concrete type behind `slots[i].ptr`.
213    pub(crate) drop_fns: Vec<DropFn>,
214}
215
216impl Storage {
217    pub(crate) fn new() -> Self {
218        Self {
219            slots: Vec::new(),
220            drop_fns: Vec::new(),
221        }
222    }
223
224    pub(crate) fn len(&self) -> usize {
225        self.slots.len()
226    }
227
228    pub(crate) fn is_empty(&self) -> bool {
229        self.slots.is_empty()
230    }
231}
232
233// SAFETY: All values stored in Storage were registered via `register<T: Send + 'static>`,
234// so every concrete type behind the raw pointers is Send. Storage exclusively owns
235// these heap allocations — they are not aliased or shared. Transferring ownership
236// to another thread is safe. Cell<Sequence> is !Sync but we're transferring
237// ownership, not sharing.
238#[allow(clippy::non_send_fields_in_send_ty)]
239unsafe impl Send for Storage {}
240
241impl Drop for Storage {
242    fn drop(&mut self) {
243        for (slot, drop_fn) in self.slots.iter().zip(&self.drop_fns) {
244            // SAFETY: each (slot.ptr, drop_fn) pair was created together in
245            // WorldBuilder::register(). drop_fn is the monomorphized
246            // destructor for the concrete type behind ptr. Called exactly
247            // once here.
248            unsafe {
249                drop_fn(slot.ptr);
250            }
251        }
252    }
253}
254
255// =============================================================================
256// WorldBuilder
257// =============================================================================
258
259/// Builder for registering resources before freezing into a [`World`] container.
260///
261/// Each resource type can only be registered once. Registration assigns a
262/// dense [`ResourceId`] index (0, 1, 2, ...).
263///
264/// The [`registry()`](Self::registry) method exposes the type-to-index mapping
265/// so that drivers can resolve systems against the builder before `build()`.
266///
267/// # Examples
268///
269/// ```
270/// use nexus_rt::WorldBuilder;
271///
272/// let mut builder = WorldBuilder::new();
273/// builder.register::<u64>(42);
274/// builder.register::<bool>(true);
275/// let world = builder.build();
276///
277/// let id = world.id::<u64>();
278/// unsafe {
279///     assert_eq!(*world.get::<u64>(id), 42);
280/// }
281/// ```
282pub struct WorldBuilder {
283    registry: Registry,
284    storage: Storage,
285}
286
287impl WorldBuilder {
288    /// Create an empty builder.
289    pub fn new() -> Self {
290        Self {
291            registry: Registry::new(),
292            storage: Storage::new(),
293        }
294    }
295
296    /// Register a resource.
297    ///
298    /// The value is heap-allocated via `Box` and ownership is transferred
299    /// to the container. The pointer is stable for the lifetime of the
300    /// resulting [`World`].
301    ///
302    /// # Panics
303    ///
304    /// Panics if a resource of the same type is already registered.
305    #[cold]
306    pub fn register<T: Send + 'static>(&mut self, value: T) -> &mut Self {
307        let type_id = TypeId::of::<T>();
308        assert!(
309            !self.registry.indices.contains_key(&type_id),
310            "resource `{}` already registered",
311            type_name::<T>(),
312        );
313
314        let ptr = Box::into_raw(Box::new(value)) as *mut u8;
315        let id = ResourceId(self.storage.slots.len());
316        self.registry.indices.insert(type_id, id);
317        self.storage.slots.push(ResourceSlot {
318            ptr,
319            changed_at: Cell::new(Sequence(0)),
320        });
321        self.storage.drop_fns.push(drop_resource::<T>);
322        self
323    }
324
325    /// Register a resource using its [`Default`] value.
326    ///
327    /// Equivalent to `self.register::<T>(T::default())`.
328    #[cold]
329    pub fn register_default<T: Default + Send + 'static>(&mut self) -> &mut Self {
330        self.register(T::default())
331    }
332
333    /// Returns a shared reference to the type registry.
334    ///
335    /// Use this for read-only queries. For construction-time calls
336    /// like [`into_system`](crate::IntoSystem::into_system), use
337    /// [`registry_mut`](Self::registry_mut) instead.
338    pub fn registry(&self) -> &Registry {
339        &self.registry
340    }
341
342    /// Returns a mutable reference to the type registry.
343    ///
344    /// Needed at construction time for
345    /// [`into_system`](crate::IntoSystem::into_system),
346    /// [`into_callback`](crate::IntoCallback::into_callback), and
347    /// [`into_stage`](crate::IntoStage::into_stage)
348    /// which call [`Registry::check_access`].
349    pub fn registry_mut(&mut self) -> &mut Registry {
350        &mut self.registry
351    }
352
353    /// Returns the number of registered resources.
354    pub fn len(&self) -> usize {
355        self.storage.len()
356    }
357
358    /// Returns `true` if no resources have been registered.
359    pub fn is_empty(&self) -> bool {
360        self.storage.is_empty()
361    }
362
363    /// Returns `true` if a resource of type `T` has been registered.
364    pub fn contains<T: 'static>(&self) -> bool {
365        self.registry.contains::<T>()
366    }
367
368    /// Install a plugin. The plugin is consumed and registers its
369    /// resources into this builder.
370    pub fn install_plugin(&mut self, plugin: impl crate::plugin::Plugin) -> &mut Self {
371        plugin.build(self);
372        self
373    }
374
375    /// Install a driver. The driver is consumed, registers its resources
376    /// into this builder, and returns a concrete handle for dispatch-time
377    /// polling.
378    pub fn install_driver<D: crate::driver::Driver>(&mut self, driver: D) -> D::Handle {
379        driver.install(self)
380    }
381
382    /// Freeze the builder into an immutable [`World`] container.
383    ///
384    /// After this call, no more resources can be registered. All
385    /// [`ResourceId`] values remain valid for the lifetime of the
386    /// returned [`World`].
387    pub fn build(self) -> World {
388        World {
389            registry: self.registry,
390            storage: self.storage,
391            current_sequence: Sequence(0),
392            _not_sync: PhantomData,
393        }
394    }
395}
396
397impl Default for WorldBuilder {
398    fn default() -> Self {
399        Self::new()
400    }
401}
402
403// =============================================================================
404// World — frozen container
405// =============================================================================
406
407/// Frozen singleton resource storage.
408///
409/// Created by [`WorldBuilder::build()`]. Resources are indexed by dense
410/// [`ResourceId`] for O(1) dispatch-time access (~3 cycles per fetch).
411///
412/// # Safe API
413///
414/// - [`resource`](Self::resource) / [`resource_mut`](Self::resource_mut) —
415///   cold-path access via HashMap lookup.
416///
417/// # Unsafe API (framework internals)
418///
419/// The low-level `get` / `get_mut` methods are `unsafe` — used by
420/// [`SystemParam::fetch`](crate::SystemParam) for ~3-cycle dispatch.
421/// The caller must ensure no mutable aliasing.
422pub struct World {
423    /// Type-to-index mapping. Same registry used during build.
424    registry: Registry,
425    /// Type-erased pointer storage. Drop handled by `Storage`.
426    storage: Storage,
427    /// Current sequence number. Advanced by the driver before
428    /// each event dispatch.
429    current_sequence: Sequence,
430    /// World must not be shared across threads — it holds interior-mutable
431    /// `Cell<Sequence>` values accessed through `&self`. `!Sync` enforced by
432    /// `PhantomData<Cell<()>>`.
433    _not_sync: PhantomData<Cell<()>>,
434}
435
436impl World {
437    /// Convenience constructor — returns a new [`WorldBuilder`].
438    pub fn builder() -> WorldBuilder {
439        WorldBuilder::new()
440    }
441
442    /// Returns a shared reference to the type registry.
443    ///
444    /// Use this for read-only queries (e.g. [`id`](Registry::id),
445    /// [`contains`](Registry::contains)). For construction-time calls
446    /// like [`into_system`](crate::IntoSystem::into_system), use
447    /// [`registry_mut`](Self::registry_mut) instead.
448    pub fn registry(&self) -> &Registry {
449        &self.registry
450    }
451
452    /// Returns a mutable reference to the type registry.
453    ///
454    /// Needed at construction time for
455    /// [`into_system`](crate::IntoSystem::into_system),
456    /// [`into_callback`](crate::IntoCallback::into_callback), and
457    /// [`into_stage`](crate::IntoStage::into_stage)
458    /// which call [`Registry::check_access`].
459    pub fn registry_mut(&mut self) -> &mut Registry {
460        &mut self.registry
461    }
462
463    /// Resolve the [`ResourceId`] for a type. Cold path — uses HashMap lookup.
464    ///
465    /// # Panics
466    ///
467    /// Panics if the resource type was not registered.
468    pub fn id<T: 'static>(&self) -> ResourceId {
469        self.registry.id::<T>()
470    }
471
472    /// Try to resolve the [`ResourceId`] for a type. Returns `None` if the
473    /// type was not registered.
474    pub fn try_id<T: 'static>(&self) -> Option<ResourceId> {
475        self.registry.try_id::<T>()
476    }
477
478    /// Returns the number of registered resources.
479    pub fn len(&self) -> usize {
480        self.storage.len()
481    }
482
483    /// Returns `true` if no resources are stored.
484    pub fn is_empty(&self) -> bool {
485        self.storage.is_empty()
486    }
487
488    /// Returns `true` if a resource of type `T` is stored.
489    pub fn contains<T: 'static>(&self) -> bool {
490        self.registry.contains::<T>()
491    }
492
493    // =========================================================================
494    // Safe resource access (cold path — HashMap lookup per call)
495    // =========================================================================
496
497    /// Safe shared access to a resource. Cold path — resolves via HashMap.
498    ///
499    /// Takes `&self` — multiple shared references can coexist. The borrow
500    /// checker prevents mixing with [`resource_mut`](Self::resource_mut)
501    /// (which takes `&mut self`).
502    ///
503    /// # Panics
504    ///
505    /// Panics if the resource type was not registered.
506    pub fn resource<T: 'static>(&self) -> &T {
507        let id = self.registry.id::<T>();
508        // SAFETY: id resolved from our own registry. &self prevents mutable
509        // aliases — resource_mut takes &mut self.
510        unsafe { self.get(id) }
511    }
512
513    /// Safe exclusive access to a resource. Cold path — resolves via HashMap.
514    ///
515    /// # Panics
516    ///
517    /// Panics if the resource type was not registered.
518    pub fn resource_mut<T: 'static>(&mut self) -> &mut T {
519        let id = self.registry.id::<T>();
520        // Cold path — stamp unconditionally. If you request &mut, you're writing.
521        self.storage.slots[id.0]
522            .changed_at
523            .set(self.current_sequence);
524        // SAFETY: id resolved from our own registry. &mut self ensures
525        // exclusive access — no other references can exist.
526        unsafe { self.get_mut(id) }
527    }
528
529    // =========================================================================
530    // Sequence / change detection
531    // =========================================================================
532
533    /// Returns the current event sequence number.
534    pub fn current_sequence(&self) -> Sequence {
535        self.current_sequence
536    }
537
538    /// Advance to the next event sequence number and return it.
539    ///
540    /// Drivers call this before dispatching each event. The returned
541    /// sequence number identifies the event being processed. Resources
542    /// mutated during dispatch will record this sequence in `changed_at`.
543    pub fn next_sequence(&mut self) -> Sequence {
544        self.current_sequence = Sequence(self.current_sequence.0.wrapping_add(1));
545        self.current_sequence
546    }
547
548    // =========================================================================
549    // Unsafe resource access (hot path — pre-resolved ResourceId)
550    // =========================================================================
551
552    /// Fetch a shared reference to a resource by pre-validated index.
553    ///
554    /// # Safety
555    ///
556    /// - `id` must have been returned by [`WorldBuilder::register`] for
557    ///   the same builder that produced this container.
558    /// - `T` must be the same type that was registered at this `id`.
559    /// - The caller must ensure no mutable reference to this resource exists.
560    #[inline(always)]
561    pub unsafe fn get<T: 'static>(&self, id: ResourceId) -> &T {
562        // SAFETY: caller guarantees id was returned by register() on the
563        // builder that produced this container, so id.0 < self.storage.slots.len().
564        // T matches the registered type. No mutable alias exists.
565        unsafe { &*(self.get_ptr(id) as *const T) }
566    }
567
568    /// Fetch a mutable reference to a resource by pre-validated index.
569    ///
570    /// Takes `&self` — the container structure is frozen, but individual
571    /// resources have interior mutability via raw pointers. Sound because
572    /// callers (single-threaded sequential dispatch) uphold no-aliasing.
573    ///
574    /// # Safety
575    ///
576    /// - `id` must have been returned by [`WorldBuilder::register`] for
577    ///   the same builder that produced this container.
578    /// - `T` must be the same type that was registered at this `id`.
579    /// - The caller must ensure no other reference (shared or mutable) to this
580    ///   resource exists.
581    #[inline(always)]
582    #[allow(clippy::mut_from_ref)]
583    pub unsafe fn get_mut<T: 'static>(&self, id: ResourceId) -> &mut T {
584        // SAFETY: caller guarantees id was returned by register() on the
585        // builder that produced this container, so id.0 < self.storage.slots.len().
586        // T matches the registered type. No aliases exist.
587        unsafe { &mut *(self.get_ptr(id) as *mut T) }
588    }
589
590    /// Fetch a raw pointer to a resource by pre-validated index.
591    ///
592    /// Intended for macro-generated dispatch code that needs direct pointer
593    /// access.
594    ///
595    /// # Safety
596    ///
597    /// - `id` must have been returned by [`WorldBuilder::register`] for
598    ///   the same builder that produced this container.
599    #[inline(always)]
600    pub unsafe fn get_ptr(&self, id: ResourceId) -> *mut u8 {
601        debug_assert!(
602            id.0 < self.storage.slots.len(),
603            "ResourceId({}) out of bounds (len {})",
604            id.0,
605            self.storage.slots.len(),
606        );
607        // SAFETY: caller guarantees id was returned by register() on the
608        // builder that produced this container, so id.0 < self.storage.slots.len().
609        unsafe { self.storage.slots.get_unchecked(id.0).ptr }
610    }
611
612    // =========================================================================
613    // Change-detection internals (framework use only)
614    // =========================================================================
615
616    /// Read the sequence at which a resource was last changed.
617    ///
618    /// # Safety
619    ///
620    /// `id` must have been returned by [`WorldBuilder::register`] for
621    /// the same builder that produced this container.
622    #[inline(always)]
623    pub(crate) unsafe fn changed_at(&self, id: ResourceId) -> Sequence {
624        unsafe { self.storage.slots.get_unchecked(id.0).changed_at.get() }
625    }
626
627    /// Get a reference to the `Cell` tracking a resource's change sequence.
628    ///
629    /// # Safety
630    ///
631    /// `id` must have been returned by [`WorldBuilder::register`] for
632    /// the same builder that produced this container.
633    #[inline(always)]
634    pub(crate) unsafe fn changed_at_cell(&self, id: ResourceId) -> &Cell<Sequence> {
635        unsafe { &self.storage.slots.get_unchecked(id.0).changed_at }
636    }
637
638    /// Stamp a resource as changed at the current sequence.
639    ///
640    /// # Safety
641    ///
642    /// `id` must have been returned by [`WorldBuilder::register`] for
643    /// the same builder that produced this container.
644    #[inline(always)]
645    #[allow(dead_code)] // Available for driver implementations.
646    pub(crate) unsafe fn stamp_changed(&self, id: ResourceId) {
647        unsafe {
648            self.storage
649                .slots
650                .get_unchecked(id.0)
651                .changed_at
652                .set(self.current_sequence);
653        }
654    }
655}
656
657// SAFETY: All resources are `T: Send` (enforced by `register`). World owns all
658// heap-allocated data exclusively — the raw pointers are not aliased or shared.
659// Transferring ownership to another thread is safe; the new thread becomes the
660// sole accessor.
661unsafe impl Send for World {}
662
663// =============================================================================
664// Tests
665// =============================================================================
666
667#[cfg(test)]
668mod tests {
669    use super::*;
670    use std::sync::{Arc, Weak};
671
672    struct Price {
673        value: f64,
674    }
675
676    struct Venue {
677        name: &'static str,
678    }
679
680    struct Config {
681        max_orders: usize,
682    }
683
684    #[test]
685    fn register_and_build() {
686        let mut builder = WorldBuilder::new();
687        builder
688            .register::<Price>(Price { value: 100.0 })
689            .register::<Venue>(Venue { name: "test" });
690        let world = builder.build();
691        assert_eq!(world.len(), 2);
692    }
693
694    #[test]
695    fn resource_ids_are_sequential() {
696        let mut builder = WorldBuilder::new();
697        builder
698            .register::<Price>(Price { value: 0.0 })
699            .register::<Venue>(Venue { name: "" })
700            .register::<Config>(Config { max_orders: 0 });
701        let world = builder.build();
702        assert_eq!(world.id::<Price>(), ResourceId(0));
703        assert_eq!(world.id::<Venue>(), ResourceId(1));
704        assert_eq!(world.id::<Config>(), ResourceId(2));
705    }
706
707    #[test]
708    fn get_returns_registered_value() {
709        let mut builder = WorldBuilder::new();
710        builder.register::<Price>(Price { value: 42.5 });
711        let world = builder.build();
712
713        let id = world.id::<Price>();
714        // SAFETY: id resolved from this container, type matches, no aliasing.
715        let price = unsafe { world.get::<Price>(id) };
716        assert_eq!(price.value, 42.5);
717    }
718
719    #[test]
720    fn get_mut_modifies_value() {
721        let mut builder = WorldBuilder::new();
722        builder.register::<Price>(Price { value: 1.0 });
723        let world = builder.build();
724
725        let id = world.id::<Price>();
726        // SAFETY: id resolved from this container, type matches, no aliasing.
727        unsafe {
728            world.get_mut::<Price>(id).value = 99.0;
729            assert_eq!(world.get::<Price>(id).value, 99.0);
730        }
731    }
732
733    #[test]
734    fn try_id_returns_none_for_unregistered() {
735        let world = WorldBuilder::new().build();
736        assert!(world.try_id::<Price>().is_none());
737    }
738
739    #[test]
740    fn try_id_returns_some_for_registered() {
741        let mut builder = WorldBuilder::new();
742        builder.register::<Price>(Price { value: 0.0 });
743        let world = builder.build();
744
745        assert!(world.try_id::<Price>().is_some());
746    }
747
748    #[test]
749    #[should_panic(expected = "already registered")]
750    fn panics_on_duplicate_registration() {
751        let mut builder = WorldBuilder::new();
752        builder.register::<Price>(Price { value: 1.0 });
753        builder.register::<Price>(Price { value: 2.0 });
754    }
755
756    #[test]
757    #[should_panic(expected = "not registered")]
758    fn panics_on_unregistered_id() {
759        let world = WorldBuilder::new().build();
760        world.id::<Price>();
761    }
762
763    #[test]
764    fn empty_builder_builds_empty_world() {
765        let world = WorldBuilder::new().build();
766        assert_eq!(world.len(), 0);
767        assert!(world.is_empty());
768    }
769
770    #[test]
771    fn drop_runs_destructors() {
772        let arc = Arc::new(42u32);
773        let weak: Weak<u32> = Arc::downgrade(&arc);
774
775        {
776            let mut builder = WorldBuilder::new();
777            builder.register::<Arc<u32>>(arc);
778            let _world = builder.build();
779            // Arc still alive — held by World
780            assert!(weak.upgrade().is_some());
781        }
782        // World dropped — Arc should be deallocated
783        assert!(weak.upgrade().is_none());
784    }
785
786    #[test]
787    fn builder_drop_cleans_up_without_build() {
788        let arc = Arc::new(99u32);
789        let weak: Weak<u32> = Arc::downgrade(&arc);
790
791        {
792            let mut builder = WorldBuilder::new();
793            builder.register::<Arc<u32>>(arc);
794        }
795        // Builder dropped without build() — Storage::drop cleans up
796        assert!(weak.upgrade().is_none());
797    }
798
799    #[test]
800    fn multiple_types_independent() {
801        let mut builder = WorldBuilder::new();
802        builder
803            .register::<Price>(Price { value: 10.0 })
804            .register::<Venue>(Venue { name: "CB" })
805            .register::<Config>(Config { max_orders: 500 });
806        let world = builder.build();
807
808        unsafe {
809            let price_id = world.id::<Price>();
810            let venue_id = world.id::<Venue>();
811            let config_id = world.id::<Config>();
812            assert_eq!(world.get::<Price>(price_id).value, 10.0);
813            assert_eq!(world.get::<Venue>(venue_id).name, "CB");
814            assert_eq!(world.get::<Config>(config_id).max_orders, 500);
815        }
816    }
817
818    #[test]
819    fn contains_reflects_registration() {
820        let mut builder = WorldBuilder::new();
821        assert!(!builder.contains::<Price>());
822
823        builder.register::<Price>(Price { value: 0.0 });
824        assert!(builder.contains::<Price>());
825        assert!(!builder.contains::<Venue>());
826
827        let world = builder.build();
828        assert!(world.contains::<Price>());
829        assert!(!world.contains::<Venue>());
830    }
831
832    #[test]
833    fn get_ptr_returns_valid_pointer() {
834        let mut builder = WorldBuilder::new();
835        builder.register::<Price>(Price { value: 77.7 });
836        let world = builder.build();
837
838        let id = world.id::<Price>();
839        unsafe {
840            let ptr = world.get_ptr(id);
841            let price = &*(ptr as *const Price);
842            assert_eq!(price.value, 77.7);
843        }
844    }
845
846    #[test]
847    fn send_to_another_thread() {
848        let mut builder = WorldBuilder::new();
849        builder.register::<Price>(Price { value: 55.5 });
850        let world = builder.build();
851
852        let handle = std::thread::spawn(move || {
853            let id = world.id::<Price>();
854            // SAFETY: sole owner on this thread, no aliasing.
855            unsafe { world.get::<Price>(id).value }
856        });
857        assert_eq!(handle.join().unwrap(), 55.5);
858    }
859
860    #[test]
861    fn registry_accessible_from_builder() {
862        let mut builder = WorldBuilder::new();
863        builder.register::<u64>(42);
864
865        let registry = builder.registry();
866        assert!(registry.contains::<u64>());
867        assert!(!registry.contains::<bool>());
868
869        let id = registry.id::<u64>();
870        assert_eq!(id, ResourceId(0));
871    }
872
873    #[test]
874    fn registry_accessible_from_world() {
875        let mut builder = WorldBuilder::new();
876        builder.register::<u64>(42);
877        let world = builder.build();
878
879        let registry = world.registry();
880        assert!(registry.contains::<u64>());
881
882        // Registry from world and world.id() agree.
883        assert_eq!(registry.id::<u64>(), world.id::<u64>());
884    }
885
886    // -- Safe accessor tests --------------------------------------------------
887
888    #[test]
889    fn resource_reads_value() {
890        let mut builder = WorldBuilder::new();
891        builder.register::<Price>(Price { value: 42.5 });
892        let world = builder.build();
893
894        assert_eq!(world.resource::<Price>().value, 42.5);
895    }
896
897    #[test]
898    fn resource_mut_modifies_value() {
899        let mut builder = WorldBuilder::new();
900        builder.register::<u64>(0);
901        let mut world = builder.build();
902
903        *world.resource_mut::<u64>() = 99;
904        assert_eq!(*world.resource::<u64>(), 99);
905    }
906
907    #[test]
908    fn register_default_works() {
909        let mut builder = WorldBuilder::new();
910        builder.register_default::<Vec<u32>>();
911        let world = builder.build();
912
913        let v = world.resource::<Vec<u32>>();
914        assert!(v.is_empty());
915    }
916
917    // -- Sequence / change detection tests ----------------------------------------
918
919    #[test]
920    fn sequence_default_is_zero() {
921        assert_eq!(Sequence::default(), Sequence(0));
922    }
923
924    #[test]
925    fn next_sequence_increments() {
926        let mut world = WorldBuilder::new().build();
927        assert_eq!(world.current_sequence(), Sequence(0));
928        world.next_sequence();
929        assert_eq!(world.current_sequence(), Sequence(1));
930        world.next_sequence();
931        assert_eq!(world.current_sequence(), Sequence(2));
932    }
933
934    #[test]
935    fn resource_registered_at_current_sequence() {
936        // Resources registered at build time get changed_at=Sequence(0).
937        // World starts at current_sequence=Sequence(0). So they match — "changed."
938        let mut builder = WorldBuilder::new();
939        builder.register::<u64>(42);
940        let world = builder.build();
941
942        let id = world.id::<u64>();
943        unsafe {
944            assert_eq!(world.changed_at(id), Sequence(0));
945            assert_eq!(world.current_sequence(), Sequence(0));
946            // changed_at == current_sequence → "changed"
947            assert_eq!(world.changed_at(id), world.current_sequence());
948        }
949    }
950
951    #[test]
952    fn resource_mut_stamps_changed_at() {
953        let mut builder = WorldBuilder::new();
954        builder.register::<u64>(0);
955        let mut world = builder.build();
956
957        world.next_sequence(); // tick=1
958        let id = world.id::<u64>();
959
960        // changed_at is still 0, current_sequence is 1 → not changed
961        unsafe {
962            assert_eq!(world.changed_at(id), Sequence(0));
963        }
964
965        // resource_mut stamps changed_at to current_sequence
966        *world.resource_mut::<u64>() = 99;
967        unsafe {
968            assert_eq!(world.changed_at(id), Sequence(1));
969        }
970    }
971
972    // -- Plugin / Driver tests ------------------------------------------------
973
974    #[test]
975    fn plugin_registers_resources() {
976        struct TestPlugin;
977
978        impl crate::plugin::Plugin for TestPlugin {
979            fn build(self, world: &mut WorldBuilder) {
980                world.register::<u64>(42);
981                world.register::<bool>(true);
982            }
983        }
984
985        let mut builder = WorldBuilder::new();
986        builder.install_plugin(TestPlugin);
987        let world = builder.build();
988
989        assert_eq!(*world.resource::<u64>(), 42);
990        assert_eq!(*world.resource::<bool>(), true);
991    }
992
993    #[test]
994    fn driver_installs_and_returns_handle() {
995        struct TestInstaller;
996        struct TestHandle {
997            counter_id: ResourceId,
998        }
999
1000        impl crate::driver::Driver for TestInstaller {
1001            type Handle = TestHandle;
1002
1003            fn install(self, world: &mut WorldBuilder) -> TestHandle {
1004                world.register::<u64>(0);
1005                let counter_id = world.registry().id::<u64>();
1006                TestHandle { counter_id }
1007            }
1008        }
1009
1010        let mut builder = WorldBuilder::new();
1011        let handle = builder.install_driver(TestInstaller);
1012        let world = builder.build();
1013
1014        // Handle's pre-resolved ID can access the resource.
1015        unsafe {
1016            assert_eq!(*world.get::<u64>(handle.counter_id), 0);
1017        }
1018    }
1019
1020    // -- check_access slow path (>128 resources) ------------------------------
1021
1022    #[test]
1023    fn check_access_slow_path_no_conflict() {
1024        // Register 130 distinct types to force the slow path (>128).
1025        macro_rules! register_many {
1026            ($builder:expr, $($i:literal),* $(,)?) => {
1027                $(
1028                    $builder.register::<[u8; $i]>([0u8; $i]);
1029                )*
1030            };
1031        }
1032
1033        let mut builder = WorldBuilder::new();
1034        register_many!(
1035            builder, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
1036            23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
1037            45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
1038            67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
1039            89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
1040            108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
1041            125, 126, 127, 128, 129, 130
1042        );
1043        assert!(builder.len() > 128);
1044
1045        // Non-conflicting accesses at high indices — exercises slow path.
1046        let accesses = [(Some(ResourceId(0)), "a"), (Some(ResourceId(129)), "b")];
1047        builder.registry_mut().check_access(&accesses);
1048    }
1049
1050    #[test]
1051    #[should_panic(expected = "conflicting access")]
1052    fn check_access_slow_path_detects_conflict() {
1053        macro_rules! register_many {
1054            ($builder:expr, $($i:literal),* $(,)?) => {
1055                $(
1056                    $builder.register::<[u8; $i]>([0u8; $i]);
1057                )*
1058            };
1059        }
1060
1061        let mut builder = WorldBuilder::new();
1062        register_many!(
1063            builder, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
1064            23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
1065            45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
1066            67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
1067            89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
1068            108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
1069            125, 126, 127, 128, 129, 130
1070        );
1071
1072        // Duplicate access at index 129 — must panic.
1073        let accesses = [(Some(ResourceId(129)), "a"), (Some(ResourceId(129)), "b")];
1074        builder.registry_mut().check_access(&accesses);
1075    }
1076
1077    #[test]
1078    fn sequence_wrapping() {
1079        let mut builder = WorldBuilder::new();
1080        builder.register::<u64>(0);
1081        let mut world = builder.build();
1082
1083        // Advance to MAX.
1084        world.current_sequence = Sequence(u64::MAX);
1085        assert_eq!(world.current_sequence(), Sequence(u64::MAX));
1086
1087        // Stamp resource at MAX.
1088        *world.resource_mut::<u64>() = 99;
1089        let id = world.id::<u64>();
1090        unsafe {
1091            assert_eq!(world.changed_at(id), Sequence(u64::MAX));
1092        }
1093
1094        // Wrap to 0.
1095        let seq = world.next_sequence();
1096        assert_eq!(seq, Sequence(0));
1097        assert_eq!(world.current_sequence(), Sequence(0));
1098
1099        // Resource changed at MAX, current is 0 → not changed.
1100        unsafe {
1101            assert_ne!(world.changed_at(id), world.current_sequence());
1102        }
1103    }
1104}