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