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::{BTreeMap, HashMap};
5use std::marker::PhantomData;
6
7use slotmap::{SecondaryMap, SlotMap};
8
9use crate::components::{
10    AccessControl, CallDirection, CarCall, DestinationQueue, Elevator, HallCall, Line, Patience,
11    Position, Preferences, Rider, Route, ServiceMode, Stop, Velocity,
12};
13#[cfg(feature = "energy")]
14use crate::energy::{EnergyMetrics, EnergyProfile};
15use crate::entity::EntityId;
16use crate::query::storage::AnyExtMap;
17
18/// Typed handle for extension component storage.
19///
20/// Constructed via [`ExtKey::new`] with an explicit name, or
21/// [`ExtKey::from_type_name`] which uses `std::any::type_name::<T>()`.
22#[derive(Debug)]
23pub struct ExtKey<T> {
24    /// Human-readable storage name, used for serialization roundtrips.
25    name: &'static str,
26    /// Binds this key to the extension component type `T`.
27    _marker: PhantomData<T>,
28}
29
30impl<T> Clone for ExtKey<T> {
31    fn clone(&self) -> Self {
32        *self
33    }
34}
35impl<T> Copy for ExtKey<T> {}
36
37impl<T> ExtKey<T> {
38    /// Create a key with an explicit storage name.
39    #[must_use]
40    pub const fn new(name: &'static str) -> Self {
41        Self {
42            name,
43            _marker: PhantomData,
44        }
45    }
46
47    /// Create a key using `std::any::type_name::<T>()` as the storage name.
48    #[must_use]
49    pub fn from_type_name() -> Self {
50        Self {
51            name: std::any::type_name::<T>(),
52            _marker: PhantomData,
53        }
54    }
55
56    /// The storage name for this key.
57    #[must_use]
58    pub const fn name(&self) -> &'static str {
59        self.name
60    }
61}
62
63impl<T> Default for ExtKey<T> {
64    fn default() -> Self {
65        Self::from_type_name()
66    }
67}
68
69/// Central storage for all simulation entities and their components.
70///
71/// Uses separate `SecondaryMap` per component type (struct-of-arrays pattern)
72/// to enable independent mutable borrows of different component storages
73/// within the same system function.
74///
75/// Built-in components are accessed via typed methods. Games can attach
76/// custom data via the extension storage (`insert_ext` / `ext`).
77/// The query builder (`world.query::<...>()`) provides ECS-style iteration.
78pub struct World {
79    /// Primary key storage. An entity exists iff its key is here.
80    pub(crate) alive: SlotMap<EntityId, ()>,
81
82    // -- Built-in component storages (crate-internal) --
83    /// Shaft-axis positions.
84    pub(crate) positions: SecondaryMap<EntityId, Position>,
85    /// Snapshot of `positions` taken at the start of the current tick.
86    /// Enables sub-tick interpolation for smooth rendering between steps.
87    pub(crate) prev_positions: SecondaryMap<EntityId, Position>,
88    /// Shaft-axis velocities.
89    pub(crate) velocities: SecondaryMap<EntityId, Velocity>,
90    /// Elevator components.
91    pub(crate) elevators: SecondaryMap<EntityId, Elevator>,
92    /// Stop (floor/station) data.
93    pub(crate) stops: SecondaryMap<EntityId, Stop>,
94    /// Rider core data.
95    pub(crate) riders: SecondaryMap<EntityId, Rider>,
96    /// Multi-leg routes.
97    pub(crate) routes: SecondaryMap<EntityId, Route>,
98    /// Line (physical path) components.
99    pub(crate) lines: SecondaryMap<EntityId, Line>,
100    /// Patience tracking.
101    pub(crate) patience: SecondaryMap<EntityId, Patience>,
102    /// Boarding preferences.
103    pub(crate) preferences: SecondaryMap<EntityId, Preferences>,
104    /// Per-rider access control (allowed stops).
105    pub(crate) access_controls: SecondaryMap<EntityId, AccessControl>,
106
107    /// Per-elevator energy cost profiles.
108    #[cfg(feature = "energy")]
109    pub(crate) energy_profiles: SecondaryMap<EntityId, EnergyProfile>,
110    /// Per-elevator accumulated energy metrics.
111    #[cfg(feature = "energy")]
112    pub(crate) energy_metrics: SecondaryMap<EntityId, EnergyMetrics>,
113    /// Elevator service modes.
114    pub(crate) service_modes: SecondaryMap<EntityId, ServiceMode>,
115    /// Per-elevator destination queues.
116    pub(crate) destination_queues: SecondaryMap<EntityId, DestinationQueue>,
117    /// Up/down hall call buttons per stop. At most two per stop.
118    pub(crate) hall_calls: SecondaryMap<EntityId, StopCalls>,
119    /// Floor buttons pressed inside each car (Classic mode).
120    pub(crate) car_calls: SecondaryMap<EntityId, Vec<CarCall>>,
121
122    /// Disabled marker (entities skipped by all systems).
123    pub(crate) disabled: SecondaryMap<EntityId, ()>,
124
125    // -- Extension storage for game-specific components --
126    /// Type-erased per-entity maps for custom components.
127    extensions: HashMap<TypeId, Box<dyn AnyExtMap>>,
128    /// `TypeId` → name mapping for extension serialization.
129    ext_names: HashMap<TypeId, String>,
130
131    // -- Global resources (singletons not attached to any entity) --
132    /// Type-erased global resources for game-specific state.
133    resources: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
134}
135
136impl World {
137    /// Create an empty world with no entities.
138    #[must_use]
139    pub fn new() -> Self {
140        Self {
141            alive: SlotMap::with_key(),
142            positions: SecondaryMap::new(),
143            prev_positions: SecondaryMap::new(),
144            velocities: SecondaryMap::new(),
145            elevators: SecondaryMap::new(),
146            stops: SecondaryMap::new(),
147            riders: SecondaryMap::new(),
148            routes: SecondaryMap::new(),
149            lines: SecondaryMap::new(),
150            patience: SecondaryMap::new(),
151            preferences: SecondaryMap::new(),
152            access_controls: SecondaryMap::new(),
153            #[cfg(feature = "energy")]
154            energy_profiles: SecondaryMap::new(),
155            #[cfg(feature = "energy")]
156            energy_metrics: SecondaryMap::new(),
157            service_modes: SecondaryMap::new(),
158            destination_queues: SecondaryMap::new(),
159            hall_calls: SecondaryMap::new(),
160            car_calls: SecondaryMap::new(),
161            disabled: SecondaryMap::new(),
162            extensions: HashMap::new(),
163            ext_names: HashMap::new(),
164            resources: HashMap::new(),
165        }
166    }
167
168    /// Allocate a new entity. Returns its id. No components attached yet.
169    pub fn spawn(&mut self) -> EntityId {
170        self.alive.insert(())
171    }
172
173    /// Remove an entity and all its components (built-in and extensions).
174    ///
175    /// `World::despawn` is a low-level operation: it removes the entity's
176    /// arena entries and performs the cross-references that `World` can
177    /// safely maintain on its own. It does **not** perform rider lifecycle
178    /// transitions (which require `RiderIndex`, owned by `Simulation`).
179    ///
180    /// Cross-references handled here:
181    /// - If the entity is a rider aboard an elevator, it is removed from
182    ///   the elevator's rider list and `current_load` is adjusted.
183    ///
184    /// **Despawning an elevator with aboard riders is the caller's
185    /// responsibility to clean up.** Use
186    /// [`Simulation::remove_elevator`](crate::sim::Simulation::remove_elevator)
187    /// (which calls [`Simulation::disable`](crate::sim::Simulation::disable)
188    /// first to transition aboard riders to `Waiting` via the transition
189    /// gateway). Calling this method directly on a populated elevator leaves
190    /// aboard riders pointing at a now-dead `EntityId` in their `phase` —
191    /// a footgun this method no longer attempts to paper over, since any
192    /// reset it produced would silently desync `RiderIndex`.
193    pub fn despawn(&mut self, id: EntityId) {
194        // Clean up rider → elevator cross-references.
195        if let Some(rider) = self.riders.get(id) {
196            let weight = rider.weight;
197            // If this rider is aboard an elevator, remove from its riders list.
198            match rider.phase {
199                crate::components::RiderPhase::Boarding(elev)
200                | crate::components::RiderPhase::Riding(elev)
201                | crate::components::RiderPhase::Exiting(elev) => {
202                    if let Some(car) = self.elevators.get_mut(elev) {
203                        car.riders.retain(|r| *r != id);
204                        car.current_load -= weight;
205                    }
206                }
207                _ => {}
208            }
209        }
210
211        self.alive.remove(id);
212        self.positions.remove(id);
213        self.prev_positions.remove(id);
214        self.velocities.remove(id);
215        self.elevators.remove(id);
216        self.stops.remove(id);
217        self.riders.remove(id);
218        self.routes.remove(id);
219        self.lines.remove(id);
220        self.patience.remove(id);
221        self.preferences.remove(id);
222        self.access_controls.remove(id);
223        #[cfg(feature = "energy")]
224        self.energy_profiles.remove(id);
225        #[cfg(feature = "energy")]
226        self.energy_metrics.remove(id);
227        self.service_modes.remove(id);
228        self.destination_queues.remove(id);
229        self.disabled.remove(id);
230        self.hall_calls.remove(id);
231        self.car_calls.remove(id);
232
233        for ext in self.extensions.values_mut() {
234            ext.remove(id);
235        }
236    }
237
238    /// Check if an entity is alive.
239    #[must_use]
240    pub fn is_alive(&self, id: EntityId) -> bool {
241        self.alive.contains_key(id)
242    }
243
244    /// Number of live entities.
245    #[must_use]
246    pub fn entity_count(&self) -> usize {
247        self.alive.len()
248    }
249
250    /// Iterate all alive entity keys (used by the query builder).
251    pub(crate) fn alive_keys(&self) -> slotmap::basic::Keys<'_, EntityId, ()> {
252        self.alive.keys()
253    }
254
255    // ── Position accessors ───────────────────────────────────────────
256
257    /// Get an entity's position.
258    #[must_use]
259    pub fn position(&self, id: EntityId) -> Option<&Position> {
260        self.positions.get(id)
261    }
262
263    /// Get an entity's position mutably.
264    pub fn position_mut(&mut self, id: EntityId) -> Option<&mut Position> {
265        self.positions.get_mut(id)
266    }
267
268    /// Set an entity's position.
269    pub fn set_position(&mut self, id: EntityId, pos: Position) {
270        self.positions.insert(id, pos);
271    }
272
273    /// Snapshot of an entity's position at the start of the current tick.
274    ///
275    /// Pairs with [`position`](Self::position) to support sub-tick interpolation
276    /// (see [`Simulation::position_at`](crate::sim::Simulation::position_at)).
277    #[must_use]
278    pub fn prev_position(&self, id: EntityId) -> Option<&Position> {
279        self.prev_positions.get(id)
280    }
281
282    /// Snapshot all current positions into `prev_positions`.
283    ///
284    /// Called at the start of each tick by
285    /// [`Simulation::step`](crate::sim::Simulation::step) before any phase
286    /// mutates positions.
287    pub(crate) fn snapshot_prev_positions(&mut self) {
288        self.prev_positions.clear();
289        for (id, pos) in &self.positions {
290            self.prev_positions.insert(id, *pos);
291        }
292    }
293
294    // ── Velocity accessors ───────────────────────────────────────────
295
296    /// Get an entity's velocity.
297    #[must_use]
298    pub fn velocity(&self, id: EntityId) -> Option<&Velocity> {
299        self.velocities.get(id)
300    }
301
302    /// Get an entity's velocity mutably.
303    pub fn velocity_mut(&mut self, id: EntityId) -> Option<&mut Velocity> {
304        self.velocities.get_mut(id)
305    }
306
307    /// Set an entity's velocity.
308    pub fn set_velocity(&mut self, id: EntityId, vel: Velocity) {
309        self.velocities.insert(id, vel);
310    }
311
312    // ── Elevator accessors ───────────────────────────────────────────
313
314    /// Get an entity's elevator component.
315    #[must_use]
316    pub fn elevator(&self, id: EntityId) -> Option<&Elevator> {
317        self.elevators.get(id)
318    }
319
320    /// Get an entity's elevator component mutably.
321    pub fn elevator_mut(&mut self, id: EntityId) -> Option<&mut Elevator> {
322        self.elevators.get_mut(id)
323    }
324
325    /// Set an entity's elevator component.
326    pub fn set_elevator(&mut self, id: EntityId, elev: Elevator) {
327        self.elevators.insert(id, elev);
328    }
329
330    // ── Rider accessors ──────────────────────────────────────────────
331
332    /// Get an entity's rider component.
333    #[must_use]
334    pub fn rider(&self, id: EntityId) -> Option<&Rider> {
335        self.riders.get(id)
336    }
337
338    /// Get an entity's rider component mutably.
339    pub fn rider_mut(&mut self, id: EntityId) -> Option<&mut Rider> {
340        self.riders.get_mut(id)
341    }
342
343    /// Set an entity's rider component.
344    ///
345    /// # Warning
346    ///
347    /// This does **not** update the [`RiderIndex`](crate::rider_index::RiderIndex).
348    /// Call [`RiderIndex::rebuild`](crate::rider_index::RiderIndex::rebuild) afterward
349    /// if the phase or `current_stop` changed.
350    pub(crate) fn set_rider(&mut self, id: EntityId, rider: Rider) {
351        self.riders.insert(id, rider);
352    }
353
354    // ── Stop accessors ───────────────────────────────────────────────
355
356    /// Get an entity's stop component.
357    #[must_use]
358    pub fn stop(&self, id: EntityId) -> Option<&Stop> {
359        self.stops.get(id)
360    }
361
362    /// Get an entity's stop component mutably.
363    pub fn stop_mut(&mut self, id: EntityId) -> Option<&mut Stop> {
364        self.stops.get_mut(id)
365    }
366
367    /// Set an entity's stop component.
368    pub fn set_stop(&mut self, id: EntityId, stop: Stop) {
369        self.stops.insert(id, stop);
370    }
371
372    // ── Route accessors ──────────────────────────────────────────────
373
374    /// Get an entity's route.
375    #[must_use]
376    pub fn route(&self, id: EntityId) -> Option<&Route> {
377        self.routes.get(id)
378    }
379
380    /// Get an entity's route mutably.
381    pub fn route_mut(&mut self, id: EntityId) -> Option<&mut Route> {
382        self.routes.get_mut(id)
383    }
384
385    /// Set an entity's route.
386    pub fn set_route(&mut self, id: EntityId, route: Route) {
387        self.routes.insert(id, route);
388    }
389
390    // ── Line accessors ─────────────────────────────────────────────��──
391
392    /// Get an entity's line component.
393    #[must_use]
394    pub fn line(&self, id: EntityId) -> Option<&Line> {
395        self.lines.get(id)
396    }
397
398    /// Get an entity's line component mutably.
399    pub fn line_mut(&mut self, id: EntityId) -> Option<&mut Line> {
400        self.lines.get_mut(id)
401    }
402
403    /// Set an entity's line component.
404    pub fn set_line(&mut self, id: EntityId, line: Line) {
405        self.lines.insert(id, line);
406    }
407
408    /// Remove an entity's line component.
409    pub fn remove_line(&mut self, id: EntityId) -> Option<Line> {
410        self.lines.remove(id)
411    }
412
413    /// Iterate all line entities.
414    pub fn iter_lines(&self) -> impl Iterator<Item = (EntityId, &Line)> {
415        self.lines.iter()
416    }
417
418    // ── Patience accessors ───────────────────────────────────────────
419
420    /// Get an entity's patience.
421    #[must_use]
422    pub fn patience(&self, id: EntityId) -> Option<&Patience> {
423        self.patience.get(id)
424    }
425
426    /// Get an entity's patience mutably.
427    pub fn patience_mut(&mut self, id: EntityId) -> Option<&mut Patience> {
428        self.patience.get_mut(id)
429    }
430
431    /// Set an entity's patience.
432    pub fn set_patience(&mut self, id: EntityId, patience: Patience) {
433        self.patience.insert(id, patience);
434    }
435
436    // ── Preferences accessors ────────────────────────────────────────
437
438    /// Get an entity's preferences.
439    #[must_use]
440    pub fn preferences(&self, id: EntityId) -> Option<&Preferences> {
441        self.preferences.get(id)
442    }
443
444    /// Set an entity's preferences.
445    pub fn set_preferences(&mut self, id: EntityId, prefs: Preferences) {
446        self.preferences.insert(id, prefs);
447    }
448
449    // ── Access control accessors ────────────────────────────────────
450
451    /// Get an entity's access control.
452    #[must_use]
453    pub fn access_control(&self, id: EntityId) -> Option<&AccessControl> {
454        self.access_controls.get(id)
455    }
456
457    /// Get an entity's access control mutably.
458    pub fn access_control_mut(&mut self, id: EntityId) -> Option<&mut AccessControl> {
459        self.access_controls.get_mut(id)
460    }
461
462    /// Set an entity's access control.
463    pub fn set_access_control(&mut self, id: EntityId, ac: AccessControl) {
464        self.access_controls.insert(id, ac);
465    }
466
467    // ── Energy accessors (feature-gated) ────────────────────────────
468
469    #[cfg(feature = "energy")]
470    /// Get an entity's energy profile.
471    #[must_use]
472    pub fn energy_profile(&self, id: EntityId) -> Option<&EnergyProfile> {
473        self.energy_profiles.get(id)
474    }
475
476    #[cfg(feature = "energy")]
477    /// Get an entity's energy metrics.
478    #[must_use]
479    pub fn energy_metrics(&self, id: EntityId) -> Option<&EnergyMetrics> {
480        self.energy_metrics.get(id)
481    }
482
483    #[cfg(feature = "energy")]
484    /// Get an entity's energy metrics mutably.
485    pub fn energy_metrics_mut(&mut self, id: EntityId) -> Option<&mut EnergyMetrics> {
486        self.energy_metrics.get_mut(id)
487    }
488
489    #[cfg(feature = "energy")]
490    /// Set an entity's energy profile.
491    pub fn set_energy_profile(&mut self, id: EntityId, profile: EnergyProfile) {
492        self.energy_profiles.insert(id, profile);
493    }
494
495    #[cfg(feature = "energy")]
496    /// Set an entity's energy metrics.
497    pub fn set_energy_metrics(&mut self, id: EntityId, metrics: EnergyMetrics) {
498        self.energy_metrics.insert(id, metrics);
499    }
500
501    // ── Service mode accessors ──────────────────────────────────────
502
503    /// Get an entity's service mode.
504    #[must_use]
505    pub fn service_mode(&self, id: EntityId) -> Option<&ServiceMode> {
506        self.service_modes.get(id)
507    }
508
509    /// Set an entity's service mode.
510    pub fn set_service_mode(&mut self, id: EntityId, mode: ServiceMode) {
511        self.service_modes.insert(id, mode);
512    }
513
514    // ── Destination queue accessors ─────────────────────────────────
515
516    /// Get an entity's destination queue.
517    #[must_use]
518    pub fn destination_queue(&self, id: EntityId) -> Option<&DestinationQueue> {
519        self.destination_queues.get(id)
520    }
521
522    /// Get an entity's destination queue mutably (crate-internal — games
523    /// mutate via the [`Simulation`](crate::sim::Simulation) helpers).
524    pub(crate) fn destination_queue_mut(&mut self, id: EntityId) -> Option<&mut DestinationQueue> {
525        self.destination_queues.get_mut(id)
526    }
527
528    /// Set an entity's destination queue.
529    pub fn set_destination_queue(&mut self, id: EntityId, queue: DestinationQueue) {
530        self.destination_queues.insert(id, queue);
531    }
532
533    // ── Hall call / car call accessors ──────────────────────────────
534    //
535    // Phase wiring in follow-up commits consumes the mutators. Until
536    // that lands, `#[allow(dead_code)]` suppresses warnings that would
537    // otherwise block the build under the workspace's `deny(warnings)`.
538
539    /// Get the `(up, down)` hall call pair at a stop, if any exist.
540    #[must_use]
541    pub fn stop_calls(&self, stop: EntityId) -> Option<&StopCalls> {
542        self.hall_calls.get(stop)
543    }
544
545    /// Get a specific directional hall call at a stop.
546    #[must_use]
547    pub fn hall_call(&self, stop: EntityId, direction: CallDirection) -> Option<&HallCall> {
548        self.hall_calls.get(stop).and_then(|c| c.get(direction))
549    }
550
551    /// Mutable access to a directional hall call (crate-internal).
552    #[allow(dead_code)]
553    pub(crate) fn hall_call_mut(
554        &mut self,
555        stop: EntityId,
556        direction: CallDirection,
557    ) -> Option<&mut HallCall> {
558        self.hall_calls
559            .get_mut(stop)
560            .and_then(|c| c.get_mut(direction))
561    }
562
563    /// Insert (or replace) a hall call at `stop` in `direction`.
564    /// Returns `false` if the stop entity no longer exists in the world.
565    #[allow(dead_code)]
566    pub(crate) fn set_hall_call(&mut self, call: HallCall) -> bool {
567        let Some(entry) = self.hall_calls.entry(call.stop) else {
568            return false;
569        };
570        let slot = entry.or_default();
571        match call.direction {
572            CallDirection::Up => slot.up = Some(call),
573            CallDirection::Down => slot.down = Some(call),
574        }
575        true
576    }
577
578    /// Remove and return the hall call at `(stop, direction)`, if any.
579    #[allow(dead_code)]
580    pub(crate) fn remove_hall_call(
581        &mut self,
582        stop: EntityId,
583        direction: CallDirection,
584    ) -> Option<HallCall> {
585        let entry = self.hall_calls.get_mut(stop)?;
586        match direction {
587            CallDirection::Up => entry.up.take(),
588            CallDirection::Down => entry.down.take(),
589        }
590    }
591
592    /// Iterate every active hall call across the world.
593    pub fn iter_hall_calls(&self) -> impl Iterator<Item = &HallCall> {
594        self.hall_calls.values().flat_map(StopCalls::iter)
595    }
596
597    /// Mutable iteration over every active hall call (crate-internal).
598    #[allow(dead_code)]
599    pub(crate) fn iter_hall_calls_mut(&mut self) -> impl Iterator<Item = &mut HallCall> {
600        self.hall_calls.values_mut().flat_map(StopCalls::iter_mut)
601    }
602
603    /// Car calls currently registered inside `car`.
604    #[must_use]
605    pub fn car_calls(&self, car: EntityId) -> &[CarCall] {
606        self.car_calls.get(car).map_or(&[], Vec::as_slice)
607    }
608
609    /// Mutable access to the car-call list (crate-internal). Returns
610    /// `None` if the car entity no longer exists.
611    #[allow(dead_code)]
612    pub(crate) fn car_calls_mut(&mut self, car: EntityId) -> Option<&mut Vec<CarCall>> {
613        Some(self.car_calls.entry(car)?.or_default())
614    }
615
616    /// Remove `rider` from every hall- and car-call's `pending_riders`
617    /// list, and drop car calls whose list becomes empty as a result.
618    ///
619    /// Call on despawn or abandonment so stale rider IDs don't hold
620    /// calls open past the rider's life. Mirrors the per-exit cleanup
621    /// in `systems::loading` (for `Exit`-time car-call pruning). Hall
622    /// calls stay alive after the list empties: they may still represent
623    /// a script-driven press with no associated rider, and are cleared
624    /// the usual way when an eligible car opens doors.
625    pub(crate) fn scrub_rider_from_pending_calls(&mut self, rider: EntityId) {
626        for call in self.iter_hall_calls_mut() {
627            call.pending_riders.retain(|r| *r != rider);
628        }
629        for calls in self.car_calls.values_mut() {
630            for c in calls.iter_mut() {
631                c.pending_riders.retain(|r| *r != rider);
632            }
633            calls.retain(|c| !c.pending_riders.is_empty());
634        }
635    }
636
637    // ── Typed query helpers ──────────────────────────────────────────
638
639    /// Iterate all elevator entities (have `Elevator` + `Position`).
640    pub fn iter_elevators(&self) -> impl Iterator<Item = (EntityId, &Position, &Elevator)> {
641        self.elevators
642            .iter()
643            .filter_map(|(id, car)| self.positions.get(id).map(|pos| (id, pos, car)))
644    }
645
646    /// Iterate all elevator entity IDs (allocates).
647    #[must_use]
648    pub fn elevator_ids(&self) -> Vec<EntityId> {
649        self.elevators.keys().collect()
650    }
651
652    /// Fill the buffer with all elevator entity IDs, clearing it first.
653    pub fn elevator_ids_into(&self, buf: &mut Vec<EntityId>) {
654        buf.clear();
655        buf.extend(self.elevators.keys());
656    }
657
658    /// Iterate all rider entities.
659    pub fn iter_riders(&self) -> impl Iterator<Item = (EntityId, &Rider)> {
660        self.riders.iter()
661    }
662
663    /// Iterate all rider entities mutably.
664    pub fn iter_riders_mut(&mut self) -> impl Iterator<Item = (EntityId, &mut Rider)> {
665        self.riders.iter_mut()
666    }
667
668    /// Iterate all rider entity IDs (allocates).
669    #[must_use]
670    pub fn rider_ids(&self) -> Vec<EntityId> {
671        self.riders.keys().collect()
672    }
673
674    /// Iterate all stop entities.
675    pub fn iter_stops(&self) -> impl Iterator<Item = (EntityId, &Stop)> {
676        self.stops.iter()
677    }
678
679    /// Iterate all stop entity IDs (allocates).
680    #[must_use]
681    pub fn stop_ids(&self) -> Vec<EntityId> {
682        self.stops.keys().collect()
683    }
684
685    /// Iterate elevators in `Idle` phase (not disabled).
686    pub fn iter_idle_elevators(&self) -> impl Iterator<Item = (EntityId, &Position, &Elevator)> {
687        use crate::components::ElevatorPhase;
688        self.iter_elevators()
689            .filter(|(id, _, car)| car.phase == ElevatorPhase::Idle && !self.is_disabled(*id))
690    }
691
692    /// Iterate elevators that are currently moving — either on a dispatched
693    /// trip (`MovingToStop`) or a repositioning trip (`Repositioning`).
694    /// Excludes disabled elevators.
695    pub fn iter_moving_elevators(&self) -> impl Iterator<Item = (EntityId, &Position, &Elevator)> {
696        self.iter_elevators()
697            .filter(|(id, _, car)| car.phase.is_moving() && !self.is_disabled(*id))
698    }
699
700    /// Iterate riders in `Waiting` phase (not disabled).
701    pub fn iter_waiting_riders(&self) -> impl Iterator<Item = (EntityId, &Rider)> {
702        use crate::components::RiderPhase;
703        self.iter_riders()
704            .filter(|(id, r)| r.phase == RiderPhase::Waiting && !self.is_disabled(*id))
705    }
706
707    /// Find the stop entity at a given position (within
708    /// [`STOP_POSITION_EPSILON`](Self::STOP_POSITION_EPSILON)).
709    ///
710    /// Global lookup — does not filter by line. When two stops on
711    /// different lines share the same physical position the result is
712    /// whichever wins the linear scan, which is rarely what the
713    /// caller actually wants. Prefer
714    /// [`find_stop_at_position_in`](Self::find_stop_at_position_in)
715    /// when the caller knows which line's stops to consider.
716    #[must_use]
717    pub fn find_stop_at_position(&self, position: f64) -> Option<EntityId> {
718        self.stops.iter().find_map(|(id, stop)| {
719            if (stop.position - position).abs() < Self::STOP_POSITION_EPSILON {
720                Some(id)
721            } else {
722                None
723            }
724        })
725    }
726
727    /// Find the stop at a given position from within `candidates`.
728    ///
729    /// `candidates` is typically the `serves` list of a particular
730    /// [`LineInfo`](crate::dispatch::LineInfo) — i.e. the stops a
731    /// specific line can reach. Use this when a car arrives at a
732    /// position and you need *its* line's stop entity, not whichever
733    /// stop on any line happens to share the position. (Two parallel
734    /// shafts at the same physical floor, or a sky-lobby served by
735    /// both a low and high bank, both produce position collisions
736    /// the global lookup can't disambiguate.)
737    ///
738    /// O(n) over `candidates`, which is typically small.
739    #[must_use]
740    pub fn find_stop_at_position_in(
741        &self,
742        position: f64,
743        candidates: &[EntityId],
744    ) -> Option<EntityId> {
745        candidates.iter().copied().find(|&id| {
746            self.stops
747                .get(id)
748                .is_some_and(|stop| (stop.position - position).abs() < Self::STOP_POSITION_EPSILON)
749        })
750    }
751
752    /// Tolerance for [`find_stop_at_position`](Self::find_stop_at_position)
753    /// and [`find_stop_at_position_in`](Self::find_stop_at_position_in).
754    /// Sub-micrometre — small enough that no two distinct floors should
755    /// land within it, large enough to absorb floating-point noise from
756    /// trapezoidal-velocity arrival math.
757    pub const STOP_POSITION_EPSILON: f64 = 1e-6;
758
759    /// Find the stop entity nearest to a given position.
760    ///
761    /// Unlike [`find_stop_at_position`](Self::find_stop_at_position), this finds
762    /// the closest stop by minimum distance rather than requiring an exact match.
763    /// Used when ejecting riders from a disabled/despawned elevator mid-transit.
764    #[must_use]
765    pub fn find_nearest_stop(&self, position: f64) -> Option<EntityId> {
766        self.stops
767            .iter()
768            .min_by(|(_, a), (_, b)| {
769                (a.position - position)
770                    .abs()
771                    .total_cmp(&(b.position - position).abs())
772            })
773            .map(|(id, _)| id)
774    }
775
776    /// Get a stop's position by entity id.
777    #[must_use]
778    pub fn stop_position(&self, id: EntityId) -> Option<f64> {
779        self.stops.get(id).map(|s| s.position)
780    }
781
782    // ── Extension (custom component) storage ─────────────────────────
783
784    /// Insert a custom component for an entity.
785    ///
786    /// Games use this to attach their own typed data to simulation entities.
787    /// Extension components must be `Serialize + DeserializeOwned` to support
788    /// snapshot save/load. An [`ExtKey`] is required for serialization roundtrips.
789    /// Extension components are automatically cleaned up on `despawn()`.
790    ///
791    /// ```
792    /// use elevator_core::world::{ExtKey, World};
793    /// use serde::{Serialize, Deserialize};
794    ///
795    /// #[derive(Debug, Clone, Serialize, Deserialize)]
796    /// struct VipTag { level: u32 }
797    ///
798    /// let mut world = World::new();
799    /// let entity = world.spawn();
800    /// world.insert_ext(entity, VipTag { level: 3 }, ExtKey::from_type_name());
801    /// ```
802    pub fn insert_ext<T: 'static + Send + Sync + serde::Serialize + serde::de::DeserializeOwned>(
803        &mut self,
804        id: EntityId,
805        value: T,
806        key: ExtKey<T>,
807    ) {
808        let type_id = TypeId::of::<T>();
809        Self::assert_ext_name_unique(&self.ext_names, type_id, key.name());
810        let map = self
811            .extensions
812            .entry(type_id)
813            .or_insert_with(|| Box::new(SecondaryMap::<EntityId, T>::new()));
814        if let Some(m) = map.as_any_mut().downcast_mut::<SecondaryMap<EntityId, T>>() {
815            m.insert(id, value);
816        }
817        self.ext_names.insert(type_id, key.name().to_owned());
818    }
819
820    /// Get a clone of a custom component for an entity.
821    #[must_use]
822    pub fn ext<T: 'static + Send + Sync + Clone>(&self, id: EntityId) -> Option<T> {
823        self.ext_map::<T>()?.get(id).cloned()
824    }
825
826    /// Shared reference to a custom component for an entity.
827    ///
828    /// Zero-copy alternative to [`ext`](Self::ext): prefer this when
829    /// `T` is large or expensive to clone, or when the caller only needs a
830    /// borrow. Unlike `ext`, `T` does not need to implement `Clone`.
831    #[must_use]
832    pub fn ext_ref<T: 'static + Send + Sync>(&self, id: EntityId) -> Option<&T> {
833        self.ext_map::<T>()?.get(id)
834    }
835
836    /// Mutable reference to a custom component for an entity.
837    pub fn ext_mut<T: 'static + Send + Sync>(&mut self, id: EntityId) -> Option<&mut T> {
838        self.ext_map_mut::<T>()?.get_mut(id)
839    }
840
841    /// Remove a custom component for an entity.
842    pub fn remove_ext<T: 'static + Send + Sync>(&mut self, id: EntityId) -> Option<T> {
843        self.ext_map_mut::<T>()?.remove(id)
844    }
845
846    /// Downcast extension storage to a typed `SecondaryMap` (shared).
847    pub(crate) fn ext_map<T: 'static + Send + Sync>(&self) -> Option<&SecondaryMap<EntityId, T>> {
848        self.extensions
849            .get(&TypeId::of::<T>())?
850            .as_any()
851            .downcast_ref::<SecondaryMap<EntityId, T>>()
852    }
853
854    /// Downcast extension storage to a typed `SecondaryMap` (mutable).
855    fn ext_map_mut<T: 'static + Send + Sync>(&mut self) -> Option<&mut SecondaryMap<EntityId, T>> {
856        self.extensions
857            .get_mut(&TypeId::of::<T>())?
858            .as_any_mut()
859            .downcast_mut::<SecondaryMap<EntityId, T>>()
860    }
861
862    /// Serialize all extension component data for snapshot.
863    /// Returns name → (`EntityId` → RON string) mapping. `BTreeMap` for
864    /// deterministic snapshot bytes across processes (#254).
865    pub(crate) fn serialize_extensions(&self) -> BTreeMap<String, BTreeMap<EntityId, String>> {
866        let mut result = BTreeMap::new();
867        for (type_id, map) in &self.extensions {
868            if let Some(name) = self.ext_names.get(type_id) {
869                result.insert(name.clone(), map.serialize_entries());
870            }
871        }
872        result
873    }
874
875    /// Deserialize extension data from snapshot. Requires that extension types
876    /// have been registered (via `register_ext_deserializer`) before calling.
877    pub(crate) fn deserialize_extensions(
878        &mut self,
879        data: &BTreeMap<String, BTreeMap<EntityId, String>>,
880    ) {
881        for (name, entries) in data {
882            // Find the TypeId by name.
883            if let Some((&type_id, _)) = self.ext_names.iter().find(|(_, n)| *n == name)
884                && let Some(map) = self.extensions.get_mut(&type_id)
885            {
886                map.deserialize_entries(entries);
887            }
888        }
889    }
890
891    /// Return names from `snapshot_names` that have no registered extension type.
892    pub(crate) fn unregistered_ext_names<'a>(
893        &self,
894        snapshot_names: impl Iterator<Item = &'a String>,
895    ) -> Vec<String> {
896        let registered: std::collections::HashSet<&str> =
897            self.ext_names.values().map(String::as_str).collect();
898        snapshot_names
899            .filter(|name| !registered.contains(name.as_str()))
900            .cloned()
901            .collect()
902    }
903
904    /// Register an extension type for deserialization (creates empty storage).
905    ///
906    /// Must be called before `restore()` for each extension type that was
907    /// present in the original simulation. Returns the key for convenience.
908    pub fn register_ext<
909        T: 'static + Send + Sync + serde::Serialize + serde::de::DeserializeOwned,
910    >(
911        &mut self,
912        key: ExtKey<T>,
913    ) -> ExtKey<T> {
914        let type_id = TypeId::of::<T>();
915        Self::assert_ext_name_unique(&self.ext_names, type_id, key.name());
916        self.extensions
917            .entry(type_id)
918            .or_insert_with(|| Box::new(SecondaryMap::<EntityId, T>::new()));
919        self.ext_names.insert(type_id, key.name().to_owned());
920        key
921    }
922
923    /// Panic if `name` is already registered to a different `TypeId`.
924    ///
925    /// Two extension types sharing one `ExtKey` name silently corrupts
926    /// snapshot serde: `serialize_extensions` collapses both types' data
927    /// into one slot in the result map, and `deserialize_extensions`
928    /// routes the data to whichever `TypeId` `HashMap::iter().find` returns
929    /// first — a non-deterministic choice. Failing fast here prevents
930    /// the corruption from ever being written.
931    ///
932    /// Panic chosen over `Result` because [`register_ext`](Self::register_ext)
933    /// and [`insert_ext`](Self::insert_ext) are non-fallible by design and
934    /// changing their signatures would break every consumer. Name collisions
935    /// are programmer errors discoverable at startup, not runtime conditions
936    /// to recover from.
937    #[allow(clippy::panic)]
938    fn assert_ext_name_unique(ext_names: &HashMap<TypeId, String>, type_id: TypeId, name: &str) {
939        if let Some((existing_tid, _)) = ext_names
940            .iter()
941            .find(|(tid, n)| **tid != type_id && n.as_str() == name)
942        {
943            panic!(
944                "ExtKey name {name:?} is already registered to a different type \
945                 ({existing_tid:?}); each extension type needs a unique key name. \
946                 If renaming is impractical, use ExtKey::from_type_name() so the \
947                 name embeds the fully-qualified Rust type name."
948            );
949        }
950    }
951
952    // ── Disabled entity management ──────────────────────────────────
953
954    /// Mark an entity as disabled. Disabled entities are skipped by all systems.
955    pub fn disable(&mut self, id: EntityId) {
956        self.disabled.insert(id, ());
957    }
958
959    /// Re-enable a disabled entity.
960    pub fn enable(&mut self, id: EntityId) {
961        self.disabled.remove(id);
962    }
963
964    /// Check if an entity is disabled.
965    #[must_use]
966    pub fn is_disabled(&self, id: EntityId) -> bool {
967        self.disabled.contains_key(id)
968    }
969
970    // ── Global resources (singletons) ───────────────────────────────
971
972    /// Insert a global resource. Replaces any existing resource of the same type.
973    ///
974    /// Resources are singletons not attached to any entity. Games use them
975    /// for event channels, score trackers, or any global state.
976    ///
977    /// ```
978    /// use elevator_core::world::World;
979    /// use elevator_core::events::EventChannel;
980    ///
981    /// #[derive(Debug)]
982    /// enum MyEvent { Score(u32) }
983    ///
984    /// let mut world = World::new();
985    /// world.insert_resource(EventChannel::<MyEvent>::new());
986    /// ```
987    pub fn insert_resource<T: 'static + Send + Sync>(&mut self, value: T) {
988        self.resources.insert(TypeId::of::<T>(), Box::new(value));
989    }
990
991    /// Get a shared reference to a global resource.
992    #[must_use]
993    pub fn resource<T: 'static + Send + Sync>(&self) -> Option<&T> {
994        self.resources.get(&TypeId::of::<T>())?.downcast_ref()
995    }
996
997    /// Get a mutable reference to a global resource.
998    pub fn resource_mut<T: 'static + Send + Sync>(&mut self) -> Option<&mut T> {
999        self.resources.get_mut(&TypeId::of::<T>())?.downcast_mut()
1000    }
1001
1002    /// Remove a global resource, returning it if it existed.
1003    pub fn remove_resource<T: 'static + Send + Sync>(&mut self) -> Option<T> {
1004        self.resources
1005            .remove(&TypeId::of::<T>())
1006            .and_then(|b| b.downcast().ok())
1007            .map(|b| *b)
1008    }
1009
1010    // ── Query builder ───────────────────────────────────────────────
1011
1012    /// Create a query builder for iterating entities by component composition.
1013    ///
1014    /// ```
1015    /// use elevator_core::components::{Position, Rider};
1016    /// use elevator_core::prelude::*;
1017    ///
1018    /// let mut sim = SimulationBuilder::demo().build().unwrap();
1019    /// sim.spawn_rider(StopId(0), StopId(1), 75.0).unwrap();
1020    ///
1021    /// let world = sim.world();
1022    /// for (id, rider, pos) in world.query::<(EntityId, &Rider, &Position)>().iter() {
1023    ///     println!("{id:?}: {:?} at {}", rider.phase(), pos.value());
1024    /// }
1025    /// ```
1026    #[must_use]
1027    pub const fn query<Q: crate::query::WorldQuery>(&self) -> crate::query::QueryBuilder<'_, Q> {
1028        crate::query::QueryBuilder::new(self)
1029    }
1030
1031    /// Create a mutable extension query builder.
1032    ///
1033    /// Uses the keys-snapshot pattern: collects matching entity IDs upfront
1034    /// into an owned `Vec`, then iterates with mutable access via
1035    /// [`for_each_mut`](crate::query::ExtQueryMut::for_each_mut).
1036    ///
1037    /// # Example
1038    ///
1039    /// ```no_run
1040    /// # use elevator_core::prelude::*;
1041    /// # use serde::{Serialize, Deserialize};
1042    /// # #[derive(Clone, Serialize, Deserialize)] struct VipTag { level: u32 }
1043    /// # fn run(world: &mut World) {
1044    /// world.query_ext_mut::<VipTag>().for_each_mut(|id, tag| {
1045    ///     tag.level += 1;
1046    /// });
1047    /// # }
1048    /// ```
1049    pub fn query_ext_mut<T: 'static + Send + Sync>(&mut self) -> crate::query::ExtQueryMut<'_, T> {
1050        crate::query::ExtQueryMut::new(self)
1051    }
1052}
1053
1054impl Default for World {
1055    fn default() -> Self {
1056        Self::new()
1057    }
1058}
1059
1060/// Stops sorted by position for efficient range queries (binary search).
1061///
1062/// Used by the movement system to detect `PassingFloor` events in O(log n)
1063/// instead of O(n) per moving elevator per tick.
1064pub(crate) struct SortedStops(pub(crate) Vec<(f64, EntityId)>);
1065
1066/// The up/down hall call pair at a single stop.
1067///
1068/// At most two calls coexist at a stop (one per [`CallDirection`]);
1069/// this struct owns the slots. Stored in `World::hall_calls` keyed by
1070/// the stop's entity id.
1071#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
1072pub struct StopCalls {
1073    /// Pending upward call, if the up button is pressed.
1074    pub up: Option<HallCall>,
1075    /// Pending downward call, if the down button is pressed.
1076    pub down: Option<HallCall>,
1077}
1078
1079impl StopCalls {
1080    /// Borrow the call for a specific direction.
1081    #[must_use]
1082    pub const fn get(&self, direction: CallDirection) -> Option<&HallCall> {
1083        match direction {
1084            CallDirection::Up => self.up.as_ref(),
1085            CallDirection::Down => self.down.as_ref(),
1086        }
1087    }
1088
1089    /// Mutable borrow of the call for a direction.
1090    pub const fn get_mut(&mut self, direction: CallDirection) -> Option<&mut HallCall> {
1091        match direction {
1092            CallDirection::Up => self.up.as_mut(),
1093            CallDirection::Down => self.down.as_mut(),
1094        }
1095    }
1096
1097    /// Iterate both calls in (Up, Down) order, skipping empty slots.
1098    pub fn iter(&self) -> impl Iterator<Item = &HallCall> {
1099        self.up.iter().chain(self.down.iter())
1100    }
1101
1102    /// Mutable iteration over both calls.
1103    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut HallCall> {
1104        self.up.iter_mut().chain(self.down.iter_mut())
1105    }
1106}