game/sandbox/gameplay/freeform/
importers.rs

1// TODO This doesn't really belong in gameplay/freeform
2
3use anyhow::Result;
4use serde::Deserialize;
5
6use abstutil::Timer;
7use map_gui::tools::{find_exe, FilePicker, RunCommand};
8use map_model::Map;
9use synthpop::{ExternalPerson, Scenario};
10use widgetry::tools::PopupMsg;
11use widgetry::EventCtx;
12
13use crate::app::Transition;
14use crate::sandbox::gameplay::GameplayMode;
15use crate::sandbox::SandboxMode;
16
17pub fn import_grid2demand(ctx: &mut EventCtx) -> Transition {
18    Transition::Push(FilePicker::new_state(
19        ctx,
20        None,
21        Box::new(|ctx, app, maybe_file| {
22            if let Ok(Some((path, _))) = maybe_file {
23                Transition::Replace(RunCommand::new_state(
24                    ctx,
25                    true,
26                    vec![
27                        find_exe("cli"),
28                        "import-grid2-demand".to_string(),
29                        format!("--map={}", app.primary.map.get_name().path()),
30                        format!("--input={}", path),
31                    ],
32                    Box::new(|_, app, success, _| {
33                        if success {
34                            // Clear out the cached scenario. If we repeatedly use this import, the
35                            // scenario name is always the same, but the file is changing.
36                            app.primary.scenario = None;
37                            Transition::Replace(SandboxMode::simple_new(
38                                app,
39                                GameplayMode::PlayScenario(
40                                    app.primary.map.get_name().clone(),
41                                    "grid2demand".to_string(),
42                                    Vec::new(),
43                                ),
44                            ))
45                        } else {
46                            // The popup already explained the failure
47                            Transition::Keep
48                        }
49                    }),
50                ))
51            } else {
52                // The user didn't pick a file, so stay on the scenario picker
53                Transition::Pop
54            }
55        }),
56    ))
57}
58
59pub fn import_json(ctx: &mut EventCtx) -> Transition {
60    Transition::Push(FilePicker::new_state(
61        ctx,
62        None,
63        Box::new(|ctx, app, maybe_file| {
64            if let Ok(Some((_, bytes))) = maybe_file {
65                let result = ctx.loading_screen("import JSON scenario", |_, timer| {
66                    import_json_scenario(&app.primary.map, bytes, timer)
67                });
68                match result {
69                    Ok(scenario_name) => {
70                        // Clear out the cached scenario. If we repeatedly use this import, the
71                        // scenario name is always the same, but the file is changing.
72                        app.primary.scenario = None;
73                        Transition::Replace(SandboxMode::simple_new(
74                            app,
75                            GameplayMode::PlayScenario(
76                                app.primary.map.get_name().clone(),
77                                scenario_name,
78                                Vec::new(),
79                            ),
80                        ))
81                    }
82                    Err(err) => Transition::Replace(PopupMsg::new_state(
83                        ctx,
84                        "Error",
85                        vec![err.to_string()],
86                    )),
87                }
88            } else {
89                // The user didn't pick a file, so stay on the scenario picker
90                Transition::Pop
91            }
92        }),
93    ))
94}
95
96// This works the same as importer/src/bin/import_traffic.rs. We should decide how to share
97// behavior between UI and CLI tools.
98fn import_json_scenario(map: &Map, bytes: Vec<u8>, timer: &mut Timer) -> Result<String> {
99    let skip_problems = true;
100    let input: Input = abstutil::from_json(&bytes)?;
101
102    let mut s = Scenario::empty(map, &input.scenario_name);
103    // Include all buses/trains
104    s.only_seed_buses = None;
105    timer.start("import from JSON");
106    s.people = ExternalPerson::import(map, input.people, skip_problems)?;
107    // Always clean up people with no-op trips (going between the same buildings)
108    s = s.remove_weird_schedules(true);
109    timer.stop("import from JSON");
110    s.save();
111    Ok(s.scenario_name)
112}
113
114#[derive(Deserialize)]
115struct Input {
116    scenario_name: String,
117    people: Vec<ExternalPerson>,
118}