1use serde_json::Value;
27
28use crate::sim::Simulation;
29
30pub const FRAME_LAYOUT_VERSION: u32 = 1;
34
35#[allow(missing_docs)]
42pub mod header {
43 pub const MINUTES: usize = 0;
44 pub const TOWER_REVISION: usize = 1;
45 pub const MEAL_OVERLAY_REVISION: usize = 2;
46 pub const LOG_SEQ: usize = 3;
47 pub const MONEY: usize = 4;
48 pub const STAR: usize = 5;
49 pub const WEATHER: usize = 6;
50 pub const SANTA_FX_SEQ: usize = 7;
51 pub const EXPLOSION_FX: usize = 8;
52 pub const THIEF_FX: usize = 11;
53 pub const TREASURE_FX: usize = 14;
54 pub const VIP_FX_SEQ: usize = 17;
55 pub const EVENT_COUNTS: usize = 18;
56 pub const PENDING_CHOICE: usize = 21;
57 pub const ON_HOUR_RUNS: usize = 22;
58 pub const PEOPLE: usize = 23;
59 pub const UNITS: usize = 24;
60 pub const TRANSPORTS: usize = 25;
61}
62
63#[allow(missing_docs)]
68pub mod person {
69 pub const ID: usize = 0;
70 pub const SEED: usize = 1;
71 pub const STAFF: usize = 2;
72 pub const STATE: usize = 3;
73 pub const FLOOR: usize = 4;
74 pub const X: usize = 5;
75 pub const FY: usize = 6;
76 pub const WAIT: usize = 7;
77 pub const ORIGIN_FLOOR: usize = 8;
78 pub const ORIGIN_UNIT_ID: usize = 9;
79 pub const VENUE_UNIT_ID: usize = 10;
80 pub const MEAL_VENUE_ID: usize = 11;
81 pub const COUNTED_HOTEL_GUEST: usize = 12;
82 pub const ROUTINE: usize = 13;
83 pub const RETURNING: usize = 14;
84 pub const DWELL_SECONDS_LEFT: usize = 15;
85 pub const FLOOR_COUNT: usize = 16;
86 pub const SHAFT_COUNT: usize = 17;
87}
88
89pub const FRAME_HEADER: usize = header::TRANSPORTS + 1;
91
92pub const PERSON_FIXED: usize = person::SHAFT_COUNT + 1;
94
95#[allow(missing_docs)]
98pub mod unit {
99 pub const ID: usize = 0;
100 pub const STATE: usize = 1;
101 pub const OCCUPANTS: usize = 2;
102 pub const CUSTOMERS_IN: usize = 3;
103 pub const HOTEL_CUSTOMERS_IN: usize = 4;
104 pub const OUT_FOR_MEAL: usize = 5;
105}
106
107#[allow(missing_docs)]
111pub mod transport {
112 pub const ID: usize = 0;
113 pub const CARS: usize = 1;
114}
115
116#[allow(missing_docs)]
119pub mod car {
120 pub const POSITION: usize = 0;
121 pub const LOAD: usize = 1;
122 pub const DIRECTION: usize = 2;
123}
124
125pub const UNIT_RECORD: usize = unit::OUT_FOR_MEAL + 1;
127
128pub const TRANSPORT_FIXED: usize = transport::CARS + 1;
130
131pub const CAR_RECORD: usize = car::DIRECTION + 1;
133
134pub const UNKNOWN_ROUTINE: f64 = 255.0;
138
139pub fn frame_view(sim: &Simulation) -> Vec<f64> {
168 use crate::crowd::PState;
169 use crate::sim_loop::Weather;
170 use crate::tower::UnitState;
171 let people = &sim.crowd.people;
172 let units = &sim.tower.units;
173 let transports = &sim.tower.transports;
174 let mut v = Vec::with_capacity(
175 FRAME_HEADER
176 + people
177 .iter()
178 .map(|p| PERSON_FIXED + p.floors.len() + p.shafts.len())
179 .sum::<usize>()
180 + units.len() * UNIT_RECORD
181 + transports
182 .iter()
183 .map(|t| TRANSPORT_FIXED + CAR_RECORD * t.cars as usize)
184 .sum::<usize>(),
185 );
186 let fx = &sim.fx;
187 v.extend_from_slice(&[
188 sim.clock.minutes,
189 sim.tower.revision as f64,
190 sim.tower.meal_overlay_revision as f64,
191 sim.log_seq as f64,
192 sim.money,
193 sim.star as f64,
194 match sim.weather {
195 Weather::Clear => 0.0,
196 Weather::Cloudy => 1.0,
197 Weather::Rain => 2.0,
198 },
199 fx.santa_seq as f64,
200 fx.explosion.seq as f64,
201 fx.explosion.floor as f64,
202 fx.explosion.x,
203 fx.thief.seq as f64,
204 if fx.thief.caught { 1.0 } else { 0.0 },
205 fx.thief.floor as f64,
206 fx.treasure.seq as f64,
207 fx.treasure.floor as f64,
208 fx.treasure.x,
209 fx.vip_seq as f64,
210 sim.events.fires_started as f64,
211 sim.events.fires_gut_rooms as f64,
212 sim.events.bombs_detonated as f64,
213 if sim.events.pending.is_some() {
214 1.0
215 } else {
216 0.0
217 },
218 sim.on_hour_runs as f64,
219 people.len() as f64,
220 units.len() as f64,
221 transports.len() as f64,
222 ]);
223 debug_assert_eq!(v.len(), FRAME_HEADER);
224 for p in people {
225 let state = match p.state {
226 PState::ToShaft => 0.0,
227 PState::Waiting => 1.0,
228 PState::Riding => 2.0,
229 PState::Climbing => 3.0,
230 PState::ToDest => 4.0,
231 PState::Dwelling => 5.0,
232 PState::Done => 6.0,
233 };
234 let id = |v: Option<i64>| v.map_or(-1.0, |n| n as f64);
235 let flag = |b: bool| if b { 1.0 } else { 0.0 };
236 let routine = match p.routine {
237 None => 0.0,
238 Some("schoolRun") => 1.0,
239 Some("salesCall") => 2.0,
240 Some(other) => {
245 if cfg!(test) {
246 panic!("routine {other} has no frame code");
247 }
248 UNKNOWN_ROUTINE
249 }
250 };
251 v.extend_from_slice(&[
252 p.id as f64,
253 p.seed as f64,
254 flag(p.staff),
255 state,
256 p.floor as f64,
257 p.x,
258 p.fy,
259 p.wait,
260 p.origin_floor as f64,
261 id(p.origin_unit_id),
262 id(p.venue_unit_id),
263 id(p.meal_venue_id),
264 flag(p.counted_hotel_guest),
265 routine,
266 flag(p.returning),
267 p.dwell_seconds_left.unwrap_or(f64::NAN),
268 p.floors.len() as f64,
269 p.shafts.len() as f64,
270 ]);
271 v.extend(p.floors.iter().map(|f| *f as f64));
272 v.extend(p.shafts.iter().map(|s| *s as f64));
273 }
274 let counter = |c: Option<i64>| c.map_or(-1.0, |n| n as f64);
275 for u in units {
276 let state = match u.state {
277 UnitState::Construction => 0.0,
278 UnitState::Empty => 1.0,
279 UnitState::Occupied => 2.0,
280 UnitState::MovingIn => 3.0,
281 UnitState::Vacating => 4.0,
282 UnitState::Asleep => 5.0,
283 UnitState::Dirty => 6.0,
284 UnitState::Infested => 7.0,
285 UnitState::Fire => 8.0,
286 UnitState::Gutted => 9.0,
287 };
288 v.extend_from_slice(&[
289 u.id as f64,
290 state,
291 u.occupants as f64,
292 counter(u.customers_in),
293 counter(u.hotel_customers_in),
294 counter(u.out_for_meal),
295 ]);
296 }
297 for t in transports {
298 v.push(t.id as f64);
299 v.push(t.cars as f64);
300 for i in 0..t.cars as usize {
301 v.push(t.car_positions.get(i).copied().unwrap_or(0.0));
302 v.push(
303 t.car_load
304 .as_ref()
305 .and_then(|l| l.get(i).copied())
306 .unwrap_or(-1.0),
307 );
308 v.push(t.car_dir.get(i).copied().unwrap_or(0) as f64);
309 }
310 }
311 v
312}
313
314pub(crate) fn person_state_code(state: crate::crowd::PState) -> f64 {
321 use crate::crowd::PState;
322 match state {
323 PState::ToShaft => 0.0,
324 PState::Waiting => 1.0,
325 PState::Riding => 2.0,
326 PState::Climbing => 3.0,
327 PState::ToDest => 4.0,
328 PState::Dwelling => 5.0,
329 PState::Done => 6.0,
330 }
331}
332
333pub(crate) fn unit_state_code(state: crate::tower::UnitState) -> f64 {
335 use crate::tower::UnitState;
336 match state {
337 UnitState::Construction => 0.0,
338 UnitState::Empty => 1.0,
339 UnitState::Occupied => 2.0,
340 UnitState::MovingIn => 3.0,
341 UnitState::Vacating => 4.0,
342 UnitState::Asleep => 5.0,
343 UnitState::Dirty => 6.0,
344 UnitState::Infested => 7.0,
345 UnitState::Fire => 8.0,
346 UnitState::Gutted => 9.0,
347 }
348}
349
350pub(crate) fn weather_code(weather: crate::sim_loop::Weather) -> f64 {
352 use crate::sim_loop::Weather;
353 match weather {
354 Weather::Clear => 0.0,
355 Weather::Cloudy => 1.0,
356 Weather::Rain => 2.0,
357 }
358}
359
360pub(crate) fn unit_fields(u: &crate::tower::Unit) -> [f64; 5] {
363 let counter = |c: Option<i64>| c.map_or(-1.0, |n| n as f64);
364 [
365 unit_state_code(u.state),
366 u.occupants as f64,
367 counter(u.customers_in),
368 counter(u.hotel_customers_in),
369 counter(u.out_for_meal),
370 ]
371}
372
373pub(crate) fn transports_len(transports: &[crate::tower::Transport]) -> usize {
375 transports
376 .iter()
377 .map(|t| TRANSPORT_FIXED + CAR_RECORD * t.cars as usize)
378 .sum()
379}
380
381pub(crate) fn push_transports(v: &mut Vec<f64>, transports: &[crate::tower::Transport]) {
384 for t in transports {
385 v.push(t.id as f64);
386 v.push(t.cars as f64);
387 for i in 0..t.cars as usize {
388 v.push(t.car_positions.get(i).copied().unwrap_or(0.0));
389 v.push(
390 t.car_load
391 .as_ref()
392 .and_then(|l| l.get(i).copied())
393 .unwrap_or(-1.0),
394 );
395 v.push(t.car_dir.get(i).copied().unwrap_or(0) as f64);
396 }
397 }
398}
399
400pub fn log_since(sim: &Simulation, seq: i64) -> Value {
404 let len = sim.log.len() as i64;
405 let first = sim.log_seq - len + 1;
406 Value::Array(
407 sim.log
408 .iter()
409 .enumerate()
410 .map(|(i, e)| (first + i as i64, e))
411 .filter(|(n, _)| *n > seq)
412 .map(|(n, e)| {
413 serde_json::json!({ "seq": n, "minute": e.minute, "text": e.text, "kind": e.kind.as_str() })
414 })
415 .collect(),
416 )
417}
418
419#[cfg(test)]
420#[path = "frame_tests.rs"]
421mod tests;