mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 03:35:51 +03:00
implement yielding in traffic signals. pretty untested.
This commit is contained in:
parent
f6562e97ea
commit
609c731fa0
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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...)
|
||||
|
@ -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?
|
||||
|
@ -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 ##
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user