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