dont serialize paths of yet-to-be-spawned stuff. drastically reduces savestate size.

This commit is contained in:
Dustin Carlino 2019-12-17 18:08:59 -08:00
parent a1ff95e80a
commit 959a89e81c
11 changed files with 218 additions and 107 deletions

View File

@ -111,7 +111,7 @@ impl State for DebugMode {
.find_previous_savestate(ui.primary.sim.time());
match prev_state
.clone()
.and_then(|path| Sim::load_savestate(path, &mut timer).ok())
.and_then(|path| Sim::load_savestate(path, &ui.primary.map, &mut timer).ok())
{
Some(new_sim) => {
ui.primary.sim = new_sim;
@ -132,7 +132,7 @@ impl State for DebugMode {
let next_state = ui.primary.sim.find_next_savestate(ui.primary.sim.time());
match next_state
.clone()
.and_then(|path| Sim::load_savestate(path, &mut timer).ok())
.and_then(|path| Sim::load_savestate(path, &ui.primary.map, &mut timer).ok())
{
Some(new_sim) => {
ui.primary.sim = new_sim;
@ -401,7 +401,8 @@ fn load_savestate(wiz: &mut Wizard, ctx: &mut EventCtx, ui: &mut UI) -> Option<T
let ss_path = format!("{}/{}.bin", ui.primary.sim.save_dir(), ss);
ctx.loading_screen("load savestate", |ctx, mut timer| {
ui.primary.sim = Sim::load_savestate(ss_path, &mut timer).expect("Can't load savestate");
ui.primary.sim = Sim::load_savestate(ss_path, &ui.primary.map, &mut timer)
.expect("Can't load savestate");
ui.recalculate_current_selection(ctx);
});
Some(Transition::Pop)

View File

@ -116,6 +116,16 @@ impl Path {
}
}
// Only used for weird serialization magic.
pub fn dummy() -> Path {
Path {
steps: VecDeque::new(),
end_dist: Distance::ZERO,
total_length: Distance::ZERO,
crossed_so_far: Distance::ZERO,
}
}
pub fn num_lanes(&self) -> usize {
let mut count = 0;
for s in &self.steps {

View File

@ -22,6 +22,11 @@ pub struct Analytics {
// TODO This subsumes finished_trips
pub trip_log: Vec<(Time, TripID, Option<PathRequest>, String)>,
pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration)>>,
// After we restore from a savestate, don't record anything. This is only going to make sense
// if savestates are only used for quickly previewing against prebaked results, where we have
// the full Analytics anyway.
record_anything: bool,
}
#[derive(Serialize, Deserialize, Derivative)]
@ -54,10 +59,15 @@ impl Analytics {
finished_trips: Vec::new(),
trip_log: Vec::new(),
intersection_delays: BTreeMap::new(),
record_anything: true,
}
}
pub fn event(&mut self, ev: Event, time: Time, map: &Map) {
if !self.record_anything {
return;
}
// TODO Plumb a flag
let raw_thruput = true;
@ -480,7 +490,9 @@ impl Analytics {
impl Default for Analytics {
fn default() -> Analytics {
Analytics::new()
let mut a = Analytics::new();
a.record_anything = false;
a
}
}

View File

@ -33,7 +33,7 @@ use abstutil::Cloneable;
use geom::{Distance, Pt2D, Speed, Time};
use map_model::{
BuildingID, BusStopID, DirectedRoadID, IntersectionID, LaneID, Map, Path, PathConstraints,
Position,
PathRequest, Position,
};
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
@ -453,6 +453,7 @@ pub struct CreatePedestrian {
pub start: SidewalkSpot,
pub speed: Speed,
pub goal: SidewalkSpot,
pub req: PathRequest,
pub path: Path,
pub trip: TripID,
}
@ -461,6 +462,7 @@ pub struct CreatePedestrian {
pub struct CreateCar {
pub vehicle: Vehicle,
pub router: Router,
pub req: PathRequest,
pub start_dist: Distance,
pub maybe_parked_car: Option<ParkedCar>,
pub trip: TripID,
@ -471,11 +473,13 @@ impl CreateCar {
vehicle: Vehicle,
start_pos: Position,
router: Router,
req: PathRequest,
trip: TripID,
) -> CreateCar {
CreateCar {
vehicle,
router,
req,
start_dist: start_pos.dist_along(),
maybe_parked_car: None,
trip,
@ -486,12 +490,14 @@ impl CreateCar {
pub fn for_parked_car(
parked_car: ParkedCar,
router: Router,
req: PathRequest,
start_dist: Distance,
trip: TripID,
) -> CreateCar {
CreateCar {
vehicle: parked_car.vehicle.clone(),
router,
req,
start_dist,
maybe_parked_car: Some(parked_car),
trip,

View File

@ -61,18 +61,21 @@ impl SimFlags {
let mut opts = self.opts.clone();
if self.load.starts_with("../data/player/save/") {
if self.load.starts_with("../data/player/saves/") {
timer.note(format!("Resuming from {}", self.load));
let sim: Sim = abstutil::read_binary(self.load.clone(), timer);
let mut sim: Sim = abstutil::read_binary(self.load.clone(), timer);
let mut map = Map::new(abstutil::path_map(&sim.map_name), false, timer);
map.apply_edits(
MapEdits::load(map.get_name(), &sim.edits_name, timer),
timer,
);
map.mark_edits_fresh();
map.recalculate_pathfinding_after_edits(timer);
if sim.edits_name != "no_edits" {
map.apply_edits(
MapEdits::load(map.get_name(), &sim.edits_name, timer),
timer,
);
map.mark_edits_fresh();
map.recalculate_pathfinding_after_edits(timer);
}
sim.restore_paths(&map, timer);
(map, sim, rng)
} else if self.load.starts_with("../data/system/scenarios/") {

View File

@ -231,8 +231,7 @@ impl TripSpawner {
scheduler.quick_push(
start_time,
Command::SpawnCar(
CreateCar::for_appearing(vehicle, start_pos, router, trip),
req,
CreateCar::for_appearing(vehicle, start_pos, router, req, trip),
retry_if_no_room,
),
);
@ -278,17 +277,15 @@ impl TripSpawner {
if let Some(path) = maybe_path {
scheduler.quick_push(
start_time,
Command::SpawnPed(
CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal: parking_spot,
path,
trip,
},
Command::SpawnPed(CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal: parking_spot,
path,
req,
),
trip,
}),
);
} else {
timer.warn(format!(
@ -311,18 +308,16 @@ impl TripSpawner {
scheduler.quick_push(
start_time,
Command::SpawnPed(
CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start: SidewalkSpot::building(start_bldg, map),
goal: walk_to,
// This is guaranteed to work, and is junk anyway.
path: maybe_path.unwrap(),
trip,
},
Command::SpawnPed(CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start: SidewalkSpot::building(start_bldg, map),
goal: walk_to,
// This is guaranteed to work, and is junk anyway.
path: maybe_path.unwrap(),
req,
),
trip,
}),
);
}
TripSpec::JustWalking {
@ -346,17 +341,15 @@ impl TripSpawner {
if let Some(path) = maybe_path {
scheduler.quick_push(
start_time,
Command::SpawnPed(
CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal,
path,
trip,
},
Command::SpawnPed(CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal,
path,
req,
),
trip,
}),
);
} else {
timer.warn(format!(
@ -404,17 +397,15 @@ impl TripSpawner {
if let Some(path) = maybe_path {
scheduler.quick_push(
start_time,
Command::SpawnPed(
CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal: walk_to,
path,
trip,
},
Command::SpawnPed(CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal: walk_to,
path,
req,
),
trip,
}),
);
} else {
timer.warn(format!(
@ -453,17 +444,15 @@ impl TripSpawner {
if let Some(path) = maybe_path {
scheduler.quick_push(
start_time,
Command::SpawnPed(
CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal: walk_to,
path,
trip,
},
Command::SpawnPed(CreatePedestrian {
id: ped_id.unwrap(),
speed: ped_speed,
start,
goal: walk_to,
path,
req,
),
trip,
}),
);
} else {
timer.warn(format!(

View File

@ -329,6 +329,10 @@ impl Router {
self.path.modify_step(2, PathStep::Lane(best_lane), map);
self.path.modify_step(3, PathStep::Turn(turn2), map);
}
pub fn replace_path_for_serialization(&mut self, path: Path) -> Path {
std::mem::replace(&mut self.path, path)
}
}
// Unrealistically assumes the driver has knowledge of currently free parking spots, even if

View File

@ -1,7 +1,7 @@
use crate::{AgentID, CarID, CreateCar, CreatePedestrian, PedestrianID};
use derivative::Derivative;
use geom::{Duration, DurationHistogram, Time};
use map_model::{IntersectionID, PathRequest};
use map_model::{IntersectionID, Path, PathRequest};
use serde_derive::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::{BTreeMap, BinaryHeap};
@ -9,8 +9,8 @@ use std::collections::{BTreeMap, BinaryHeap};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Command {
// If true, retry when there's no room to spawn somewhere
SpawnCar(CreateCar, PathRequest, bool),
SpawnPed(CreatePedestrian, PathRequest),
SpawnCar(CreateCar, bool),
SpawnPed(CreatePedestrian),
UpdateCar(CarID),
// Distinguish this from UpdateCar to avoid confusing things
UpdateLaggyHead(CarID),
@ -29,8 +29,8 @@ impl Command {
pub fn to_type(&self) -> CommandType {
match self {
Command::SpawnCar(ref create, _, _) => CommandType::Car(create.vehicle.id),
Command::SpawnPed(ref create, _) => CommandType::Ped(create.id),
Command::SpawnCar(ref create, _) => CommandType::Car(create.vehicle.id),
Command::SpawnPed(ref create) => CommandType::Ped(create.id),
Command::UpdateCar(id) => CommandType::Car(*id),
Command::UpdateLaggyHead(id) => CommandType::CarLaggyHead(*id),
Command::UpdatePed(id) => CommandType::Ped(*id),
@ -184,4 +184,63 @@ impl Scheduler {
pub fn describe_stats(&self) -> String {
format!("delta times for events: {}", self.delta_times.describe())
}
// It's much more efficient to save without the paths, and to recalculate them when loading
// later.
// TODO Why not just implement Default on Path and use skip_serializing? Because we want to
// serialize paths inside Router for live agents. We need to defer calling make_router and just
// store the input in CreateCar.
pub fn get_requests_for_savestate(&self) -> Vec<PathRequest> {
let mut reqs = Vec::new();
for (cmd, _) in self.queued_commands.values() {
match cmd {
Command::SpawnCar(ref create_car, _) => {
reqs.push(create_car.req.clone());
}
Command::SpawnPed(ref create_ped) => {
reqs.push(create_ped.req.clone());
}
_ => {}
}
}
reqs
}
pub fn before_savestate(&mut self) -> Vec<Path> {
let mut restore = Vec::new();
for (cmd, _) in self.queued_commands.values_mut() {
match cmd {
Command::SpawnCar(ref mut create_car, _) => {
restore.push(
create_car
.router
.replace_path_for_serialization(Path::dummy()),
);
}
Command::SpawnPed(ref mut create_ped) => {
restore.push(std::mem::replace(&mut create_ped.path, Path::dummy()));
}
_ => {}
}
}
restore
}
pub fn after_savestate(&mut self, mut restore: Vec<Path>) {
restore.reverse();
for (cmd, _) in self.queued_commands.values_mut() {
match cmd {
Command::SpawnCar(ref mut create_car, _) => {
create_car
.router
.replace_path_for_serialization(restore.pop().unwrap());
}
Command::SpawnPed(ref mut create_ped) => {
std::mem::replace(&mut create_ped.path, restore.pop().unwrap());
}
_ => {}
}
}
assert!(restore.is_empty());
}
}

View File

@ -214,7 +214,7 @@ impl Sim {
// Try to spawn just ONE bus anywhere.
// TODO Be more realistic. One bus per stop is too much, one is too little.
for (next_stop_idx, mut path, end_dist) in
for (next_stop_idx, req, mut path, end_dist) in
self.transit.create_empty_route(route, map).into_iter()
{
let id = CarID(self.car_id_counter, VehicleType::Bus);
@ -251,10 +251,12 @@ impl Sim {
l
} else {
path.shift(map);
// TODO Technically should update request, but it shouldn't matter
continue;
};
if map.get_l(start_lane).length() < vehicle.length {
path.shift(map);
// TODO Technically should update request, but it shouldn't matter
continue;
}
@ -264,6 +266,7 @@ impl Sim {
CreateCar {
start_dist: vehicle.length,
vehicle: vehicle.clone(),
req: req.clone(),
router: Router::follow_bus_route(path.clone(), end_dist),
maybe_parked_car: None,
trip,
@ -377,7 +380,7 @@ impl Sim {
self.time = time;
let mut events = Vec::new();
match cmd {
Command::SpawnCar(create_car, req, retry_if_no_room) => {
Command::SpawnCar(create_car, retry_if_no_room) => {
if self.driving.start_car_on_lane(
self.time,
create_car.clone(),
@ -395,7 +398,7 @@ impl Sim {
}
events.push(Event::TripPhaseStarting(
create_car.trip,
Some(req),
Some(create_car.req.clone()),
format!("{}", create_car.vehicle.id),
));
self.analytics
@ -404,7 +407,7 @@ impl Sim {
// TODO Record this in the trip log
self.scheduler.push(
self.time + BLIND_RETRY_TO_SPAWN,
Command::SpawnCar(create_car, req, retry_if_no_room),
Command::SpawnCar(create_car, retry_if_no_room),
);
} else {
println!(
@ -414,19 +417,19 @@ impl Sim {
self.trips.abort_trip_failed_start(create_car.trip);
}
}
Command::SpawnPed(mut create_ped, mut req) => {
Command::SpawnPed(mut create_ped) => {
let ok = if let SidewalkPOI::DeferredParkingSpot(b, driving_goal) =
create_ped.goal.connection.clone()
{
if let Some(parked_car) = self.parking.dynamically_reserve_car(b) {
create_ped.goal =
SidewalkSpot::parking_spot(parked_car.spot, map, &self.parking);
req = PathRequest {
create_ped.req = PathRequest {
start: create_ped.start.sidewalk_pos,
end: create_ped.goal.sidewalk_pos,
constraints: PathConstraints::Pedestrian,
};
if let Some(path) = map.pathfind(req.clone()) {
if let Some(path) = map.pathfind(create_ped.req.clone()) {
create_ped.path = path;
let mut legs = vec![
TripLeg::Walk(
@ -478,7 +481,7 @@ impl Sim {
);
events.push(Event::TripPhaseStarting(
create_ped.trip,
Some(req),
Some(create_ped.req.clone()),
format!(
"{} from {:?} to {:?}",
create_ped.id,
@ -724,7 +727,9 @@ impl Sim {
)
}
pub fn save(&self) -> String {
pub fn save(&mut self) -> String {
let restore = self.scheduler.before_savestate();
if true {
println!("sim savestate breakdown:");
println!(
@ -763,6 +768,9 @@ impl Sim {
let path = self.save_path(self.time);
abstutil::write_binary(path.clone(), self);
self.scheduler.after_savestate(restore);
path
}
@ -774,8 +782,23 @@ impl Sim {
abstutil::find_next_file(self.save_path(base_time))
}
pub fn load_savestate(path: String, timer: &mut Timer) -> Result<Sim, std::io::Error> {
abstutil::maybe_read_binary(path, timer)
pub fn load_savestate(
path: String,
map: &Map,
timer: &mut Timer,
) -> Result<Sim, std::io::Error> {
let mut sim: Sim = abstutil::maybe_read_binary(path, timer)?;
sim.restore_paths(map, timer);
Ok(sim)
}
pub fn restore_paths(&mut self, map: &Map, timer: &mut Timer) {
let paths = timer.parallelize(
"calculate paths",
self.scheduler.get_requests_for_savestate(),
|req| map.pathfind(req).unwrap(),
);
self.scheduler.after_savestate(paths);
}
}

View File

@ -14,6 +14,7 @@ type StopIdx = usize;
struct StopForRoute {
id: BusStopID,
driving_pos: Position,
req: PathRequest,
path_to_next_stop: Path,
next_stop_idx: StopIdx,
}
@ -74,7 +75,7 @@ impl TransitSimState {
&mut self,
bus_route: &BusRoute,
map: &Map,
) -> Vec<(StopIdx, Path, Distance)> {
) -> Vec<(StopIdx, PathRequest, Path, Distance)> {
assert!(bus_route.stops.len() > 1);
let route = Route {
@ -90,19 +91,19 @@ impl TransitSimState {
} else {
idx + 1
};
let path = map
.pathfind(PathRequest {
start: stop1.driving_pos,
end: map.get_bs(bus_route.stops[stop2_idx]).driving_pos,
constraints: PathConstraints::Bus,
})
.expect(&format!(
"No route between bus stops {:?} and {:?}",
stop1_id, bus_route.stops[stop2_idx]
));
let req = PathRequest {
start: stop1.driving_pos,
end: map.get_bs(bus_route.stops[stop2_idx]).driving_pos,
constraints: PathConstraints::Bus,
};
let path = map.pathfind(req.clone()).expect(&format!(
"No route between bus stops {:?} and {:?}",
stop1_id, bus_route.stops[stop2_idx]
));
StopForRoute {
id: *stop1_id,
driving_pos: stop1.driving_pos,
req,
path_to_next_stop: path,
next_stop_idx: stop2_idx,
}
@ -116,6 +117,7 @@ impl TransitSimState {
.map(|s| {
(
s.next_stop_idx,
s.req.clone(),
s.path_to_next_stop.clone(),
route.stops[s.next_stop_idx].driving_pos.dist_along(),
)

View File

@ -212,8 +212,13 @@ impl TripManager {
scheduler.push(
now,
Command::SpawnCar(
CreateCar::for_parked_car(parked_car.clone(), router, start.dist_along(), trip.id),
req,
CreateCar::for_parked_car(
parked_car.clone(),
router,
req,
start.dist_along(),
trip.id,
),
true,
),
);
@ -266,8 +271,7 @@ impl TripManager {
scheduler.push(
now,
Command::SpawnCar(
CreateCar::for_appearing(vehicle, driving_pos, router, trip.id),
req,
CreateCar::for_appearing(vehicle, driving_pos, router, req, trip.id),
true,
),
);
@ -630,17 +634,15 @@ impl Trip {
scheduler.push(
now,
Command::SpawnPed(
CreatePedestrian {
id: ped,
speed,
start,
goal: walk_to,
path,
trip: self.id,
},
Command::SpawnPed(CreatePedestrian {
id: ped,
speed,
start,
goal: walk_to,
path,
req,
),
trip: self.id,
}),
);
true
}