1use crate::units::time::Duration;
2use crate::vehicles::AdjustTimetableEntry;
3use crate::vehicles::entries::{TimetableEntry, TimetableEntryCache, TravelMode, VehicleSchedule};
4use bevy::prelude::*;
5use thiserror::Error;
6
7pub struct TroubleShootPlugin;
8
9impl Plugin for TroubleShootPlugin {
10 fn build(&self, app: &mut App) {
11 app.add_systems(
12 FixedPostUpdate,
13 analyze_entry.run_if(on_message::<AdjustTimetableEntry>),
14 );
15 }
16}
17
18#[derive(Component)]
19#[component(storage = "SparseSet")]
20pub struct ScheduleProblem(pub Vec<ScheduleProblemType>);
21#[derive(Debug, PartialEq, Eq, Error)]
22pub enum ScheduleProblemType {
23 #[error("Schedule is too short")]
24 TooShort,
25 #[error("Schedule collides with another vehicle")]
26 CollidesWithAnotherVehicle {
27 own_entry: Entity,
28 other_entry: Entity,
29 location: Entity,
30 },
31}
32
33#[derive(Component)]
34#[component(storage = "SparseSet")]
35pub struct EntryProblem(pub Vec<EntryProblemType>);
36#[derive(Debug, PartialEq, Eq, Error)]
37pub enum EntryProblemType {
38 #[error("No estimation available for this entry")]
39 NoEstimation,
40 #[error("Travel duration is too short or negative")]
41 TravelDurationTooShort,
42 #[error("Arrival is flexible but departure is at a fixed time")]
43 ReversedFlexibleMode,
44 #[error("This entry collides with another entry")]
45 CollidesWithAnotherEntry(Entity),
46}
47
48pub fn analyze_schedules(
49 mut commands: Commands,
50 regular_schedules: Query<&VehicleSchedule, Without<ScheduleProblem>>,
51 problematic_schedules: Query<&VehicleSchedule, With<ScheduleProblem>>,
52) {
53}
54
55pub fn analyze_entry(
56 mut commands: Commands,
57 mut msg_read_entry: MessageReader<AdjustTimetableEntry>,
58 mut entries: Query<(
59 Option<&mut EntryProblem>,
60 &TimetableEntry,
61 &TimetableEntryCache,
62 )>,
63) {
64 for entry_entity in msg_read_entry.read().map(|msg| msg.entity) {
65 let Ok((mut existing_problem, entry, entry_cache)) = entries.get_mut(entry_entity) else {
66 continue;
67 };
68
69 let check = |problems: &mut Vec<EntryProblemType>| {
70 if entry_cache.estimate.is_none() {
71 problems.push(EntryProblemType::NoEstimation)
72 }
73 if let TravelMode::For(a) = entry.arrival
74 && a <= Duration(0)
75 {
76 problems.push(EntryProblemType::TravelDurationTooShort);
77 } else if let Some(TravelMode::For(d)) = entry.departure
78 && d <= Duration(0)
79 {
80 problems.push(EntryProblemType::TravelDurationTooShort);
81 }
82 if matches!(entry.arrival, TravelMode::Flexible)
83 && matches!(entry.departure, Some(TravelMode::At(_)))
84 {
85 problems.push(EntryProblemType::ReversedFlexibleMode)
86 }
87 problems.dedup();
88 };
89
90 if let Some(ref mut problem) = existing_problem {
91 problem.0.clear();
92 check(&mut problem.0);
93 if problem.0.is_empty() {
94 commands.entity(entry_entity).remove::<EntryProblem>();
95 }
96 } else {
97 let mut problems = Vec::new();
98 check(&mut problems);
99 if !problems.is_empty() {
100 commands.entity(entry_entity).insert(EntryProblem(problems));
101 }
102 }
103 }
104}