traffic_sim/
light.rs

1use crate::{LinkId, TrafficControl};
2use serde::{Deserialize, Serialize};
3use std::cell::Cell;
4
5/// A set of coordinated traffic lights.
6#[derive(Serialize, Deserialize, Clone, Default)]
7pub struct TrafficLight {
8    /// The movements.
9    movements: Vec<Movement>,
10    /// The links controlled by each movement.
11    links: Vec<(u8, LinkId)>,
12    /// The conflicts between the movements.
13    conflicts: Vec<Conflict>,
14    /// The traffic light phases.
15    phases: Vec<Phase>,
16    /// The current traffic light phase.
17    phase: usize,
18}
19
20/// A single traffic light movement.
21#[derive(Serialize, Deserialize, Clone, Debug)]
22struct Movement {
23    /// The current state.
24    state: LightState,
25    /// The next state.
26    next_state: Cell<LightState>,
27    /// Whether the target state is green.
28    active: bool,
29    /// The time since the current state was entered.
30    since: f64,
31    /// The duration of the amber phase in seconds.
32    amber_time: f64,
33}
34
35/// Represents one traffic light movement's conflict with another.
36#[derive(Serialize, Deserialize, Clone, Copy)]
37struct Conflict {
38    /// The movement which is the subject of the conflict.
39    subject: usize,
40    /// The movement which conflicts with the subject.
41    other: usize,
42    /// The number of seconds that the conflicting movement must be red
43    /// before the subject movement is allowed to turn green.
44    wait: f64,
45}
46
47/// The state of a traffic light movement.
48#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)]
49pub enum LightState {
50    Red,
51    Amber,
52    Green,
53}
54
55/// A traffic light phase.
56#[derive(Serialize, Deserialize, Clone, Copy)]
57pub struct Phase {
58    /// A bit mask denoting which movements should be green in this phase.
59    mask: u64,
60    /// The duration of the phase once all lights have turned green.
61    duration: f64,
62}
63
64impl TrafficLight {
65    /// Creates a new traffic light system.
66    pub fn new() -> Self {
67        Default::default()
68    }
69
70    /// Adds a movement to the traffic light, which is a group of one or more links
71    /// controlled by the same signal head(s) and so go and stop together.
72    ///
73    /// # Parameters
74    /// * `amber_time` - The duration of the amber phase of this movement, in seconds.
75    /// * `links` - An iterator which produces the set of links that belong to this movement.
76    pub fn add_movement(&mut self, amber_time: f64, links: impl Iterator<Item = LinkId>) {
77        let idx = self.movements.len() as u8;
78        self.movements.push(Movement {
79            state: LightState::Red,
80            next_state: Cell::new(LightState::Red),
81            active: false,
82            since: 0.0,
83            amber_time,
84        });
85        self.links.extend(links.map(|link| (idx, link)));
86    }
87
88    /// Adds a conflict between two movements to the traffic light.
89    pub fn add_conflict(&mut self, subject: usize, other: usize, wait: f64) {
90        self.conflicts.push(Conflict {
91            subject,
92            other,
93            wait,
94        });
95    }
96
97    /// Adds a phase to the traffic light timing.
98    pub fn add_phase(&mut self, mask: u64, duration: f64) {
99        self.phases.push(Phase { mask, duration });
100    }
101
102    /// Advances the traffic light timing by one frame.
103    pub fn step(&mut self, dt: f64) {
104        self.update_phase();
105        self.apply_phase();
106
107        // Compute which movements should change state
108        for (idx, movement) in self.movements.iter().enumerate() {
109            use LightState::*;
110            let next = match (movement.active, movement.state) {
111                (false, Green) => Amber,
112                (false, Amber) if movement.since >= movement.amber_time => Red,
113                (true, Amber) => Green,
114                (true, Red) if self.can_turn_green(idx) => Green,
115                (_, state) => state,
116            };
117            movement.next_state.set(next);
118        }
119
120        // Update each movement
121        for movement in &mut self.movements {
122            movement.step(dt);
123        }
124    }
125
126    /// Advances to the next traffic light phase if appropriate.
127    fn update_phase(&mut self) {
128        if self.phases.is_empty() {
129            return;
130        }
131
132        let Phase { duration, mask } = self.phases[self.phase];
133        let phase_complete = self
134            .movements
135            .iter()
136            .enumerate()
137            .filter(|(index, _)| mask & (1 << index) != 0)
138            .all(|(_, m)| m.state == LightState::Green && m.since >= duration);
139
140        if phase_complete {
141            self.phase = (self.phase + 1) % self.phases.len();
142        }
143    }
144
145    /// Applies the current traffic light phase to each movement.
146    fn apply_phase(&mut self) {
147        let Phase { mask, .. } = self.phases[self.phase];
148        for (idx, movement) in self.movements.iter_mut().enumerate() {
149            movement.active = (mask >> idx) & 1 != 0;
150        }
151    }
152
153    /// Gets the current state of each link
154    pub fn get_states(&self) -> impl Iterator<Item = (LinkId, TrafficControl)> + '_ {
155        self.links.iter().map(|(idx, link)| {
156            let control = match self.movements[*idx as usize].state {
157                LightState::Red => TrafficControl::Closed,
158                LightState::Amber => TrafficControl::Closed,
159                LightState::Green => TrafficControl::Open,
160            };
161            (*link, control)
162        })
163    }
164
165    /// Checks that a movement is not blocked by any other movements.
166    fn can_turn_green(&self, movement: usize) -> bool {
167        self.conflicts
168            .iter()
169            .filter(|conflict| conflict.subject == movement)
170            .all(|conflict| {
171                let movement = &self.movements[conflict.other];
172                movement.state == LightState::Red && movement.since >= conflict.wait
173            })
174    }
175}
176
177impl Movement {
178    fn step(&mut self, dt: f64) {
179        if self.next_state.get() != self.state {
180            self.state = self.next_state.get();
181            self.since = dt;
182        } else {
183            self.since += dt;
184        }
185    }
186}