Skip to main content

elevator_core/
world.rs

1//! Central entity/component storage (struct-of-arrays ECS).
2
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5
6use slotmap::{SecondaryMap, SlotMap};
7
8use crate::components::{
9    AccessControl, Elevator, Line, Patience, Position, Preferences, Rider, Route, ServiceMode,
10    Stop, Velocity,
11};
12#[cfg(feature = "energy")]
13use crate::energy::{EnergyMetrics, EnergyProfile};
14use crate::entity::EntityId;
15use crate::query::storage::AnyExtMap;
16
17/// Central storage for all simulation entities and their components.
18///
19/// Uses separate `SecondaryMap` per component type (struct-of-arrays pattern)
20/// to enable independent mutable borrows of different component storages
21/// within the same system function.
22///
23/// Built-in components are accessed via typed methods. Games can attach
24/// custom data via the extension storage (`insert_ext` / `get_ext`).
25/// The query builder (`world.query::<...>()`) provides ECS-style iteration.
26pub struct World {
27    /// Primary key storage. An entity exists iff its key is here.
28    pub(crate) alive: SlotMap<EntityId, ()>,
29
30    // -- Built-in component storages (crate-internal) --
31    /// Shaft-axis positions.
32    pub(crate) positions: SecondaryMap<EntityId, Position>,
33    /// Shaft-axis velocities.
34    pub(crate) velocities: SecondaryMap<EntityId, Velocity>,
35    /// Elevator components.
36    pub(crate) elevators: SecondaryMap<EntityId, Elevator>,
37    /// Stop (floor/station) data.
38    pub(crate) stops: SecondaryMap<EntityId, Stop>,
39    /// Rider core data.
40    pub(crate) riders: SecondaryMap<EntityId, Rider>,
41    /// Multi-leg routes.
42    pub(crate) routes: SecondaryMap<EntityId, Route>,
43    /// Line (physical path) components.
44    pub(crate) lines: SecondaryMap<EntityId, Line>,
45    /// Patience tracking.
46    pub(crate) patience: SecondaryMap<EntityId, Patience>,
47    /// Boarding preferences.
48    pub(crate) preferences: SecondaryMap<EntityId, Preferences>,
49    /// Per-rider access control (allowed stops).
50    pub(crate) access_controls: SecondaryMap<EntityId, AccessControl>,
51
52    /// Per-elevator energy cost profiles.
53    #[cfg(feature = "energy")]
54    pub(crate) energy_profiles: SecondaryMap<EntityId, EnergyProfile>,
55    /// Per-elevator accumulated energy metrics.
56    #[cfg(feature = "energy")]
57    pub(crate) energy_metrics: SecondaryMap<EntityId, EnergyMetrics>,
58    /// Elevator service modes.
59    pub(crate) service_modes: SecondaryMap<EntityId, ServiceMode>,
60
61    /// Disabled marker (entities skipped by all systems).
62    pub(crate) disabled: SecondaryMap<EntityId, ()>,
63
64    // -- Extension storage for game-specific components --
65    /// Type-erased per-entity maps for custom components.
66    extensions: HashMap<TypeId, Box<dyn AnyExtMap>>,
67    /// `TypeId` → name mapping for extension serialization.
68    ext_names: HashMap<TypeId, String>,
69
70    // -- Global resources (singletons not attached to any entity) --
71    /// Type-erased global resources for game-specific state.
72    resources: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
73}
74
75impl World {
76    /// Create an empty world with no entities.
77    #[must_use]
78    pub fn new() -> Self {
79        Self {
80            alive: SlotMap::with_key(),
81            positions: SecondaryMap::new(),
82            velocities: SecondaryMap::new(),
83            elevators: SecondaryMap::new(),
84            stops: SecondaryMap::new(),
85            riders: SecondaryMap::new(),
86            routes: SecondaryMap::new(),
87            lines: SecondaryMap::new(),
88            patience: SecondaryMap::new(),
89            preferences: SecondaryMap::new(),
90            access_controls: SecondaryMap::new(),
91            #[cfg(feature = "energy")]
92            energy_profiles: SecondaryMap::new(),
93            #[cfg(feature = "energy")]
94            energy_metrics: SecondaryMap::new(),
95            service_modes: SecondaryMap::new(),
96            disabled: SecondaryMap::new(),
97            extensions: HashMap::new(),
98            ext_names: HashMap::new(),
99            resources: HashMap::new(),
100        }
101    }
102
103    /// Allocate a new entity. Returns its id. No components attached yet.
104    pub fn spawn(&mut self) -> EntityId {
105        self.alive.insert(())
106    }
107
108    /// Remove an entity and all its components (built-in and extensions).
109    ///
110    /// Cross-references are cleaned up automatically:
111    /// - If the entity is a rider aboard an elevator, it is removed from the
112    ///   elevator's rider list and `current_load` is adjusted.
113    /// - If the entity is an elevator, its riders' phases are reset to `Waiting`.
114    pub fn despawn(&mut self, id: EntityId) {
115        // Clean up rider → elevator cross-references.
116        if let Some(rider) = self.riders.get(id) {
117            let weight = rider.weight;
118            // If this rider is aboard an elevator, remove from its riders list.
119            match rider.phase {
120                crate::components::RiderPhase::Boarding(elev)
121                | crate::components::RiderPhase::Riding(elev)
122                | crate::components::RiderPhase::Exiting(elev) => {
123                    if let Some(car) = self.elevators.get_mut(elev) {
124                        car.riders.retain(|r| *r != id);
125                        car.current_load = (car.current_load - weight).max(0.0);
126                    }
127                }
128                _ => {}
129            }
130        }
131
132        // Clean up elevator → rider cross-references.
133        if let Some(car) = self.elevators.get(id) {
134            let rider_ids: Vec<EntityId> = car.riders.clone();
135            let elev_pos = self.positions.get(id).map(|p| p.value);
136            let nearest_stop = elev_pos.and_then(|p| self.find_nearest_stop(p));
137            for rid in rider_ids {
138                if let Some(rider) = self.riders.get_mut(rid) {
139                    rider.phase = crate::components::RiderPhase::Waiting;
140                    rider.current_stop = nearest_stop;
141                }
142            }
143        }
144
145        self.alive.remove(id);
146        self.positions.remove(id);
147        self.velocities.remove(id);
148        self.elevators.remove(id);
149        self.stops.remove(id);
150        self.riders.remove(id);
151        self.routes.remove(id);
152        self.lines.remove(id);
153        self.patience.remove(id);
154        self.preferences.remove(id);
155        self.access_controls.remove(id);
156        #[cfg(feature = "energy")]
157        self.energy_profiles.remove(id);
158        #[cfg(feature = "energy")]
159        self.energy_metrics.remove(id);
160        self.service_modes.remove(id);
161        self.disabled.remove(id);
162
163        for ext in self.extensions.values_mut() {
164            ext.remove(id);
165        }
166    }
167
168    /// Check if an entity is alive.
169    #[must_use]
170    pub fn is_alive(&self, id: EntityId) -> bool {
171        self.alive.contains_key(id)
172    }
173
174    /// Number of live entities.
175    #[must_use]
176    pub fn entity_count(&self) -> usize {
177        self.alive.len()
178    }
179
180    /// Iterate all alive entity keys (used by the query builder).
181    pub(crate) fn alive_keys(&self) -> slotmap::basic::Keys<'_, EntityId, ()> {
182        self.alive.keys()
183    }
184
185    // ── Position accessors ───────────────────────────────────────────
186
187    /// Get an entity's position.
188    #[must_use]
189    pub fn position(&self, id: EntityId) -> Option<&Position> {
190        self.positions.get(id)
191    }
192
193    /// Get an entity's position mutably.
194    pub fn position_mut(&mut self, id: EntityId) -> Option<&mut Position> {
195        self.positions.get_mut(id)
196    }
197
198    /// Set an entity's position.
199    pub fn set_position(&mut self, id: EntityId, pos: Position) {
200        self.positions.insert(id, pos);
201    }
202
203    // ── Velocity accessors ───────────────────────────────────────────
204
205    /// Get an entity's velocity.
206    #[must_use]
207    pub fn velocity(&self, id: EntityId) -> Option<&Velocity> {
208        self.velocities.get(id)
209    }
210
211    /// Get an entity's velocity mutably.
212    pub fn velocity_mut(&mut self, id: EntityId) -> Option<&mut Velocity> {
213        self.velocities.get_mut(id)
214    }
215
216    /// Set an entity's velocity.
217    pub fn set_velocity(&mut self, id: EntityId, vel: Velocity) {
218        self.velocities.insert(id, vel);
219    }
220
221    // ── Elevator accessors ───────────────────────────────────────────
222
223    /// Get an entity's elevator component.
224    #[must_use]
225    pub fn elevator(&self, id: EntityId) -> Option<&Elevator> {
226        self.elevators.get(id)
227    }
228
229    /// Get an entity's elevator component mutably.
230    pub fn elevator_mut(&mut self, id: EntityId) -> Option<&mut Elevator> {
231        self.elevators.get_mut(id)
232    }
233
234    /// Set an entity's elevator component.
235    pub fn set_elevator(&mut self, id: EntityId, elev: Elevator) {
236        self.elevators.insert(id, elev);
237    }
238
239    // ── Rider accessors ──────────────────────────────────────────────
240
241    /// Get an entity's rider component.
242    #[must_use]
243    pub fn rider(&self, id: EntityId) -> Option<&Rider> {
244        self.riders.get(id)
245    }
246
247    /// Get an entity's rider component mutably.
248    pub fn rider_mut(&mut self, id: EntityId) -> Option<&mut Rider> {
249        self.riders.get_mut(id)
250    }
251
252    /// Set an entity's rider component.
253    pub fn set_rider(&mut self, id: EntityId, rider: Rider) {
254        self.riders.insert(id, rider);
255    }
256
257    // ── Stop accessors ───────────────────────────────────────────────
258
259    /// Get an entity's stop component.
260    #[must_use]
261    pub fn stop(&self, id: EntityId) -> Option<&Stop> {
262        self.stops.get(id)
263    }
264
265    /// Get an entity's stop component mutably.
266    pub fn stop_mut(&mut self, id: EntityId) -> Option<&mut Stop> {
267        self.stops.get_mut(id)
268    }
269
270    /// Set an entity's stop component.
271    pub fn set_stop(&mut self, id: EntityId, stop: Stop) {
272        self.stops.insert(id, stop);
273    }
274
275    // ── Route accessors ──────────────────────────────────────────────
276
277    /// Get an entity's route.
278    #[must_use]
279    pub fn route(&self, id: EntityId) -> Option<&Route> {
280        self.routes.get(id)
281    }
282
283    /// Get an entity's route mutably.
284    pub fn route_mut(&mut self, id: EntityId) -> Option<&mut Route> {
285        self.routes.get_mut(id)
286    }
287
288    /// Set an entity's route.
289    pub fn set_route(&mut self, id: EntityId, route: Route) {
290        self.routes.insert(id, route);
291    }
292
293    // ── Line accessors ─────────────────────────────────────────────��──
294
295    /// Get an entity's line component.
296    #[must_use]
297    pub fn line(&self, id: EntityId) -> Option<&Line> {
298        self.lines.get(id)
299    }
300
301    /// Get an entity's line component mutably.
302    pub fn line_mut(&mut self, id: EntityId) -> Option<&mut Line> {
303        self.lines.get_mut(id)
304    }
305
306    /// Set an entity's line component.
307    pub fn set_line(&mut self, id: EntityId, line: Line) {
308        self.lines.insert(id, line);
309    }
310
311    /// Remove an entity's line component.
312    pub fn remove_line(&mut self, id: EntityId) -> Option<Line> {
313        self.lines.remove(id)
314    }
315
316    /// Iterate all line entities.
317    pub fn iter_lines(&self) -> impl Iterator<Item = (EntityId, &Line)> {
318        self.lines.iter()
319    }
320
321    // ── Patience accessors ───────────────────────────────────────────
322
323    /// Get an entity's patience.
324    #[must_use]
325    pub fn patience(&self, id: EntityId) -> Option<&Patience> {
326        self.patience.get(id)
327    }
328
329    /// Get an entity's patience mutably.
330    pub fn patience_mut(&mut self, id: EntityId) -> Option<&mut Patience> {
331        self.patience.get_mut(id)
332    }
333
334    /// Set an entity's patience.
335    pub fn set_patience(&mut self, id: EntityId, patience: Patience) {
336        self.patience.insert(id, patience);
337    }
338
339    // ── Preferences accessors ────────────────────────────────────────
340
341    /// Get an entity's preferences.
342    #[must_use]
343    pub fn preferences(&self, id: EntityId) -> Option<&Preferences> {
344        self.preferences.get(id)
345    }
346
347    /// Set an entity's preferences.
348    pub fn set_preferences(&mut self, id: EntityId, prefs: Preferences) {
349        self.preferences.insert(id, prefs);
350    }
351
352    // ── Access control accessors ────────────────────────────────────
353
354    /// Get an entity's access control.
355    #[must_use]
356    pub fn access_control(&self, id: EntityId) -> Option<&AccessControl> {
357        self.access_controls.get(id)
358    }
359
360    /// Get an entity's access control mutably.
361    pub fn access_control_mut(&mut self, id: EntityId) -> Option<&mut AccessControl> {
362        self.access_controls.get_mut(id)
363    }
364
365    /// Set an entity's access control.
366    pub fn set_access_control(&mut self, id: EntityId, ac: AccessControl) {
367        self.access_controls.insert(id, ac);
368    }
369
370    // ── Energy accessors (feature-gated) ────────────────────────────
371
372    #[cfg(feature = "energy")]
373    /// Get an entity's energy profile.
374    #[must_use]
375    pub fn energy_profile(&self, id: EntityId) -> Option<&EnergyProfile> {
376        self.energy_profiles.get(id)
377    }
378
379    #[cfg(feature = "energy")]
380    /// Get an entity's energy metrics.
381    #[must_use]
382    pub fn energy_metrics(&self, id: EntityId) -> Option<&EnergyMetrics> {
383        self.energy_metrics.get(id)
384    }
385
386    #[cfg(feature = "energy")]
387    /// Get an entity's energy metrics mutably.
388    pub fn energy_metrics_mut(&mut self, id: EntityId) -> Option<&mut EnergyMetrics> {
389        self.energy_metrics.get_mut(id)
390    }
391
392    #[cfg(feature = "energy")]
393    /// Set an entity's energy profile.
394    pub fn set_energy_profile(&mut self, id: EntityId, profile: EnergyProfile) {
395        self.energy_profiles.insert(id, profile);
396    }
397
398    #[cfg(feature = "energy")]
399    /// Set an entity's energy metrics.
400    pub fn set_energy_metrics(&mut self, id: EntityId, metrics: EnergyMetrics) {
401        self.energy_metrics.insert(id, metrics);
402    }
403
404    // ── Service mode accessors ──────────────────────────────────────
405
406    /// Get an entity's service mode.
407    #[must_use]
408    pub fn service_mode(&self, id: EntityId) -> Option<&ServiceMode> {
409        self.service_modes.get(id)
410    }
411
412    /// Set an entity's service mode.
413    pub fn set_service_mode(&mut self, id: EntityId, mode: ServiceMode) {
414        self.service_modes.insert(id, mode);
415    }
416
417    // ── Typed query helpers ──────────────────────────────────────────
418
419    /// Iterate all elevator entities (have `Elevator` + `Position`).
420    pub fn iter_elevators(&self) -> impl Iterator<Item = (EntityId, &Position, &Elevator)> {
421        self.elevators
422            .iter()
423            .filter_map(|(id, car)| self.positions.get(id).map(|pos| (id, pos, car)))
424    }
425
426    /// Iterate all elevator entity IDs (allocates).
427    #[must_use]
428    pub fn elevator_ids(&self) -> Vec<EntityId> {
429        self.elevators.keys().collect()
430    }
431
432    /// Fill the buffer with all elevator entity IDs, clearing it first.
433    pub fn elevator_ids_into(&self, buf: &mut Vec<EntityId>) {
434        buf.clear();
435        buf.extend(self.elevators.keys());
436    }
437
438    /// Iterate all rider entities.
439    pub fn iter_riders(&self) -> impl Iterator<Item = (EntityId, &Rider)> {
440        self.riders.iter()
441    }
442
443    /// Iterate all rider entities mutably.
444    pub fn iter_riders_mut(&mut self) -> impl Iterator<Item = (EntityId, &mut Rider)> {
445        self.riders.iter_mut()
446    }
447
448    /// Iterate all rider entity IDs (allocates).
449    #[must_use]
450    pub fn rider_ids(&self) -> Vec<EntityId> {
451        self.riders.keys().collect()
452    }
453
454    /// Iterate all stop entities.
455    pub fn iter_stops(&self) -> impl Iterator<Item = (EntityId, &Stop)> {
456        self.stops.iter()
457    }
458
459    /// Iterate all stop entity IDs (allocates).
460    #[must_use]
461    pub fn stop_ids(&self) -> Vec<EntityId> {
462        self.stops.keys().collect()
463    }
464
465    /// Iterate elevators in `Idle` phase (not disabled).
466    pub fn iter_idle_elevators(&self) -> impl Iterator<Item = (EntityId, &Position, &Elevator)> {
467        use crate::components::ElevatorPhase;
468        self.iter_elevators()
469            .filter(|(id, _, car)| car.phase == ElevatorPhase::Idle && !self.is_disabled(*id))
470    }
471
472    /// Iterate elevators in `MovingToStop` phase (not disabled).
473    pub fn iter_moving_elevators(&self) -> impl Iterator<Item = (EntityId, &Position, &Elevator)> {
474        self.iter_elevators().filter(|(id, _, car)| {
475            matches!(car.phase, crate::components::ElevatorPhase::MovingToStop(_))
476                && !self.is_disabled(*id)
477        })
478    }
479
480    /// Iterate riders in `Waiting` phase (not disabled).
481    pub fn iter_waiting_riders(&self) -> impl Iterator<Item = (EntityId, &Rider)> {
482        use crate::components::RiderPhase;
483        self.iter_riders()
484            .filter(|(id, r)| r.phase == RiderPhase::Waiting && !self.is_disabled(*id))
485    }
486
487    /// Find the stop entity at a given position (within epsilon).
488    #[must_use]
489    pub fn find_stop_at_position(&self, position: f64) -> Option<EntityId> {
490        const EPSILON: f64 = 1e-6;
491        self.stops.iter().find_map(|(id, stop)| {
492            if (stop.position - position).abs() < EPSILON {
493                Some(id)
494            } else {
495                None
496            }
497        })
498    }
499
500    /// Find the stop entity nearest to a given position.
501    ///
502    /// Unlike [`find_stop_at_position`](Self::find_stop_at_position), this finds
503    /// the closest stop by minimum distance rather than requiring an exact match.
504    /// Used when ejecting riders from a disabled/despawned elevator mid-transit.
505    #[must_use]
506    pub fn find_nearest_stop(&self, position: f64) -> Option<EntityId> {
507        self.stops
508            .iter()
509            .min_by(|(_, a), (_, b)| {
510                (a.position - position)
511                    .abs()
512                    .total_cmp(&(b.position - position).abs())
513            })
514            .map(|(id, _)| id)
515    }
516
517    /// Get a stop's position by entity id.
518    #[must_use]
519    pub fn stop_position(&self, id: EntityId) -> Option<f64> {
520        self.stops.get(id).map(|s| s.position)
521    }
522
523    // ── Extension (custom component) storage ─────────────────────────
524
525    /// Insert a custom component for an entity.
526    ///
527    /// Games use this to attach their own typed data to simulation entities.
528    /// Extension components must be `Serialize + DeserializeOwned` to support
529    /// snapshot save/load. A `name` string is required for serialization roundtrips.
530    /// Extension components are automatically cleaned up on `despawn()`.
531    ///
532    /// ```
533    /// use elevator_core::world::World;
534    /// use serde::{Serialize, Deserialize};
535    ///
536    /// #[derive(Debug, Clone, Serialize, Deserialize)]
537    /// struct VipTag { level: u32 }
538    ///
539    /// let mut world = World::new();
540    /// let entity = world.spawn();
541    /// world.insert_ext(entity, VipTag { level: 3 }, "vip_tag");
542    /// ```
543    pub fn insert_ext<T: 'static + Send + Sync + serde::Serialize + serde::de::DeserializeOwned>(
544        &mut self,
545        id: EntityId,
546        value: T,
547        name: &str,
548    ) {
549        let type_id = TypeId::of::<T>();
550        let map = self
551            .extensions
552            .entry(type_id)
553            .or_insert_with(|| Box::new(SecondaryMap::<EntityId, T>::new()));
554        if let Some(m) = map.as_any_mut().downcast_mut::<SecondaryMap<EntityId, T>>() {
555            m.insert(id, value);
556        }
557        self.ext_names.insert(type_id, name.to_owned());
558    }
559
560    /// Get a clone of a custom component for an entity.
561    #[must_use]
562    pub fn get_ext<T: 'static + Send + Sync + Clone>(&self, id: EntityId) -> Option<T> {
563        self.ext_map::<T>()?.get(id).cloned()
564    }
565
566    /// Get a mutable reference to a custom component for an entity.
567    pub fn get_ext_mut<T: 'static + Send + Sync>(&mut self, id: EntityId) -> Option<&mut T> {
568        self.ext_map_mut::<T>()?.get_mut(id)
569    }
570
571    /// Remove a custom component for an entity.
572    pub fn remove_ext<T: 'static + Send + Sync>(&mut self, id: EntityId) -> Option<T> {
573        self.ext_map_mut::<T>()?.remove(id)
574    }
575
576    /// Downcast extension storage to a typed `SecondaryMap` (shared).
577    pub(crate) fn ext_map<T: 'static + Send + Sync>(&self) -> Option<&SecondaryMap<EntityId, T>> {
578        self.extensions
579            .get(&TypeId::of::<T>())?
580            .as_any()
581            .downcast_ref::<SecondaryMap<EntityId, T>>()
582    }
583
584    /// Downcast extension storage to a typed `SecondaryMap` (mutable).
585    fn ext_map_mut<T: 'static + Send + Sync>(&mut self) -> Option<&mut SecondaryMap<EntityId, T>> {
586        self.extensions
587            .get_mut(&TypeId::of::<T>())?
588            .as_any_mut()
589            .downcast_mut::<SecondaryMap<EntityId, T>>()
590    }
591
592    /// Serialize all extension component data for snapshot.
593    /// Returns name → (`EntityId` → RON string) mapping.
594    pub(crate) fn serialize_extensions(&self) -> HashMap<String, HashMap<EntityId, String>> {
595        let mut result = HashMap::new();
596        for (type_id, map) in &self.extensions {
597            if let Some(name) = self.ext_names.get(type_id) {
598                result.insert(name.clone(), map.serialize_entries());
599            }
600        }
601        result
602    }
603
604    /// Deserialize extension data from snapshot. Requires that extension types
605    /// have been registered (via `register_ext_deserializer`) before calling.
606    pub(crate) fn deserialize_extensions(
607        &mut self,
608        data: &HashMap<String, HashMap<EntityId, String>>,
609    ) {
610        for (name, entries) in data {
611            // Find the TypeId by name.
612            if let Some((&type_id, _)) = self.ext_names.iter().find(|(_, n)| *n == name) {
613                if let Some(map) = self.extensions.get_mut(&type_id) {
614                    map.deserialize_entries(entries);
615                }
616            }
617        }
618    }
619
620    /// Register an extension type for deserialization (creates empty storage).
621    ///
622    /// Must be called before `restore()` for each extension type that was
623    /// present in the original simulation.
624    pub fn register_ext<
625        T: 'static + Send + Sync + serde::Serialize + serde::de::DeserializeOwned,
626    >(
627        &mut self,
628        name: &str,
629    ) {
630        let type_id = TypeId::of::<T>();
631        self.extensions
632            .entry(type_id)
633            .or_insert_with(|| Box::new(SecondaryMap::<EntityId, T>::new()));
634        self.ext_names.insert(type_id, name.to_owned());
635    }
636
637    // ── Disabled entity management ──────────────────────────────────
638
639    /// Mark an entity as disabled. Disabled entities are skipped by all systems.
640    pub fn disable(&mut self, id: EntityId) {
641        self.disabled.insert(id, ());
642    }
643
644    /// Re-enable a disabled entity.
645    pub fn enable(&mut self, id: EntityId) {
646        self.disabled.remove(id);
647    }
648
649    /// Check if an entity is disabled.
650    #[must_use]
651    pub fn is_disabled(&self, id: EntityId) -> bool {
652        self.disabled.contains_key(id)
653    }
654
655    // ── Global resources (singletons) ───────────────────────────────
656
657    /// Insert a global resource. Replaces any existing resource of the same type.
658    ///
659    /// Resources are singletons not attached to any entity. Games use them
660    /// for event channels, score trackers, or any global state.
661    ///
662    /// ```
663    /// use elevator_core::world::World;
664    /// use elevator_core::events::EventChannel;
665    ///
666    /// #[derive(Debug)]
667    /// enum MyEvent { Score(u32) }
668    ///
669    /// let mut world = World::new();
670    /// world.insert_resource(EventChannel::<MyEvent>::new());
671    /// ```
672    pub fn insert_resource<T: 'static + Send + Sync>(&mut self, value: T) {
673        self.resources.insert(TypeId::of::<T>(), Box::new(value));
674    }
675
676    /// Get a shared reference to a global resource.
677    #[must_use]
678    pub fn resource<T: 'static + Send + Sync>(&self) -> Option<&T> {
679        self.resources.get(&TypeId::of::<T>())?.downcast_ref()
680    }
681
682    /// Get a mutable reference to a global resource.
683    pub fn resource_mut<T: 'static + Send + Sync>(&mut self) -> Option<&mut T> {
684        self.resources.get_mut(&TypeId::of::<T>())?.downcast_mut()
685    }
686
687    /// Remove a global resource, returning it if it existed.
688    pub fn remove_resource<T: 'static + Send + Sync>(&mut self) -> Option<T> {
689        self.resources
690            .remove(&TypeId::of::<T>())
691            .and_then(|b| b.downcast().ok())
692            .map(|b| *b)
693    }
694
695    // ── Query builder ───────────────────────────────────────────────
696
697    /// Create a query builder for iterating entities by component composition.
698    ///
699    /// ```
700    /// use elevator_core::prelude::*;
701    ///
702    /// let mut sim = SimulationBuilder::new().build().unwrap();
703    /// sim.spawn_rider_by_stop_id(StopId(0), StopId(1), 75.0).unwrap();
704    ///
705    /// let world = sim.world();
706    /// for (id, rider, pos) in world.query::<(EntityId, &Rider, &Position)>().iter() {
707    ///     println!("{id:?}: {:?} at {}", rider.phase(), pos.value());
708    /// }
709    /// ```
710    #[must_use]
711    pub const fn query<Q: crate::query::WorldQuery>(&self) -> crate::query::QueryBuilder<'_, Q> {
712        crate::query::QueryBuilder::new(self)
713    }
714
715    /// Create a mutable extension query builder.
716    ///
717    /// Uses the keys-snapshot pattern: collects matching entity IDs upfront
718    /// into an owned `Vec`, then iterates with mutable access via
719    /// [`for_each_mut`](crate::query::ExtQueryMut::for_each_mut).
720    ///
721    /// # Example
722    ///
723    /// ```ignore
724    /// world.query_ext_mut::<VipTag>().for_each_mut(|id, tag| {
725    ///     tag.level += 1;
726    /// });
727    /// ```
728    pub fn query_ext_mut<T: 'static + Send + Sync>(&mut self) -> crate::query::ExtQueryMut<'_, T> {
729        crate::query::ExtQueryMut::new(self)
730    }
731}
732
733impl Default for World {
734    fn default() -> Self {
735        Self::new()
736    }
737}
738
739/// Stops sorted by position for efficient range queries (binary search).
740///
741/// Used by the movement system to detect `PassingFloor` events in O(log n)
742/// instead of O(n) per moving elevator per tick.
743pub(crate) struct SortedStops(pub(crate) Vec<(f64, EntityId)>);