associate buses with trips, so some UI plugins can handle them

This commit is contained in:
Dustin Carlino 2018-11-27 14:03:52 -08:00
parent 149758d5ae
commit 5c3ca41722
11 changed files with 122 additions and 43 deletions

View File

@ -389,3 +389,58 @@ no context. So how about this for an alternative:
- should those be defined in one of the places arbitrarily, that we know will be invoked early?
- could a macro help with registration?
- get() being mutable means we have to use RefCell or propogate mutability in lots of sad places
## Mutex plugins
The state of the world today:
- always let turn_cycler draw, even when it's not "active"
- bug today: edit a traffic signal, the "current phase" stays highlighted while editing
- show_owner can always have an opinion on color, even when "inactive"
- want some plugins to be able to coexist... show route, follow, sim ctrl
- other plugins should not stack... while traffic_signal_editor, tooltip is OK, but not much else
I don't want to think through the O(n^2) conflict matrix for plugins, but maybe just a quick list of conflicts...
- things that truly need to be the only active plugin, usually due to a wizard
- ab test manager
- color picker
- draw neighborhood
- floodfiller (debug fine)
- geom validator (debug fine)
- log display (maybe want to let sim run while this is active, but not allow keys?)
- edits manager
- road editor (debug fine)
- scenario manager
- stop sign editor (debug fine)
- time travel (debug is NOT fine and most other stuff also breaks)
- traffic signal editor (debug fine)
- warping (really should pause the sim while animating)
- things that display stuff and can be deactivated (mutex or not?)
- chokepoint finder
- osm classifier
- diff all | diff worlds
- toggleable layers
- neighborhood summary
- search (in one state, needs to eat the whole keyboard)
- activity heatmap
- show owner
- show route
- steepness viz
- turn cycler (blocks input sometimes)
- things that can kinda be active almost anytime (when the real sim is available)
- debug objects
- hider
- follower
- sim controller (maybe need to split this up into different pieces)
Another way of looking at it: what consumes the entire keyboard?
- anything with a wizard
- search (in only one state)
Or maybe another:
- does a plugin block the sim from running?
Or another:
- is a plugin just a toggleable effect? can it compose with others?

View File

@ -70,3 +70,13 @@ routing: https://stackoverflow.com/questions/483488/strategy-to-find-your-best-r
- in helpers, start trip from/to bldg maybe using transit. pathfind first using transit, then figure out the sequence of bus stops from the route, and turn that into the trip.
- so ideally it's easy to know (stop1, route, stop2) almost as a step of the path. can translate that into trip legs pretty easily.
- feels like a different interface, especially because the point is to just generate the stuff for the trip manager. throwing away the rest of the pathfinding stuff! hmm. same algorithm doesn't fit well at all.
## Trips
Buses have no trip, and that breaks some UI stuff. What if we assign some dummy trip for transit that never ends?
- Trips currently have a ped associated; would have to change that.
- Would need to invent a TripLeg called ServeRoute that can be the only one.
- Have to filter out buses from score summary. Can detect easily by lack of ped.
This seems actually not bad. Let's do that.

View File

