diff --git a/.gitignore b/.gitignore index cf82c386a9..a3edd0bc01 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ editor/editor_state editor/road_edits.json -editor/sim_state -headless/sim_state data/*.abst data/input/* +data/save/* diff --git a/docs/design.md b/docs/design.md index 17682ed11f..602401eba6 100644 --- a/docs/design.md +++ b/docs/design.md @@ -604,3 +604,25 @@ SidewalkSpot ## Stop sign priority Use OSM highway tags to rank. For all the turns on the higher priority road, detect priority/yield based on turn angle, I guess. + +## Watch tests easily + +- need to organize savestate captures + - dedicated place: data/savestates/MAP/scenario/time + - plumb map name, scenario name + - should be able to just point to one of these saves, not refer to the map or RNG seed again + - also kinda needed for time traveling later + +- when a problem happens, we want to back up a little bit + - probably just need automatic occasional savestating, and to print a nice command to rerun from it + +## Diffing for A/B tests + +Basic problem: how do we show map edits/diffs? + - could be useful for debugging as new data sources come in + - and is vital part of the game + - UI + - highlight edited things + - hold a button to show the original versions of things in a transparentish overlay + +How to show diffs for agents? diff --git a/docs/references.md b/docs/references.md index 5306af69f2..43650182ce 100644 --- a/docs/references.md +++ b/docs/references.md @@ -3,3 +3,4 @@ ## Groups that may be eventually interested - Seattle Times Traffic Lab +- https://www.citylab.com/transportation/2018/08/is-it-time-to-rethink-what-a-bike-lane-is/568483/ diff --git a/editor/src/main.rs b/editor/src/main.rs index bf2ad5a386..cc7eb5a0e3 100644 --- a/editor/src/main.rs +++ b/editor/src/main.rs @@ -65,6 +65,10 @@ struct Flags { /// Optional savestate to load #[structopt(long = "load_from")] load_from: Option, + + /// Scenario name for savestating + #[structopt(long = "scenario_name", default_value = "editor")] + scenario_name: String, } fn main() { @@ -106,6 +110,7 @@ fn main() { glyphs, ui::UI::new( &flags.abst_input, + flags.scenario_name, window_size, flags.rng_seed, flags.kml, diff --git a/editor/src/plugins/sim_controls.rs b/editor/src/plugins/sim_controls.rs index 9ae2f15b0d..8a371a7648 100644 --- a/editor/src/plugins/sim_controls.rs +++ b/editor/src/plugins/sim_controls.rs @@ -21,9 +21,9 @@ pub struct SimController { } impl SimController { - pub fn new(map: &Map, rng_seed: Option) -> SimController { + pub fn new(map: &Map, scenario_name: String, rng_seed: Option) -> SimController { SimController { - sim: Sim::new(map, rng_seed), + sim: Sim::new(map, scenario_name, rng_seed), desired_speed: 1.0, last_step: None, benchmark: None, @@ -41,8 +41,7 @@ impl SimController { self.desired_speed += ADJUST_SPEED; } if input.unimportant_key_pressed(Key::O, "save sim state") { - abstutil::write_json("sim_state", &self.sim).expect("Writing sim state failed"); - println!("Wrote sim_state"); + self.sim.save(); } if input.unimportant_key_pressed(Key::P, "load sim state") { self.sim = abstutil::read_json("sim_state").expect("sim state failed"); diff --git a/editor/src/ui.rs b/editor/src/ui.rs index 12ca981f85..a4ddc95abd 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -81,6 +81,7 @@ pub struct UI { impl UI { pub fn new( abst_path: &str, + scenario_name: String, window_size: Size, rng_seed: Option, kml: Option, @@ -102,7 +103,7 @@ impl UI { let steepness_viz = SteepnessVisualizer::new(&map); let turn_colors = TurnColors::new(&control_map); - let mut sim_ctrl = SimController::new(&map, rng_seed); + let mut sim_ctrl = SimController::new(&map, scenario_name, rng_seed); if let Some(path) = load_sim_from { sim_ctrl.sim = abstutil::read_json(&path).expect("loading sim state failed"); println!("Loaded {}", path); diff --git a/headless/src/main.rs b/headless/src/main.rs index 2c7c447895..5e6bf04ffd 100644 --- a/headless/src/main.rs +++ b/headless/src/main.rs @@ -31,6 +31,10 @@ struct Flags { /// Big or large random scenario? #[structopt(long = "big_sim")] big_sim: bool, + + /// Scenario name for savestating + #[structopt(long = "scenario_name", default_value = "editor")] + scenario_name: String, } fn main() { @@ -41,7 +45,7 @@ fn main() { .expect("Couldn't load map"); // TODO could load savestate let control_map = control::ControlMap::new(&map); - let mut sim = sim::Sim::new(&map, flags.rng_seed); + let mut sim = sim::Sim::new(&map, flags.scenario_name, flags.rng_seed); if let Some(path) = flags.load_from { sim = abstutil::read_json(&path).expect("loading sim state failed"); @@ -77,8 +81,7 @@ fn main() { println!("{0}, speed = {1:.2}x", sim.summary(), speed); } if Some(sim.time) == save_at { - abstutil::write_json("sim_state", &sim).expect("Writing sim state failed"); - println!("Wrote sim_state at {}", sim.time); + sim.save(); } } } diff --git a/map_model/src/map.rs b/map_model/src/map.rs index 781d22b1bc..c697bc0497 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -13,6 +13,7 @@ use raw_data; use road::{Road, RoadID}; use std::collections::{BTreeMap, HashMap}; use std::io::Error; +use std::path; use turn::{Turn, TurnID}; #[derive(Serialize, Deserialize, Debug)] @@ -26,17 +27,29 @@ pub struct Map { // TODO maybe dont need to retain GPS stuff later bounds: Bounds, + + name: String, } impl Map { pub fn new(path: &str, edits: &Edits) -> Result { let data: raw_data::Map = abstutil::read_binary(path)?; - Ok(Map::create_from_raw(data, edits)) + Ok(Map::create_from_raw( + path::Path::new(path) + .file_stem() + .unwrap() + .to_os_string() + .into_string() + .unwrap(), + data, + edits, + )) } - pub fn create_from_raw(data: raw_data::Map, edits: &Edits) -> Map { + pub fn create_from_raw(name: String, data: raw_data::Map, edits: &Edits) -> Map { let bounds = data.get_gps_bounds(); let mut m = Map { + name, bounds, roads: Vec::new(), lanes: Vec::new(), @@ -339,4 +352,8 @@ impl Map { pub fn get_driving_lane_from_parking(&self, parking: LaneID) -> Option { self.get_parent(parking).find_driving_lane(parking) } + + pub fn get_name(&self) -> &String { + &self.name + } } diff --git a/sim/src/lib.rs b/sim/src/lib.rs index b93babe966..892f1b318e 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -126,6 +126,30 @@ impl Tick { pub fn is_multiple_of_minute(&self) -> bool { self.0 % 600 == 0 } + + fn get_parts(&self) -> (u32, u32, u32, u32) { + // TODO hardcoding these to avoid floating point issues... urgh. :\ + let ticks_per_second = 10; + let ticks_per_minute = 60 * ticks_per_second; + let ticks_per_hour = 60 * ticks_per_minute; + + let hours = self.0 / ticks_per_hour; + let mut remainder = self.0 % ticks_per_hour; + let minutes = remainder / ticks_per_minute; + remainder = remainder % ticks_per_minute; + let seconds = remainder / ticks_per_second; + remainder = remainder % ticks_per_second; + + (hours, minutes, seconds, remainder) + } + + pub fn as_filename(&self) -> String { + let (hours, minutes, seconds, remainder) = self.get_parts(); + format!( + "{0:02}h{1:02}m{2:02}.{3}s", + hours, minutes, seconds, remainder + ) + } } impl std::ops::Add