Skip to main content

paiagram_core/
entry.rs

1use bevy::{ecs::query::QueryData, prelude::*};
2use moonshine_core::prelude::{MapEntities, ReflectMapEntities};
3
4use crate::{
5    trip::TripQueryItem,
6    units::time::{Duration, TimetableTime},
7};
8
9pub struct EntryPlugin;
10impl Plugin for EntryPlugin {
11    fn build(&self, app: &mut App) {
12        app.add_observer(update_entry_mode)
13            .add_observer(update_entry_stop);
14    }
15}
16
17/// Marker component given to derived entries
18#[derive(Reflect, Component)]
19#[reflect(Component)]
20pub struct IsDerivedEntry;
21
22/// Travel mode for entries
23#[derive(Reflect, Default, Debug, Clone, Copy)]
24pub enum TravelMode {
25    At(TimetableTime),
26    For(Duration),
27    #[default]
28    Flexible,
29}
30
31/// The entry's [`TravelMode`]s.
32#[derive(Default, Reflect, Component, Clone, Copy)]
33#[reflect(Component)]
34pub struct EntryMode {
35    pub arr: Option<TravelMode>,
36    pub dep: TravelMode,
37}
38
39impl EntryMode {
40    pub fn new_derived() -> Self {
41        Self {
42            arr: None,
43            dep: TravelMode::Flexible,
44        }
45    }
46    /// Shift the arrival time. The function does nothing if the arrival mode is [`TravelMode::Flexible`]
47    /// or [`Option::None`]
48    pub fn shift_arr(&mut self, d: Duration) {
49        match &mut self.arr {
50            Some(TravelMode::At(t)) => *t += d,
51            Some(TravelMode::For(t)) => *t += d,
52            Some(TravelMode::Flexible) | None => (),
53        }
54    }
55    /// Shift the departure time. The function does nothing if the departure mode is [`TravelMode::Flexible`]
56    pub fn shift_dep(&mut self, d: Duration) {
57        match &mut self.dep {
58            TravelMode::At(t) => *t += d,
59            TravelMode::For(t) => *t += d,
60            TravelMode::Flexible => (),
61        }
62    }
63}
64
65/// Where the vehicle stops. The stop could be a station, or a platform that belongs to the station.
66#[derive(Reflect, Component, MapEntities, Deref, DerefMut)]
67#[reflect(Component, MapEntities)]
68#[relationship(relationship_target = crate::station::PlatformEntries)]
69#[require(EntryMode)]
70pub struct EntryStop(
71    #[relationship]
72    #[entities]
73    pub Entity,
74);
75
76/// The estimated arrival and departure times of the entry. This is not a hard requirement for entries.
77#[derive(Reflect, Component, Clone, Copy)]
78#[reflect(Component)]
79pub struct EntryEstimate {
80    pub arr: TimetableTime,
81    pub dep: TimetableTime,
82}
83
84impl EntryEstimate {
85    pub fn new(arr: TimetableTime, dep: TimetableTime) -> Self {
86        Self { arr, dep }
87    }
88}
89
90/// Bundle for easy spawning
91#[derive(Bundle)]
92pub struct EntryBundle {
93    time: EntryMode,
94    stop: EntryStop,
95}
96
97impl EntryBundle {
98    pub fn new(arr: Option<TravelMode>, dep: TravelMode, stop: Entity) -> Self {
99        Self {
100            time: EntryMode { arr, dep },
101            stop: EntryStop(stop),
102        }
103    }
104    pub fn new_derived(stop: Entity) -> Self {
105        Self {
106            time: EntryMode::new_derived(),
107            stop: EntryStop(stop),
108        }
109    }
110}
111
112/// Bundle for easy spawning
113#[derive(Bundle)]
114pub struct DerivedEntryBundle {
115    mode: EntryMode,
116    stop: EntryStop,
117    derived: IsDerivedEntry,
118}
119
120impl DerivedEntryBundle {
121    pub fn new(stop: Entity) -> Self {
122        Self {
123            mode: EntryMode::new_derived(),
124            stop: EntryStop(stop),
125            derived: IsDerivedEntry,
126        }
127    }
128}
129
130#[derive(QueryData)]
131pub struct EntryQuery {
132    pub entity: Entity,
133    pub mode: &'static EntryMode,
134    pub estimate: Option<&'static EntryEstimate>,
135    pub parent_schedule: &'static ChildOf,
136    stop: &'static EntryStop,
137    is_derived: Option<&'static IsDerivedEntry>,
138}
139
140impl<'w, 's> EntryQueryItem<'w, 's> {
141    pub fn is_derived(&self) -> bool {
142        self.is_derived.is_some()
143    }
144    pub fn is_not_derived(&self) -> bool {
145        self.is_derived.is_none()
146    }
147    pub fn stop(&self) -> Entity {
148        self.stop.entity()
149    }
150    pub fn stop_duration(&self) -> Option<Duration> {
151        self.estimate.map(|e| e.dep - e.arr)
152    }
153    pub fn travel_duration(
154        &self,
155        parent_it: &TripQueryItem,
156        entry_q: &Query<(&EntryMode, Option<&EntryEstimate>)>,
157    ) -> Option<Duration> {
158        assert_eq!(parent_it.entity, self.parent_schedule.parent());
159        let arr = self.estimate?.arr;
160        let parent_schedule = parent_it.schedule;
161        let idx = parent_schedule
162            .iter()
163            .copied()
164            .position(|e| e == self.entity)?;
165        if idx == 0 {
166            return Some(arr.as_duration());
167        }
168        let prev_dep = entry_q
169            .iter_many(parent_schedule[0..idx].iter().rev())
170            .find(|(mode, _)| match (mode.arr, mode.dep) {
171                (Some(TravelMode::For(_)), _) => true,
172                (Some(TravelMode::At(_)), _) => true,
173                (_, TravelMode::At(_)) => true,
174                _ => false,
175            })?
176            .1?
177            .dep;
178        Some(arr - prev_dep)
179    }
180}
181
182/// Changes the entry's stop.
183/// This would trigger a route recalculation
184#[derive(Debug, EntityEvent)]
185pub struct ChangeEntryStop {
186    pub entity: Entity,
187    pub stop: Entity,
188}
189
190/// Changes the entry's mode
191/// This would trigger a schedule estimate recalculation
192#[derive(Reflect, Debug, EntityEvent, Clone, Copy)]
193pub struct AdjustEntryMode {
194    pub entity: Entity,
195    pub adj: EntryModeAdjustment,
196}
197
198#[derive(Reflect, Debug, Clone, Copy)]
199pub enum EntryModeAdjustment {
200    SetArrival(Option<TravelMode>),
201    SetDeparture(TravelMode),
202    ShiftArrival(Duration),
203    ShiftDeparture(Duration),
204}
205
206fn update_entry_stop(event: On<ChangeEntryStop>, mut commands: Commands) {
207    commands.entity(event.entity).insert(EntryStop(event.stop));
208}
209
210fn update_entry_mode(event: On<AdjustEntryMode>, mut entry_modes: Query<&mut EntryMode>) {
211    let mut entry_mode = entry_modes
212        .get_mut(event.entity)
213        .expect("Entity does not carry an EntryMode component");
214    *entry_mode = transform_entry_mode(*entry_mode, event.adj);
215}
216
217pub fn transform_entry_mode(mut old: EntryMode, adjustment: EntryModeAdjustment) -> EntryMode {
218    use EntryModeAdjustment::*;
219    match adjustment {
220        SetArrival(m) => {
221            old.arr = m;
222        }
223        SetDeparture(m) => {
224            old.dep = m;
225        }
226        ShiftArrival(d) => {
227            old.shift_arr(d);
228        }
229        ShiftDeparture(d) => {
230            old.shift_dep(d);
231        }
232    }
233    old
234}