make sim stepping way more precise, doing minimal amount of time

advancement in the innermost layer

tested by making sure prebaked results don't budge
This commit is contained in:
Dustin Carlino 2020-02-28 14:52:41 -08:00
parent 0385b96301
commit e5d9d80c14
11 changed files with 289 additions and 261 deletions

View File

@ -146,7 +146,9 @@ fn launch_test(test: &ABTest, ui: &mut UI, ctx: &mut EventCtx) -> ABTestMode {
ui.primary.clear_sim();
let mut rng = ui.primary.current_flags.sim_flags.make_rng();
scenario.instantiate(&mut ui.primary.sim, &ui.primary.map, &mut rng, &mut timer);
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
ui.primary
.sim
.normal_step(&ui.primary.map, Duration::seconds(0.1));
timer.stop("load primary");
}
{
@ -203,7 +205,9 @@ fn launch_test(test: &ABTest, ui: &mut UI, ctx: &mut EventCtx) -> ABTestMode {
secondary.clear_sim();
let mut rng = secondary.current_flags.sim_flags.make_rng();
scenario.instantiate(&mut secondary.sim, &secondary.map, &mut rng, &mut timer);
secondary.sim.step(&secondary.map, Duration::seconds(0.1));
secondary
.sim
.normal_step(&secondary.map, Duration::seconds(0.1));
timer.stop("load secondary");
secondary
}

View File

@ -255,7 +255,9 @@ impl State for DebugMode {
.action(ctx, Key::Backspace, "forcibly kill this car")
{
ui.primary.sim.kill_stuck_car(id, &ui.primary.map);
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
ui.primary
.sim
.normal_step(&ui.primary.map, Duration::seconds(0.1));
ui.primary.current_selection = None;
} else if ui.per_obj.action(ctx, Key::G, "find front of blockage") {
return Transition::Push(msg(

View File

@ -789,7 +789,7 @@ fn make_previewer(i: IntersectionID, phase: usize, suspended_sim: Sim) -> Box<dy
for idx in 0..phase {
step += signal.phases[idx].duration;
}
ui.primary.sim.step(&ui.primary.map, step);
ui.primary.sim.normal_step(&ui.primary.map, step);
spawn_agents_around(i, ui);
}

View File

@ -193,7 +193,9 @@ impl GameplayMode {
&mut ui.primary.current_flags.sim_flags.make_rng(),
timer,
);
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
ui.primary
.sim
.normal_step(&ui.primary.map, Duration::seconds(0.1));
// Maybe we've already got prebaked data for this map+scenario.
if !ui

View File

@ -273,7 +273,7 @@ impl State for AgentSpawner {
&mut rng,
);
sim.spawn_all_trips(map, &mut Timer::new("spawn trip"), false);
sim.step(map, SMALL_DT);
sim.normal_step(map, SMALL_DT);
ui.recalculate_current_selection(ctx);
if let Some(e) = err {
return Transition::Replace(msg("Spawning error", vec![e]));
@ -366,7 +366,7 @@ pub fn spawn_agents_around(i: IntersectionID, ui: &mut UI) {
}
sim.spawn_all_trips(map, &mut timer, false);
sim.step(map, SMALL_DT);
sim.normal_step(map, SMALL_DT);
}
// Returns optional error message

View File

@ -971,7 +971,9 @@ impl TutorialState {
ui.primary.current_flags.sim_flags.rng_seed = Some(42);
(cb)(ui);
ui.primary.current_flags.sim_flags.rng_seed = old;
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
ui.primary
.sim
.normal_step(&ui.primary.map, Duration::seconds(0.1));
}
let last_finished_task = if self.current.stage == 0 {

View File

@ -151,9 +151,11 @@ impl SpeedControls {
.cb(
"step forwards 0.1 seconds",
Box::new(|ctx, ui| {
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
ui.primary
.sim
.normal_step(&ui.primary.map, Duration::seconds(0.1));
if let Some(ref mut s) = ui.secondary {
s.sim.step(&s.map, Duration::seconds(0.1));
s.sim.normal_step(&s.map, Duration::seconds(0.1));
}
ui.recalculate_current_selection(ctx);
None
@ -495,7 +497,7 @@ impl State for TimeWarpScreen {
ui.primary.sim.time_limited_step(
&ui.primary.map,
self.target - ui.primary.sim.time(),
Duration::seconds(0.1),
Duration::seconds(0.033),
);
// TODO secondary for a/b test mode

View File

@ -175,6 +175,12 @@ impl ops::Add<Duration> for Time {
}
}
impl ops::AddAssign<Duration> for Time {
fn add_assign(&mut self, other: Duration) {
*self = *self + other;
}
}
impl ops::Sub<Duration> for Time {
type Output = Time;

View File

@ -157,28 +157,28 @@ impl Scheduler {
self.queued_commands.remove(&cmd.to_type());
}
// This next command might've actually been rescheduled to a later time; the caller won't know
// that here.
pub fn peek_next_time(&self) -> Option<Time> {
self.items.peek().as_ref().map(|cmd| cmd.time)
}
// This API is safer than handing out a batch of items at a time, because while processing one
// item, we might change the priority of other items or add new items. Don't make the caller
// reconcile those changes -- just keep pulling items from here, one at a time.
pub fn get_next(&mut self, now: Time) -> Option<(Command, Time)> {
loop {
let next_time = self.items.peek().as_ref()?.time;
if next_time > now {
return None;
}
self.latest_time = next_time;
let item = self.items.pop().unwrap();
if let Some((_, cmd_time)) = self.queued_commands.get(&item.cmd_type) {
// Command was re-scheduled for later.
if *cmd_time > next_time {
continue;
}
return self.queued_commands.remove(&item.cmd_type);
}
// If the command was outright canceled, fall-through here and pull from the queue
// again.
//
// TODO Above description is a little vague. This should be used with peek_next_time in a
// particular way...
pub fn get_next(&mut self) -> Option<Command> {
let item = self.items.pop().unwrap();
self.latest_time = item.time;
let (_, cmd_time) = self.queued_commands.get(&item.cmd_type)?;
// Command was re-scheduled for later.
if *cmd_time > item.time {
return None;
}
let (cmd, _) = self.queued_commands.remove(&item.cmd_type)?;
Some(cmd)
}
pub fn describe_stats(&self) -> String {

View File

@ -364,268 +364,277 @@ impl GetDrawAgents for Sim {
// Running
impl Sim {
pub fn step(&mut self, map: &Map, dt: Duration) {
// Advances time as minimally as possible, also limited by max_dt.
fn minimal_step(&mut self, map: &Map, max_dt: Duration) {
self.step_count += 1;
if !self.spawner.is_done() {
panic!("Forgot to call spawn_all_trips");
}
let target_time = self.time + dt;
let mut savestate_at: Option<Time> = None;
while let Some((cmd, time)) = self.scheduler.get_next(target_time) {
// Many commands might be scheduled for a particular time. Savestate at the END of a
// certain time.
if let Some(t) = savestate_at {
if time > t {
self.time = t;
self.save();
savestate_at = None;
let max_time = if let Some(t) = self.scheduler.peek_next_time() {
if t > self.time + max_dt {
// Next event is after when we want to stop.
self.time += max_dt;
return;
}
t
} else {
// No events left at all
self.time += max_dt;
return;
};
let mut savestate = false;
while let Some(time) = self.scheduler.peek_next_time() {
if time > max_time {
return;
}
if let Some(cmd) = self.scheduler.get_next() {
if self.do_step(map, time, cmd) {
savestate = true;
}
}
}
self.time = time;
let mut events = Vec::new();
match cmd {
Command::SpawnCar(create_car, retry_if_no_room) => {
if self.driving.start_car_on_lane(
self.time,
create_car.clone(),
map,
&self.intersections,
&self.parking,
&mut self.scheduler,
) {
self.trips.agent_starting_trip_leg(
AgentID::Car(create_car.vehicle.id),
create_car.trip,
);
if let Some(parked_car) = create_car.maybe_parked_car {
self.parking.remove_parked_car(parked_car);
}
events.push(Event::TripPhaseStarting(
create_car.trip,
// TODO sketchy...
if create_car.vehicle.id.1 == VehicleType::Car {
TripMode::Drive
} else {
TripMode::Bike
},
Some(create_car.req.clone()),
if create_car.vehicle.id.1 == VehicleType::Car {
"driving".to_string()
} else {
"biking".to_string()
},
));
self.analytics
.record_demand(create_car.router.get_path(), map);
} else if retry_if_no_room {
// TODO Record this in the trip log
self.scheduler.push(
self.time + BLIND_RETRY_TO_SPAWN,
Command::SpawnCar(create_car, retry_if_no_room),
);
} else {
println!(
"No room to spawn car for {}. Not retrying!",
create_car.trip
);
self.trips.abort_trip_failed_start(create_car.trip);
self.trip_positions = None;
if savestate {
self.save();
}
}
// If true, savestate was requested.
fn do_step(&mut self, map: &Map, time: Time, cmd: Command) -> bool {
self.time = time;
let mut events = Vec::new();
let mut savestate = false;
match cmd {
Command::SpawnCar(create_car, retry_if_no_room) => {
if self.driving.start_car_on_lane(
self.time,
create_car.clone(),
map,
&self.intersections,
&self.parking,
&mut self.scheduler,
) {
self.trips.agent_starting_trip_leg(
AgentID::Car(create_car.vehicle.id),
create_car.trip,
);
if let Some(parked_car) = create_car.maybe_parked_car {
self.parking.remove_parked_car(parked_car);
}
events.push(Event::TripPhaseStarting(
create_car.trip,
// TODO sketchy...
if create_car.vehicle.id.1 == VehicleType::Car {
TripMode::Drive
} else {
TripMode::Bike
},
Some(create_car.req.clone()),
if create_car.vehicle.id.1 == VehicleType::Car {
"driving".to_string()
} else {
"biking".to_string()
},
));
self.analytics
.record_demand(create_car.router.get_path(), map);
} else if retry_if_no_room {
// TODO Record this in the trip log
self.scheduler.push(
self.time + BLIND_RETRY_TO_SPAWN,
Command::SpawnCar(create_car, retry_if_no_room),
);
} else {
println!(
"No room to spawn car for {}. Not retrying!",
create_car.trip
);
self.trips.abort_trip_failed_start(create_car.trip);
}
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);
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(create_ped.req.clone()) {
create_ped.path = path;
let mut legs = vec![
TripLeg::Walk(
}
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);
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(create_ped.req.clone()) {
create_ped.path = path;
let mut legs = vec![
TripLeg::Walk(
create_ped.id,
create_ped.speed,
create_ped.goal.clone(),
),
TripLeg::Drive(parked_car.vehicle.clone(), driving_goal.clone()),
];
match driving_goal {
DrivingGoal::ParkNear(b) => {
legs.push(TripLeg::Walk(
create_ped.id,
create_ped.speed,
create_ped.goal.clone(),
),
TripLeg::Drive(
parked_car.vehicle.clone(),
driving_goal.clone(),
),
];
match driving_goal {
DrivingGoal::ParkNear(b) => {
legs.push(TripLeg::Walk(
create_ped.id,
create_ped.speed,
SidewalkSpot::building(b, map),
));
}
DrivingGoal::Border(_, _) => {}
SidewalkSpot::building(b, map),
));
}
self.trips.dynamically_override_legs(create_ped.trip, legs);
true
} else {
println!(
"WARNING: At {}, {} giving up because no path from {} to {:?}",
self.time, create_ped.id, b, create_ped.goal.connection
);
self.parking.dynamically_return_car(parked_car);
false
DrivingGoal::Border(_, _) => {}
}
self.trips.dynamically_override_legs(create_ped.trip, legs);
true
} else {
println!(
"WARNING: At {}, no free car for {} spawning at {}",
self.time, create_ped.id, b
"WARNING: At {}, {} giving up because no path from {} to {:?}",
self.time, create_ped.id, b, create_ped.goal.connection
);
self.parking.dynamically_return_car(parked_car);
false
}
} else {
true
};
if ok {
// Do the order a bit backwards so we don't have to clone the
// CreatePedestrian. spawn_ped can't fail.
self.trips.agent_starting_trip_leg(
AgentID::Pedestrian(create_ped.id),
create_ped.trip,
println!(
"WARNING: At {}, no free car for {} spawning at {}",
self.time, create_ped.id, b
);
events.push(Event::TripPhaseStarting(
create_ped.trip,
TripMode::Walk,
Some(create_ped.req.clone()),
"walking".to_string(),
));
self.analytics.record_demand(&create_ped.path, map);
// Maybe there's actually no work to do!
match (&create_ped.start.connection, &create_ped.goal.connection) {
(
SidewalkPOI::Building(b1),
SidewalkPOI::ParkingSpot(ParkingSpot::Offstreet(b2, idx)),
) if b1 == b2 => {
self.trips.ped_reached_parking_spot(
self.time,
create_ped.id,
ParkingSpot::Offstreet(*b2, *idx),
map,
&self.parking,
&mut self.scheduler,
);
}
_ => {
self.walking.spawn_ped(
self.time,
create_ped,
map,
&mut self.scheduler,
);
}
}
} else {
self.trips.abort_trip_failed_start(create_ped.trip);
false
}
}
Command::UpdateCar(car) => {
self.driving.update_car(
car,
self.time,
map,
&mut self.parking,
&mut self.intersections,
&mut self.trips,
&mut self.scheduler,
&mut self.transit,
&mut self.walking,
} else {
true
};
if ok {
// Do the order a bit backwards so we don't have to clone the
// CreatePedestrian. spawn_ped can't fail.
self.trips.agent_starting_trip_leg(
AgentID::Pedestrian(create_ped.id),
create_ped.trip,
);
}
Command::UpdateLaggyHead(car) => {
self.driving.update_laggy_head(
car,
self.time,
map,
&mut self.intersections,
&mut self.scheduler,
);
}
Command::UpdatePed(ped) => {
self.walking.update_ped(
ped,
self.time,
map,
&mut self.intersections,
&self.parking,
&mut self.scheduler,
&mut self.trips,
&mut self.transit,
);
}
Command::UpdateIntersection(i) => {
self.intersections
.update_intersection(self.time, i, map, &mut self.scheduler);
}
Command::Savestate(frequency) => {
self.scheduler
.push(self.time + frequency, Command::Savestate(frequency));
assert_eq!(savestate_at, None);
savestate_at = Some(self.time);
events.push(Event::TripPhaseStarting(
create_ped.trip,
TripMode::Walk,
Some(create_ped.req.clone()),
"walking".to_string(),
));
self.analytics.record_demand(&create_ped.path, map);
// Maybe there's actually no work to do!
match (&create_ped.start.connection, &create_ped.goal.connection) {
(
SidewalkPOI::Building(b1),
SidewalkPOI::ParkingSpot(ParkingSpot::Offstreet(b2, idx)),
) if b1 == b2 => {
self.trips.ped_reached_parking_spot(
self.time,
create_ped.id,
ParkingSpot::Offstreet(*b2, *idx),
map,
&self.parking,
&mut self.scheduler,
);
}
_ => {
self.walking
.spawn_ped(self.time, create_ped, map, &mut self.scheduler);
}
}
} else {
self.trips.abort_trip_failed_start(create_ped.trip);
}
}
// Record events at precisely the time they occur.
events.extend(self.trips.collect_events());
events.extend(self.transit.collect_events());
events.extend(self.driving.collect_events());
events.extend(self.walking.collect_events());
events.extend(self.intersections.collect_events());
for ev in events {
self.analytics.event(ev, self.time, map);
Command::UpdateCar(car) => {
self.driving.update_car(
car,
self.time,
map,
&mut self.parking,
&mut self.intersections,
&mut self.trips,
&mut self.scheduler,
&mut self.transit,
&mut self.walking,
);
}
Command::UpdateLaggyHead(car) => {
self.driving.update_laggy_head(
car,
self.time,
map,
&mut self.intersections,
&mut self.scheduler,
);
}
Command::UpdatePed(ped) => {
self.walking.update_ped(
ped,
self.time,
map,
&mut self.intersections,
&self.parking,
&mut self.scheduler,
&mut self.trips,
&mut self.transit,
);
}
Command::UpdateIntersection(i) => {
self.intersections
.update_intersection(self.time, i, map, &mut self.scheduler);
}
Command::Savestate(frequency) => {
self.scheduler
.push(self.time + frequency, Command::Savestate(frequency));
savestate = true;
}
}
if let Some(t) = savestate_at {
self.time = t;
self.save();
}
self.time = target_time;
self.trip_positions = None;
// Record events at precisely the time they occur.
events.extend(self.trips.collect_events());
events.extend(self.transit.collect_events());
events.extend(self.driving.collect_events());
events.extend(self.walking.collect_events());
events.extend(self.intersections.collect_events());
for ev in events {
self.analytics.event(ev, self.time, map);
}
savestate
}
pub fn timed_step(&mut self, map: &Map, dt: Duration, timer: &mut Timer) {
// TODO Ideally print every second or so
let orig_time = self.time;
let chunks = (dt / Duration::seconds(10.0)).ceil() as usize;
timer.start_iter(format!("advance simulation by {}", dt), chunks);
for i in 0..chunks {
timer.next();
self.step(
map,
if i == chunks - 1 {
orig_time + dt - self.time
} else {
dt * (1.0 / (chunks as f64))
},
);
let end_time = self.time + dt;
let start = Instant::now();
let mut last_update = Instant::now();
timer.start(format!("Advance sim to {}", end_time));
while self.time < end_time {
self.minimal_step(map, end_time - self.time);
if Duration::realtime_elapsed(last_update) >= Duration::seconds(1.0) {
// TODO Not timer?
println!(
"- After {}, the sim is at {}",
Duration::realtime_elapsed(start),
self.time
);
last_update = Instant::now();
}
}
assert_eq!(self.time, orig_time + dt);
timer.stop(format!("Advance sim to {}", end_time));
}
pub fn normal_step(&mut self, map: &Map, dt: Duration) {
self.timed_step(map, dt, &mut Timer::throwaway());
}
pub fn time_limited_step(&mut self, map: &Map, dt: Duration, real_time_limit: Duration) {
let started_at = Instant::now();
let goal_time = self.time + dt;
let end_time = self.time + dt;
loop {
if Duration::realtime_elapsed(started_at) > real_time_limit || self.time == goal_time {
break;
}
// Don't exceed the goal_time. But if we have a large step to make, break it into 0.1s
// chunks, so we get a chance to abort if real_time_limit is passed.
self.step(map, Duration::seconds(0.1).min(goal_time - self.time));
while self.time < end_time && Duration::realtime_elapsed(started_at) < real_time_limit {
self.minimal_step(map, end_time - self.time);
}
}
@ -641,6 +650,7 @@ impl Sim {
}
// Helpers to run the sim
// TODO Old and gunky
impl Sim {
pub fn just_run_until_done(&mut self, map: &Map, time_limit: Option<Duration>) {
self.run_until_done(map, |_, _| {}, time_limit);
@ -661,7 +671,7 @@ impl Sim {
let dt = time_limit.unwrap_or_else(|| Duration::seconds(30.0));
match panic::catch_unwind(panic::AssertUnwindSafe(|| {
self.step(&map, dt);
self.normal_step(&map, dt);
})) {
Ok(()) => {}
Err(err) => {
@ -716,7 +726,7 @@ impl Sim {
// TODO No benchmark printing at all this way.
// TODO Doesn't stop early once all expectations are met.
self.analytics.test_expectations.extend(all_expectations);
self.step(&map, time_limit);
self.normal_step(&map, time_limit);
if self.analytics.test_expectations.is_empty() {
return;
}

View File

@ -47,8 +47,8 @@ pub fn run(t: &mut TestRunner) {
sim2.save()
);
}
sim1.step(&map, dt);
sim2.step(&map, dt);
sim1.normal_step(&map, dt);
sim2.normal_step(&map, dt);
}
});
@ -74,8 +74,8 @@ pub fn run(t: &mut TestRunner) {
&mut Timer::throwaway(),
);
sim1.step(&map, Duration::minutes(10));
sim2.step(&map, Duration::minutes(10));
sim1.normal_step(&map, Duration::minutes(10));
sim2.normal_step(&map, Duration::minutes(10));
if sim1 != sim2 {
panic!(
@ -87,7 +87,7 @@ pub fn run(t: &mut TestRunner) {
let sim1_save = sim1.save();
sim1.step(&map, Duration::seconds(30.0));
sim1.normal_step(&map, Duration::seconds(30.0));
if sim1 == sim2 {
panic!(