paiagram/interface/tabs/diagram/
calculate_lines.rs1use bevy::prelude::*;
2use egui::Pos2;
3
4use crate::graph::{Station, StationEntries};
5use crate::lines::DisplayedLine;
6use crate::units::time::TimetableTime;
7use crate::vehicles::entries::{
8 TimetableEntry, TimetableEntryCache, VehicleSchedule, VehicleScheduleCache,
9};
10use crate::vehicles::vehicle_set::VehicleSet;
11
12use super::{
13 DiagramLineCache, DiagramLineParams, PointData, RenderedVehicle, TICKS_PER_SECOND,
14 ensure_heights, ticks_to_screen_x,
15};
16
17pub fn calculate_lines(
18 (In(displayed_line_entity), InMut(mut line_cache), InMut(buffer), In(line_params)): (
19 In<Entity>,
20 InMut<DiagramLineCache>,
21 InMut<Vec<RenderedVehicle>>,
22 In<DiagramLineParams>,
23 ),
24 displayed_lines: Populated<Ref<DisplayedLine>>,
25 vehicles_query: Populated<(Entity, &Name, &VehicleSchedule, &VehicleScheduleCache)>,
26 entry_parents: Query<&ChildOf, With<TimetableEntry>>,
27 timetable_entries: Query<(&TimetableEntry, &TimetableEntryCache)>,
28 station_updated: Query<&StationEntries, Changed<StationEntries>>,
29 station_caches: Query<&StationEntries, With<Station>>,
30 vehicle_sets: Query<&Children, With<VehicleSet>>,
31 mut previous_vehicle_set: Local<Option<Entity>>,
32) {
33 let Ok(displayed_line) = displayed_lines.get(displayed_line_entity) else {
34 line_cache.line_missing = true;
35 buffer.clear();
36 return;
37 };
38 line_cache.line_missing = false;
39
40 let entries_updated = displayed_line.is_changed()
41 || line_cache.vehicle_entities.is_empty()
42 || displayed_line
43 .stations()
44 .iter()
45 .copied()
46 .any(|(s, _)| station_updated.get(s.entity()).is_ok())
47 || previous_vehicle_set.as_ref() != line_cache.vehicle_set.as_ref();
48
49 if entries_updated {
50 info!("Updating vehicle entities for diagram display");
51
52 for station in
53 station_caches.iter_many(displayed_line.stations().iter().map(|(e, _)| e.entity()))
54 {
55 station.passing_vehicles(&mut line_cache.vehicle_entities, |e| {
56 entry_parents.get(e).ok()
57 });
58 }
59 line_cache.vehicle_entities.sort();
60 if let Some(vehicle_set_entity) = line_cache.vehicle_set {
62 if let Ok(vehicle_set) = vehicle_sets.get(vehicle_set_entity) {
63 line_cache
64 .vehicle_entities
65 .retain(|e| vehicle_set.contains(e));
66 }
67 }
68 line_cache.vehicle_entities.dedup();
69 *previous_vehicle_set = line_cache.vehicle_set;
70 }
71
72 if displayed_line.is_changed() || line_cache.heights.is_none() {
73 ensure_heights(&mut line_cache, &displayed_line);
74 }
75
76 let Some(render_context) = line_cache.last_render_context.clone() else {
77 buffer.clear();
78 return;
79 };
80
81 let visible_stations = match line_cache.heights.as_ref() {
82 Some(heights) => {
83 let first_visible = heights
84 .iter()
85 .position(|(_, h)| *h > render_context.vertical_visible.start);
86 let last_visible = heights
87 .iter()
88 .rposition(|(_, h)| *h < render_context.vertical_visible.end);
89 if let (Some(mut first_visible), Some(mut last_visible)) = (first_visible, last_visible)
90 {
91 first_visible = first_visible.saturating_sub(2);
92 last_visible = (last_visible + 1).min(heights.len() - 1);
93 &heights[first_visible..=last_visible]
94 } else {
95 &[]
96 }
97 }
98 None => &[],
99 };
100
101 let mut rendered_vehicles = Vec::new();
102
103 for (vehicle_entity, _name, schedule, schedule_cache) in line_cache
104 .vehicle_entities
105 .iter()
106 .copied()
107 .filter_map(|e| vehicles_query.get(e).ok())
108 {
109 let Some(visible_sets) = schedule_cache.get_entries_range(
111 schedule,
112 TimetableTime((render_context.horizontal_visible.start / TICKS_PER_SECOND) as i32)
113 ..TimetableTime((render_context.horizontal_visible.end / TICKS_PER_SECOND) as i32),
114 |e| timetable_entries.get(e).ok(),
115 ) else {
116 continue;
117 };
118
119 let mut segments = Vec::new();
120
121 for (initial_offset, set) in visible_sets {
122 let mut local_edges: Vec<(Vec<PointData>, usize)> = Vec::new();
124 let mut previous_indices: Vec<usize> = Vec::new();
125
126 if let Some((ce_data, _)) = set.first() {
128 let (ce, _) = ce_data;
129 previous_indices = visible_stations
130 .iter()
131 .enumerate()
132 .filter_map(|(i, (s, _))| if *s == ce.station { Some(i) } else { None })
133 .collect();
134 }
135
136 for entry_idx in 0..set.len() {
137 let (ce_data, ce_actual) = &set[entry_idx];
138 let (ce, ce_cache) = ce_data;
139 let ne = set.get(entry_idx + 1);
140
141 if previous_indices.is_empty() {
143 if let Some((ne_data, _)) = ne {
144 let (ne_entry, _) = ne_data;
145 previous_indices = visible_stations
146 .iter()
147 .enumerate()
148 .filter_map(|(i, (s, _))| {
149 if *s == ne_entry.station {
150 Some(i)
151 } else {
152 None
153 }
154 })
155 .collect();
156 }
157 for (segment, _) in local_edges.drain(..) {
158 if segment.len() >= 2 {
159 segments.push(segment);
160 }
161 }
162 continue;
163 }
164
165 let mut next_local_edges = Vec::new();
166
167 let Some(estimate) = ce_cache.estimate.as_ref() else {
169 for (segment, _) in local_edges.drain(..) {
170 if segment.len() >= 2 {
171 segments.push(segment);
172 }
173 }
174 if let Some((ne_data, _)) = ne {
175 let (ne_entry, _) = ne_data;
176 previous_indices = visible_stations
177 .iter()
178 .enumerate()
179 .filter_map(|(i, (s, _))| {
180 if *s == ne_entry.station {
181 Some(i)
182 } else {
183 None
184 }
185 })
186 .collect();
187 }
188 continue;
189 };
190
191 let arrival_ticks = (initial_offset.0 as i64
193 + (estimate.arrival.0 - schedule.start.0) as i64)
194 * TICKS_PER_SECOND;
195 let departure_ticks = (initial_offset.0 as i64
196 + (estimate.departure.0 - schedule.start.0) as i64)
197 * TICKS_PER_SECOND;
198
199 for ¤t_line_index in &previous_indices {
201 let height = visible_stations[current_line_index].1;
202
203 let matched_idx = local_edges
208 .iter()
209 .position(|(_, idx)| current_line_index.abs_diff(*idx) <= 1);
210
211 let mut segment = if let Some(idx) = matched_idx {
212 local_edges.swap_remove(idx).0
213 } else {
214 Vec::new()
215 };
216
217 let arrival_pos = Pos2::new(
218 ticks_to_screen_x(
219 arrival_ticks,
220 &render_context.screen_rect,
221 render_context.ticks_per_screen_unit,
222 line_params.tick_offset,
223 ),
224 (height - line_params.vertical_offset) * line_params.zoom_y
225 + render_context.screen_rect.top(),
226 );
227
228 let departure_pos = if ce.departure.is_some() {
229 Some(Pos2::new(
230 ticks_to_screen_x(
231 departure_ticks,
232 &render_context.screen_rect,
233 render_context.ticks_per_screen_unit,
234 line_params.tick_offset,
235 ),
236 (height - line_params.vertical_offset) * line_params.zoom_y
237 + render_context.screen_rect.top(),
238 ))
239 } else {
240 None
241 };
242
243 segment.push((arrival_pos, departure_pos, *ce_actual));
244
245 let mut continued = false;
253 if let Some((ne_data, _)) = ne {
254 let (ne_entry, _) = ne_data;
255 for offset in [-1, 0, 1] {
256 let next_idx = (current_line_index as isize + offset) as usize;
257 if let Some((s, _)) = visible_stations.get(next_idx) {
258 if *s == ne_entry.station {
259 next_local_edges.push((segment.clone(), next_idx));
260 continued = true;
261 break;
262 }
263 }
264 }
265 }
266
267 if !continued {
269 if segment.len() >= 2 {
270 segments.push(segment);
271 }
272 }
273 }
274
275 for (segment, _) in local_edges.drain(..) {
277 if segment.len() >= 2 {
278 segments.push(segment);
279 }
280 }
281
282 local_edges = next_local_edges;
283 if let Some((ne_data, _)) = ne {
284 let (ne_entry, _) = ne_data;
285 previous_indices = visible_stations
286 .iter()
287 .enumerate()
288 .filter_map(|(i, (s, _))| {
289 if *s == ne_entry.station {
290 Some(i)
291 } else {
292 None
293 }
294 })
295 .collect();
296 }
297 }
298
299 for (segment, _) in local_edges {
301 if segment.len() >= 2 {
302 segments.push(segment);
303 }
304 }
305 }
306
307 rendered_vehicles.push(RenderedVehicle {
308 segments,
309 stroke: line_params.stroke.clone(),
310 entity: vehicle_entity,
311 });
312 }
313
314 buffer.clear();
315 buffer.extend(rendered_vehicles);
316}