implement yielding in traffic signals. pretty untested.

This commit is contained in:
Dustin Carlino 2018-11-30 11:30:25 -08:00
parent f6562e97ea
commit 609c731fa0
8 changed files with 158 additions and 69 deletions

View File

@ -1,8 +1,8 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use dimensioned::si;
use map_model::{IntersectionID, Map, TurnID};
use std;
use std::collections::BTreeSet;
use TurnPriority;
const CYCLE_DURATION: si::Second<f64> = si::Second {
value_unsafe: 15.0,
@ -41,90 +41,106 @@ impl ControlTrafficSignal {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Cycle {
pub turns: Vec<TurnID>,
// in the future, what pedestrian crossings this cycle includes, etc
pub priority_turns: BTreeSet<TurnID>,
pub yield_turns: BTreeSet<TurnID>,
changed: bool,
duration: si::Second<f64>,
}
impl Cycle {
pub fn conflicts_with(&self, t1: TurnID, map: &Map) -> bool {
for t2 in &self.turns {
pub fn could_be_priority_turn(&self, t1: TurnID, map: &Map) -> bool {
for t2 in &self.priority_turns {
if map.get_t(t1).conflicts_with(map.get_t(*t2)) {
return true;
return false;
}
}
false
true
}
pub fn contains(&self, t: TurnID) -> bool {
self.turns.contains(&t)
pub fn get_priority(&self, t: TurnID) -> TurnPriority {
if self.priority_turns.contains(&t) {
TurnPriority::Priority
} else if self.yield_turns.contains(&t) {
TurnPriority::Yield
} else {
TurnPriority::Stop
}
}
pub fn add(&mut self, t: TurnID) {
pub fn add(&mut self, t: TurnID, pri: TurnPriority) {
// should assert parent matches, compatible with cycle so far, not in the current set
self.turns.push(t);
self.changed = true;
match pri {
TurnPriority::Priority => {
self.priority_turns.insert(t);
self.changed = true;
}
TurnPriority::Yield => {
self.yield_turns.insert(t);
self.changed = true;
}
TurnPriority::Stop => {}
}
}
pub fn remove(&mut self, t: TurnID) {
// should assert in current set
let idx = self.turns.iter().position(|&id| id == t).unwrap();
self.turns.remove(idx);
self.changed = true;
if self.priority_turns.contains(&t) {
self.priority_turns.remove(&t);
} else if self.yield_turns.contains(&t) {
self.yield_turns.remove(&t);
} else {
panic!(
"Cycle {:?} doesn't have {} as a priority or yield turn; why remove it?",
self, t
);
}
}
}
fn greedy_assignment(map: &Map, intersection: IntersectionID) -> Vec<Cycle> {
// TODO should be a tmp hack; intersections with no turns aren't even valid
if map.get_turns_in_intersection(intersection).is_empty() {
error!("WARNING: {} has no turns", intersection);
return vec![Cycle {
turns: Vec::new(),
changed: false,
duration: CYCLE_DURATION,
}];
panic!("{} has no turns", intersection);
}
let mut cycles = Vec::new();
// Greedily partition turns into cycles. More clever things later.
// Greedily partition turns into cycles. More clever things later. No yields.
let mut remaining_turns: Vec<TurnID> = map
.get_turns_in_intersection(intersection)
.iter()
.map(|t| t.id)
.collect();
let mut current_cycle = Cycle {
turns: Vec::new(),
priority_turns: BTreeSet::new(),
yield_turns: BTreeSet::new(),
changed: false,
duration: CYCLE_DURATION,
};
while !remaining_turns.is_empty() {
loop {
let add_turn = remaining_turns
.iter()
.position(|&t| !current_cycle.conflicts_with(t, map));
.position(|&t| current_cycle.could_be_priority_turn(t, map));
match add_turn {
Some(idx) => {
current_cycle.turns.push(remaining_turns[idx]);
remaining_turns.remove(idx);
current_cycle
.priority_turns
.insert(remaining_turns.remove(idx));
}
None => {
cycles.push(current_cycle.clone());
current_cycle.turns = Vec::new();
current_cycle.priority_turns.clear();
if remaining_turns.is_empty() {
break;
}
}
}
}
// TODO not sure this condition is needed
if !current_cycle.turns.is_empty() {
cycles.push(current_cycle.clone());
}
expand_all_cycles(&mut cycles, map, intersection);
cycles
}
// Add all legal turns to existing cycles.
// Add all legal priority turns to existing cycles.
fn expand_all_cycles(cycles: &mut Vec<Cycle>, map: &Map, intersection: IntersectionID) {
let all_turns: Vec<TurnID> = map
.get_turns_in_intersection(intersection)
@ -133,8 +149,8 @@ fn expand_all_cycles(cycles: &mut Vec<Cycle>, map: &Map, intersection: Intersect
.collect();
for cycle in cycles.iter_mut() {
for t in &all_turns {
if !cycle.contains(*t) && !cycle.conflicts_with(*t, map) {
cycle.turns.push(*t);
if !cycle.priority_turns.contains(t) && cycle.could_be_priority_turn(*t, map) {
cycle.priority_turns.insert(*t);
}
}
}

View File

@ -46,7 +46,6 @@
- code cleanup
- move map_model geometry stuff elsewhere (sim stuff also needs it though)
- merge control map into one of the other layers?
- better drawing
- detailed turns, like https://i.ytimg.com/vi/NH6R3RH_ZDY/maxresdefault.jpg

View File

@ -6,4 +6,4 @@
- create a bike network with minimal hills, dedicated roads, minimal crossings
- easter eggs
- name agents, with some good names scattered in
- name agents, with some good names scattered in (Dustin Carlino, Dustin Bikelino, Dustin Buslino...)

View File

@ -119,6 +119,11 @@ At some point, geometry was a separate layer from the graph base-layer of
map_model. That doesn't work -- we can't even reason about what turns logically
exist without operating on cleaned-up geometry.
Control used to be separate from map_model for similar "purity" reasons.
map_model was supposed to just be unbiased representation of the world, no
semantics on top. Except bus stops and routes crept it, and map edits lived
there. Separate control layer is just awkward.
## IDs
Should LaneID have LaneType bundled in for convenience? CarID and VehicleType?

View File

@ -47,7 +47,7 @@ walking figure, probably at both tips of a sidewalk.
### Yielding
Similar to a stop sign, there are turn priorities for cycles. Some turns have
right-of-way, others need to yield, but can go if there's room.
right-of-way, others need to yield, but can go if there's room
## Intersection policies for pedestrians ##

View File

@ -2,6 +2,7 @@
// TODO how to edit cycle time?
use control::TurnPriority;
use ezgui::Color;
use map_model::IntersectionID;
use objects::{Ctx, ID};
@ -86,15 +87,23 @@ impl Plugin for TrafficSignalEditor {
let cycle =
&mut control_map.traffic_signals.get_mut(&i).unwrap().cycles
[*current_cycle];
if cycle.contains(id) {
if cycle.get_priority(id) == TurnPriority::Priority {
if input
.key_pressed(Key::Backspace, "remove this turn from this cycle")
{
cycle.remove(id);
}
} else if !cycle.conflicts_with(id, map) {
if input.key_pressed(Key::Space, "add this turn to this cycle") {
cycle.add(id);
} else if cycle.could_be_priority_turn(id, map) {
if input.key_pressed(
Key::Space,
"add this turn to this cycle as priority",
) {
cycle.add(id, TurnPriority::Priority);
}
} else if cycle.get_priority(id) == TurnPriority::Stop {
if input.key_pressed(Key::Y, "add this turn to this cycle as yield")
{
cycle.add(id, TurnPriority::Yield);
}
}
}
@ -121,17 +130,29 @@ impl Plugin for TrafficSignalEditor {
let cycle = &ctx.control_map.traffic_signals[&i].cycles[*current_cycle];
if cycle.contains(t) {
Some(ctx.cs.get("turn in current cycle", Color::GREEN))
} else if !cycle.conflicts_with(t, ctx.map) {
Some(ctx.cs.get(
"turn could be in current cycle",
Color::rgba(0, 255, 0, 0.2),
))
} else {
Some(ctx.cs.get("turn conflicts with current cycle", Color::RED))
}
// TODO maybe something to indicate unused in any cycle so far
let could_be_priority = cycle.could_be_priority_turn(t, ctx.map);
match cycle.get_priority(t) {
TurnPriority::Priority => {
Some(ctx.cs.get("priority turn in current cycle", Color::GREEN))
}
TurnPriority::Yield => if could_be_priority {
Some(ctx.cs.get(
"yield turn that could be priority turn",
Color::rgb(154, 205, 50),
))
} else {
Some(ctx.cs.get("yield turn in current cycle", Color::YELLOW))
},
TurnPriority::Stop => if could_be_priority {
Some(
ctx.cs
.get("stop turn that could be priority", Color::rgb(103, 49, 71)),
)
} else {
Some(ctx.cs.get("turn conflicts with current cycle", Color::RED))
},
}
}
_ => None,
}

View File

@ -1,4 +1,4 @@
use control::ControlTrafficSignal;
use control::{ControlTrafficSignal, TurnPriority};
use dimensioned::si;
use ezgui::{Color, GfxCtx};
use geom::Circle;
@ -123,6 +123,7 @@ impl Plugin for TurnCyclerState {
}
fn draw_traffic_signal(signal: &ControlTrafficSignal, g: &mut GfxCtx, ctx: Ctx) {
// TODO Cycle might be over-run; should depict that by asking sim layer.
// TODO It'd be cool to indicate remaining time in the cycle by slowly dimming the color or
// something.
let (cycle, _) = signal.current_cycle_and_remaining_time(ctx.sim.time.as_time());
@ -135,7 +136,7 @@ fn draw_traffic_signal(signal: &ControlTrafficSignal, g: &mut GfxCtx, ctx: Ctx)
);
// TODO Maybe don't show SharedSidewalkCorners at all, and highlight the
// Crosswalks.
for t in &cycle.turns {
for t in &cycle.priority_turns {
for m in turn_markings(ctx.map.get_t(*t), ctx.map) {
m.draw(g, ctx.cs, color);
}
@ -143,24 +144,31 @@ fn draw_traffic_signal(signal: &ControlTrafficSignal, g: &mut GfxCtx, ctx: Ctx)
}
// Second style: draw little circles on the incoming lanes to indicate what turns are possible.
// All of them -> green. Some of them -> yellow. None of them -> don't draw.
{
for l in &ctx.map.get_i(signal.id).incoming_lanes {
let mut num_green = 0;
let mut num_yield = 0;
let mut num_red = 0;
for (t, _) in ctx.map.get_next_turns_and_lanes(*l, signal.id) {
if cycle.contains(t.id) {
num_green += 1;
} else {
num_red += 1;
}
match cycle.get_priority(t.id) {
TurnPriority::Priority => {
num_green += 1;
}
TurnPriority::Yield => {
num_yield += 1;
}
TurnPriority::Stop => {
num_red += 1;
}
};
}
if num_green == 0 {
// TODO Adjust this more.
if num_green == 0 && num_yield == 0 {
continue;
}
let color = if num_red == 0 {
let color = if num_yield == 0 && num_red == 0 {
ctx.cs.get(
"all turns from lane allowed by traffic signal right now",
Color::GREEN,

View File

@ -109,7 +109,7 @@ impl IntersectionSimState {
p.step(events, time, map, control_map, view)
}
IntersectionPolicy::TrafficSignalPolicy(ref mut p) => {
p.step(events, time, control_map, view)
p.step(events, time, map, control_map, view)
}
IntersectionPolicy::BorderPolicy => {}
}
@ -346,6 +346,7 @@ impl TrafficSignal {
&mut self,
events: &mut Vec<Event>,
time: Tick,
map: &Map,
control_map: &ControlMap,
view: &WorldView,
) {
@ -355,7 +356,7 @@ impl TrafficSignal {
// For now, just maintain safety when agents over-run.
for req in self.accepted.iter() {
if !cycle.contains(req.turn) {
if cycle.get_priority(req.turn) == TurnPriority::Stop {
if self.debug {
debug!(
"{:?} is still doing {:?} after the cycle is over",
@ -366,17 +367,47 @@ impl TrafficSignal {
}
}
let priority_requests: BTreeSet<TurnID> = self
.requests
.iter()
.filter_map(|req| {
if cycle.get_priority(req.turn) == TurnPriority::Priority {
Some(req.turn)
} else {
None
}
}).collect();
let mut keep_requests: BTreeSet<Request> = BTreeSet::new();
for req in self.requests.iter() {
assert_eq!(req.turn.parent, self.id);
assert_eq!(self.accepted.contains(&req), false);
// Don't accept cars unless they're in front. TODO or behind other accepted cars.
if !cycle.contains(req.turn) || !view.is_leader(req.agent) {
// Can't go at all this cycle.
if cycle.get_priority(req.turn) == TurnPriority::Stop
// Don't accept cars unless they're in front. TODO or behind other accepted cars.
|| !view.is_leader(req.agent)
|| self.conflicts_with_accepted(req.turn, map)
{
keep_requests.insert(req.clone());
continue;
}
// If there's a conflicting Priority request, don't go, even if that Priority
// request can't go right now (due to a conflicting previously-accepted accepted
// Yield).
if cycle.get_priority(req.turn) == TurnPriority::Yield {
let base_t = map.get_t(req.turn);
if priority_requests
.iter()
.find(|t| base_t.conflicts_with(map.get_t(**t)))
.is_some()
{
keep_requests.insert(req.clone());
continue;
}
}
// TODO Don't accept agents if they won't make the light. But calculating that is
// hard...
//let crossing_time = turn.length() / speeds[&agent];
@ -391,4 +422,13 @@ impl TrafficSignal {
self.requests = keep_requests;
}
// TODO Code duplication :(
fn conflicts_with_accepted(&self, turn: TurnID, map: &Map) -> bool {
let base_t = map.get_t(turn);
self.accepted
.iter()
.find(|req| base_t.conflicts_with(map.get_t(req.turn)))
.is_some()
}
}