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