1use crate::{
2 graph::{Graph, Interval, Station, StationEntries},
3 units::time::{Duration, TimetableTime},
4 vehicles::{AdjustTimetableEntry, services::VehicleService},
5};
6use bevy::platform::collections::HashSet;
7use bevy::prelude::*;
8use either::Either;
9use moonshine_core::kind::Instance;
10use moonshine_core::save::prelude::*;
11use petgraph::algo::astar;
12use smallvec::{SmallVec, smallvec};
13
14#[derive(Reflect, Debug, Default, Clone, Copy)]
16pub enum TravelMode {
17 At(TimetableTime),
19 For(Duration),
21 #[default]
24 Flexible,
25}
26
27impl TravelMode {
28 pub fn adjust_time(&mut self, adjustment: Duration) {
29 match self {
30 TravelMode::At(t) => {
31 t.0 += adjustment.0;
32 }
33 TravelMode::For(dur) => {
34 dur.0 += adjustment.0;
35 }
36 TravelMode::Flexible => (),
37 }
38 }
39}
40
41impl std::fmt::Display for TravelMode {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Self::At(t) => write!(f, "{}", t),
45 Self::For(dur) => write!(f, "{}", dur),
46 Self::Flexible => write!(f, ">>"),
47 }
48 }
49}
50
51#[derive(Debug, Reflect, Component, Clone)]
53#[reflect(Component, MapEntities)]
54#[require(TimetableEntryCache, Save)]
55#[component(map_entities)]
56#[relationship(relationship_target = StationEntries)]
57pub struct TimetableEntry {
58 #[relationship]
60 pub station: Entity,
61 pub arrival: TravelMode,
63 pub departure: Option<TravelMode>,
65 pub service: Option<Instance<VehicleService>>,
67 pub track: Option<Entity>,
69}
70
71impl MapEntities for TimetableEntry {
72 fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
73 self.station.map_entities(entity_mapper);
74 self.track.map_entities(entity_mapper);
75 self.service.map_entities(entity_mapper);
76 }
77}
78
79impl TimetableEntry {
80 pub fn station(&self) -> Instance<Station> {
81 unsafe { Instance::from_entity_unchecked(self.station) }
83 }
84}
85
86#[derive(Reflect, Debug, Component, Default)]
87#[reflect(Component)]
88pub struct TimetableEntryCache {
89 pub estimate: Option<TimeEstimate>,
90}
91
92#[derive(Reflect, Debug)]
93pub struct TimeEstimate {
94 pub arrival: TimetableTime,
97 pub departure: TimetableTime,
100}
101
102#[derive(Reflect, Debug, Component)]
104#[component(map_entities)]
105#[reflect(Component, MapEntities)]
106#[require(VehicleScheduleCache)]
107pub struct VehicleSchedule {
108 pub start: TimetableTime,
110 pub repeat: Option<Duration>,
112 pub departures: Vec<Duration>,
116 pub entities: Vec<Entity>,
118}
119
120impl MapEntities for VehicleSchedule {
121 fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
122 for e in self.entities.iter_mut() {
123 e.map_entities(entity_mapper);
124 }
125 }
126}
127
128#[derive(Reflect, Debug, Clone, Copy)]
129pub enum ActualRouteEntry {
130 Nominal(Entity),
131 Derived(Entity),
132}
133
134impl ActualRouteEntry {
135 pub fn inner(self) -> Entity {
136 match self {
137 Self::Nominal(e) => e,
138 Self::Derived(e) => e,
139 }
140 }
141 pub fn inner_mut(&mut self) -> &mut Entity {
142 match self {
143 Self::Nominal(e) => e,
144 Self::Derived(e) => e,
145 }
146 }
147}
148
149#[derive(Component, Reflect, Debug, Default)]
150#[reflect(Component, MapEntities)]
151#[component(map_entities)]
152pub struct VehicleScheduleCache {
153 pub actual_route: Option<Vec<ActualRouteEntry>>,
154 #[reflect(ignore)]
158 pub service_entities: Vec<(
159 Instance<VehicleService>,
160 SmallVec<[std::ops::Range<usize>; 1]>,
161 )>,
162}
163
164impl MapEntities for VehicleScheduleCache {
165 fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
166 if let Some(actual_route) = &mut self.actual_route {
167 for entry in actual_route.iter_mut() {
168 entry.inner_mut().map_entities(entity_mapper);
169 }
170 }
171 }
172}
173
174impl VehicleScheduleCache {
175 pub fn position<'a>(
176 &self,
177 time: f32,
178 get_info: impl Fn(Entity) -> Option<(&'a TimetableEntry, &'a TimetableEntryCache)>,
179 ) -> Option<Either<(Entity, Entity, f32), Entity>> {
180 let actual_route = self.actual_route.as_ref()?;
181 let i = actual_route.iter().rposition(|e| {
182 let Some((_, cache)) = get_info(e.inner()) else {
183 return false;
184 };
185 let Some(entry_time) = cache.estimate.as_ref().map(|est| est.arrival) else {
186 return false;
187 };
188 entry_time.0 <= time as i32
189 })?;
190 let (this_entry, this_cache) = get_info(actual_route[i].inner())?;
191 let this_times = this_cache.estimate.as_ref()?;
192 if this_times.arrival.0 <= (time as i32) && (time as i32) <= this_times.departure.0 {
193 return Some(Either::Right(this_entry.station.entity()));
194 }
195 let (next_entry, next_cache) = get_info(actual_route.get(i + 1)?.inner())?;
196 let next_times = next_cache.estimate.as_ref()?;
197 let duration = next_times.arrival - this_times.departure;
198 let elapsed = time - this_times.departure.0 as f32;
199 if duration.0 <= 0 {
200 return Some(Either::Right(next_entry.station.entity()));
201 }
202 let factor = elapsed / (duration.0 as f32);
203 Some(Either::Left((
204 this_entry.station.entity(),
205 next_entry.station.entity(),
206 factor,
207 )))
208 }
209}
210
211pub fn calculate_actual_route(
212 mut vehicles: Query<(&mut VehicleScheduleCache, &VehicleSchedule)>,
213 mut msg_vehicle_changes: MessageReader<AdjustTimetableEntry>,
214 mut commands: Commands,
215 timetable_entries: Query<(&ChildOf, &TimetableEntry)>,
216 names: Query<&Name>,
217 graph: Res<Graph>,
218 intervals: Query<&Interval>,
219 mut actual_route_list: Local<Vec<ActualRouteEntry>>,
220 mut warned_pairs: Local<HashSet<(Instance<Station>, Instance<Station>)>>,
221 mut processed: Local<Vec<Entity>>,
222) {
223 processed.clear();
224 for vehicle_entity in msg_vehicle_changes
225 .read()
226 .filter_map(|msg| timetable_entries.get(msg.entity).ok().map(|(c, _)| c.0))
227 {
228 if processed.contains(&vehicle_entity) {
229 continue;
230 }
231 processed.push(vehicle_entity);
232 let Ok((mut cache, schedule)) = vehicles.get_mut(vehicle_entity) else {
233 continue;
234 };
235 let mut previous_route = cache.actual_route.take().unwrap_or_default();
236
237 for entry in previous_route.iter().copied() {
239 if let ActualRouteEntry::Derived(id) = entry {
240 commands.entity(id).despawn();
241 }
242 }
243
244 let mut prev_entry: Option<&TimetableEntry> = None;
245
246 actual_route_list.clear();
247 warned_pairs.clear();
248 for entity in schedule
249 .entities
250 .iter()
251 .copied()
252 .map(ActualRouteEntry::Nominal)
253 {
254 let Ok((_, entry)) = timetable_entries.get(entity.inner()) else {
255 continue;
256 };
257 let Some(prev) = prev_entry.replace(entry) else {
258 actual_route_list.push(entity);
259 continue;
260 };
261 if prev.station() == entry.station() {
262 actual_route_list.push(entity);
263 continue;
264 }
265 if graph.contains_edge(prev.station(), entry.station()) {
266 actual_route_list.push(entity);
267 continue;
268 }
269
270 let prev_in_graph = graph.contains_node(prev.station());
273 let next_in_graph = graph.contains_node(entry.station());
274 let service_name = prev
275 .service
276 .map(|e| names.get(e.entity()).ok())
277 .flatten()
278 .map(Name::as_str)
279 .unwrap_or("<unnammed>");
280 if !(prev_in_graph && next_in_graph) {
281 let pair = (prev.station(), entry.station());
282 if warned_pairs.insert(pair) {
283 let prev_name = names
284 .get(prev.station().entity())
285 .ok()
286 .map(Name::as_str)
287 .unwrap_or("<unnamed>");
288 let next_name = names
289 .get(entry.station().entity())
290 .ok()
291 .map(Name::as_str)
292 .unwrap_or("<unnamed>");
293 warn!(
294 "Skipping routing for timetable stations of {service_name} not in graph: {prev_name}({:?}) -> {next_name}({:?}); nodes_in_graph=({prev_in_graph},{next_in_graph})",
295 prev.station(),
296 entry.station()
297 );
298 }
299 actual_route_list.push(entity);
300 continue;
301 }
302 let Some((_, path)) = astar(
304 &graph.inner(),
305 graph.node_index(prev.station()).unwrap(),
306 |finish| finish == graph.node_index(entry.station()).unwrap(),
307 |edge| {
308 if let Ok(interval) = intervals.get(edge.weight().entity()) {
309 interval.length.0
310 } else {
311 i32::MAX
312 }
313 },
314 |_| 0,
315 ) else {
316 let pair = (prev.station(), entry.station());
319 if warned_pairs.insert(pair) {
320 let prev_name = names
321 .get(prev.station().entity())
322 .ok()
323 .map(Name::as_str)
324 .unwrap_or("<unnamed>");
325 let next_name = names
326 .get(entry.station().entity())
327 .ok()
328 .map(Name::as_str)
329 .unwrap_or("<unnamed>");
330 warn!(
331 "No route in graph between consecutive timetable stations: {prev_name}({:?}) -> {next_name}({:?})",
332 prev.station(),
333 entry.station()
334 );
335 }
336 actual_route_list.push(entity);
337 continue;
338 };
339 if path.len() >= 2 {
340 for &node_index in &path[1..path.len() - 1] {
341 let station_entity = graph.entity(node_index).unwrap();
342 let station_name = names
343 .get(station_entity.entity())
344 .ok()
345 .map(Name::as_str)
346 .unwrap_or("<unnamed>");
347 info!(?station_name, ?service_name);
348 let derived_entity = commands
349 .spawn((TimetableEntry {
350 station: station_entity.entity(),
351 service: prev.service,
352 arrival: TravelMode::Flexible,
353 departure: None,
354 track: None,
355 },))
356 .id();
357 commands.entity(vehicle_entity).add_child(derived_entity);
358 actual_route_list.push(ActualRouteEntry::Derived(derived_entity));
359 }
360 }
361 actual_route_list.push(entity);
362 }
363
364 previous_route.clear();
365 previous_route.extend(actual_route_list.iter());
366 cache.actual_route = Some(previous_route);
367 }
368}
369
370pub fn populate_services(
371 mut msg_reader: MessageReader<super::AdjustTimetableEntry>,
372 mut schedules: Populated<(&mut VehicleScheduleCache, &VehicleSchedule)>,
373 entries: Populated<(&TimetableEntry, &ChildOf)>,
374) {
375 for msg in msg_reader.read() {
376 let super::AdjustTimetableEntry { entity, .. } = msg;
377 let Ok((_, parent)) = entries.get(*entity) else {
378 continue;
379 };
380 let Ok((mut schedule_cache, schedule)) = schedules.get_mut(parent.0) else {
381 continue;
382 };
383 let mut pool: Vec<(
384 Instance<VehicleService>,
385 SmallVec<[std::ops::Range<usize>; 1]>,
386 )> = Vec::new();
387 let mut start: usize = 0;
388 let mut previous_service: Option<Instance<VehicleService>> = None;
389 for (idx, entry_entity) in schedule.entities.iter().enumerate() {
390 let Ok((entry, _)) = entries.get(*entry_entity) else {
391 start = idx;
392 continue;
393 };
394 let Some(current_service) = entry.service else {
395 start = idx;
396 continue;
397 };
398
399 if let Some(prev) = previous_service {
400 if prev != current_service {
401 match pool.binary_search_by_key(&prev, |(e, _)| *e) {
402 Ok(j) => {
403 pool[j].1.push(start..idx);
404 }
405 Err(j) => {
406 pool.insert(j, (prev, smallvec![start..idx]));
407 }
408 }
409 start = idx;
410 previous_service = Some(current_service);
411 }
412 } else {
413 previous_service = Some(current_service);
414 start = idx;
415 }
416 }
417 if let Some(prev) = previous_service {
418 let idx = schedule.entities.len();
419 match pool.binary_search_by_key(&prev, |(e, _)| *e) {
420 Ok(j) => {
421 pool[j].1.push(start..idx);
422 }
423 Err(j) => {
424 pool.insert(j, (prev, smallvec![start..idx]));
425 }
426 }
427 }
428 schedule_cache.service_entities = pool;
429 }
430}
431
432impl Default for VehicleSchedule {
433 fn default() -> Self {
434 Self {
435 start: TimetableTime(0),
436 repeat: Some(Duration(86400)),
437 departures: vec![Duration(0)],
438 entities: Vec::new(),
439 }
440 }
441}
442
443impl VehicleSchedule {
444 pub fn into_entries<'a, F>(
445 &self,
446 mut lookup: F,
447 ) -> impl Iterator<Item = (&'a TimetableEntry, Entity)>
448 where
449 F: FnMut(Entity) -> Option<&'a TimetableEntry> + 'a,
450 {
451 self.entities
452 .iter()
453 .filter_map(move |e| lookup(*e).map(|r| (r, *e)))
454 }
455}
456
457impl VehicleScheduleCache {
458 pub fn get_service_entries(&self, service: Entity) -> Option<Vec<&[ActualRouteEntry]>> {
459 let Some(actual_route) = &self.actual_route else {
460 return None;
461 };
462 let i = self
463 .service_entities
464 .binary_search_by_key(&service, |(e, _)| e.entity());
465 let Ok(i) = i else { return None };
466 let (_, entries) = &self.service_entities[i];
467 let mut ret = Vec::with_capacity(entries.len());
468 for entry in entries {
469 ret.push(&actual_route[entry.clone()]);
470 }
471 Some(ret)
472 }
473 pub fn get_service_first_entry(&self, service: Entity) -> Option<ActualRouteEntry> {
474 let Some(actual_route) = &self.actual_route else {
475 return None;
476 };
477 let i = self
478 .service_entities
479 .binary_search_by_key(&service, |(e, _)| e.entity());
480 let Ok(i) = i else { return None };
481 return self.service_entities[i]
482 .1
483 .first()
484 .and_then(|e| Some(actual_route[e.start]));
485 }
486 pub fn get_service_last_entry(
487 &self,
488 service: Instance<VehicleService>,
489 ) -> Option<ActualRouteEntry> {
490 let Some(actual_route) = &self.actual_route else {
491 return None;
492 };
493 let i = self
494 .service_entities
495 .binary_search_by_key(&service, |(e, _)| *e);
496 let Ok(i) = i else { return None };
497 return self.service_entities[i]
498 .1
499 .last()
500 .and_then(|e| Some(actual_route[e.end.saturating_sub(1)]));
501 }
502
503 pub fn get_entries_range<'a, F>(
504 &self,
505 parent: &'a VehicleSchedule,
506 range: std::ops::Range<TimetableTime>,
507 mut lookup: F,
508 ) -> Option<
509 Vec<(
510 TimetableTime,
511 Vec<(
512 (&'a TimetableEntry, &'a TimetableEntryCache),
513 ActualRouteEntry,
514 )>,
515 )>,
516 >
517 where
518 F: FnMut(Entity) -> Option<(&'a TimetableEntry, &'a TimetableEntryCache)> + 'a,
519 {
520 let Some(actual_route) = &self.actual_route else {
521 return None;
522 };
523 let timetable_entries = actual_route
524 .iter()
525 .filter_map(move |e| lookup(e.inner()).map(|t| (t, *e)))
526 .collect::<Vec<_>>();
527 let mut ret = Vec::with_capacity(timetable_entries.len());
528 let repeats_iter = match parent.repeat {
529 None => Either::Left(std::iter::once(parent.start)),
530 Some(duration) => {
531 let start = parent.start
532 + duration * {
533 let main = (range.start - parent.start).0.div_euclid(duration.0);
534 let last_dep = *parent.departures.last()?
535 + timetable_entries
536 .last()
537 .map(|((_, tec), _)| tec.estimate.as_ref())??
538 .departure
539 .as_duration();
540 let sub = last_dep.0.div_euclid(duration.0);
541 main - sub
542 };
543 Either::Right(std::iter::successors(Some(start), move |t| {
544 let time = *t + duration;
545 if time > range.end {
546 return None;
547 }
548 Some(time)
549 }))
550 }
551 };
552 for base_time in repeats_iter {
553 for departure in parent.departures.iter().copied() {
554 let start_index = timetable_entries.iter().position(|((_, tec), _)| {
555 tec.estimate.as_ref().map_or(false, |e| {
556 departure + base_time + e.arrival.as_duration() > range.start
557 })
558 });
559 let end_index = timetable_entries.iter().rposition(|((_, tec), _)| {
560 tec.estimate.as_ref().map_or(false, |e| {
561 departure + base_time + e.departure.as_duration() < range.end
562 })
563 });
564 let (Some(mut si), Some(mut ei)) = (start_index, end_index) else {
565 continue;
566 };
567 si = si.saturating_sub(1);
568 ei = (ei + 1).min(timetable_entries.len() - 1);
569 if si > ei {
571 continue;
572 }
573 let v = &timetable_entries[si..=ei];
574 ret.push((departure + base_time, v.to_vec()))
575 }
576 }
577 Some(ret)
578 }
579}