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         &params,
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(&params["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(&params["t1"])?;
140             let t2 = Time::parse(&params["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