mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-30 18:24:04 +03:00
Stub out a native-only UI to download extra cities. Move the config
about what cities somebody has opted into downloading to JSON and a more common place. #326 Note this is a breaking change for this config file, but I don't think many people have started using this yet.
This commit is contained in:
parent
104c987edd
commit
54ced5b5b4
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,7 +3,6 @@
|
||||
__pycache__
|
||||
.idea/
|
||||
|
||||
data/config
|
||||
data/player
|
||||
importer.json
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Timer;
|
||||
|
||||
/// A list of all canonical data files for A/B Street that're uploaded somewhere. The file formats
|
||||
/// are tied to the latest version of the git repo. Players use the updater crate to sync these
|
||||
/// files with local copies.
|
||||
@ -23,8 +25,7 @@ pub struct Entry {
|
||||
impl Manifest {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn load() -> Manifest {
|
||||
crate::maybe_read_json(crate::path("MANIFEST.json"), &mut crate::Timer::throwaway())
|
||||
.unwrap()
|
||||
crate::maybe_read_json(crate::path("MANIFEST.json"), &mut Timer::throwaway()).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@ -32,3 +33,40 @@ impl Manifest {
|
||||
crate::from_json(&include_bytes!("../../data/MANIFEST.json").to_vec()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Player-chosen groups of files to opt into downloading
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DataPacks {
|
||||
/// A list of cities to download for using in A/B Street.
|
||||
pub runtime: BTreeSet<String>,
|
||||
/// A list of cities to download for running the map importer.
|
||||
pub input: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl DataPacks {
|
||||
/// Load the player's config for what files to download, or create the config.
|
||||
pub fn load_or_create() -> DataPacks {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
panic!("DataPacks::load_or_create shouldn't be called on wasm");
|
||||
}
|
||||
|
||||
let path = crate::path("player/data.json");
|
||||
match crate::maybe_read_json::<DataPacks>(path.clone(), &mut Timer::throwaway()) {
|
||||
Ok(mut cfg) => {
|
||||
// The game breaks without this required data pack.
|
||||
cfg.runtime.insert("seattle".to_string());
|
||||
cfg
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("player/data.json invalid, assuming defaults: {}", err);
|
||||
let mut cfg = DataPacks {
|
||||
runtime: BTreeSet::new(),
|
||||
input: BTreeSet::new(),
|
||||
};
|
||||
cfg.runtime.insert("seattle".to_string());
|
||||
crate::write_json(path, &cfg);
|
||||
cfg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,15 +56,8 @@ version control will get out of date. At any time, you can run
|
||||
files that have changed.
|
||||
|
||||
You can also opt into downloading updates for more cities by editing
|
||||
`data/config`. Opting into everything looks like this:
|
||||
|
||||
```
|
||||
runtime: seattle,huge_seattle,krakow,berlin,xian,tel_aviv,london
|
||||
input: seattle,huge_seattle,krakow,berlin,xian,tel_aviv,london
|
||||
```
|
||||
|
||||
`runtime` downloads new maps and scenarios in `data/system/`. `input` is used
|
||||
for building those maps -- see below.
|
||||
`data/player/data.json`. In the main UI, there's a button to download more
|
||||
cities that will help you manage this config file.
|
||||
|
||||
## Building map data
|
||||
|
||||
|
@ -130,6 +130,11 @@ impl CityPicker {
|
||||
)
|
||||
.build_def(ctx, None),
|
||||
]),
|
||||
if cfg!(not(target_arch = "wasm32")) {
|
||||
Btn::text_fg("Download more cities").build_def(ctx, None)
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
]))
|
||||
.build(ctx),
|
||||
})
|
||||
@ -154,6 +159,9 @@ impl State<App> for CityPicker {
|
||||
"https://dabreegster.github.io/abstreet/howto/new_city.html".to_string(),
|
||||
);
|
||||
}
|
||||
"Download more cities" => {
|
||||
return Transition::Replace(crate::common::updater::Picker::new(ctx));
|
||||
}
|
||||
path => {
|
||||
return Transition::Replace(MapLoader::new(
|
||||
ctx,
|
||||
|
@ -25,6 +25,7 @@ mod heatmap;
|
||||
mod isochrone;
|
||||
mod minimap;
|
||||
mod navigate;
|
||||
mod updater;
|
||||
mod warp;
|
||||
|
||||
// TODO This is now just used in two modes...
|
||||
|
77
game/src/common/updater.rs
Normal file
77
game/src/common/updater.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use abstutil::{DataPacks, Manifest};
|
||||
use widgetry::{Btn, Checkbox, EventCtx, GfxCtx, Line, Outcome, Panel, State, TextExt, Widget};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::game::Transition;
|
||||
|
||||
pub struct Picker {
|
||||
panel: Panel,
|
||||
}
|
||||
|
||||
impl Picker {
|
||||
pub fn new(ctx: &mut EventCtx) -> Box<dyn State<App>> {
|
||||
let manifest = Manifest::load();
|
||||
let data_packs = DataPacks::load_or_create();
|
||||
|
||||
let mut col = vec![Widget::row(vec![
|
||||
Line("Download more cities").small_heading().draw(ctx),
|
||||
Btn::close(ctx),
|
||||
])];
|
||||
for (city, bytes) in size_per_city(&manifest) {
|
||||
col.push(Widget::row(vec![
|
||||
Checkbox::checkbox(ctx, &city, None, data_packs.runtime.contains(&city)),
|
||||
prettyprint_bytes(bytes).draw_text(ctx),
|
||||
]));
|
||||
}
|
||||
|
||||
Box::new(Picker {
|
||||
panel: Panel::new(Widget::col(col)).build(ctx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State<App> for Picker {
|
||||
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
|
||||
match self.panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"close" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
// For each city, how many total bytes do the runtime files cost?
|
||||
fn size_per_city(manifest: &Manifest) -> BTreeMap<String, usize> {
|
||||
let mut per_city = BTreeMap::new();
|
||||
for (path, entry) in &manifest.entries {
|
||||
let parts = path.split("/").collect::<Vec<_>>();
|
||||
if parts[1] == "system" {
|
||||
*per_city.entry(parts[2].to_string()).or_insert(0) += entry.size_bytes;
|
||||
}
|
||||
}
|
||||
per_city
|
||||
}
|
||||
|
||||
fn prettyprint_bytes(bytes: usize) -> String {
|
||||
if bytes < 1024 {
|
||||
return format!("{} bytes", bytes);
|
||||
}
|
||||
let kb = (bytes as f64) / 1024.0;
|
||||
if kb < 1024.0 {
|
||||
return format!("{} kb", kb as usize);
|
||||
}
|
||||
let mb = kb / 1024.0;
|
||||
format!("{} mb", mb as usize)
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::process::Command;
|
||||
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use abstutil::{Entry, Manifest, Timer};
|
||||
use abstutil::{DataPacks, Entry, Manifest, Timer};
|
||||
|
||||
const MD5_BUF_READ_SIZE: usize = 4096;
|
||||
const VERSION: &str = "dev";
|
||||
@ -33,9 +33,9 @@ async fn main() {
|
||||
}
|
||||
|
||||
async fn download() {
|
||||
let cities = Cities::load_or_create();
|
||||
let data_packs = DataPacks::load_or_create();
|
||||
let local = generate_manifest();
|
||||
let truth = filter_manifest(Manifest::load(), cities);
|
||||
let truth = filter_manifest(Manifest::load(), data_packs);
|
||||
|
||||
// Anything local need deleting?
|
||||
for path in local.entries.keys() {
|
||||
@ -77,9 +77,9 @@ async fn download() {
|
||||
}
|
||||
|
||||
fn just_compare() {
|
||||
let cities = Cities::load_or_create();
|
||||
let data_packs = DataPacks::load_or_create();
|
||||
let local = generate_manifest();
|
||||
let truth = filter_manifest(Manifest::load(), cities);
|
||||
let truth = filter_manifest(Manifest::load(), data_packs);
|
||||
|
||||
// Anything local need deleting?
|
||||
for path in local.entries.keys() {
|
||||
@ -199,11 +199,11 @@ fn generate_manifest() -> Manifest {
|
||||
Manifest { entries: kv }
|
||||
}
|
||||
|
||||
fn filter_manifest(mut manifest: Manifest, cities: Cities) -> Manifest {
|
||||
fn filter_manifest(mut manifest: Manifest, data_packs: DataPacks) -> Manifest {
|
||||
let mut remove = Vec::new();
|
||||
for path in manifest.entries.keys() {
|
||||
// TODO Some hardcoded weird exceptions
|
||||
if !cities.runtime.contains(&"huge_seattle".to_string())
|
||||
if !data_packs.runtime.contains("huge_seattle")
|
||||
&& path == "data/system/seattle/scenarios/montlake/everyone_weekday.bin"
|
||||
{
|
||||
remove.push(path.clone());
|
||||
@ -212,11 +212,11 @@ fn filter_manifest(mut manifest: Manifest, cities: Cities) -> Manifest {
|
||||
|
||||
let parts = path.split("/").collect::<Vec<_>>();
|
||||
if parts[1] == "input" {
|
||||
if cities.input.contains(&parts[2].to_string()) {
|
||||
if data_packs.input.contains(parts[2]) {
|
||||
continue;
|
||||
}
|
||||
} else if parts[1] == "system" {
|
||||
if cities.runtime.contains(&parts[2].to_string()) {
|
||||
if data_packs.runtime.contains(parts[2]) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
@ -230,61 +230,6 @@ fn filter_manifest(mut manifest: Manifest, cities: Cities) -> Manifest {
|
||||
manifest
|
||||
}
|
||||
|
||||
// What data to download?
|
||||
struct Cities {
|
||||
runtime: Vec<String>,
|
||||
input: Vec<String>,
|
||||
}
|
||||
|
||||
impl Cities {
|
||||
fn load_or_create() -> Cities {
|
||||
let path = "data/config";
|
||||
if let Ok(f) = File::open(path) {
|
||||
let mut cities = Cities {
|
||||
runtime: Vec::new(),
|
||||
input: Vec::new(),
|
||||
};
|
||||
for line in BufReader::new(f).lines() {
|
||||
let line = line.unwrap();
|
||||
let parts = line.split(": ").collect::<Vec<_>>();
|
||||
assert_eq!(parts.len(), 2);
|
||||
let list = parts[1]
|
||||
.split(",")
|
||||
.map(|x| x.to_string())
|
||||
.filter(|x| !x.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
if parts[0] == "runtime" {
|
||||
cities.runtime = list;
|
||||
} else if parts[0] == "input" {
|
||||
cities.input = list;
|
||||
} else {
|
||||
panic!("{} is corrupted, what's {}", path, parts[0]);
|
||||
}
|
||||
}
|
||||
if !cities.runtime.contains(&"seattle".to_string()) {
|
||||
panic!(
|
||||
"{}: runtime must contain seattle; the game breaks without this",
|
||||
path
|
||||
);
|
||||
}
|
||||
cities
|
||||
} else {
|
||||
let mut f = File::create(&path).unwrap();
|
||||
writeln!(f, "runtime: seattle,berlin,krakow").unwrap();
|
||||
writeln!(f, "input: ").unwrap();
|
||||
println!("- Wrote {}", path);
|
||||
Cities {
|
||||
runtime: vec![
|
||||
"seattle".to_string(),
|
||||
"berlin".to_string(),
|
||||
"krakow".to_string(),
|
||||
],
|
||||
input: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn must_run_cmd(cmd: &mut Command) {
|
||||
println!("> Running {:?}", cmd);
|
||||
match cmd.status() {
|
||||
|
Loading…
Reference in New Issue
Block a user