removing many external dependencies on Tick. moving time parsing to

Duration.
This commit is contained in:
Dustin Carlino 2019-02-26 13:32:31 -08:00
parent d5181f6bf8
commit 42c7c21246
18 changed files with 167 additions and 226 deletions

View File

@ -1,6 +1,6 @@
use crate::objects::DrawCtx; use crate::objects::DrawCtx;
use crate::plugins::{ use crate::plugins::{
choose_intersection, choose_neighborhood, choose_origin_destination, input_tick, choose_intersection, choose_neighborhood, choose_origin_destination, input_time,
input_weighted_usize, load_scenario, BlockingPlugin, PluginCtx, input_weighted_usize, load_scenario, BlockingPlugin, PluginCtx,
}; };
use abstutil::Timer; use abstutil::Timer;
@ -134,9 +134,9 @@ fn edit_scenario(map: &Map, scenario: &mut Scenario, mut wizard: WrappedWizard)
x if x == spawn => { x if x == spawn => {
scenario.spawn_over_time.push(SpawnOverTime { scenario.spawn_over_time.push(SpawnOverTime {
num_agents: wizard.input_usize("Spawn how many agents?")?, num_agents: wizard.input_usize("Spawn how many agents?")?,
start_tick: input_tick(&mut wizard, "Start spawning when?")?, start_time: input_time(&mut wizard, "Start spawning when?")?,
// TODO input interval, or otherwise enforce stop_tick > start_tick // TODO input interval, or otherwise enforce stop_time > start_time
stop_tick: input_tick(&mut wizard, "Stop spawning when?")?, stop_time: input_time(&mut wizard, "Stop spawning when?")?,
start_from_neighborhood: choose_neighborhood( start_from_neighborhood: choose_neighborhood(
map, map,
&mut wizard, &mut wizard,
@ -155,9 +155,9 @@ fn edit_scenario(map: &Map, scenario: &mut Scenario, mut wizard: WrappedWizard)
num_peds: wizard.input_usize("Spawn how many pedestrians?")?, num_peds: wizard.input_usize("Spawn how many pedestrians?")?,
num_cars: wizard.input_usize("Spawn how many cars?")?, num_cars: wizard.input_usize("Spawn how many cars?")?,
num_bikes: wizard.input_usize("Spawn how many bikes?")?, num_bikes: wizard.input_usize("Spawn how many bikes?")?,
start_tick: input_tick(&mut wizard, "Start spawning when?")?, start_time: input_time(&mut wizard, "Start spawning when?")?,
// TODO input interval, or otherwise enforce stop_tick > start_tick // TODO input interval, or otherwise enforce stop_time > start_time
stop_tick: input_tick(&mut wizard, "Stop spawning when?")?, stop_time: input_time(&mut wizard, "Stop spawning when?")?,
// TODO validate it's a border! // TODO validate it's a border!
start_from_border: choose_intersection( start_from_border: choose_intersection(
&mut wizard, &mut wizard,

View File

@ -6,13 +6,14 @@ pub mod view;
use crate::colors::ColorScheme; use crate::colors::ColorScheme;
use crate::objects::{DrawCtx, RenderingHints, ID}; use crate::objects::{DrawCtx, RenderingHints, ID};
use crate::state::{PerMapUI, PluginsPerMap}; use crate::state::{PerMapUI, PluginsPerMap};
use ::sim::{ABTest, OriginDestination, Scenario, Tick}; use ::sim::{ABTest, OriginDestination, Scenario};
use abstutil; use abstutil;
use abstutil::WeightedUsizeChoice; use abstutil::WeightedUsizeChoice;
use downcast::{ use downcast::{
downcast, downcast_methods, downcast_methods_core, downcast_methods_std, impl_downcast, Any, downcast, downcast_methods, downcast_methods_core, downcast_methods_std, impl_downcast, Any,
}; };
use ezgui::{Canvas, Color, GfxCtx, Prerender, UserInput, WrappedWizard}; use ezgui::{Canvas, Color, GfxCtx, Prerender, UserInput, WrappedWizard};
use geom::Duration;
use map_model::{IntersectionID, Map, Neighborhood, NeighborhoodBuilder}; use map_model::{IntersectionID, Map, Neighborhood, NeighborhoodBuilder};
// TODO Split into two types, but then State needs two possible types in its exclusive blocking // TODO Split into two types, but then State needs two possible types in its exclusive blocking
@ -143,8 +144,8 @@ pub fn load_ab_test(map: &Map, wizard: &mut WrappedWizard, query: &str) -> Optio
.map(|(_, t)| t) .map(|(_, t)| t)
} }
pub fn input_tick(wizard: &mut WrappedWizard, query: &str) -> Option<Tick> { pub fn input_time(wizard: &mut WrappedWizard, query: &str) -> Option<Duration> {
wizard.input_something(query, None, Box::new(|line| Tick::parse(&line))) wizard.input_something(query, None, Box::new(|line| Duration::parse(&line)))
} }
pub fn input_weighted_usize( pub fn input_weighted_usize(

View File

@ -2,17 +2,18 @@ use crate::plugins::{AmbientPluginWithPrimaryPlugins, PluginCtx};
use crate::state::PluginsPerMap; use crate::state::PluginsPerMap;
use abstutil::elapsed_seconds; use abstutil::elapsed_seconds;
use ezgui::EventLoopMode; use ezgui::EventLoopMode;
use sim::{Benchmark, Event, Sim, Tick, TIMESTEP}; use geom::Duration;
use sim::{Benchmark, Event, Sim, TIMESTEP};
use std::mem; use std::mem;
use std::time::{Duration, Instant}; use std::time::Instant;
const ADJUST_SPEED: f64 = 0.1; const ADJUST_SPEED: f64 = 0.1;
pub struct SimControls { pub struct SimControls {
desired_speed: f64, // sim seconds per real second desired_speed: f64, // sim seconds per real second
state: State, state: State,
// Optional because the 0th tick actually happens, and callers comparing wouldn't see that. // Optional because Duration::ZERO actually happens, and callers comparing wouldn't see that.
primary_events: Option<(Tick, Vec<Event>)>, primary_events: Option<(Duration, Vec<Event>)>,
} }
enum State { enum State {
@ -43,11 +44,11 @@ impl SimControls {
pub fn get_new_primary_events( pub fn get_new_primary_events(
&self, &self,
last_seen_tick: Option<Tick>, last_seen_time: Option<Duration>,
) -> Option<(Tick, &Vec<Event>)> { ) -> Option<(Duration, &Vec<Event>)> {
let (tick, events) = self.primary_events.as_ref()?; let (time, events) = self.primary_events.as_ref()?;
if last_seen_tick.is_none() || last_seen_tick != Some(*tick) { if last_seen_time.is_none() || last_seen_time != Some(*time) {
Some((*tick, events)) Some((*time, events))
} else { } else {
None None
} }
@ -146,9 +147,9 @@ impl AmbientPluginWithPrimaryPlugins for SimControls {
if ctx.input.action_chosen("run/pause sim") { if ctx.input.action_chosen("run/pause sim") {
self.run_sim(&mut ctx.primary.sim); self.run_sim(&mut ctx.primary.sim);
} else if ctx.input.action_chosen("run one step of sim") { } else if ctx.input.action_chosen("run one step of sim") {
let tick = ctx.primary.sim.time; let time = ctx.primary.sim.time();
let events = ctx.primary.sim.step(&ctx.primary.map); let events = ctx.primary.sim.step(&ctx.primary.map);
self.primary_events = Some((tick, events)); self.primary_events = Some((time, events));
*ctx.recalculate_current_selection = true; *ctx.recalculate_current_selection = true;
if let Some((s, _)) = ctx.secondary { if let Some((s, _)) = ctx.secondary {
@ -174,9 +175,9 @@ impl AmbientPluginWithPrimaryPlugins for SimControls {
// the speed says we should. // the speed says we should.
let dt_s = elapsed_seconds(*last_step); let dt_s = elapsed_seconds(*last_step);
if dt_s >= TIMESTEP.inner_seconds() / self.desired_speed { if dt_s >= TIMESTEP.inner_seconds() / self.desired_speed {
let tick = ctx.primary.sim.time; let time = ctx.primary.sim.time();
let events = ctx.primary.sim.step(&ctx.primary.map); let events = ctx.primary.sim.step(&ctx.primary.map);
self.primary_events = Some((tick, events)); self.primary_events = Some((time, events));
*ctx.recalculate_current_selection = true; *ctx.recalculate_current_selection = true;
if let Some((s, _)) = ctx.secondary { if let Some((s, _)) = ctx.secondary {
@ -185,7 +186,7 @@ impl AmbientPluginWithPrimaryPlugins for SimControls {
*last_step = Instant::now(); *last_step = Instant::now();
} }
if benchmark.has_real_time_passed(Duration::from_secs(1)) { if benchmark.has_real_time_passed(std::time::Duration::from_secs(1)) {
// I think the benchmark should naturally account for the delay of the // I think the benchmark should naturally account for the delay of the
// secondary sim. // secondary sim.
*speed = format!("{0:.2}x", ctx.primary.sim.measure_speed(benchmark)); *speed = format!("{0:.2}x", ctx.primary.sim.measure_speed(benchmark));

View File

@ -162,7 +162,7 @@ fn spawn_car(sim: &mut new_des_model::Sim, rng: &mut XorShiftRng, map: &Map, sta
let last_lane = path.last().unwrap().as_lane(); let last_lane = path.last().unwrap().as_lane();
let vehicle = rand_vehicle(rng); let vehicle = rand_vehicle(rng);
let start_dist = rand_dist(rng, vehicle.length, map.get_l(start_lane).length()); let start_dist = rand_dist(rng, vehicle.length, map.get_l(start_lane).length());
let spawn_time = Duration::seconds(0.2) * rng.gen_range(0, 5) as f64; let spawn_time = Duration::seconds(0.2) * f64::from(rng.gen_range(0, 5));
sim.schedule_trip( sim.schedule_trip(
spawn_time, spawn_time,
@ -252,7 +252,7 @@ fn random_ped_near(
map: &Map, map: &Map,
rng: &mut XorShiftRng, rng: &mut XorShiftRng,
) { ) {
let spawn_time = Duration::seconds(0.2) * rng.gen_range(0, 5) as f64; let spawn_time = Duration::seconds(0.2) * f64::from(rng.gen_range(0, 5));
let end_near = random_path(start_near, rng, map).last().unwrap().as_lane(); let end_near = random_path(start_near, rng, map).last().unwrap().as_lane();
let (spot1, spot2) = match ( let (spot1, spot2) = match (
random_bldg_near(start_near, map, rng), random_bldg_near(start_near, map, rng),

View File

@ -265,18 +265,16 @@ impl Pedestrian {
} }
PedState::LeavingBuilding(b, ref time_int) => { PedState::LeavingBuilding(b, ref time_int) => {
let front_path = &map.get_b(b).front_path; let front_path = &map.get_b(b).front_path;
let pt = front_path front_path
.line .line
.dist_along(time_int.percent(time) * front_path.line.length()); .dist_along(time_int.percent(time) * front_path.line.length())
pt
} }
PedState::EnteringBuilding(b, ref time_int) => { PedState::EnteringBuilding(b, ref time_int) => {
let front_path = &map.get_b(b).front_path; let front_path = &map.get_b(b).front_path;
let pt = front_path front_path
.line .line
.reverse() .reverse()
.dist_along(time_int.percent(time) * front_path.line.length()); .dist_along(time_int.percent(time) * front_path.line.length())
pt
} }
PedState::StartingToBike(_, ref line, ref time_int) => { PedState::StartingToBike(_, ref line, ref time_int) => {
line.percent_along(time_int.percent(time)) line.percent_along(time_int.percent(time))

View File

@ -161,7 +161,7 @@ impl TripManager {
_ => unreachable!(), _ => unreachable!(),
}; };
let driving_pos = match spot.connection { let driving_pos = match spot.connection {
SidewalkPOI::BikeRack(ref p) => p.clone(), SidewalkPOI::BikeRack(p) => p,
_ => unreachable!(), _ => unreachable!(),
}; };

View File

@ -3,8 +3,9 @@ use crate::plugins::view::legend::Legend;
use crate::state::{DefaultUIState, Flags, PerMapUI, UIState}; use crate::state::{DefaultUIState, Flags, PerMapUI, UIState};
use abstutil::Timer; use abstutil::Timer;
use ezgui::{EventCtx, GfxCtx, LogScroller, Prerender, Text}; use ezgui::{EventCtx, GfxCtx, LogScroller, Prerender, Text};
use geom::Duration;
use map_model::Traversable; use map_model::Traversable;
use sim::{Event, Tick}; use sim::Event;
pub struct TutorialState { pub struct TutorialState {
main: DefaultUIState, main: DefaultUIState,
@ -14,7 +15,7 @@ pub struct TutorialState {
enum State { enum State {
GiveInstructions(LogScroller), GiveInstructions(LogScroller),
Play { Play {
last_tick_observed: Option<Tick>, last_time_observed: Option<Duration>,
spawned_from_south: usize, spawned_from_south: usize,
spawned_from_north: usize, spawned_from_north: usize,
}, },
@ -60,25 +61,25 @@ impl UIState for TutorialState {
self.main.sim_controls.run_sim(&mut self.main.primary.sim); self.main.sim_controls.run_sim(&mut self.main.primary.sim);
self.main.legend = Some(Legend::start(ctx.input, ctx.canvas)); self.main.legend = Some(Legend::start(ctx.input, ctx.canvas));
self.state = State::Play { self.state = State::Play {
last_tick_observed: None, last_time_observed: None,
spawned_from_north: 0, spawned_from_north: 0,
spawned_from_south: 0, spawned_from_south: 0,
}; };
} }
} }
State::Play { State::Play {
ref mut last_tick_observed, ref mut last_time_observed,
ref mut spawned_from_north, ref mut spawned_from_north,
ref mut spawned_from_south, ref mut spawned_from_south,
} => { } => {
self.main.event(ctx, hints, recalculate_current_selection); self.main.event(ctx, hints, recalculate_current_selection);
if let Some((tick, events)) = self if let Some((time, events)) = self
.main .main
.sim_controls .sim_controls
.get_new_primary_events(*last_tick_observed) .get_new_primary_events(*last_time_observed)
{ {
*last_tick_observed = Some(tick); *last_time_observed = Some(time);
for ev in events { for ev in events {
if let Event::AgentEntersTraversable(_, Traversable::Lane(lane)) = ev { if let Event::AgentEntersTraversable(_, Traversable::Lane(lane)) = ev {
if *lane == self.main.primary.map.driving_lane("north entrance").id { if *lane == self.main.primary.map.driving_lane("north entrance").id {
@ -126,7 +127,7 @@ impl UIState for TutorialState {
} }
fn setup_scenario(primary: &mut PerMapUI) { fn setup_scenario(primary: &mut PerMapUI) {
use sim::{BorderSpawnOverTime, OriginDestination, Scenario, Tick}; use sim::{BorderSpawnOverTime, OriginDestination, Scenario};
let map = &primary.map; let map = &primary.map;
fn border_spawn(primary: &PerMapUI, from: &str, to: &str) -> BorderSpawnOverTime { fn border_spawn(primary: &PerMapUI, from: &str, to: &str) -> BorderSpawnOverTime {
@ -136,8 +137,8 @@ fn setup_scenario(primary: &mut PerMapUI) {
num_cars: SPAWN_CARS_PER_BORDER, num_cars: SPAWN_CARS_PER_BORDER,
num_bikes: 0, num_bikes: 0,
percent_use_transit: 0.0, percent_use_transit: 0.0,
start_tick: Tick::zero(), start_time: Duration::ZERO,
stop_tick: Tick::from_minutes(10), stop_time: Duration::minutes(10),
start_from_border: primary.map.intersection(from).id, start_from_border: primary.map.intersection(from).id,
goal: OriginDestination::Border(primary.map.intersection(to).id), goal: OriginDestination::Border(primary.map.intersection(to).id),
} }

View File

@ -9,5 +9,6 @@ aabb-quadtree = "0.1.0"
abstutil = { path = "../abstutil" } abstutil = { path = "../abstutil" }
geo = "0.11.0" geo = "0.11.0"
ordered-float = "1.0.1" ordered-float = "1.0.1"
regex = "1.0.6"
serde = "1.0.87" serde = "1.0.87"
serde_derive = "1.0.87" serde_derive = "1.0.87"

View File

@ -26,3 +26,5 @@ pub const EPSILON_DIST: Distance = Distance::const_meters(0.01);
pub(crate) fn trim_f64(x: f64) -> f64 { pub(crate) fn trim_f64(x: f64) -> f64 {
(x * 10_000.0).round() / 10_000.0 (x * 10_000.0).round() / 10_000.0
} }
impl abstutil::Cloneable for Duration {}

View File

@ -1,4 +1,5 @@
use crate::{trim_f64, EPSILON_DIST}; use crate::{trim_f64, EPSILON_DIST};
use regex::Regex;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::{cmp, f64, fmt, ops}; use std::{cmp, f64, fmt, ops};
@ -175,6 +176,10 @@ impl Duration {
Duration(trim_f64(value)) Duration(trim_f64(value))
} }
pub fn minutes(mins: usize) -> Duration {
Duration::seconds((mins as f64) * 60.0)
}
pub const fn const_seconds(value: f64) -> Duration { pub const fn const_seconds(value: f64) -> Duration {
Duration(value) Duration(value)
} }
@ -195,11 +200,83 @@ impl Duration {
pub fn is_multiple_of(self, other: Duration) -> bool { pub fn is_multiple_of(self, other: Duration) -> bool {
self.inner_seconds() % other.inner_seconds() == 0.0 self.inner_seconds() % other.inner_seconds() == 0.0
} }
// TODO Why have these two forms? Consolidate
pub fn parse(string: &str) -> Option<Duration> {
let parts: Vec<&str> = string.split(':').collect();
if parts.is_empty() {
return None;
}
let mut seconds: f64 = 0.0;
if parts.last().unwrap().contains('.') {
let last_parts: Vec<&str> = parts.last().unwrap().split('.').collect();
if last_parts.len() != 2 {
return None;
}
seconds += last_parts[1].parse::<f64>().ok()? / 10.0;
seconds += last_parts[0].parse::<f64>().ok()?;
} else {
seconds += parts.last().unwrap().parse::<f64>().ok()?;
}
match parts.len() {
1 => Some(Duration::seconds(seconds)),
2 => {
seconds += 60.0 * parts[0].parse::<f64>().ok()?;
Some(Duration(seconds))
}
3 => {
seconds += 60.0 * parts[1].parse::<f64>().ok()?;
seconds += 3600.0 * parts[0].parse::<f64>().ok()?;
Some(Duration(seconds))
}
_ => None,
}
}
// TODO Unused right now.
pub fn parse_filename(string: &str) -> Option<Duration> {
// TODO lazy_static! {
let regex = Regex::new(r"(\d+)h(\d+)m(\d+)\.(\d+)s").unwrap();
let caps = regex.captures(string)?;
let hours = 3600.0 * caps[1].parse::<f64>().ok()?;
let minutes = 60.0 * caps[2].parse::<f64>().ok()?;
let seconds = caps[3].parse::<f64>().ok()?;
let ms = caps[4].parse::<f64>().ok()? / 10.0;
Some(Duration::seconds(hours + minutes + seconds + ms))
}
fn get_parts(self) -> (f64, f64, f64, f64) {
let hours = self.inner_seconds() / 3600.0;
let mut remainder = self.inner_seconds() % 3600.0;
let minutes = remainder / 60.0;
remainder %= 60.0;
let seconds = remainder.floor();
remainder -= seconds;
(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 fmt::Display for Duration { impl std::fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}s", self.0) let (hours, minutes, seconds, remainder) = self.get_parts();
write!(
f,
"{0:02}:{1:02}:{2:02}.{3}",
hours, minutes, seconds, remainder
)
} }
} }

View File

@ -17,7 +17,6 @@ pretty_assertions = "0.5.1"
rand = { version = "0.6.5", features = ["serde1"] } rand = { version = "0.6.5", features = ["serde1"] }
rand_xorshift = "0.1.1" rand_xorshift = "0.1.1"
rayon = "1.0" rayon = "1.0"
regex = "1.0.6"
serde = "1.0.87" serde = "1.0.87"
serde_derive = "1.0.87" serde_derive = "1.0.87"
structopt = "0.2" structopt = "0.2"

View File

@ -5,6 +5,7 @@ use crate::{
Sim, SpawnOverTime, Tick, Sim, SpawnOverTime, Tick,
}; };
use abstutil::{Timer, WeightedUsizeChoice}; use abstutil::{Timer, WeightedUsizeChoice};
use geom::Duration;
use map_model::{ use map_model::{
BuildingID, BusRoute, BusRouteID, BusStopID, IntersectionID, LaneID, LaneType, Map, Position, BuildingID, BusRoute, BusRouteID, BusStopID, IntersectionID, LaneID, LaneType, Map, Position,
RoadID, RoadID,
@ -106,8 +107,8 @@ impl Sim {
}], }],
spawn_over_time: vec![SpawnOverTime { spawn_over_time: vec![SpawnOverTime {
num_agents: 100, num_agents: 100,
start_tick: Tick::zero(), start_time: Duration::ZERO,
stop_tick: Tick::from_seconds(5), stop_time: Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(), start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::Neighborhood("_everywhere_".to_string()), goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_biking: 0.5, percent_biking: 0.5,
@ -122,8 +123,8 @@ impl Sim {
num_peds: 10, num_peds: 10,
num_cars: 10, num_cars: 10,
num_bikes: 10, num_bikes: 10,
start_tick: Tick::zero(), start_time: Duration::ZERO,
stop_tick: Tick::from_seconds(5), stop_time: Duration::seconds(5.0),
start_from_border: i.id, start_from_border: i.id,
goal: OriginDestination::Neighborhood("_everywhere_".to_string()), goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_use_transit: 0.5, percent_use_transit: 0.5,
@ -133,8 +134,8 @@ impl Sim {
for i in map.all_outgoing_borders() { for i in map.all_outgoing_borders() {
s.spawn_over_time.push(SpawnOverTime { s.spawn_over_time.push(SpawnOverTime {
num_agents: 10, num_agents: 10,
start_tick: Tick::zero(), start_time: Duration::ZERO,
stop_tick: Tick::from_seconds(5), stop_time: Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(), start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::Border(i.id), goal: OriginDestination::Border(i.id),
percent_biking: 0.5, percent_biking: 0.5,

View File

@ -4,7 +4,7 @@ use crate::walking::SidewalkSpot;
use crate::{CarID, Sim, Tick}; use crate::{CarID, Sim, Tick};
use abstutil; use abstutil;
use abstutil::{Timer, WeightedUsizeChoice}; use abstutil::{Timer, WeightedUsizeChoice};
use geom::Distance; use geom::{Distance, Duration};
use map_model::{FullNeighborhoodInfo, IntersectionID, LaneType, Map, Pathfinder, Position}; use map_model::{FullNeighborhoodInfo, IntersectionID, LaneType, Map, Pathfinder, Position};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use rand::Rng; use rand::Rng;
@ -29,8 +29,8 @@ pub struct Scenario {
pub struct SpawnOverTime { pub struct SpawnOverTime {
pub num_agents: usize, pub num_agents: usize,
// TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html // TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html
pub start_tick: Tick, pub start_time: Duration,
pub stop_tick: Tick, pub stop_time: Duration,
pub start_from_neighborhood: String, pub start_from_neighborhood: String,
pub goal: OriginDestination, pub goal: OriginDestination,
pub percent_biking: f64, pub percent_biking: f64,
@ -43,8 +43,8 @@ pub struct BorderSpawnOverTime {
pub num_cars: usize, pub num_cars: usize,
pub num_bikes: usize, pub num_bikes: usize,
// TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html // TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html
pub start_tick: Tick, pub start_time: Duration,
pub stop_tick: Tick, pub stop_time: Duration,
// TODO A serialized Scenario won't last well as the map changes... // TODO A serialized Scenario won't last well as the map changes...
pub start_from_border: IntersectionID, pub start_from_border: IntersectionID,
pub goal: OriginDestination, pub goal: OriginDestination,
@ -98,7 +98,7 @@ impl Scenario {
timer.start_iter("SpawnOverTime each agent", s.num_agents); timer.start_iter("SpawnOverTime each agent", s.num_agents);
for _ in 0..s.num_agents { for _ in 0..s.num_agents {
timer.next(); timer.next();
let spawn_time = Tick::uniform(s.start_tick, s.stop_tick, &mut sim.rng); let spawn_time = Tick::uniform(s.start_time, s.stop_time, &mut sim.rng);
// Note that it's fine for agents to start/end at the same building. Later we might // Note that it's fine for agents to start/end at the same building. Later we might
// want a better assignment of people per household, or workers per office building. // want a better assignment of people per household, or workers per office building.
let from_bldg = *neighborhoods[&s.start_from_neighborhood] let from_bldg = *neighborhoods[&s.start_from_neighborhood]
@ -195,7 +195,7 @@ impl Scenario {
timer.next(); timer.next();
if let Some(start) = SidewalkSpot::start_at_border(s.start_from_border, map) { if let Some(start) = SidewalkSpot::start_at_border(s.start_from_border, map) {
for _ in 0..s.num_peds { for _ in 0..s.num_peds {
let spawn_time = Tick::uniform(s.start_tick, s.stop_tick, &mut sim.rng); let spawn_time = Tick::uniform(s.start_time, s.stop_time, &mut sim.rng);
if let Some(goal) = if let Some(goal) =
s.goal s.goal
.pick_walking_goal(map, &neighborhoods, &mut sim.rng, timer) .pick_walking_goal(map, &neighborhoods, &mut sim.rng, timer)
@ -249,7 +249,7 @@ impl Scenario {
)); ));
} else { } else {
for _ in 0..s.num_cars { for _ in 0..s.num_cars {
let spawn_time = Tick::uniform(s.start_tick, s.stop_tick, &mut sim.rng); let spawn_time = Tick::uniform(s.start_time, s.stop_time, &mut sim.rng);
if let Some(goal) = if let Some(goal) =
s.goal s.goal
.pick_driving_goal(map, &neighborhoods, &mut sim.rng, timer) .pick_driving_goal(map, &neighborhoods, &mut sim.rng, timer)
@ -283,7 +283,7 @@ impl Scenario {
} }
if !starting_biking_lanes.is_empty() { if !starting_biking_lanes.is_empty() {
for _ in 0..s.num_bikes { for _ in 0..s.num_bikes {
let spawn_time = Tick::uniform(s.start_tick, s.stop_tick, &mut sim.rng); let spawn_time = Tick::uniform(s.start_time, s.stop_time, &mut sim.rng);
if let Some(goal) = if let Some(goal) =
s.goal s.goal
.pick_biking_goal(map, &neighborhoods, &mut sim.rng, timer) .pick_biking_goal(map, &neighborhoods, &mut sim.rng, timer)

View File

@ -1,8 +1,6 @@
use geom::Duration; use geom::Duration;
use lazy_static::lazy_static;
use rand::Rng; use rand::Rng;
use rand_xorshift::XorShiftRng; use rand_xorshift::XorShiftRng;
use regex::Regex;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
pub const TIMESTEP: Duration = Duration::const_seconds(0.1); pub const TIMESTEP: Duration = Duration::const_seconds(0.1);
@ -32,54 +30,6 @@ impl Tick {
Tick(t) Tick(t)
} }
// TODO Why have these two forms? Consolidate
pub fn parse(string: &str) -> Option<Tick> {
let parts: Vec<&str> = string.split(':').collect();
if parts.is_empty() {
return None;
}
let mut ticks: u32 = 0;
if parts.last().unwrap().contains('.') {
let last_parts: Vec<&str> = parts.last().unwrap().split('.').collect();
if last_parts.len() != 2 {
return None;
}
ticks += u32::from_str_radix(last_parts[1], 10).ok()?;
ticks += 10 * u32::from_str_radix(last_parts[0], 10).ok()?;
} else {
ticks += 10 * u32::from_str_radix(parts.last().unwrap(), 10).ok()?;
}
match parts.len() {
1 => Some(Tick(ticks)),
2 => {
ticks += 60 * 10 * u32::from_str_radix(parts[0], 10).ok()?;
Some(Tick(ticks))
}
3 => {
ticks += 60 * 10 * u32::from_str_radix(parts[1], 10).ok()?;
ticks += 60 * 60 * 10 * u32::from_str_radix(parts[0], 10).ok()?;
Some(Tick(ticks))
}
_ => None,
}
}
// TODO Unused right now.
pub fn parse_filename(string: &str) -> Option<Tick> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(\d+)h(\d+)m(\d+)\.(\d+)s").unwrap();
}
let caps = RE.captures(string)?;
let hours = 60 * 60 * 10 * u32::from_str_radix(&caps[1], 10).ok()?;
let minutes = 60 * 10 * u32::from_str_radix(&caps[2], 10).ok()?;
let seconds = 10 * u32::from_str_radix(&caps[3], 10).ok()?;
let ms = u32::from_str_radix(&caps[4], 10).ok()?;
Some(Tick(hours + minutes + seconds + ms))
}
// TODO as_duration? // TODO as_duration?
pub fn as_time(self) -> Duration { pub fn as_time(self) -> Duration {
TIMESTEP * f64::from(self.0) TIMESTEP * f64::from(self.0)
@ -122,9 +72,9 @@ impl Tick {
} }
// TODO options for sampling normal distribution // TODO options for sampling normal distribution
pub fn uniform(start: Tick, stop: Tick, rng: &mut XorShiftRng) -> Tick { pub fn uniform(start: Duration, stop: Duration, rng: &mut XorShiftRng) -> Tick {
assert!(start < stop); assert!(start < stop);
Tick(rng.gen_range(start.0, stop.0)) Tick(rng.gen_range((start / TIMESTEP) as u32, (stop / TIMESTEP) as u32))
} }
} }

View File

@ -15,7 +15,7 @@ use crate::{
use abstutil; use abstutil;
use abstutil::{Error, Profiler}; use abstutil::{Error, Profiler};
use derivative::Derivative; use derivative::Derivative;
use geom::{Distance, Pt2D}; use geom::{Distance, Duration, Pt2D};
use map_model::{ use map_model::{
BuildingID, IntersectionID, LaneID, LaneType, Map, Path, Trace, Traversable, Turn, BuildingID, IntersectionID, LaneID, LaneType, Map, Path, Trace, Traversable, Turn,
}; };
@ -493,4 +493,9 @@ impl Sim {
} }
} }
} }
// TODO Confusing to clash with the GetDrawAgents definition?
pub fn time(&self) -> Duration {
self.time.as_time()
}
} }

View File

@ -1,5 +1,5 @@
use crate::runner::TestRunner; use crate::runner::TestRunner;
use geom::{Line, PolyLine, Pt2D}; use geom::{Duration, Line, PolyLine, Pt2D};
//use rand; //use rand;
#[allow(clippy::unreadable_literal)] #[allow(clippy::unreadable_literal)]
@ -87,6 +87,21 @@ pub fn run(t: &mut TestRunner) {
pl.get_slice_ending_at(pt); pl.get_slice_ending_at(pt);
}); });
t.run_fast("time_parsing", |_| {
assert_eq!(Duration::parse("2.3"), Some(Duration::seconds(2.3)));
assert_eq!(Duration::parse("02.3"), Some(Duration::seconds(2.3)));
assert_eq!(Duration::parse("00:00:02.3"), Some(Duration::seconds(2.3)));
assert_eq!(
Duration::parse("00:02:03.5"),
Some(Duration::seconds(123.5))
);
assert_eq!(
Duration::parse("01:02:03.5"),
Some(Duration::seconds(3723.5))
);
});
} }
// TODO test that shifting lines and polylines is a reversible operation // TODO test that shifting lines and polylines is a reversible operation

View File

@ -1,7 +1,6 @@
mod geom; mod geom;
mod map_conversion; mod map_conversion;
mod parking; mod parking;
mod physics;
mod runner; mod runner;
mod sim_completion; mod sim_completion;
mod sim_determinism; mod sim_determinism;
@ -15,7 +14,6 @@ fn main() {
geom::run(t.suite("geom")); geom::run(t.suite("geom"));
map_conversion::run(t.suite("map_conversion")); map_conversion::run(t.suite("map_conversion"));
parking::run(t.suite("parking")); parking::run(t.suite("parking"));
physics::run(t.suite("physics"));
sim_completion::run(t.suite("sim_completion")); sim_completion::run(t.suite("sim_completion"));
sim_determinism::run(t.suite("sim_determinism")); sim_determinism::run(t.suite("sim_determinism"));
transit::run(t.suite("transit")); transit::run(t.suite("transit"));

View File

@ -1,108 +0,0 @@
use crate::runner::TestRunner;
use geom::{Acceleration, Distance, Speed, EPSILON_DIST};
use sim::kinematics::{results_of_accel_for_one_tick, Vehicle};
use sim::{Tick, TIMESTEP};
#[allow(clippy::unreadable_literal)]
pub fn run(t: &mut TestRunner) {
// TODO table driven test style?
/*t.run_fast("accel_to_stop_in_dist/easy", |_| {
let v = Vehicle {
id: CarID(0),
debug: true,
vehicle_type: VehicleType::Car,
length: geom::Distance(3.0),
max_accel: Acceleration::meters_per_second_squared(2.7),
max_deaccel: Acceleration::meters_per_second_squared(-2.7),
max_speed: None,
};
test_accel_to_stop_in_dist(v, Distance::meters(23.554161711896512), Speed::meters_per_second(8.5817572532688));
});
t.run_fast("accel_to_stop_in_dist/hard", |_| {
let v = Vehicle {
id: CarID(0),
debug: true,
vehicle_type: VehicleType::Car,
length: geom::Distance(3.0),
max_accel: Acceleration::meters_per_second_squared(2.7),
max_deaccel: Acceleration::meters_per_second_squared(-2.7),
max_speed: None,
};
test_accel_to_stop_in_dist(v, Distance::meters(4.543071997281501), Speed::meters_per_second(0.003911613164279909));
});
t.run_fast("accel_to_stop_in_dist/bike", |_| {
let v = Vehicle {
id: CarID(1481),
debug: true,
vehicle_type: VehicleType::Bike,
max_accel: Acceleration::meters_per_second_squared(0.2515536204703175),
max_deaccel: Acceleration::meters_per_second_squared(-0.23358239419143578),
length: Distance::meters(1.9474688967345983),
max_speed: Some(Speed::meters_per_second(4.10644207854944)),
};
test_accel_to_stop_in_dist(v, Distance::meters(19.34189455075048), Speed::meters_per_second(1.6099431710100307));
});*/
t.run_fast("time_parsing", |_| {
assert_eq!(Tick::parse("2.3"), Some(Tick::testonly_from_raw(23)));
assert_eq!(Tick::parse("02.3"), Some(Tick::testonly_from_raw(23)));
assert_eq!(Tick::parse("00:00:02.3"), Some(Tick::testonly_from_raw(23)));
assert_eq!(
Tick::parse("00:02:03.5"),
Some(Tick::testonly_from_raw(35 + 1200))
);
assert_eq!(
Tick::parse("01:02:03.5"),
Some(Tick::testonly_from_raw(35 + 1200 + 36000))
);
});
t.run_fast("min_accel_doesnt_round_to_zero", |_| {
// Copied from kinematics.rs, for bikes.
let min_accel = Acceleration::meters_per_second_squared(1.1);
let speed = min_accel * TIMESTEP;
assert!(!speed.is_zero(TIMESTEP));
});
}
// TODO Make sure speed never exceeds the vehicle's cap
#[allow(dead_code)]
fn test_accel_to_stop_in_dist(vehicle: Vehicle, orig_dist_left: Distance, orig_speed: Speed) {
// Can we successfully stop in a certain distance from some initial conditions?
let mut speed = orig_speed;
let mut dist_left = orig_dist_left;
for step in 0..200 {
let desired_accel = vehicle.accel_to_stop_in_dist(speed, dist_left).unwrap();
let accel = vehicle.clamp_accel(desired_accel);
println!(
"Step {}: speed {}, dist_left {}, want accel {} but doing {}",
step, speed, dist_left, desired_accel, accel
);
let (dist_covered, new_speed) = results_of_accel_for_one_tick(speed, accel);
speed = new_speed;
dist_left -= dist_covered;
if dist_left < -EPSILON_DIST {
println!(" Result: speed {}, dist_left {}", speed, dist_left);
panic!("We overshot too much!");
}
if dist_left <= EPSILON_DIST {
println!(" Result: speed {}, dist_left {}", speed, dist_left);
if !speed.is_zero(TIMESTEP) {
panic!("Finished, but going too fast");
}
return;
}
}
println!(" Result: speed {}, dist_left {}", speed, dist_left);
panic!(
"Didn't finish in 20s; only covered {} of {}",
orig_dist_left - dist_left,
orig_dist_left
);
}