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

View File

@ -255,7 +255,9 @@ impl State for DebugMode {
.action(ctx, Key::Backspace, "forcibly kill this car") .action(ctx, Key::Backspace, "forcibly kill this car")
{ {
ui.primary.sim.kill_stuck_car(id, &ui.primary.map); 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; ui.primary.current_selection = None;
} else if ui.per_obj.action(ctx, Key::G, "find front of blockage") { } else if ui.per_obj.action(ctx, Key::G, "find front of blockage") {
return Transition::Push(msg( 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 { for idx in 0..phase {
step += signal.phases[idx].duration; 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); spawn_agents_around(i, ui);
} }

View File

@ -193,7 +193,9 @@ impl GameplayMode {
&mut ui.primary.current_flags.sim_flags.make_rng(), &mut ui.primary.current_flags.sim_flags.make_rng(),
timer, 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. // Maybe we've already got prebaked data for this map+scenario.
if !ui if !ui

View File

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

View File

@ -971,7 +971,9 @@ impl TutorialState {
ui.primary.current_flags.sim_flags.rng_seed = Some(42); ui.primary.current_flags.sim_flags.rng_seed = Some(42);
(cb)(ui); (cb)(ui);
ui.primary.current_flags.sim_flags.rng_seed = old; 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 { let last_finished_task = if self.current.stage == 0 {

View File

@ -151,9 +151,11 @@ impl SpeedControls {
.cb( .cb(
"step forwards 0.1 seconds", "step forwards 0.1 seconds",
Box::new(|ctx, ui| { 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 { 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); ui.recalculate_current_selection(ctx);
None None
@ -495,7 +497,7 @@ impl State for TimeWarpScreen {
ui.primary.sim.time_limited_step( ui.primary.sim.time_limited_step(
&ui.primary.map, &ui.primary.map,
self.target - ui.primary.sim.time(), self.target - ui.primary.sim.time(),
Duration::seconds(0.1), Duration::seconds(0.033),
); );
// TODO secondary for a/b test mode // 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 { impl ops::Sub<Duration> for Time {
type Output = Time; type Output = Time;

View File

@ -157,28 +157,28 @@ impl Scheduler {
self.queued_commands.remove(&cmd.to_type()); 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 // 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 // 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. // 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 { // TODO Above description is a little vague. This should be used with peek_next_time in a
let next_time = self.items.peek().as_ref()?.time; // particular way...
if next_time > now { 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; return None;
} }
let (cmd, _) = self.queued_commands.remove(&item.cmd_type)?;
self.latest_time = next_time; Some(cmd)
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.
}
} }
pub fn describe_stats(&self) -> String { pub fn describe_stats(&self) -> String {

View File

@ -364,27 +364,50 @@ impl GetDrawAgents for Sim {
// Running // Running
impl Sim { 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; self.step_count += 1;
if !self.spawner.is_done() { if !self.spawner.is_done() {
panic!("Forgot to call spawn_all_trips"); panic!("Forgot to call spawn_all_trips");
} }
let target_time = self.time + dt; let max_time = if let Some(t) = self.scheduler.peek_next_time() {
let mut savestate_at: Option<Time> = None; if t > self.time + max_dt {
while let Some((cmd, time)) = self.scheduler.get_next(target_time) { // Next event is after when we want to stop.
// Many commands might be scheduled for a particular time. Savestate at the END of a self.time += max_dt;
// certain time. return;
if let Some(t) = savestate_at { }
if time > t { t
self.time = t; } else {
self.save(); // No events left at all
savestate_at = None; 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.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; self.time = time;
let mut events = Vec::new(); let mut events = Vec::new();
let mut savestate = false;
match cmd { match cmd {
Command::SpawnCar(create_car, retry_if_no_room) => { Command::SpawnCar(create_car, retry_if_no_room) => {
if self.driving.start_car_on_lane( if self.driving.start_car_on_lane(
@ -453,10 +476,7 @@ impl Sim {
create_ped.speed, create_ped.speed,
create_ped.goal.clone(), create_ped.goal.clone(),
), ),
TripLeg::Drive( TripLeg::Drive(parked_car.vehicle.clone(), driving_goal.clone()),
parked_car.vehicle.clone(),
driving_goal.clone(),
),
]; ];
match driving_goal { match driving_goal {
DrivingGoal::ParkNear(b) => { DrivingGoal::ParkNear(b) => {
@ -519,12 +539,8 @@ impl Sim {
); );
} }
_ => { _ => {
self.walking.spawn_ped( self.walking
self.time, .spawn_ped(self.time, create_ped, map, &mut self.scheduler);
create_ped,
map,
&mut self.scheduler,
);
} }
} }
} else { } else {
@ -572,8 +588,7 @@ impl Sim {
Command::Savestate(frequency) => { Command::Savestate(frequency) => {
self.scheduler self.scheduler
.push(self.time + frequency, Command::Savestate(frequency)); .push(self.time + frequency, Command::Savestate(frequency));
assert_eq!(savestate_at, None); savestate = true;
savestate_at = Some(self.time);
} }
} }
@ -586,46 +601,40 @@ impl Sim {
for ev in events { for ev in events {
self.analytics.event(ev, self.time, map); self.analytics.event(ev, self.time, map);
} }
}
if let Some(t) = savestate_at {
self.time = t;
self.save();
}
self.time = target_time;
self.trip_positions = None; savestate
} }
pub fn timed_step(&mut self, map: &Map, dt: Duration, timer: &mut Timer) { pub fn timed_step(&mut self, map: &Map, dt: Duration, timer: &mut Timer) {
// TODO Ideally print every second or so let end_time = self.time + dt;
let orig_time = self.time; let start = Instant::now();
let chunks = (dt / Duration::seconds(10.0)).ceil() as usize; let mut last_update = Instant::now();
timer.start_iter(format!("advance simulation by {}", dt), chunks);
for i in 0..chunks { timer.start(format!("Advance sim to {}", end_time));
timer.next(); while self.time < end_time {
self.step( self.minimal_step(map, end_time - self.time);
map, if Duration::realtime_elapsed(last_update) >= Duration::seconds(1.0) {
if i == chunks - 1 { // TODO Not timer?
orig_time + dt - self.time println!(
} else { "- After {}, the sim is at {}",
dt * (1.0 / (chunks as f64)) 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) { pub fn time_limited_step(&mut self, map: &Map, dt: Duration, real_time_limit: Duration) {
let started_at = Instant::now(); let started_at = Instant::now();
let goal_time = self.time + dt; let end_time = self.time + dt;
loop { while self.time < end_time && Duration::realtime_elapsed(started_at) < real_time_limit {
if Duration::realtime_elapsed(started_at) > real_time_limit || self.time == goal_time { self.minimal_step(map, end_time - self.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));
} }
} }
@ -641,6 +650,7 @@ impl Sim {
} }
// Helpers to run the sim // Helpers to run the sim
// TODO Old and gunky
impl Sim { impl Sim {
pub fn just_run_until_done(&mut self, map: &Map, time_limit: Option<Duration>) { pub fn just_run_until_done(&mut self, map: &Map, time_limit: Option<Duration>) {
self.run_until_done(map, |_, _| {}, time_limit); self.run_until_done(map, |_, _| {}, time_limit);
@ -661,7 +671,7 @@ impl Sim {
let dt = time_limit.unwrap_or_else(|| Duration::seconds(30.0)); let dt = time_limit.unwrap_or_else(|| Duration::seconds(30.0));
match panic::catch_unwind(panic::AssertUnwindSafe(|| { match panic::catch_unwind(panic::AssertUnwindSafe(|| {
self.step(&map, dt); self.normal_step(&map, dt);
})) { })) {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
@ -716,7 +726,7 @@ impl Sim {
// TODO No benchmark printing at all this way. // TODO No benchmark printing at all this way.
// TODO Doesn't stop early once all expectations are met. // TODO Doesn't stop early once all expectations are met.
self.analytics.test_expectations.extend(all_expectations); 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() { if self.analytics.test_expectations.is_empty() {
return; return;
} }

View File

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