mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 23:43:25 +03:00
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:
parent
0385b96301
commit
e5d9d80c14
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
118
sim/src/sim.rs
118
sim/src/sim.rs
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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!(
|
||||||
|
Loading…
Reference in New Issue
Block a user