Skip to main content

concinnity_core/ecs/
world.rs

1//! A world: its data, the systems built to run over it, and their schedule.
2//!
3//! The data half is components, resources, events, the compiled-payload store,
4//! the frame profile, and the frame scratch -- exactly the five things a
5//! [`PipelineContext`] borrows, owned in one place. Over it run the systems a
6//! host's [`SystemTable`] gates in, in table order, under a schedule derived
7//! from what each declares it touches.
8//!
9//! Building and running one needs no operating system: the two ties a step
10//! would otherwise have are seams instead -- the [`Clock`] resource for the
11//! per-system profile micros, and the debug-build access validator's hooks in
12//! [`access_check`](crate::ecs::access_check).
13
14use crate::memory::{Arena, MemTag};
15use alloc::boxed::Box;
16use alloc::vec::Vec;
17
18use crate::ecs::asset_id::{AssetId, MintedIds};
19use crate::ecs::waves::{self, ExecSchedule};
20use crate::ecs::{
21    BuiltSystem, Clock, ComponentAsset, ComponentId, ComponentSlot, ComponentStorage, Entity,
22    EnvironmentMapHandle, EventStore, Events, FrameContext, MaterialHandle, MeshHandle, NoPayloads,
23    PayloadStore, PipelineContext, Resources, RuntimeComponent, StepResult, SystemEntry,
24    SystemTable,
25};
26use crate::gfx::profile::FrameProfile;
27use crate::result::CnResult;
28
29// The per-frame scratch reserve. An engine constant rather than an authored
30// field: a schema field would be blob churn for a knob nobody should have to
31// set, and the frame loop reports any frame that outgrows it.
32//
33// A frame's draw scales with the runtime requests it drains: 2,000 visibility
34// requests in one frame measured 24 KiB, so this holds on the order of 87,000.
35const FRAME_SCRATCH_BYTES: usize = 1 << 20;
36
37/// What one frame's scratch reserve cost and whether it held. A non-zero
38/// `overflows` means some frame fell back to the heap, so `peak` understates
39/// what the frame actually wanted.
40#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
41pub struct ScratchStats {
42    /// The reserve's size in bytes.
43    pub capacity: usize,
44    /// The most bytes any frame took from it.
45    pub peak: usize,
46    /// Requests the reserve declined, sending the caller to the heap.
47    pub overflows: u64,
48}
49
50/// A world: its component storage, its resources, the compiled payloads it
51/// loads from, and the systems that run over all three.
52///
53/// Constructing one and filling it with components needs no systems, so this is
54/// the whole world for any caller that only builds or inspects content.
55/// [`start`](World::start) is what gives it systems, from the table the caller
56/// hands it.
57pub struct World {
58    components: ComponentStorage,
59    // Compiled payloads, behind the store seam rather than a concrete type, so
60    // a world names no blob file format and no filesystem.
61    blob: Box<dyn PayloadStore + Send>,
62    profile: FrameProfile,
63    // Type-keyed engine singletons (e.g. the per-frame FrameInput snapshot
64    // GraphicsSystem publishes) and the event queues.
65    resources: Resources,
66    // Per-frame scratch, reset at the top of every step. Owned here because
67    // `reset` needs `&mut`, which is what proves no system still holds an
68    // allocation from the frame just finished.
69    scratch: Arena,
70    // Requests the scratch reserve could not satisfy, over the world's whole
71    // life. The arena's own counter is cleared each frame once reported, so
72    // this is what survives to say the reserve wants raising.
73    scratch_overflows: u64,
74    // The systems built for this world, in table order.
75    systems: Vec<BuiltSystem>,
76    // The table `start` built them from, kept for the schedule rebuild a
77    // finished system triggers.
78    entries: &'static [SystemEntry],
79    // Set once the systems have been built, so a second `start()` on the same
80    // world does not append them twice.
81    systems_built: bool,
82    // The executable schedule over the built systems: declared ordering edges
83    // validated + conflict waves from each system's declared access. Built at
84    // the end of `start()` (after init, when data-dependent declarations are
85    // final) and rebuilt when a `Done` system leaves the set.
86    schedule: Option<ExecSchedule>,
87}
88
89// A world must stay movable to the simulation thread; a !Send member in any
90// system, component, or resource breaks the pipelined driver's thread handoff.
91const _: () = {
92    const fn require_send<T: Send>() {}
93    require_send::<World>()
94};
95
96impl core::fmt::Debug for World {
97    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
98        f.debug_struct("World")
99            .field("components", &self.components.len())
100            .field("systems", &self.systems.len())
101            .finish()
102    }
103}
104
105impl Default for World {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111// The next minted id, drawn from the world's shared counter so ids handed out
112// before start and by the completion pass never collide.
113/// A mesh value a world takes a baked geometry payload for, through
114/// [`World::add_mesh`].
115///
116/// A [`ProceduralMesh`](crate::components::ProceduralMesh) stays in the world
117/// as the record of what was generated; a raw [`Mesh`](crate::components::Mesh)
118/// is nothing but its geometry once baked, so only the payload is kept.
119pub trait BakedMesh: sealed::Sealed {
120    #[doc(hidden)]
121    fn install(self, ctx: &mut PipelineContext, id: AssetId);
122}
123
124impl BakedMesh for crate::components::ProceduralMesh {
125    fn install(mut self, ctx: &mut PipelineContext, id: AssetId) {
126        self.asset_id = id;
127        ctx.push(self);
128    }
129}
130
131impl BakedMesh for crate::components::Mesh {
132    fn install(self, _ctx: &mut PipelineContext, _id: AssetId) {}
133}
134
135mod sealed {
136    pub trait Sealed {}
137    impl Sealed for crate::components::ProceduralMesh {}
138    impl Sealed for crate::components::Mesh {}
139}
140
141fn mint_id(ctx: &mut PipelineContext) -> AssetId {
142    if ctx.resource::<MintedIds>().is_none() {
143        ctx.insert_resource(MintedIds::default());
144    }
145    ctx.resource_mut::<MintedIds>()
146        .expect("the counter was just ensured")
147        .next_id()
148}
149
150impl World {
151    /// An empty world, for contexts that have no compiled payloads (e.g. unit
152    /// tests, or worlds built entirely from runtime-only components).
153    pub fn new() -> Self {
154        Self::from_payloads(Box::new(NoPayloads))
155    }
156
157    /// A world backed by a compiled payload store.
158    pub fn from_payloads(blob: Box<dyn PayloadStore + Send>) -> Self {
159        Self {
160            components: ComponentStorage::default(),
161            blob,
162            profile: FrameProfile::default(),
163            resources: Resources::new(),
164            scratch: Arena::tagged(FRAME_SCRATCH_BYTES, MemTag::Scratch),
165            scratch_overflows: 0,
166            systems: Vec::new(),
167            entries: &[],
168            systems_built: false,
169            schedule: None,
170        }
171    }
172
173    /// Pre-size the component columns from the blob manifest's per-type record
174    /// counts, so the bulk `add` loop that follows never reallocates mid-push.
175    pub fn reserve_components(&mut self, counts: &[(u8, u32)]) {
176        for &(discriminant, count) in counts {
177            self.components
178                .reserve(ComponentId::new(discriminant), count as usize);
179        }
180    }
181
182    /// Add a component loaded from a blob def, returning its minted entity so
183    /// the loaders can index it by name.
184    pub fn add(&mut self, component: ComponentAsset) -> Entity {
185        self.components.push(component)
186    }
187
188    /// Add one component to the world.
189    ///
190    /// Only a [`RuntimeComponent`] can be added: a build-only asset is consumed
191    /// by the cook and never reaches a world.
192    pub fn add_component<C: RuntimeComponent>(&mut self, c: C) {
193        self.components.push(c.into());
194    }
195
196    /// Add a mesh with its baked geometry `payload` and return the handle a
197    /// [`Prop`](crate::components::Prop) references it by.
198    ///
199    /// The world names the mesh itself (from the minted range) and holds the
200    /// payload directly, so no compiled blob is involved; handles count up in
201    /// call order, after any the build assigned.
202    pub fn add_mesh<M: BakedMesh>(&mut self, mesh: M, payload: Vec<u8>) -> MeshHandle {
203        let mut ctx = self.context();
204        let id = mint_id(&mut ctx);
205        let handle = crate::resource::append_mesh(&mut ctx, id, payload);
206        mesh.install(&mut ctx, id);
207        handle
208    }
209
210    /// Add a material and return the handle a
211    /// [`Prop`](crate::components::Prop) references it by. The value's fields
212    /// are clamped into their valid ranges on the way in, the same way the
213    /// cook clamps an authored material.
214    pub fn add_material(&mut self, material: crate::components::Material) -> MaterialHandle {
215        crate::resource::append_material(&mut self.context(), material)
216    }
217
218    /// Add a baked image-based-lighting `payload` (see
219    /// [`bake::payload::environment_map`](crate::bake::payload::environment_map))
220    /// and return its handle. The renderer lights with the map at handle 0.
221    pub fn add_environment_map(&mut self, payload: Vec<u8>) -> EnvironmentMapHandle {
222        crate::resource::append_environment_map(&mut self.context(), payload)
223    }
224
225    /// Remove and drop every component of type C.
226    pub fn remove_all<C: ComponentSlot>(&mut self) {
227        let _ = self.components.drain::<C>();
228    }
229
230    /// Whether the world holds neither components nor systems.
231    pub fn is_empty(&self) -> bool {
232        self.components.is_empty() && self.systems.is_empty()
233    }
234
235    /// Components across every typed column.
236    pub fn component_count(&self) -> usize {
237        self.components.len()
238    }
239
240    /// Iterate every stored component of a given type. Mirrors
241    /// `PipelineContext::query`; useful in tests that hold a `World` directly.
242    pub fn query<C: ComponentSlot>(&self) -> core::slice::Iter<'_, C> {
243        C::slot(&self.components).iter()
244    }
245
246    /// Mutable iteration over all components of type C. Mirror of
247    /// `PipelineContext::query_mut` for code holding a `World` directly rather
248    /// than a per-system `PipelineContext`.
249    pub fn query_mut<C: ComponentSlot>(&mut self) -> core::slice::IterMut<'_, C> {
250        self.components.values_mut::<C>().iter_mut()
251    }
252
253    /// Push a runtime-produced component into the matching typed slot,
254    /// returning its minted entity. Mirror of `PipelineContext::push`.
255    pub fn push<C: ComponentSlot>(&mut self, c: C) -> Entity {
256        self.components.push_typed(c)
257    }
258
259    /// Borrow one entity's component, for code holding a `World` directly.
260    /// Mirror of `PipelineContext::get`.
261    pub fn get<C: ComponentSlot>(&self, entity: Entity) -> Option<&C> {
262        self.components.get::<C>(entity)
263    }
264
265    /// Mutably borrow one entity's component. Mirror of
266    /// `PipelineContext::get_mut`.
267    pub fn get_mut<C: ComponentSlot>(&mut self, entity: Entity) -> Option<&mut C> {
268        self.components.get_mut::<C>(entity)
269    }
270
271    /// Add a component to an existing entity. Mirror of
272    /// `PipelineContext::insert`.
273    pub fn insert<C: ComponentSlot>(&mut self, entity: Entity, c: C) {
274        self.components.insert_typed(entity, c);
275    }
276
277    /// Overwrite an existing component with a rebuilt one, keeping the entity
278    /// and its other components. `false` when the entity holds no component of
279    /// that type. An editing tool that rebuilds one component from changed
280    /// authoring data writes it back through here rather than reloading the
281    /// world around it.
282    pub fn replace_component(&mut self, entity: Entity, asset: ComponentAsset) -> bool {
283        self.components.replace(entity, asset)
284    }
285
286    /// Whether an entity is still live. Mirror of `PipelineContext::is_alive`;
287    /// guards name-index resolves against entities despawned by the start-time
288    /// drains (Window, GraphicsConfig, Scene, ...).
289    pub fn is_alive(&self, entity: Entity) -> bool {
290        self.components.is_alive(entity)
291    }
292
293    /// Despawn an entity (all its components, recycling its id). Stands in for
294    /// the GraphicsSystem-mediated despawn in system tests that need an entity
295    /// gone before a later system step (e.g. physics-body reaping).
296    pub fn despawn(&mut self, entity: Entity) {
297        self.components.despawn(entity);
298    }
299
300    /// Read-only join over two component types, for code holding a `World`
301    /// directly (the decomposition round-trip tests). Mirror of
302    /// `PipelineContext::join2`.
303    pub fn join2<A: ComponentSlot, B: ComponentSlot>(
304        &self,
305    ) -> impl Iterator<Item = (Entity, &A, &B)> {
306        self.components.join2::<A, B>()
307    }
308
309    /// How many components of each type the world holds, one entry per
310    /// populated type.
311    pub fn component_census(&self) -> Vec<(u8, u32)> {
312        self.components.component_census()
313    }
314
315    /// Borrow the event queue for event type E, if any have been sent. Mirror of
316    /// `PipelineContext::events`, for code holding a `World` directly (tests).
317    pub fn events<E: 'static>(&self) -> Option<&Events<E>> {
318        self.resources.get::<EventStore>()?.get::<E>()
319    }
320
321    /// Mutably borrow (creating if absent) the event queue for event type E.
322    /// Mirror of `PipelineContext::events_mut`, for code holding a `World`
323    /// directly: tests, and the editor's debug-driven command injection.
324    pub fn events_mut<E: Send + 'static>(&mut self) -> &mut Events<E> {
325        self.event_store().get_mut_or_create::<E>()
326    }
327
328    /// Seed (or replace) a singleton resource that persists across steps.
329    pub fn insert_resource<T: core::any::Any + Send>(&mut self, value: T) {
330        self.resources.insert(value);
331    }
332
333    /// Borrow a published singleton resource.
334    pub fn resource<T: core::any::Any>(&self) -> Option<&T> {
335        self.resources.get::<T>()
336    }
337
338    /// Mutably borrow a published singleton resource.
339    pub fn resource_mut<T: core::any::Any>(&mut self) -> Option<&mut T> {
340        self.resources.get_mut::<T>()
341    }
342
343    /// Withdraw a published singleton resource. Presence-keyed protocols turn
344    /// off by removing their resource, so the reading system pays nothing
345    /// beyond noticing the absence.
346    pub fn remove_resource<T: core::any::Any>(&mut self) -> Option<T> {
347        self.resources.remove::<T>()
348    }
349
350    /// Per-frame profiling data: system CPU timings and render-backend stats
351    /// from the most recently completed frame.
352    pub fn profile(&self) -> &FrameProfile {
353        &self.profile
354    }
355
356    /// What the frame scratch cost and whether it was big enough, for the
357    /// `memory` query and the Health panel. `peak` is what sizes the reserve.
358    pub fn scratch_stats(&self) -> ScratchStats {
359        ScratchStats {
360            capacity: self.scratch.capacity(),
361            peak: self.scratch.peak(),
362            overflows: self.scratch_overflows,
363        }
364    }
365
366    /// The systems' view of this world for one tick. The caller holds the
367    /// returned context for the whole tick, so the borrow of `self` is what
368    /// keeps the world's data still while systems run over it.
369    pub fn context(&mut self) -> PipelineContext<'_> {
370        self.systems_and_context().1
371    }
372
373    /// The `EventStore` resource, created on first use. Every queue
374    /// `events_mut` ever handed out (here or on a `PipelineContext`) lives in
375    /// this one resource, so no per-type rotation list can fall out of sync.
376    pub fn event_store(&mut self) -> &mut EventStore {
377        if !self.resources.contains::<EventStore>() {
378            self.resources.insert(EventStore::new());
379        }
380        self.resources
381            .get_mut::<EventStore>()
382            .expect("EventStore was just inserted")
383    }
384
385    /// Advance every event queue once, before systems run, so each queue's
386    /// two-frame retention holds for readers that run after the writer.
387    pub fn update_events(&mut self) {
388        if let Some(store) = self.resources.get_mut::<EventStore>() {
389            store.update_all();
390        }
391    }
392
393    /// Hand the whole frame's scratch back. `&mut self` is the proof that no
394    /// allocation from the last frame survives.
395    pub fn reset_scratch(&mut self) {
396        self.scratch.reset();
397    }
398
399    /// Release every resident compiled payload, returning the bytes freed. Run
400    /// once every system has inited and cached what it keeps.
401    pub fn release_payloads(&mut self) -> usize {
402        self.blob.release_all_resident()
403    }
404
405    /// The world's systems, in schedule order.
406    pub fn systems(&self) -> &[BuiltSystem] {
407        &self.systems
408    }
409
410    /// Mutable view of the active systems. Lets a caller holding the world
411    /// downcast one system out of the boxed set and drive it from outside the
412    /// per-system step (the `cn debug` hot-reload drive).
413    pub fn systems_mut(&mut self) -> &mut [BuiltSystem] {
414        &mut self.systems
415    }
416
417    /// Disjoint mutable borrows of the system list and the resource map, for a
418    /// caller that drives a system against something parked in a resource (the
419    /// `cn debug` hot-reload drive reaches the render backend that way).
420    pub fn systems_and_resources(&mut self) -> (&mut [BuiltSystem], &mut Resources) {
421        (&mut self.systems, &mut self.resources)
422    }
423
424    /// Systems built for this world.
425    pub fn system_count(&self) -> usize {
426        self.systems.len()
427    }
428
429    /// The system names `table` would build for this world's current content,
430    /// in run order. Runs the same gates [`start`](World::start) runs, so
431    /// tooling that reports a world's schedule cannot drift from the runtime;
432    /// the probe constructs and discards each gated system, which is why
433    /// constructors must stay cheap and side-effect-free. It reads the world
434    /// as it stands: before `start` the table's `complete_world` pass has not
435    /// run, so a system only an injected default turns on is not listed yet,
436    /// and after `start` has drained the gating components it reports the
437    /// systems a rebuild of the CURRENT content would get, not the built set.
438    pub fn system_manifest(&self, table: &SystemTable) -> Vec<&'static str> {
439        table
440            .entries
441            .iter()
442            .filter(|entry| (entry.gate)(self).is_some())
443            .map(|entry| entry.name)
444            .collect()
445    }
446
447    // Disjoint borrows of the system list and the tick's context over the data
448    // half. Splitting the two is what lets a system step against the world it
449    // lives in.
450    fn systems_and_context(&mut self) -> (&mut Vec<BuiltSystem>, PipelineContext<'_>) {
451        (
452            &mut self.systems,
453            PipelineContext {
454                components: &mut self.components,
455                blob: &mut *self.blob,
456                profile: &mut self.profile,
457                resources: &mut self.resources,
458                frame: FrameContext::new(&self.scratch),
459            },
460        )
461    }
462
463    /// Build the systems `table` gates in for this world's content and run
464    /// their `init`.
465    pub fn start(&mut self, table: &SystemTable) -> Result<(), CnResult> {
466        // The host's completion pass, before the gates read the world: an
467        // injected component brings its own system into the schedule. Guarded
468        // by the same once-per-world flag as the build below, so a second
469        // `start` neither re-injects nor re-gates.
470        if !self.systems_built
471            && let Some(complete) = table.complete_world
472        {
473            let mut ctx = self.context();
474            complete(&mut ctx)?;
475        }
476        self.build_systems(table);
477        let (systems, mut ctx) = self.systems_and_context();
478        // The host's load-time pass, before systems init: the engine gives each
479        // loaded placement its per-instance components here.
480        if let Some(before_init) = table.before_init {
481            before_init(&mut ctx);
482        }
483        for system in systems.iter_mut() {
484            system.init(&mut ctx);
485        }
486        // Every system has inited and cached the payloads it keeps; nothing
487        // reads compiled payloads at runtime. Free every blob section still
488        // resident: the shipped runtime's blob 0, the audio / SDF / terrain
489        // blobs the GraphicsSystem init sweep held back for their later
490        // consumers, and every blob in a world with no GraphicsSystem to run
491        // that sweep at all.
492        self.release_payloads();
493        // Access declarations are final once every system has inited, so this
494        // is the earliest the edges can be validated and the waves derived.
495        let schedule = waves::build(&self.systems, self.entries);
496        // Pre-create the event queues declared systems can touch, so their
497        // `events_mut` never grows the store's map mid-tick.
498        if let Some(prepare_events) = table.prepare_events
499            && !schedule.is_empty()
500        {
501            for i in 0..schedule.len() {
502                let access = schedule.access(i);
503                prepare_events(self.event_store(), access);
504            }
505        }
506        self.schedule = Some(schedule);
507        Ok(())
508    }
509
510    // Construct the systems the table gates in, in table order, just before
511    // `init`. Each entry is present only when its gating content is, and is
512    // built from it by the entry's gate. Runs at most once per world (guarded
513    // by `systems_built`) so a system whose gating components survive `init` is
514    // not built twice.
515    fn build_systems(&mut self, table: &SystemTable) {
516        if self.systems_built {
517            return;
518        }
519        self.systems_built = true;
520        self.entries = table.entries;
521        for entry in table.entries {
522            if let Some(system) = (entry.gate)(self) {
523                self.systems.push(BuiltSystem::new(entry.name, system));
524            }
525        }
526    }
527
528    /// Tick -- systems run in order, Done systems are removed.
529    /// Returns Done when no systems remain, Stop on hard halt.
530    pub fn step(&mut self) -> StepResult {
531        // Dev builds sample the tracked heap around the frame and each system
532        // step, so per-frame allocation churn is visible in the profile. The
533        // counters are process-wide: a delta includes concurrent threads
534        // (streaming workers, the pipelined render half), so per-system
535        // attribution is approximate while the frame total is exact churn.
536        #[cfg(debug_assertions)]
537        let frame_alloc_start = crate::memory::alloc_count();
538        // Rotate the profiler's system-timing buffers so the frame that just
539        // finished becomes the readable snapshot for this frame's readers.
540        self.profile.begin_frame();
541        // Advance every event queue once per frame, before systems run, so each
542        // queue's two-frame retention holds for readers that run after the
543        // writer.
544        self.update_events();
545        // Hand the whole frame's scratch back before anything runs.
546        self.reset_scratch();
547        // The host's monotonic clock, read once per tick. A world running
548        // without one records zero micros per system.
549        let clock = self.resources.get::<Clock>().map(|c| c.0);
550        let (systems, mut ctx) = self.systems_and_context();
551        let mut i = 0;
552        let mut removed_any = false;
553        while i < systems.len() {
554            let name = systems[i].name();
555            let started = clock.map_or(0, |now| now());
556            #[cfg(debug_assertions)]
557            let alloc_start = crate::memory::alloc_count();
558            #[cfg(debug_assertions)]
559            crate::ecs::access_check::set_active(Some((systems[i].access(), name)));
560            let result = systems[i].step(&mut ctx);
561            #[cfg(debug_assertions)]
562            crate::ecs::access_check::set_active(None);
563            let micros = clock.map_or(0, |now| {
564                now().saturating_sub(started).min(u32::MAX as u64) as u32
565            });
566            ctx.profile.record_system(name, micros);
567            #[cfg(debug_assertions)]
568            if let (Some(start), Some(end)) = (alloc_start, crate::memory::alloc_count()) {
569                ctx.profile.record_system_allocs(
570                    name,
571                    end.saturating_sub(start).min(u32::MAX as u64) as u32,
572                );
573            }
574            match result {
575                StepResult::Stop => return StepResult::Stop,
576                StepResult::Done => {
577                    systems.remove(i);
578                    removed_any = true;
579                }
580                StepResult::Continue => {
581                    i += 1;
582                }
583            }
584        }
585        if removed_any && self.schedule.is_some() {
586            self.schedule = Some(waves::build(&self.systems, self.entries));
587        }
588        self.take_scratch_overflows();
589        #[cfg(debug_assertions)]
590        if let (Some(start), Some(end)) = (frame_alloc_start, crate::memory::alloc_count()) {
591            self.profile
592                .set_frame_allocs(end.saturating_sub(start).min(u32::MAX as u64) as u32);
593        }
594        if self.systems.is_empty() {
595            StepResult::Done
596        } else {
597            StepResult::Continue
598        }
599    }
600
601    /// Fold the frame's declined scratch requests into the world's running
602    /// total, returning what this frame declined. A frame that outgrew the
603    /// reserve fell back to the heap and still rendered, so nothing breaks; the
604    /// count is what a host surfaces to say the reserve is undersized.
605    pub fn take_scratch_overflows(&mut self) -> u32 {
606        let overflows = self.scratch.overflows();
607        if overflows > 0 {
608            self.scratch.clear_overflows();
609            self.scratch_overflows = self.scratch_overflows.saturating_add(overflows as u64);
610        }
611        overflows
612    }
613}
614
615#[cfg(test)]
616mod tests {
617    use super::*;
618    use crate::components::TextLabel;
619
620    #[test]
621    fn a_new_world_is_empty() {
622        let world = World::new();
623        assert!(world.is_empty());
624        assert_eq!(world.component_count(), 0);
625    }
626
627    #[test]
628    fn components_are_queryable_after_add() {
629        let mut world = World::new();
630        world.add_component(TextLabel {
631            content: "hello".into(),
632            ..Default::default()
633        });
634        assert!(!world.is_empty());
635        assert_eq!(world.component_count(), 1);
636        assert_eq!(world.query::<TextLabel>().count(), 1);
637        assert_eq!(world.query::<TextLabel>().next().unwrap().content, "hello");
638    }
639
640    #[test]
641    fn reserve_components_leaves_the_world_empty() {
642        let mut world = World::new();
643        world.reserve_components(&[(TextLabel::DISCRIMINANT, 8)]);
644        assert!(world.is_empty());
645        assert_eq!(world.query::<TextLabel>().count(), 0);
646    }
647
648    #[test]
649    fn a_pushed_component_is_reachable_by_its_entity() {
650        let mut world = World::new();
651        let entity = world.push(TextLabel {
652            content: "one".into(),
653            ..Default::default()
654        });
655        assert!(world.is_alive(entity));
656        assert_eq!(world.get::<TextLabel>(entity).unwrap().content, "one");
657        world.get_mut::<TextLabel>(entity).unwrap().content = "two".into();
658        assert_eq!(world.get::<TextLabel>(entity).unwrap().content, "two");
659        world.despawn(entity);
660        assert!(!world.is_alive(entity));
661    }
662
663    #[test]
664    fn remove_all_drains_one_column() {
665        let mut world = World::new();
666        world.add_component(TextLabel::default());
667        world.add_component(TextLabel::default());
668        assert_eq!(world.component_count(), 2);
669        world.remove_all::<TextLabel>();
670        assert!(world.is_empty());
671    }
672
673    #[test]
674    fn the_census_counts_each_populated_type() {
675        let mut world = World::new();
676        world.add_component(TextLabel::default());
677        world.add_component(TextLabel::default());
678        let census = world.component_census();
679        assert_eq!(census, alloc::vec![(TextLabel::DISCRIMINANT, 2)]);
680    }
681
682    #[test]
683    fn resources_round_trip() {
684        let mut world = World::new();
685        assert!(world.resource::<u32>().is_none());
686        world.insert_resource(7u32);
687        assert_eq!(world.resource::<u32>(), Some(&7));
688        *world.resource_mut::<u32>().unwrap() = 9;
689        assert_eq!(world.remove_resource::<u32>(), Some(9));
690        assert!(world.resource::<u32>().is_none());
691    }
692
693    #[test]
694    fn events_are_readable_after_send() {
695        let mut world = World::new();
696        assert!(world.events::<u8>().is_none());
697        world.events_mut::<u8>().send(3);
698        assert_eq!(
699            world.events::<u8>().expect("queue was just created").len(),
700            1
701        );
702    }
703
704    // Two frames' worth of rotation: the queue's retention must outlive one
705    // update so a reader running after the writer still sees the send.
706    #[test]
707    fn update_events_retains_a_send_for_one_frame() {
708        let mut world = World::new();
709        world.events_mut::<u8>().send(3);
710        world.update_events();
711        assert_eq!(world.events::<u8>().unwrap().len(), 1);
712        world.update_events();
713        assert_eq!(world.events::<u8>().unwrap().len(), 0);
714    }
715
716    #[test]
717    fn the_context_sees_the_worlds_components() {
718        let mut world = World::new();
719        world.add_component(TextLabel {
720            content: "ctx".into(),
721            ..Default::default()
722        });
723        let ctx = world.context();
724        assert_eq!(ctx.query::<TextLabel>().next().unwrap().content, "ctx");
725    }
726
727    // The reserve is whole at rest, and a world that never allocated from it
728    // has declined nothing.
729    #[test]
730    fn a_quiet_world_reports_no_scratch_overflow() {
731        let mut world = World::new();
732        assert_eq!(world.take_scratch_overflows(), 0);
733        let stats = world.scratch_stats();
734        assert_eq!(stats.capacity, FRAME_SCRATCH_BYTES);
735        assert_eq!(stats.overflows, 0);
736    }
737
738    #[test]
739    fn an_empty_payload_store_frees_nothing() {
740        let mut world = World::new();
741        assert_eq!(world.release_payloads(), 0);
742    }
743}