paiagram/rw_data/
custom.rs

1// TODO: currently this module contains the processing functions for Beijing Subway sim,
2// and is not "custom" at all.
3// Replace this with a QuickJS based script loader in the future.
4use crate::{
5    graph::{Graph, Station},
6    rw_data::ModifyData,
7    units::{distance::Distance, time::TimetableTime},
8    vehicles::{
9        entries::{TimetableEntry, TravelMode, VehicleSchedule},
10        vehicle_set::VehicleSet,
11    },
12};
13use bevy::{platform::collections::HashMap, prelude::*};
14use moonshine_core::kind::*;
15use serde::Deserialize;
16use serde_json;
17
18#[derive(Deserialize)]
19struct Root {
20    #[serde(rename = "工作日")]
21    weekday: HashMap<String, LineMeta>,
22    #[serde(rename = "双休日")]
23    holiday: HashMap<String, LineMeta>,
24}
25
26#[derive(Deserialize)]
27struct LineMeta(HashMap<String, HashMap<String, Vec<TrainInfo>>>);
28
29#[derive(Deserialize)]
30struct TrainInfo((String, String));
31
32pub fn load_qetrc(
33    mut commands: Commands,
34    mut reader: MessageReader<ModifyData>,
35    mut graph: ResMut<Graph>,
36) {
37    let mut str: Option<&str> = None;
38    for modification in reader.read() {
39        match modification {
40            ModifyData::LoadCustom(s) => str = Some(s.as_str()),
41            _ => {}
42        }
43    }
44    let Some(str) = str else {
45        return;
46    };
47    let root: Root = match serde_json::from_str(str) {
48        Ok(r) => r,
49        Err(e) => {
50            error!("Failed to parse custom data: {}", e);
51            return;
52        }
53    };
54    info!("Reading...");
55    let mut station_map: HashMap<String, Instance<Station>> = HashMap::new();
56    let vehicle_set: Entity = commands
57        .spawn((VehicleSet, Name::new("New vehicle set")))
58        .id();
59    for (line_name, line_meta) in root.weekday.into_iter().chain(root.holiday.into_iter()) {
60        for (train_number, train_info) in line_meta.0.into_iter().flat_map(|(_, d)| d.into_iter()) {
61            let get_interval = |commands: &mut Commands,
62                                graph: &mut Graph,
63                                from: Instance<Station>,
64                                to: Instance<Station>| {
65                if let Some(&weight) = graph.edge_weight(from, to) {
66                    return weight;
67                };
68                let interval_entity = commands
69                    .spawn(Name::new(format!(
70                        "Interval {} - {}",
71                        from.entity().index(),
72                        to.entity().index()
73                    )))
74                    .insert_instance(crate::graph::Interval {
75                        length: Distance(1000),
76                        speed_limit: None,
77                    })
78                    .into();
79                graph.add_edge(from, to, interval_entity);
80                interval_entity
81            };
82            let mut get_station = |commands: &mut Commands, graph: &mut Graph, name: String| {
83                if let Some(&entity) = station_map.get(&name) {
84                    return entity;
85                }
86                let station_entity = commands
87                    .spawn(Name::new(name.clone()))
88                    .insert_instance(crate::graph::Station::default())
89                    .into();
90                station_map.insert(name, station_entity);
91                graph.add_node(station_entity);
92                station_entity
93            };
94            let mut vehicle_schedule: Vec<Entity> = Vec::new();
95            let mut times: Vec<(String, TimetableTime)> = Vec::new();
96            let mut previous_station: Option<Instance<Station>> = None;
97            let vehicle_entity = commands
98                .spawn((crate::vehicles::Vehicle, Name::new(train_number)))
99                .id();
100            for TrainInfo((station_name, time)) in train_info {
101                let stripped_time = time.strip_prefix("(").unwrap_or(&time);
102                let stripped_time = stripped_time.strip_suffix("-").unwrap_or(stripped_time);
103                let Some(timetable_time) = TimetableTime::from_str(stripped_time) else {
104                    warn!("Invalid time format: {}", time);
105                    continue;
106                };
107                times.push((station_name, timetable_time));
108            }
109            super::normalize_times(times.iter_mut().map(|(_, t)| t));
110            for (station_name, timetable_time) in times {
111                let station_entity = get_station(&mut commands, &mut graph, station_name);
112                if let Some(previous_station) = previous_station {
113                    let _ =
114                        get_interval(&mut commands, &mut graph, previous_station, station_entity);
115                    let _ =
116                        get_interval(&mut commands, &mut graph, station_entity, previous_station);
117                }
118                previous_station = Some(station_entity);
119                let entry_entity = commands
120                    .spawn((TimetableEntry {
121                        station: station_entity.entity(),
122                        arrival: TravelMode::At(timetable_time),
123                        departure: Some(TravelMode::Flexible),
124                        service: None,
125                        track: None,
126                    },))
127                    .id();
128                vehicle_schedule.push(entry_entity);
129                commands.entity(vehicle_entity).add_child(entry_entity);
130            }
131            commands.entity(vehicle_entity).insert(VehicleSchedule {
132                entities: vehicle_schedule,
133                ..Default::default()
134            });
135            commands.entity(vehicle_set).add_child(vehicle_entity);
136        }
137    }
138}