paiagram/
troubleshoot.rs

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}