1 // This runs a simulation without any graphics and serves a very basic API to control things. The
2 // API is not documented yet. To run this:
3 //
4 // > cd headless; cargo run -- --port=1234 ../data/system/scenarios/montlake/weekday.bin
5 // > curl http://localhost:1234/get-time
6 // 00:00:00.0
7 // > curl http://localhost:1234/goto-time?t=01:01:00
8 // it's now 01:01:00.0
9 // > curl http://localhost:1234/get-delays
10 // ... huge JSON blob
11
12 use abstutil::{serialize_btreemap, CmdArgs, Timer};
13 use geom::{Duration, LonLat, Time};
14 use hyper::{Body, Request, Response, Server};
15 use map_model::{
16 CompressedMovementID, ControlTrafficSignal, EditCmd, EditIntersection, IntersectionID, Map,
17 MovementID, PermanentMapEdits,
18 };
19 use serde::Serialize;
20 use sim::{
21 AlertHandler, GetDrawAgents, PersonID, Sim, SimFlags, SimOptions, TripID, TripMode, VehicleType,
22 };
23 use std::collections::{BTreeMap, HashMap};
24 use std::convert::TryFrom;
25 use std::error::Error;
26 use std::sync::RwLock;
27
28 lazy_static::lazy_static! {
29 static ref MAP: RwLock<Map> = RwLock::new(Map::blank());
30 static ref SIM: RwLock<Sim> = RwLock::new(Sim::new(&Map::blank(), SimOptions::new("tmp"), &mut Timer::throwaway()));
31 // TODO Readonly?
32 static ref FLAGS: RwLock<SimFlags> = RwLock::new(SimFlags::for_test("tmp"));
33 }
34
35 #[tokio::main]
main()36 async fn main() {
37 let mut args = CmdArgs::new();
38 let mut sim_flags = SimFlags::from_args(&mut args);
39 let port = args.required("--port").parse::<u16>().unwrap();
40 args.done();
41
42 // Less spam
43 sim_flags.opts.alerts = AlertHandler::Silence;
44 let (map, sim, _) = sim_flags.load(&mut Timer::new("setup headless"));
45 *MAP.write().unwrap() = map;
46 *SIM.write().unwrap() = sim;
47 *FLAGS.write().unwrap() = sim_flags;
48
49 let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
50 println!("Listening on http://{}", addr);
51 let serve_future = Server::bind(&addr).serve(hyper::service::make_service_fn(|_| async {
52 Ok::<_, hyper::Error>(hyper::service::service_fn(serve_req))
53 }));
54 if let Err(err) = serve_future.await {
55 panic!("Server error: {}", err);
56 }
57 }
58
serve_req(req: Request<Body>) -> Result<Response<Body>, hyper::Error>59 async fn serve_req(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
60 let path = req.uri().path().to_string();
61 // Url::parse needs an absolute URL
62 let params: HashMap<String, String> =
63 url::Url::parse(&format!("http://localhost{}", req.uri()))
64 .unwrap()
65 .query_pairs()
66 .map(|(k, v)| (k.to_string(), v.to_string()))
67 .collect();
68 let body = hyper::body::to_bytes(req).await?.to_vec();
69 let resp = match handle_command(
70 &path,
71 ¶ms,
72 &body,
73 &mut SIM.write().unwrap(),
74 &mut MAP.write().unwrap(),
75 ) {
76 Ok(resp) => resp,
77 Err(err) => {
78 // TODO Error codes
79 format!("Bad command {} with params {:?}: {}", path, params, err)
80 }
81 };
82 Ok(Response::new(Body::from(resp)))
83 }
84
handle_command( path: &str, params: &HashMap<String, String>, body: &Vec<u8>, sim: &mut Sim, map: &mut Map, ) -> Result<String, Box<dyn Error>>85 fn handle_command(
86 path: &str,
87 params: &HashMap<String, String>,
88 body: &Vec<u8>,
89 sim: &mut Sim,
90 map: &mut Map,
91 ) -> Result<String, Box<dyn Error>> {
92 match path {
93 // Controlling the simulation
94 "/sim/reset" => {
95 let (new_map, new_sim, _) = FLAGS.read().unwrap().load(&mut Timer::new("reset sim"));
96 *map = new_map;
97 *sim = new_sim;
98 Ok(format!("sim reloaded"))
99 }
100 "/sim/get-time" => Ok(sim.time().to_string()),
101 "/sim/goto-time" => {
102 let t = Time::parse(¶ms["t"])?;
103 if t <= sim.time() {
104 Err(format!("{} is in the past. call /sim/reset first?", t).into())
105 } else {
106 let dt = t - sim.time();
107 sim.timed_step(map, dt, &mut None, &mut Timer::new("goto-time"));
108 Ok(format!("it's now {}", t))
109 }
110 }
111 // Traffic signals
112 "/traffic-signals/get" => {
113 let i = IntersectionID(params["id"].parse::<usize>()?);
114 if let Some(ts) = map.maybe_get_traffic_signal(i) {
115 Ok(abstutil::to_json(ts))
116 } else {
117 Err(format!("{} isn't a traffic signal", i).into())
118 }
119 }
120 "/traffic-signals/set" => {
121 let ts: ControlTrafficSignal = abstutil::from_json(body)?;
122 let id = ts.id;
123
124 // incremental_edit_traffic_signal is the cheap option, but since we may need to call
125 // get-edits later, go through the proper flow.
126 let mut edits = map.get_edits().clone();
127 edits.commands.push(EditCmd::ChangeIntersection {
128 i: id,
129 old: map.get_i_edit(id),
130 new: EditIntersection::TrafficSignal(ts.export(map)),
131 });
132 map.must_apply_edits(edits, &mut Timer::throwaway());
133 map.recalculate_pathfinding_after_edits(&mut Timer::throwaway());
134
135 Ok(format!("{} has been updated", id))
136 }
137 "/traffic-signals/get-delays" => {
138 let i = IntersectionID(params["id"].parse::<usize>()?);
139 let t1 = Time::parse(¶ms["t1"])?;
140 let t2 = Time::parse(¶ms["t2"])?;
141 let ts = if let Some(ts) = map.maybe_get_traffic_signal(i) {
142 ts
143 } else {
144 return Err(format!("{} isn't a traffic signal", i).into());
145 };
146 let movements: Vec<&MovementID> = ts.movements.keys().collect();
147
148 let mut delays = Delays {
149 per_direction: BTreeMap::new(),
150 };
151 for m in ts.movements.keys() {
152 delays.per_direction.insert(m.clone(), Vec::new());
153 }
154 if let Some(list) = sim.get_analytics().intersection_delays.get(&i) {
155 for (idx, t, dt, _) in list {
156 if *t >= t1 && *t <= t2 {
157 delays
158 .per_direction
159 .get_mut(movements[*idx as usize])
160 .unwrap()
161 .push(*dt);
162 }
163 }
164 }
165 Ok(abstutil::to_json(&delays))
166 }
167 "/traffic-signals/get-cumulative-thruput" => {
168 let i = IntersectionID(params["id"].parse::<usize>()?);
169 let ts = if let Some(ts) = map.maybe_get_traffic_signal(i) {
170 ts
171 } else {
172 return Err(format!("{} isn't a traffic signal", i).into());
173 };
174
175 let mut thruput = Throughput {
176 per_direction: BTreeMap::new(),
177 };
178 for (idx, m) in ts.movements.keys().enumerate() {
179 thruput.per_direction.insert(
180 m.clone(),
181 sim.get_analytics()
182 .traffic_signal_thruput
183 .total_for(CompressedMovementID {
184 i,
185 idx: u8::try_from(idx).unwrap(),
186 }),
187 );
188 }
189 Ok(abstutil::to_json(&thruput))
190 }
191 // Querying data
192 "/data/get-finished-trips" => Ok(abstutil::to_json(&FinishedTrips {
193 trips: sim.get_analytics().finished_trips.clone(),
194 })),
195 "/data/get-agent-positions" => Ok(abstutil::to_json(&AgentPositions {
196 agents: sim
197 .get_unzoomed_agents(map)
198 .into_iter()
199 .map(|a| AgentPosition {
200 vehicle_type: a.vehicle_type,
201 pos: a.pos.to_gps(map.get_gps_bounds()),
202 person: a.person,
203 })
204 .collect(),
205 })),
206 // Querying the map
207 "/map/get-edits" => {
208 let mut edits = map.get_edits().clone();
209 edits.commands.clear();
210 edits.compress(map);
211 Ok(abstutil::to_json(&PermanentMapEdits::to_permanent(
212 &edits, map,
213 )))
214 }
215 _ => Err("Unknown command".into()),
216 }
217 }
218
219 // TODO I think specifying the API with protobufs or similar will be a better idea.
220
221 #[derive(Serialize)]
222 struct FinishedTrips {
223 // TODO Hack: No TripMode means aborted
224 // Finish time, ID, mode (or None as aborted), trip duration
225 trips: Vec<(Time, TripID, Option<TripMode>, Duration)>,
226 }
227
228 #[derive(Serialize)]
229 struct Delays {
230 #[serde(serialize_with = "serialize_btreemap")]
231 per_direction: BTreeMap<MovementID, Vec<Duration>>,
232 }
233
234 #[derive(Serialize)]
235 struct Throughput {
236 #[serde(serialize_with = "serialize_btreemap")]
237 per_direction: BTreeMap<MovementID, usize>,
238 }
239
240 #[derive(Serialize)]
241 struct AgentPositions {
242 agents: Vec<AgentPosition>,
243 }
244
245 #[derive(Serialize)]
246 struct AgentPosition {
247 // None for pedestrians
248 vehicle_type: Option<VehicleType>,
249 pos: LonLat,
250 // None for buses
251 person: Option<PersonID>,
252 }
253