mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 20:29:04 +03:00
writing savestates in a consistent place
This commit is contained in:
parent
6eb935a88d
commit
269235d1e1
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,7 +4,6 @@
|
||||
|
||||
editor/editor_state
|
||||
editor/road_edits.json
|
||||
editor/sim_state
|
||||
headless/sim_state
|
||||
data/*.abst
|
||||
data/input/*
|
||||
data/save/*
|
||||
|
@ -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?
|
||||
|
@ -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/
|
||||
|
@ -65,6 +65,10 @@ struct Flags {
|
||||
/// Optional savestate to load
|
||||
#[structopt(long = "load_from")]
|
||||
load_from: Option<String>,
|
||||
|
||||
/// 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,
|
||||
|
@ -21,9 +21,9 @@ pub struct 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 {
|
||||
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");
|
||||
|
@ -81,6 +81,7 @@ pub struct UI {
|
||||
impl UI {
|
||||
pub fn new(
|
||||
abst_path: &str,
|
||||
scenario_name: String,
|
||||
window_size: Size,
|
||||
rng_seed: Option<u8>,
|
||||
kml: Option<String>,
|
||||
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Map, Error> {
|
||||
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<LaneID> {
|
||||
self.get_parent(parking).find_driving_lane(parking)
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
@ -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<Time> for Tick {
|
||||
@ -148,18 +172,7 @@ impl std::ops::Sub for Tick {
|
||||
|
||||
impl std::fmt::Display for Tick {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
// 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;
|
||||
|
||||
let (hours, minutes, seconds, remainder) = self.get_parts();
|
||||
write!(
|
||||
f,
|
||||
"{0:02}:{1:02}:{2:02}.{3}",
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
use abstutil;
|
||||
use control::ControlMap;
|
||||
use dimensioned::si;
|
||||
use draw_car::DrawCar;
|
||||
@ -11,6 +12,7 @@ use map_model::{IntersectionID, LaneID, LaneType, Map, Turn, TurnID};
|
||||
use parking::ParkingSimState;
|
||||
use rand::{FromEntropy, SeedableRng, XorShiftRng};
|
||||
use spawn::Spawner;
|
||||
use std;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::f64;
|
||||
use std::time::{Duration, Instant};
|
||||
@ -25,6 +27,8 @@ pub struct Sim {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
rng: XorShiftRng,
|
||||
pub time: Tick,
|
||||
map_name: String,
|
||||
scenario_name: String,
|
||||
|
||||
spawner: Spawner,
|
||||
intersection_state: IntersectionSimState,
|
||||
@ -36,7 +40,7 @@ pub struct 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();
|
||||
if let Some(seed) = rng_seed {
|
||||
rng = XorShiftRng::from_seed([seed; 16]);
|
||||
@ -50,6 +54,8 @@ impl Sim {
|
||||
parking_state: ParkingSimState::new(map),
|
||||
walking_state: WalkingSimState::new(),
|
||||
time: Tick::zero(),
|
||||
map_name: map.get_name().to_string(),
|
||||
scenario_name,
|
||||
car_properties: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
@ -306,6 +312,22 @@ impl Sim {
|
||||
pub fn debug_intersection(&mut self, id: IntersectionID, control_map: &ControlMap) {
|
||||
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 {
|
||||
|
@ -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 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_walking_trips(&map, spawn_count);
|
||||
sim.seed_driving_trips(&map, spawn_count);
|
||||
|
@ -12,7 +12,7 @@ fn serialization() {
|
||||
|
||||
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_walking_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 control_map = control::ControlMap::new(&map);
|
||||
|
||||
let mut sim1 = sim::Sim::new(&map, Some(rng_seed));
|
||||
let mut sim2 = 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, "from_scratch_2".to_string(), Some(rng_seed));
|
||||
sim1.seed_parked_cars(0.5);
|
||||
sim1.seed_walking_trips(&map, spawn_count);
|
||||
sim1.seed_driving_trips(&map, spawn_count);
|
||||
@ -45,13 +45,11 @@ fn from_scratch() {
|
||||
|
||||
for _ in 1..600 {
|
||||
if sim1 != sim2 {
|
||||
// TODO write to temporary files somewhere
|
||||
// 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!(
|
||||
"sim state differs at {}. compare sim1_state.json and sim2_state.json",
|
||||
sim1.time
|
||||
"sim state differs between {} and {}",
|
||||
sim1.save(),
|
||||
sim2.save()
|
||||
);
|
||||
}
|
||||
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 control_map = control::ControlMap::new(&map);
|
||||
|
||||
let mut sim1 = sim::Sim::new(&map, Some(rng_seed));
|
||||
let mut sim2 = 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, "with_savestating_2".to_string(), Some(rng_seed));
|
||||
sim1.seed_parked_cars(0.5);
|
||||
sim1.seed_walking_trips(&map, spawn_count);
|
||||
sim1.seed_driving_trips(&map, spawn_count);
|
||||
@ -85,38 +83,35 @@ fn with_savestating() {
|
||||
}
|
||||
|
||||
if sim1 != sim2 {
|
||||
abstutil::write_json("sim1_state.json", &sim1).unwrap();
|
||||
abstutil::write_json("sim2_state.json", &sim2).unwrap();
|
||||
panic!(
|
||||
"sim state differs at {}. compare sim1_state.json and sim2_state.json",
|
||||
sim1.time
|
||||
"sim state differs between {} and {}",
|
||||
sim1.save(),
|
||||
sim2.save()
|
||||
);
|
||||
}
|
||||
|
||||
abstutil::write_json("sim1_savestate.json", &sim1).unwrap();
|
||||
let sim1_save = sim1.save();
|
||||
|
||||
for _ in 1..60 {
|
||||
sim1.step(&map, &control_map);
|
||||
}
|
||||
|
||||
if sim1 == sim2 {
|
||||
abstutil::write_json("sim1_state.json", &sim1).unwrap();
|
||||
abstutil::write_json("sim2_state.json", &sim2).unwrap();
|
||||
panic!(
|
||||
"sim state unexpectedly the same at {}. compare sim1_state.json and sim2_state.json",
|
||||
sim1.time
|
||||
"sim state unexpectly the same -- {} and {}",
|
||||
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 {
|
||||
abstutil::write_json("sim3_state.json", &sim3).unwrap();
|
||||
abstutil::write_json("sim2_state.json", &sim2).unwrap();
|
||||
panic!(
|
||||
"sim state differs at {}. compare sim3_state.json and sim2_state.json",
|
||||
sim1.time
|
||||
"sim state differs between {} and {}",
|
||||
sim3.save(),
|
||||
sim2.save()
|
||||
);
|
||||
}
|
||||
|
||||
std::fs::remove_file("sim1_savestate.json").unwrap();
|
||||
std::fs::remove_file(sim1_save).unwrap();
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use map_model::LaneID;
|
||||
|
||||
#[test]
|
||||
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));
|
||||
|
||||
assert_eq!(map.get_l(parking1).number_parking_spots(), 8);
|
||||
@ -37,7 +37,7 @@ fn park_on_goal_st() {
|
||||
|
||||
#[test]
|
||||
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));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
|
||||
@ -83,6 +86,7 @@ fn make_test_map() -> map_model::Map {
|
||||
let south_pts = triangle_around(150.0, 90.0);
|
||||
|
||||
let map = map_model::Map::create_from_raw(
|
||||
"test_map".to_string(),
|
||||
raw_data::Map {
|
||||
roads: vec![raw_data::Road {
|
||||
points: vec![left, right],
|
||||
|
Loading…
Reference in New Issue
Block a user