writing savestates in a consistent place

This commit is contained in:
Dustin Carlino 2018-08-27 10:18:08 -07:00
parent 6eb935a88d
commit 269235d1e1
13 changed files with 137 additions and 55 deletions

3
.gitignore vendored
View File

@ -4,7 +4,6 @@
editor/editor_state editor/editor_state
editor/road_edits.json editor/road_edits.json
editor/sim_state
headless/sim_state
data/*.abst data/*.abst
data/input/* data/input/*
data/save/*

View File

@ -604,3 +604,25 @@ SidewalkSpot
## Stop sign priority ## 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. 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?

View File

@ -3,3 +3,4 @@
## Groups that may be eventually interested ## Groups that may be eventually interested
- Seattle Times Traffic Lab - Seattle Times Traffic Lab
- https://www.citylab.com/transportation/2018/08/is-it-time-to-rethink-what-a-bike-lane-is/568483/

View File

@ -65,6 +65,10 @@ struct Flags {
/// Optional savestate to load /// Optional savestate to load
#[structopt(long = "load_from")] #[structopt(long = "load_from")]
load_from: Option<String>, load_from: Option<String>,
/// Scenario name for savestating
#[structopt(long = "scenario_name", default_value = "editor")]
scenario_name: String,
} }
fn main() { fn main() {
@ -106,6 +110,7 @@ fn main() {
glyphs, glyphs,
ui::UI::new( ui::UI::new(
&flags.abst_input, &flags.abst_input,
flags.scenario_name,
window_size, window_size,
flags.rng_seed, flags.rng_seed,
flags.kml, flags.kml,

View File

@ -21,9 +21,9 @@ pub struct SimController {
} }
impl SimController { impl SimController {
pub fn new(map: &Map, rng_seed: Option<u8>) -> SimController { pub fn new(map: &Map, scenario_name: String, rng_seed: Option<u8>) -> SimController {
SimController { SimController {
sim: Sim::new(map, rng_seed), sim: Sim::new(map, scenario_name, rng_seed),
desired_speed: 1.0, desired_speed: 1.0,
last_step: None, last_step: None,
benchmark: None, benchmark: None,
@ -41,8 +41,7 @@ impl SimController {
self.desired_speed += ADJUST_SPEED; self.desired_speed += ADJUST_SPEED;
} }
if input.unimportant_key_pressed(Key::O, "save sim state") { if input.unimportant_key_pressed(Key::O, "save sim state") {
abstutil::write_json("sim_state", &self.sim).expect("Writing sim state failed"); self.sim.save();
println!("Wrote sim_state");
} }
if input.unimportant_key_pressed(Key::P, "load sim state") { if input.unimportant_key_pressed(Key::P, "load sim state") {
self.sim = abstutil::read_json("sim_state").expect("sim state failed"); self.sim = abstutil::read_json("sim_state").expect("sim state failed");

View File

@ -81,6 +81,7 @@ pub struct UI {
impl UI { impl UI {
pub fn new( pub fn new(
abst_path: &str, abst_path: &str,
scenario_name: String,
window_size: Size, window_size: Size,
rng_seed: Option<u8>, rng_seed: Option<u8>,
kml: Option<String>, kml: Option<String>,
@ -102,7 +103,7 @@ impl UI {
let steepness_viz = SteepnessVisualizer::new(&map); let steepness_viz = SteepnessVisualizer::new(&map);
let turn_colors = TurnColors::new(&control_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 { if let Some(path) = load_sim_from {
sim_ctrl.sim = abstutil::read_json(&path).expect("loading sim state failed"); sim_ctrl.sim = abstutil::read_json(&path).expect("loading sim state failed");
println!("Loaded {}", path); println!("Loaded {}", path);

View File

@ -31,6 +31,10 @@ struct Flags {
/// Big or large random scenario? /// Big or large random scenario?
#[structopt(long = "big_sim")] #[structopt(long = "big_sim")]
big_sim: bool, big_sim: bool,
/// Scenario name for savestating
#[structopt(long = "scenario_name", default_value = "editor")]
scenario_name: String,
} }
fn main() { fn main() {
@ -41,7 +45,7 @@ fn main() {
.expect("Couldn't load map"); .expect("Couldn't load map");
// TODO could load savestate // TODO could load savestate
let control_map = control::ControlMap::new(&map); 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 { if let Some(path) = flags.load_from {
sim = abstutil::read_json(&path).expect("loading sim state failed"); 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); println!("{0}, speed = {1:.2}x", sim.summary(), speed);
} }
if Some(sim.time) == save_at { if Some(sim.time) == save_at {
abstutil::write_json("sim_state", &sim).expect("Writing sim state failed"); sim.save();
println!("Wrote sim_state at {}", sim.time);
} }
} }
} }

View File

@ -13,6 +13,7 @@ use raw_data;
use road::{Road, RoadID}; use road::{Road, RoadID};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::io::Error; use std::io::Error;
use std::path;
use turn::{Turn, TurnID}; use turn::{Turn, TurnID};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -26,17 +27,29 @@ pub struct Map {
// TODO maybe dont need to retain GPS stuff later // TODO maybe dont need to retain GPS stuff later
bounds: Bounds, bounds: Bounds,
name: String,
} }
impl Map { impl Map {
pub fn new(path: &str, edits: &Edits) -> Result<Map, Error> { pub fn new(path: &str, edits: &Edits) -> Result<Map, Error> {
let data: raw_data::Map = abstutil::read_binary(path)?; 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 bounds = data.get_gps_bounds();
let mut m = Map { let mut m = Map {
name,
bounds, bounds,
roads: Vec::new(), roads: Vec::new(),
lanes: Vec::new(), lanes: Vec::new(),
@ -339,4 +352,8 @@ impl Map {
pub fn get_driving_lane_from_parking(&self, parking: LaneID) -> Option<LaneID> { pub fn get_driving_lane_from_parking(&self, parking: LaneID) -> Option<LaneID> {
self.get_parent(parking).find_driving_lane(parking) self.get_parent(parking).find_driving_lane(parking)
} }
pub fn get_name(&self) -> &String {
&self.name
}
} }

View File

@ -126,6 +126,30 @@ impl Tick {
pub fn is_multiple_of_minute(&self) -> bool { pub fn is_multiple_of_minute(&self) -> bool {
self.0 % 600 == 0 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<Time> for Tick { impl std::ops::Add<Time> for Tick {
@ -148,18 +172,7 @@ impl std::ops::Sub for Tick {
impl std::fmt::Display for Tick { impl std::fmt::Display for Tick {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// TODO hardcoding these to avoid floating point issues... urgh. :\ let (hours, minutes, seconds, remainder) = self.get_parts();
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;
write!( write!(
f, f,
"{0:02}:{1:02}:{2:02}.{3}", "{0:02}:{1:02}:{2:02}.{3}",

View File

@ -1,5 +1,6 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use abstutil;
use control::ControlMap; use control::ControlMap;
use dimensioned::si; use dimensioned::si;
use draw_car::DrawCar; use draw_car::DrawCar;
@ -11,6 +12,7 @@ use map_model::{IntersectionID, LaneID, LaneType, Map, Turn, TurnID};
use parking::ParkingSimState; use parking::ParkingSimState;
use rand::{FromEntropy, SeedableRng, XorShiftRng}; use rand::{FromEntropy, SeedableRng, XorShiftRng};
use spawn::Spawner; use spawn::Spawner;
use std;
use std::collections::{BTreeMap, HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::f64; use std::f64;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -25,6 +27,8 @@ pub struct Sim {
#[derivative(PartialEq = "ignore")] #[derivative(PartialEq = "ignore")]
rng: XorShiftRng, rng: XorShiftRng,
pub time: Tick, pub time: Tick,
map_name: String,
scenario_name: String,
spawner: Spawner, spawner: Spawner,
intersection_state: IntersectionSimState, intersection_state: IntersectionSimState,
@ -36,7 +40,7 @@ pub struct Sim {
} }
impl Sim { impl Sim {
pub fn new(map: &Map, rng_seed: Option<u8>) -> Sim { pub fn new(map: &Map, scenario_name: String, rng_seed: Option<u8>) -> Sim {
let mut rng = XorShiftRng::from_entropy(); let mut rng = XorShiftRng::from_entropy();
if let Some(seed) = rng_seed { if let Some(seed) = rng_seed {
rng = XorShiftRng::from_seed([seed; 16]); rng = XorShiftRng::from_seed([seed; 16]);
@ -50,6 +54,8 @@ impl Sim {
parking_state: ParkingSimState::new(map), parking_state: ParkingSimState::new(map),
walking_state: WalkingSimState::new(), walking_state: WalkingSimState::new(),
time: Tick::zero(), time: Tick::zero(),
map_name: map.get_name().to_string(),
scenario_name,
car_properties: BTreeMap::new(), car_properties: BTreeMap::new(),
} }
} }
@ -306,6 +312,22 @@ impl Sim {
pub fn debug_intersection(&mut self, id: IntersectionID, control_map: &ControlMap) { pub fn debug_intersection(&mut self, id: IntersectionID, control_map: &ControlMap) {
self.intersection_state.debug(id, control_map); self.intersection_state.debug(id, control_map);
} }
pub fn save(&self) -> String {
// If we wanted to be even more reproducible, we'd encode RNG seed, version of code, etc,
// but that's overkill right now.
let path = format!(
"../data/save/{}/{}/{}",
self.map_name,
self.scenario_name,
self.time.as_filename()
);
std::fs::create_dir_all(std::path::Path::new(&path).parent().unwrap())
.expect("Creating parent dir failed");
abstutil::write_json(&path, &self).expect("Writing sim state failed");
println!("Saved to {}", path);
path
}
} }
pub struct Benchmark { pub struct Benchmark {

View File

@ -13,7 +13,8 @@ fn aorta_model_completes() {
let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map"); let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map");
let control_map = control::ControlMap::new(&map); let control_map = control::ControlMap::new(&map);
let mut sim = sim::Sim::new(&map, Some(rng_seed)); // TODO need https://github.com/rust-lang/rfcs/issues/1743 to be less repetitive :(
let mut sim = sim::Sim::new(&map, "aorta_model_completes".to_string(), Some(rng_seed));
sim.seed_parked_cars(0.5); sim.seed_parked_cars(0.5);
sim.seed_walking_trips(&map, spawn_count); sim.seed_walking_trips(&map, spawn_count);
sim.seed_driving_trips(&map, spawn_count); sim.seed_driving_trips(&map, spawn_count);

View File

@ -12,7 +12,7 @@ fn serialization() {
let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map"); let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map");
let mut sim = sim::Sim::new(&map, Some(rng_seed)); let mut sim = sim::Sim::new(&map, "serialization".to_string(), Some(rng_seed));
sim.seed_parked_cars(0.5); sim.seed_parked_cars(0.5);
sim.seed_walking_trips(&map, spawn_count); sim.seed_walking_trips(&map, spawn_count);
sim.seed_driving_trips(&map, spawn_count); sim.seed_driving_trips(&map, spawn_count);
@ -34,8 +34,8 @@ fn from_scratch() {
let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map"); let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map");
let control_map = control::ControlMap::new(&map); let control_map = control::ControlMap::new(&map);
let mut sim1 = sim::Sim::new(&map, Some(rng_seed)); let mut sim1 = sim::Sim::new(&map, "from_scratch_1".to_string(), Some(rng_seed));
let mut sim2 = sim::Sim::new(&map, Some(rng_seed)); let mut sim2 = sim::Sim::new(&map, "from_scratch_2".to_string(), Some(rng_seed));
sim1.seed_parked_cars(0.5); sim1.seed_parked_cars(0.5);
sim1.seed_walking_trips(&map, spawn_count); sim1.seed_walking_trips(&map, spawn_count);
sim1.seed_driving_trips(&map, spawn_count); sim1.seed_driving_trips(&map, spawn_count);
@ -45,13 +45,11 @@ fn from_scratch() {
for _ in 1..600 { for _ in 1..600 {
if sim1 != sim2 { if sim1 != sim2 {
// TODO write to temporary files somewhere
// TODO need to sort dicts in json output to compare // TODO need to sort dicts in json output to compare
abstutil::write_json("sim1_state.json", &sim1).unwrap();
abstutil::write_json("sim2_state.json", &sim2).unwrap();
panic!( panic!(
"sim state differs at {}. compare sim1_state.json and sim2_state.json", "sim state differs between {} and {}",
sim1.time sim1.save(),
sim2.save()
); );
} }
sim1.step(&map, &control_map); sim1.step(&map, &control_map);
@ -70,8 +68,8 @@ fn with_savestating() {
let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map"); let map = map_model::Map::new(input, &map_model::Edits::new()).expect("Couldn't load map");
let control_map = control::ControlMap::new(&map); let control_map = control::ControlMap::new(&map);
let mut sim1 = sim::Sim::new(&map, Some(rng_seed)); let mut sim1 = sim::Sim::new(&map, "with_savestating_1".to_string(), Some(rng_seed));
let mut sim2 = sim::Sim::new(&map, Some(rng_seed)); let mut sim2 = sim::Sim::new(&map, "with_savestating_2".to_string(), Some(rng_seed));
sim1.seed_parked_cars(0.5); sim1.seed_parked_cars(0.5);
sim1.seed_walking_trips(&map, spawn_count); sim1.seed_walking_trips(&map, spawn_count);
sim1.seed_driving_trips(&map, spawn_count); sim1.seed_driving_trips(&map, spawn_count);
@ -85,38 +83,35 @@ fn with_savestating() {
} }
if sim1 != sim2 { if sim1 != sim2 {
abstutil::write_json("sim1_state.json", &sim1).unwrap();
abstutil::write_json("sim2_state.json", &sim2).unwrap();
panic!( panic!(
"sim state differs at {}. compare sim1_state.json and sim2_state.json", "sim state differs between {} and {}",
sim1.time sim1.save(),
sim2.save()
); );
} }
abstutil::write_json("sim1_savestate.json", &sim1).unwrap(); let sim1_save = sim1.save();
for _ in 1..60 { for _ in 1..60 {
sim1.step(&map, &control_map); sim1.step(&map, &control_map);
} }
if sim1 == sim2 { if sim1 == sim2 {
abstutil::write_json("sim1_state.json", &sim1).unwrap();
abstutil::write_json("sim2_state.json", &sim2).unwrap();
panic!( panic!(
"sim state unexpectedly the same at {}. compare sim1_state.json and sim2_state.json", "sim state unexpectly the same -- {} and {}",
sim1.time sim1.save(),
sim2.save()
); );
} }
let sim3: sim::Sim = abstutil::read_json("sim1_savestate.json").unwrap(); let sim3: sim::Sim = abstutil::read_json(&sim1_save).unwrap();
if sim3 != sim2 { if sim3 != sim2 {
abstutil::write_json("sim3_state.json", &sim3).unwrap();
abstutil::write_json("sim2_state.json", &sim2).unwrap();
panic!( panic!(
"sim state differs at {}. compare sim3_state.json and sim2_state.json", "sim state differs between {} and {}",
sim1.time sim3.save(),
sim2.save()
); );
} }
std::fs::remove_file("sim1_savestate.json").unwrap(); std::fs::remove_file(sim1_save).unwrap();
} }

View File

@ -10,7 +10,7 @@ use map_model::LaneID;
#[test] #[test]
fn park_on_goal_st() { fn park_on_goal_st() {
let (map, control_map, mut sim) = setup(make_test_map()); let (map, control_map, mut sim) = setup("park_on_goal_st", make_test_map());
let (parking1, parking2, driving2) = (LaneID(1), LaneID(4), LaneID(3)); let (parking1, parking2, driving2) = (LaneID(1), LaneID(4), LaneID(3));
assert_eq!(map.get_l(parking1).number_parking_spots(), 8); assert_eq!(map.get_l(parking1).number_parking_spots(), 8);
@ -37,7 +37,7 @@ fn park_on_goal_st() {
#[test] #[test]
fn wander_around_for_parking() { fn wander_around_for_parking() {
let (map, control_map, mut sim) = setup(make_test_map()); let (map, control_map, mut sim) = setup("wander_around_for_parking", make_test_map());
let (parking1, parking2, driving2) = (LaneID(1), LaneID(4), LaneID(3)); let (parking1, parking2, driving2) = (LaneID(1), LaneID(4), LaneID(3));
assert_eq!(map.get_l(parking1).number_parking_spots(), 8); assert_eq!(map.get_l(parking1).number_parking_spots(), 8);
@ -63,10 +63,13 @@ fn wander_around_for_parking() {
println!("Expected conditions met at {}", sim.time); println!("Expected conditions met at {}", sim.time);
} }
fn setup(map: map_model::Map) -> (map_model::Map, control::ControlMap, sim::Sim) { fn setup(
scenario_name: &str,
map: map_model::Map,
) -> (map_model::Map, control::ControlMap, sim::Sim) {
let rng_seed = 123; let rng_seed = 123;
let control_map = control::ControlMap::new(&map); let control_map = control::ControlMap::new(&map);
let sim = sim::Sim::new(&map, Some(rng_seed)); let sim = sim::Sim::new(&map, scenario_name.to_string(), Some(rng_seed));
(map, control_map, sim) (map, control_map, sim)
} }
@ -83,6 +86,7 @@ fn make_test_map() -> map_model::Map {
let south_pts = triangle_around(150.0, 90.0); let south_pts = triangle_around(150.0, 90.0);
let map = map_model::Map::create_from_raw( let map = map_model::Map::create_from_raw(
"test_map".to_string(),
raw_data::Map { raw_data::Map {
roads: vec![raw_data::Road { roads: vec![raw_data::Road {
points: vec![left, right], points: vec![left, right],