Skip to main content

paiagram_core/
entry.rs

1//! Entries define a [`crate::trip`]'s location at a given time.
2
3use bevy::ecs::query::QueryData;
4use bevy::prelude::*;
5use moonshine_core::prelude::{MapEntities, ReflectMapEntities};
6
7use crate::trip::TripQueryItem;
8use crate::units::time::{Duration, TimetableTime};
9
10/// The entry plugin
11pub struct EntryPlugin;
12impl Plugin for EntryPlugin {
13    fn build(&self, app: &mut App) {
14        app.add_observer(update_entry_mode)
15            .add_observer(update_entry_stop);
16    }
17}
18
19/// Marker component given to derived entries
20#[derive(Reflect, Component)]
21#[reflect(Component)]
22pub struct IsDerivedEntry;
23
24/// Travel mode for entries' arrival and departure fields.
25#[derive(Reflect, Default, Debug, Clone, Copy)]
26pub enum TravelMode {
27    /// The event is guaranteed to happen at this point.
28    At(TimetableTime),
29    /// The event is guaranteed to happen [`Duration`] after the previous event.
30    For(Duration),
31    /// The event does not have a fixed timepoint
32    #[default]
33    Flexible,
34}
35
36/// The entry's arrival and departure times.
37#[derive(Default, Reflect, Component, Clone, Copy)]
38#[reflect(Component)]
39pub struct EntryMode {
40    /// Arrival. Arrival is defined as [`Option<TravelMode>`]. The [`None`] value covers the case
41    /// where the trip does not stop at this station.
42    pub arr: Option<TravelMode>,
43    /// Departure. Every entry must have a departure mode.
44    pub dep: TravelMode,
45}
46
47impl EntryMode {
48    /// Generate a derived entry that doesn't stop at the station and has flexible departure mode.
49    /// This must be used with [`IsDerivedEntry`].
50    pub fn new_derived() -> Self {
51        Self {
52            arr: None,
53            dep: TravelMode::Flexible,
54        }
55    }
56    /// Shift the arrival mode. The function does nothing if the arrival mode is
57    /// [`TravelMode::Flexible`] or [`Option::None`]
58    pub fn shift_arr(&mut self, d: Duration) {
59        match &mut self.arr {
60            Some(TravelMode::At(t)) => *t += d,
61            Some(TravelMode::For(t)) => *t += d,
62            Some(TravelMode::Flexible) | None => (),
63        }
64    }
65    /// Shift the departure mode. The function does nothing if the departure
66    /// mode is [`TravelMode::Flexible`]
67    pub fn shift_dep(&mut self, d: Duration) {
68        match &mut self.dep {
69            TravelMode::At(t) => *t += d,
70            TravelMode::For(t) => *t += d,
71            TravelMode::Flexible => (),
72        }
73    }
74}
75
76/// Where the vehicle stops. The stop could be a station, or a platform that
77/// belongs to the station.
78#[derive(Reflect, Component, MapEntities, Deref, DerefMut)]
79#[reflect(Component, MapEntities)]
80#[relationship(relationship_target = crate::station::PlatformEntries)]
81#[require(EntryMode)]
82pub struct EntryStop(
83    #[relationship]
84    #[entities]
85    pub Entity,
86);
87
88/// The estimated arrival and departure times of the entry. This is not a hard
89/// requirement for entries.
90#[derive(Reflect, Component, Clone, Copy)]
91#[reflect(Component)]
92pub struct EntryEstimate {
93    /// The arrival time. This must be a determined time
94    pub arr: TimetableTime,
95    /// The departure time. This must be a determined time
96    pub dep: TimetableTime,
97}
98
99impl EntryEstimate {
100    /// Creates a new [`EntryEstimate`] given the arrival and departure times
101    pub fn new(arr: TimetableTime, dep: TimetableTime) -> Self {
102        Self { arr, dep }
103    }
104}
105
106/// Bundle for spawning entries easily.
107#[derive(Bundle)]
108pub struct EntryBundle {
109    /// The time component.
110    time: EntryMode,
111    /// The stop component.
112    stop: EntryStop,
113}
114
115impl EntryBundle {
116    /// Create a new [`EntryBundle`].
117    pub fn new(arr: Option<TravelMode>, dep: TravelMode, stop: Entity) -> Self {
118        Self {
119            time: EntryMode { arr, dep },
120            stop: EntryStop(stop),
121        }
122    }
123}
124
125/// Bundle for easy spawning
126#[derive(Bundle)]
127pub struct DerivedEntryBundle {
128    /// The time component
129    mode: EntryMode,
130    /// The stop component
131    stop: EntryStop,
132    /// Marker for derived entry
133    derived: IsDerivedEntry,
134}
135
136impl DerivedEntryBundle {
137    /// Create a new [`DerivedEntryBundle`].
138    pub fn new(stop: Entity) -> Self {
139        Self {
140            mode: EntryMode::new_derived(),
141            stop: EntryStop(stop),
142            derived: IsDerivedEntry,
143        }
144    }
145}
146
147// TODO: rewrite this in functional style? And only pick the required components?
148// I don't actually know if Rust would optimize it so that unused components are not touched at all
149
150/// A set of common components related with the entry.
151#[derive(QueryData)]
152pub struct EntryQuery {
153    pub entity: Entity,
154    pub mode: &'static EntryMode,
155    pub estimate: Option<&'static EntryEstimate>,
156    pub parent_schedule: &'static ChildOf,
157    stop: &'static EntryStop,
158    is_derived: Option<&'static IsDerivedEntry>,
159}
160
161impl<'w, 's> EntryQueryItem<'w, 's> {
162    /// Check if the current entry is derived
163    pub fn is_derived(&self) -> bool {
164        self.is_derived.is_some()
165    }
166    /// Returns the stop of the entry
167    pub fn stop(&self) -> Entity {
168        self.stop.entity()
169    }
170    /// Returns how long the stop duration is. Returns [`None`] if the entry does not have an
171    /// estimate.
172    pub fn stop_duration(&self) -> Option<Duration> {
173        self.estimate.map(|e| e.dep - e.arr)
174    }
175    /// Returns the travel duration (The previous entry of the current entry to the current entry).
176    /// Returns [`None`] if any of the two entries don't have estimates.
177    pub fn travel_duration(
178        &self,
179        parent_it: &TripQueryItem,
180        entry_q: &Query<(&EntryMode, Option<&EntryEstimate>)>,
181    ) -> Option<Duration> {
182        assert_eq!(parent_it.entity, self.parent_schedule.parent());
183        let arr = self.estimate?.arr;
184        let parent_schedule = parent_it.schedule;
185        let idx = parent_schedule
186            .iter()
187            .copied()
188            .position(|e| e == self.entity)?;
189        if idx == 0 {
190            return Some(arr.as_duration());
191        }
192        let prev_dep = entry_q
193            .iter_many(parent_schedule[0..idx].iter().rev())
194            .find(|(mode, _)| match (mode.arr, mode.dep) {
195                (Some(TravelMode::For(_)), _) => true,
196                (Some(TravelMode::At(_)), _) => true,
197                (_, TravelMode::At(_)) => true,
198                _ => false,
199            })?
200            .1?
201            .dep;
202        Some(arr - prev_dep)
203    }
204}
205
206/// Changes the entry's stop.
207/// This would trigger a route recalculation
208#[derive(Debug, EntityEvent)]
209pub struct ChangeEntryStop {
210    /// The entry's entity
211    pub entity: Entity,
212    /// The stop's entity
213    pub stop: Entity,
214}
215
216/// Changes the entry's mode
217/// This would trigger a schedule estimate recalculation
218#[derive(Reflect, Debug, EntityEvent, Clone, Copy)]
219pub struct AdjustEntryMode {
220    /// The entry's entity
221    pub entity: Entity,
222    /// The adjustment to the entry's times
223    pub adj: EntryModeAdjustment,
224}
225
226/// How to adjust an [`EntryMode`]
227#[derive(Reflect, Debug, Clone, Copy)]
228pub enum EntryModeAdjustment {
229    /// Set the arrival mode to a new value
230    SetArrival(Option<TravelMode>),
231    /// Set the departure mode to a new value
232    SetDeparture(TravelMode),
233    /// Shift the arrival mode by [`Duration`].
234    /// Has no effect when the mode is [`TravelMode::Flexible`]
235    ShiftArrival(Duration),
236    /// Shift the departure mode by [`Duration`]
237    /// Has no effect when the mode is [`TravelMode::Flexible`]
238    ShiftDeparture(Duration),
239}
240
241fn update_entry_stop(event: On<ChangeEntryStop>, mut commands: Commands) {
242    commands.entity(event.entity).insert(EntryStop(event.stop));
243}
244
245fn update_entry_mode(event: On<AdjustEntryMode>, mut entry_modes: Query<&mut EntryMode>) {
246    let mut entry_mode = entry_modes
247        .get_mut(event.entity)
248        .expect("Entity does not carry an EntryMode component");
249    *entry_mode = transform_entry_mode(*entry_mode, event.adj);
250}
251
252pub fn transform_entry_mode(mut old: EntryMode, adjustment: EntryModeAdjustment) -> EntryMode {
253    use EntryModeAdjustment::*;
254    match adjustment {
255        SetArrival(m) => {
256            old.arr = m;
257        }
258        SetDeparture(m) => {
259            old.dep = m;
260        }
261        ShiftArrival(d) => {
262            old.shift_arr(d);
263        }
264        ShiftDeparture(d) => {
265            old.shift_dep(d);
266        }
267    }
268    old
269}