1use 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#[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#[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
302fn 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}