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