Skip to main content

paiagram_core/
route.rs

1//! Routes are slices of the graph that can be used as the foundation of
2//! diagrams. Diagrams use routes as their station list.
3
4use bevy::ecs::entity::EntityHashSet;
5use bevy::prelude::*;
6use moonshine_core::prelude::{MapEntities, ReflectMapEntities};
7
8pub struct RoutePlugin;
9impl Plugin for RoutePlugin {
10    fn build(&self, app: &mut App) {
11        app.add_observer(auto_update_length)
12            .add_observer(sort_route_by_direction_trips)
13            .add_systems(Update, (update_route_trips, auto_generate_display_modes));
14    }
15}
16
17use crate::entry::{EntryMode, EntryQuery};
18use crate::graph::Graph;
19use crate::interval::{Interval, UpdateInterval};
20use crate::station::{ParentStationOrStation, Platform, PlatformEntries, Station, StationQuery};
21use crate::trip::TripQuery;
22
23/// Marker component for automatically updating route interval length.
24#[derive(Reflect, Component)]
25#[reflect(Component)]
26pub struct AutoUpdateLength;
27
28#[derive(Reflect, Component, MapEntities)]
29#[reflect(Component, MapEntities)]
30#[require(Name, RouteTrips, RouteByDirectionTrips)]
31pub struct Route {
32    #[entities]
33    pub stops: Vec<Entity>,
34    pub lengths: Vec<f32>,
35}
36
37#[derive(Reflect)]
38pub struct AllTripsDisplayMode {
39    pub departure: bool,
40    pub arrival: bool,
41}
42
43impl AllTripsDisplayMode {
44    pub fn count(&self) -> usize {
45        self.departure as usize + self.arrival as usize
46    }
47}
48
49#[derive(Reflect, Component, Deref, DerefMut)]
50#[reflect(Component)]
51pub struct RouteDisplayModes(Vec<AllTripsDisplayMode>);
52
53fn auto_generate_display_modes(
54    routes: Populated<(Entity, &Route), Without<RouteDisplayModes>>,
55    graph: Res<Graph>,
56    mut commands: Commands,
57) {
58    for (route_entity, route) in routes.iter().filter(|(_, it)| it.stops.len() > 0) {
59        let mut modes: Vec<AllTripsDisplayMode> = Vec::new();
60        modes.resize_with(route.stops.len(), || AllTripsDisplayMode {
61            departure: true,
62            arrival: false,
63        });
64        modes.last_mut().unwrap().arrival = true;
65        modes.last_mut().unwrap().departure = false;
66        for (idx, s) in route.stops.windows(2).enumerate() {
67            let [prev, curr] = s else { unreachable!() };
68            if graph.contains_edge(*prev, *curr) || graph.contains_edge(*curr, *prev) {
69                continue;
70            }
71            modes[idx].departure = false;
72            modes[idx].arrival = true;
73        }
74        commands
75            .entity(route_entity)
76            .insert(RouteDisplayModes(modes));
77    }
78}
79
80// TODO: handle update of route
81// TODO: improve sorting logic
82#[derive(Default, Reflect, Component, MapEntities, Deref, DerefMut)]
83#[reflect(Component, MapEntities)]
84#[require(Name)]
85pub struct RouteTrips(#[entities] Vec<Entity>);
86
87#[derive(Default, Reflect, Component, MapEntities)]
88#[reflect(Component, MapEntities)]
89pub struct RouteByDirectionTrips {
90    #[entities]
91    pub downward: Vec<Entity>,
92    #[entities]
93    pub upward: Vec<Entity>,
94}
95
96#[derive(EntityEvent)]
97pub struct SortRouteByDirectionTrips {
98    pub entity: Entity,
99}
100
101fn compute_sorted_by_first_entry_estimate(
102    trip_entities: &[Entity],
103    trip_q: &Query<TripQuery>,
104    entry_q: &Query<EntryQuery>,
105) -> Vec<Entity> {
106    let mut out = trip_entities.to_vec();
107    out.sort_unstable_by_key(|trip_entity| {
108        let trip = trip_q.get(*trip_entity).ok();
109        let first_time = trip
110            .and_then(|trip| {
111                entry_q
112                    .iter_many(trip.schedule.iter())
113                    .find_map(|entry| entry.estimate.map(|it| it.arr.min(it.dep)))
114            })
115            .unwrap_or(crate::units::time::TimetableTime(i32::MAX));
116        (first_time, trip_entity.to_bits())
117    });
118    out
119}
120
121fn compute_directional_members(
122    route: &Route,
123    trip_entities: &[Entity],
124    downwards: bool,
125    trip_q: &Query<TripQuery>,
126    entry_q: &Query<EntryQuery>,
127    parent_station_or_station: &Query<ParentStationOrStation>,
128) -> Vec<Entity> {
129    trip_entities
130        .iter()
131        .copied()
132        .filter_map(|trip_entity| {
133            let trip = trip_q.get(trip_entity).ok()?;
134            let mut stations = if downwards {
135                either::Either::Left(route.stops.iter())
136            } else {
137                either::Either::Right(route.stops.iter().rev())
138            };
139            let mut found_counter = 0;
140            for it in entry_q.iter_many(trip.schedule.iter()) {
141                let station_entity = parent_station_or_station.get(it.stop()).ok()?.parent();
142                if stations.any(|it| *it == station_entity) {
143                    found_counter += 1;
144                    if found_counter >= 2 {
145                        return Some(trip_entity);
146                    }
147                }
148            }
149            None
150        })
151        .collect()
152}
153
154fn sync_direction_order(existing: &mut Vec<Entity>, members: &[Entity], fallback_order: &[Entity]) {
155    let member_set: EntityHashSet = members.iter().copied().collect();
156    let mut next = Vec::with_capacity(members.len());
157
158    for entity in existing.iter().copied() {
159        if member_set.contains(&entity) {
160            next.push(entity);
161        }
162    }
163
164    for entity in fallback_order.iter().copied() {
165        if member_set.contains(&entity) && !next.contains(&entity) {
166            next.push(entity);
167        }
168    }
169
170    *existing = next;
171}
172
173impl Route {
174    pub fn iter(&self) -> impl Iterator<Item = (Entity, f32)> {
175        self.stops
176            .iter()
177            .copied()
178            .zip(self.lengths.iter().copied())
179            .scan(0.0_f32, |acc, (stop, len)| {
180                let out = (stop, *acc);
181                *acc += len;
182                Some(out)
183            })
184    }
185}
186
187fn update_route_trips(
188    mut routes: Query<(Entity, &Route, &mut RouteTrips, &mut RouteByDirectionTrips)>,
189    changed_routes: Query<Entity, (With<Route>, Changed<Route>)>,
190    changed_station_entries: Query<Entity, (With<Station>, Changed<PlatformEntries>)>,
191    changed_platform_entries: Query<&ChildOf, (With<Platform>, Changed<PlatformEntries>)>,
192    stations: Query<StationQuery>,
193    platform_entries: Query<&PlatformEntries>,
194    entries: Query<&ChildOf, With<EntryMode>>,
195    trip_q: Query<TripQuery>,
196    entry_q: Query<EntryQuery>,
197    parent_station_or_station: Query<ParentStationOrStation>,
198) {
199    let mut affected_routes = EntityHashSet::default();
200
201    for route_entity in &changed_routes {
202        affected_routes.insert(route_entity);
203    }
204
205    let mut changed_stations = EntityHashSet::default();
206    for station in &changed_station_entries {
207        changed_stations.insert(station);
208    }
209    for parent in &changed_platform_entries {
210        changed_stations.insert(parent.parent());
211    }
212
213    if !changed_stations.is_empty() {
214        for (route_entity, route, _, _) in &routes {
215            if route
216                .stops
217                .iter()
218                .any(|station| changed_stations.contains(station))
219            {
220                affected_routes.insert(route_entity);
221            }
222        }
223    }
224
225    if affected_routes.is_empty() {
226        return;
227    }
228
229    for (route_entity, route, mut route_trips, mut by_direction) in &mut routes {
230        if !affected_routes.contains(&route_entity) {
231            continue;
232        }
233
234        let mut trips = EntityHashSet::default();
235        for station_entity in route.stops.iter().copied() {
236            let Ok(station) = stations.get(station_entity) else {
237                continue;
238            };
239            for entry in station.passing_entries(&platform_entries) {
240                let Ok(parent) = entries.get(entry) else {
241                    continue;
242                };
243                trips.insert(parent.parent());
244            }
245        }
246
247        let mut next = route_trips
248            .0
249            .iter()
250            .copied()
251            .filter(|entity| trips.contains(entity))
252            .collect::<Vec<_>>();
253        for entity in trips.iter().copied() {
254            if !next.contains(&entity) {
255                next.push(entity);
256            }
257        }
258        if route_trips.0 != next {
259            route_trips.0 = next.clone();
260        }
261
262        let new_downward = compute_directional_members(
263            route,
264            &next,
265            true,
266            &trip_q,
267            &entry_q,
268            &parent_station_or_station,
269        );
270        let new_upward = compute_directional_members(
271            route,
272            &next,
273            false,
274            &trip_q,
275            &entry_q,
276            &parent_station_or_station,
277        );
278        sync_direction_order(&mut by_direction.downward, &new_downward, &next);
279        sync_direction_order(&mut by_direction.upward, &new_upward, &next);
280    }
281}
282
283fn sort_route_by_direction_trips(
284    trigger: On<SortRouteByDirectionTrips>,
285    mut routes: Query<(&RouteTrips, &mut RouteByDirectionTrips)>,
286    trip_q: Query<TripQuery>,
287    entry_q: Query<EntryQuery>,
288) {
289    let Ok((route_trips, mut by_direction)) = routes.get_mut(trigger.entity) else {
290        return;
291    };
292
293    let sorted_downward =
294        compute_sorted_by_first_entry_estimate(&by_direction.downward, &trip_q, &entry_q);
295    let sorted_upward =
296        compute_sorted_by_first_entry_estimate(&by_direction.upward, &trip_q, &entry_q);
297
298    by_direction.downward = sorted_downward;
299    by_direction.upward = sorted_upward;
300}
301
302// TODO: revise this
303fn auto_update_length(
304    updated: On<UpdateInterval>,
305    routes: Populated<&mut Route, With<AutoUpdateLength>>,
306    intervals: Query<&Interval>,
307    graph: Res<Graph>,
308) {
309    for mut route in routes {
310        let Route { stops, lengths } = &mut *route;
311        for (i, w) in stops.windows(2).enumerate() {
312            let [p, c] = w else { unreachable!() };
313            let (p, c) = (*p, *c);
314            if (p == updated.source && c == updated.target)
315                || (p == updated.target && c == updated.source)
316            {
317            } else {
318                continue;
319            }
320            let i1 = graph.edge_weight(p, c).cloned();
321            let i2 = graph.edge_weight(c, p).cloned();
322            match (i1, i2) {
323                (Some(e1), Some(e2)) => {
324                    let d1 = intervals.get(e1).unwrap().length;
325                    let d2 = intervals.get(e2).unwrap().length;
326                    let avg_len = (d1.0 as f32 + d2.0 as f32) / 2.0;
327                    lengths[i] = avg_len;
328                }
329                (Some(e), None) | (None, Some(e)) => {
330                    let d = intervals.get(e).unwrap().length;
331                    lengths[i] = d.0 as f32;
332                }
333                (None, None) => {
334                    panic!("Interval disappeared???")
335                }
336            }
337        }
338    }
339}