@ -34,13 +34,11 @@ impl Plugin for DiffWorldsState {
DiffWorldsState::Inactive => {
if ctx.secondary.is_some() {
if let Some(id) = ctx.primary.current_selection.and_then(|id| id.agent_id()) {
if let Some(trip) = ctx.primary.sim.agent_to_trip(id) {
if ctx
.input
.key_pressed(Key::B, &format!("Show {}'s parallel world", trip))
{
new_state = Some(diff_world(trip, ctx));
}
if ctx
.input
.key_pressed(Key::B, &format!("Show {}'s parallel world", id))
{
new_state = Some(diff_world(ctx.primary.sim.agent_to_trip(id), ctx));
}
}
}

View File

@ -19,14 +19,12 @@ impl Plugin for FollowState {
fn event(&mut self, ctx: PluginCtx) -> bool {
if *self == FollowState::Empty {
if let Some(agent) = ctx.primary.current_selection.and_then(|id| id.agent_id()) {
if let Some(trip) = ctx.primary.sim.agent_to_trip(agent) {
if ctx
.input
.key_pressed(Key::F, &format!("follow {:?}", agent))
{
*self = FollowState::Active(trip);
return true;
}
if ctx
.input
.key_pressed(Key::F, &format!("follow {:?}", agent))
{
*self = FollowState::Active(ctx.primary.sim.agent_to_trip(agent));
return true;
}
}
}

View File

@ -29,7 +29,7 @@ impl Plugin for ShowRouteState {
.primary
.current_selection
.and_then(|id| id.agent_id())
.and_then(|agent| ctx.primary.sim.agent_to_trip(agent))
.map(|agent| ctx.primary.sim.agent_to_trip(agent))
{
if ctx
.input

View File

@ -45,8 +45,7 @@ struct ParkingState {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct Car {
id: CarID,
// None for buses
trip: Option<TripID>,
trip: TripID,
owner: Option<BuildingID>,
on: Traversable,
speed: Speed,
@ -893,7 +892,7 @@ impl DrivingSimState {
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub struct CreateCar {
pub car: CarID,
pub trip: Option<TripID>,
pub trip: TripID,
pub owner: Option<BuildingID>,
pub maybe_parked_car: Option<ParkedCar>,
pub vehicle: Vehicle,

View File

@ -227,6 +227,7 @@ impl Sim {
map,
&mut self.driving_state,
&mut self.transit_state,
&mut self.trips_state,
self.time,
)
}

View File

@ -65,10 +65,8 @@ impl Scheduler {
match cmd {
Command::SpawnCar(_, create_car) => {
if driving_sim.start_car_on_lane(events, now, map, create_car.clone()) {
trips.agent_starting_trip_leg(
AgentID::Car(create_car.car),
create_car.trip.unwrap(),
);
trips
.agent_starting_trip_leg(AgentID::Car(create_car.car), create_car.trip);
if let Some(parked_car) = create_car.maybe_parked_car {
parking_sim.remove_parked_car(parked_car);
}

View File

@ -374,7 +374,7 @@ impl Sim {
}
}
pub fn agent_to_trip(&self, id: AgentID) -> Option<TripID> {
pub fn agent_to_trip(&self, id: AgentID) -> TripID {
self.trips_state.agent_to_trip(id)
}

View File

@ -14,7 +14,9 @@ use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
use transit::TransitSimState;
use trips::{TripLeg, TripManager};
use walking::{CreatePedestrian, SidewalkSpot};
use {CarID, Event, ParkedCar, ParkingSpot, PedestrianID, RouteID, Tick, TripID, VehicleType};
use {
AgentID, CarID, Event, ParkedCar, ParkingSpot, PedestrianID, RouteID, Tick, TripID, VehicleType,
};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
enum Command {
@ -184,7 +186,7 @@ impl Spawner {
now,
CreateCar {
car,
trip: Some(trip),
trip: trip,
owner: parked_car.owner,
maybe_parked_car: Some(parked_car.clone()),
vehicle: parked_car.vehicle.clone(),
@ -212,7 +214,7 @@ impl Spawner {
now,
CreateCar {
car,
trip: Some(trip),
trip: trip,
// TODO need a way to specify this in the scenario
owner: None,
maybe_parked_car: None,
@ -239,7 +241,7 @@ impl Spawner {
now,
CreateCar {
car: vehicle.id,
trip: Some(trip),
trip: trip,
owner: None,
maybe_parked_car: None,
vehicle: vehicle.clone(),
@ -286,6 +288,7 @@ impl Spawner {
map: &Map,
driving_sim: &mut DrivingSimState,
transit_sim: &mut TransitSimState,
trips: &mut TripManager,
now: Tick,
) -> (RouteID, Vec<CarID>) {
let route_id = transit_sim.create_empty_route(route, map);
@ -302,13 +305,15 @@ impl Spawner {
start_dist_along,
);
// TODO Aww, we create an orphan trip if the bus can't spawn.
let trip = trips.new_trip(now, None, vec![TripLeg::ServeBusRoute(id, route_id)]);
if driving_sim.start_car_on_lane(
events,
now,
map,
CreateCar {
car: id,
trip: None,
trip,
owner: None,
maybe_parked_car: None,
vehicle,
@ -316,6 +321,7 @@ impl Spawner {
router: Router::make_router_for_bus(path),
},
) {
trips.agent_starting_trip_leg(AgentID::Car(id), trip);
transit_sim.bus_created(id, route_id, next_stop_idx);
info!("Spawned bus {} for route {} ({})", id, route.name, route_id);
results.push(id);
@ -447,7 +453,7 @@ impl Spawner {
}
self.enqueue_command(Command::DriveFromBorder {
at,
trip: trips.new_trip(at, ped_id, legs),
trip: trips.new_trip(at, Some(ped_id), legs),
car: car_id,
vehicle: Vehicle::generate_car(car_id, base_rng),
start: first_lane,
@ -490,7 +496,7 @@ impl Spawner {
}
self.enqueue_command(Command::Walk(
at,
trips.new_trip(at, ped_id, legs),
trips.new_trip(at, Some(ped_id), legs),
ped_id,
SidewalkSpot::building(start_bldg, map),
parking_spot,
@ -525,7 +531,7 @@ impl Spawner {
}
self.enqueue_command(Command::Walk(
at,
trips.new_trip(at, ped_id, legs),
trips.new_trip(at, Some(ped_id), legs),
ped_id,
SidewalkSpot::building(start_bldg, map),
first_spot,
@ -554,7 +560,7 @@ impl Spawner {
];
self.enqueue_command(Command::Walk(
at,
trips.new_trip(at, ped_id, legs),
trips.new_trip(at, Some(ped_id), legs),
ped_id,
start,
first_stop,
@ -584,7 +590,7 @@ impl Spawner {
}
self.enqueue_command(Command::DriveFromBorder {
at,
trip: trips.new_trip(at, ped_id, legs),
trip: trips.new_trip(at, Some(ped_id), legs),
car: bike_id,
vehicle,
start: first_lane,
@ -604,7 +610,7 @@ impl Spawner {
self.enqueue_command(Command::Walk(
at,
trips.new_trip(at, ped_id, vec![TripLeg::Walk(goal.clone())]),
trips.new_trip(at, Some(ped_id), vec![TripLeg::Walk(goal.clone())]),
ped_id,
start,
goal,

View File

@ -51,7 +51,7 @@ impl TripManager {
TripLeg::Walk(ref to) => to,
ref x => panic!("Next trip leg is {:?}, not walking", x),
};
(trip.id, trip.ped, walk_to.clone())
(trip.id, trip.ped.unwrap(), walk_to.clone())
}
// Where are we driving next?
@ -105,7 +105,7 @@ impl TripManager {
TripLeg::Walk(ref to) => to,
ref x => panic!("Next trip leg is {:?}, not walking", x),
};
(trip.id, trip.ped, walk_to.clone())
(trip.id, trip.ped.unwrap(), walk_to.clone())
}
pub fn ped_reached_building_or_border(&mut self, ped: PedestrianID, now: Tick) {
@ -192,7 +192,12 @@ impl TripManager {
}
// Creation from the interactive part of spawner
pub fn new_trip(&mut self, spawned_at: Tick, ped: PedestrianID, legs: Vec<TripLeg>) -> TripID {
pub fn new_trip(
&mut self,
spawned_at: Tick,
ped: Option<PedestrianID>,
legs: Vec<TripLeg>,
) -> TripID {
assert!(!legs.is_empty());
// TODO Make sure the legs constitute a valid state machine.
@ -207,6 +212,7 @@ impl TripManager {
.find(|l| match l {
TripLeg::Drive(_, _) => true,
TripLeg::DriveFromBorder(_, _) => true,
TripLeg::ServeBusRoute(_, _) => true,
_ => false,
}).is_some(),
legs: VecDeque::from(legs),
@ -236,6 +242,10 @@ impl TripManager {
};
// TODO or would it make more sense to aggregate events as they happen?
for t in &self.trips {
// Don't count transit
if t.ped.is_none() {
continue;
}
if t.uses_car {
if let Some(at) = t.finished_at {
summary.total_driving_trip_time += at - t.spawned_at;
@ -268,17 +278,18 @@ impl TripManager {
pub fn trip_to_agent(&self, id: TripID) -> Option<AgentID> {
let trip = self.trips.get(id.0)?;
match trip.legs.get(0)? {
TripLeg::Walk(_) => Some(AgentID::Pedestrian(trip.ped)),
TripLeg::Walk(_) => Some(AgentID::Pedestrian(trip.ped.unwrap())),
TripLeg::Drive(ref parked, _) => Some(AgentID::Car(parked.car)),
TripLeg::DriveFromBorder(id, _) => Some(AgentID::Car(*id)),
TripLeg::Bike(vehicle, _) => Some(AgentID::Car(vehicle.id)),
// TODO Should be the bus, but apparently transit sim tracks differently?
TripLeg::RideBus(_, _) => Some(AgentID::Pedestrian(trip.ped)),
TripLeg::RideBus(_, _) => Some(AgentID::Pedestrian(trip.ped.unwrap())),
TripLeg::ServeBusRoute(id, _) => Some(AgentID::Car(*id)),
}
}
pub fn agent_to_trip(&self, id: AgentID) -> Option<TripID> {
self.active_trip_mode.get(&id).map(|t| *t)
pub fn agent_to_trip(&self, id: AgentID) -> TripID {
self.active_trip_mode[&id]
}
pub fn get_active_trips(&self) -> Vec<TripID> {
@ -293,7 +304,8 @@ struct Trip {
finished_at: Option<Tick>,
// TODO also uses_bike, so we can track those stats differently too
uses_car: bool,
ped: PedestrianID,
// If none, then this is a bus. The trip will never end.
ped: Option<PedestrianID>,
legs: VecDeque<TripLeg>,
}
@ -309,6 +321,7 @@ pub enum TripLeg {
DriveFromBorder(CarID, DrivingGoal),
Bike(Vehicle, DrivingGoal),
RideBus(RouteID, BusStopID),
ServeBusRoute(CarID, RouteID),
}
impl TripLeg {
@ -317,6 +330,7 @@ impl TripLeg {
TripLeg::Drive(parked, _) => parked.car == id,
TripLeg::DriveFromBorder(car, _) => *car == id,
TripLeg::Bike(vehicle, _) => vehicle.id == id,
TripLeg::ServeBusRoute(car, _) => *car == id,
_ => false,
}
}