Read/write scenarios in JSON, so people can manipulate them in any language. #313

This commit is contained in:
Dustin Carlino 2020-09-04 10:57:52 -07:00
parent 38ba8d55f3
commit 65e07d9cc7
7 changed files with 89 additions and 39 deletions

View File

@ -156,6 +156,14 @@ pub fn read_binary<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {
}
}
pub fn read_object<T: DeserializeOwned>(path: String, timer: &mut Timer) -> Result<T, Error> {
if path.ends_with(".bin") {
maybe_read_binary(path, timer)
} else {
maybe_read_json(path, timer)
}
}
// For BTreeMaps with struct keys. See https://github.com/serde-rs/json/issues/402.
pub fn serialize_btreemap<S: Serializer, K: Serialize, V: Serialize>(
@ -438,13 +446,13 @@ pub fn basename(path: &str) -> String {
}
#[cfg(not(target_arch = "wasm32"))]
pub fn file_exists(path: String) -> bool {
Path::new(&path).exists()
pub fn file_exists<I: Into<String>>(path: I) -> bool {
Path::new(&path.into()).exists()
}
#[cfg(target_arch = "wasm32")]
pub fn file_exists(path: String) -> bool {
pub fn file_exists<I: Into<String>>(path: I) -> bool {
SYSTEM_DATA
.get_file(path.trim_start_matches("../data/system/"))
.get_file(path.into().trim_start_matches("../data/system/"))
.is_some()
}

View File

@ -12,7 +12,7 @@ pub use crate::collections::{
pub use crate::io::{
basename, deserialize_btreemap, deserialize_multimap, deserialize_usize, file_exists,
find_next_file, find_prev_file, from_json, list_all_objects, list_dir, load_all_objects,
maybe_read_binary, maybe_read_json, read_binary, read_json, serialize_btreemap,
maybe_read_binary, maybe_read_json, read_binary, read_json, read_object, serialize_btreemap,
serialize_multimap, serialize_usize, serialized_size_bytes, slurp_file, to_json, write_binary,
write_json, FileWithProgress,
};
@ -119,9 +119,17 @@ pub fn path_prebaked_results(map_name: &str, scenario_name: &str) -> String {
}
pub fn path_scenario(map_name: &str, scenario_name: &str) -> String {
path(format!(
// TODO Getting complicated. Looks for .bin, then .json.
let p = path(format!(
"system/scenarios/{}/{}.bin",
map_name, scenario_name
));
if file_exists(&p) {
return p;
}
path(format!(
"system/scenarios/{}/{}.json",
map_name, scenario_name
))
}
pub fn path_all_scenarios(map_name: &str) -> String {

View File

@ -652,13 +652,15 @@ impl<'a> Read for Timer<'a> {
prettyprint_usize(file.total_bytes / 1024 / 1024),
prettyprint_time(elapsed)
);
if file.last_printed_at.is_none() {
self.println(line.clone());
} else {
clear_current_line();
println!("{}", line);
if let Some(ref mut sink) = self.sink {
sink.reprintln(line.clone());
if self.outermost_name != "throwaway" {
if file.last_printed_at.is_none() {
self.println(line.clone());
} else {
clear_current_line();
println!("{}", line);
if let Some(ref mut sink) = self.sink {
sink.reprintln(line.clone());
}
}
}
self.stack.pop();
@ -666,23 +668,25 @@ impl<'a> Read for Timer<'a> {
} else if file.last_printed_at.is_none()
|| elapsed_seconds(file.last_printed_at.unwrap()) >= PROGRESS_FREQUENCY_SECONDS
{
let line = format!(
"Reading {}: {}/{} MB... {}",
file.path,
prettyprint_usize(file.processed_bytes / 1024 / 1024),
prettyprint_usize(file.total_bytes / 1024 / 1024),
prettyprint_time(elapsed_seconds(file.started_at))
);
// TODO Refactor this pattern...
clear_current_line();
print!("{}", line);
stdout().flush().unwrap();
if self.outermost_name != "throwaway" {
let line = format!(
"Reading {}: {}/{} MB... {}",
file.path,
prettyprint_usize(file.processed_bytes / 1024 / 1024),
prettyprint_usize(file.total_bytes / 1024 / 1024),
prettyprint_time(elapsed_seconds(file.started_at))
);
// TODO Refactor this pattern...
clear_current_line();
print!("{}", line);
stdout().flush().unwrap();
if let Some(ref mut sink) = self.sink {
if file.last_printed_at.is_none() {
sink.println(line);
} else {
sink.reprintln(line);
if let Some(ref mut sink) = self.sink {
if file.last_printed_at.is_none() {
sink.println(line);
} else {
sink.reprintln(line);
}
}
}

View File

@ -154,7 +154,8 @@ problems is useful. It's also fine to crash when initially constructing all of
the renderable map objects, because this crash will consistently happen at
startup-time and be noticed by somebody developing before a player gets to it.
Regarding Testing: You'll surely note the lack of unit tests. If it bothers you, let's talk about
what tests should exist. In the meantime, note lots of validation does happen
via importing maps, running the prebaked scenarios, and screenshot diffing.
Please read more in depth in the [testing strategy](https://dabreegster.github.io/abstreet/dev/testing.html) doc.
Regarding Testing: You'll surely note the lack of unit tests. If it bothers you,
let's talk about what tests should exist. In the meantime, note lots of
validation does happen via importing maps, running the prebaked scenarios, and
screenshot diffing. Please read more in depth in the
[testing strategy](https://dabreegster.github.io/abstreet/dev/testing.html) doc.

View File

@ -51,18 +51,38 @@ are missing, etc. A summary of the commands available so far:
normally. You can also later run the `headless` server with
`--edits=name_of_edits`.
## Related tools
There's no API to create trips. Instead, you can
[import trips from your own data](https://dabreegster.github.io/abstreet/trafficsim/travel_demand.html#custom-import).
## Working with the map model
If you need to deeply inspect the map, you can dump it to JSON:
```
cargo run --bin dump_map data/system/maps/montlake.bin
cargo run --bin dump_map data/system/maps/montlake.bin > montlake.json
```
The format of the map isn't well-documented yet. See the
[generated API docs](https://dabreegster.github.io/abstreet/rustdoc/map_model/index.html)
and [the map model docs](https://dabreegster.github.io/abstreet/map/index.html)
in the meantime.
## Creating trips
There's no API yet to create trips. Instead, you can
[import trips from your own data](https://dabreegster.github.io/abstreet/trafficsim/travel_demand.html#custom-import).
You can also dump Scenarios (the file that defines all of the people and trips)
to JSON:
```
cargo run --bin dump_scenario data/system/scenarios/montlake/weekday.bin > montlake_weekday.json
```
You can modify the JSON, then put the file back in the appropriate directory and
use it in-game:
```
cargo run --bin game data/system/scenarios/montlake/modified_scenario.json
```
The Scenario format is also undocumented, but see the
[generated API docs](https://dabreegster.github.io/abstreet/rustdoc/sim/struct.Scenario.html)
anyway.

View File

@ -117,7 +117,7 @@ impl GameplayMode {
ScenarioGenerator::proletariat_robot(map, &mut rng, timer)
} else {
let path = abstutil::path_scenario(map.get_name(), &name);
let mut scenario = match abstutil::maybe_read_binary(path.clone(), timer) {
let mut scenario = match abstutil::read_object(path.clone(), timer) {
Ok(s) => s,
Err(err) => {
Map::corrupt_err(path, err);

View File

@ -0,0 +1,9 @@
use abstutil::{CmdArgs, Timer};
use sim::Scenario;
fn main() {
let mut args = CmdArgs::new();
let scenario: Scenario = abstutil::read_binary(args.required_free(), &mut Timer::throwaway());
println!("{}", abstutil::to_json(&scenario));
args.done();
}