get the game crate running in wasm. bundle in lots of data files, fake out a few more IO things, glue timer println's to console log

This commit is contained in:
Dustin Carlino 2020-02-15 16:44:06 -08:00
parent 9a9619ec32
commit d292e29ad0
9 changed files with 129 additions and 51 deletions

View File

@ -20,3 +20,7 @@ procfs = "0.4.7"
[target.'cfg(unix)'.dependencies]
termion = "1.5.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
include_dir = "0.5.0"
stdweb = "0.4.20"

View File

@ -12,6 +12,9 @@ use std::fs::File;
use std::io::{stdout, BufReader, BufWriter, Error, ErrorKind, Read, Write};
use std::path::Path;
#[cfg(target_arch = "wasm32")]
static SYSTEM_DATA: include_dir::Dir = include_dir::include_dir!("../data/system");
pub fn to_json<T: Serialize>(obj: &T) -> String {
serde_json::to_string_pretty(obj).unwrap()
}
@ -36,6 +39,26 @@ pub fn write_json<T: Serialize>(path: String, obj: &T) {
println!("Wrote {}", path);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn slurp_file(path: &str) -> Result<Vec<u8>, Error> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(buffer)
}
#[cfg(target_arch = "wasm32")]
pub fn slurp_file(path: &str) -> Result<Vec<u8>, Error> {
if let Some(raw) = SYSTEM_DATA.get_file(path.trim_start_matches("../data/system/")) {
Ok(raw.contents().to_vec())
} else {
Err(Error::new(
ErrorKind::Other,
format!("Can't slurp_file {}, it doesn't exist", path),
))
}
}
pub fn maybe_read_json<T: DeserializeOwned>(path: String, timer: &mut Timer) -> Result<T, Error> {
if !path.ends_with(".json") && !path.ends_with(".geojson") {
panic!("read_json needs {} to end with .json or .geojson", path);
@ -43,19 +66,11 @@ pub fn maybe_read_json<T: DeserializeOwned>(path: String, timer: &mut Timer) ->
timer.start(format!("parse {}", path));
// TODO timer.read_file isn't working here. And we need to call stop() if there's no file.
match File::open(&path) {
Ok(mut file) => {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let obj: T = serde_json::from_str(&contents)?;
timer.stop(format!("parse {}", path));
Ok(obj)
}
Err(e) => {
timer.stop(format!("parse {}", path));
Err(e)
}
}
let result: Result<T, Error> = slurp_file(&path).and_then(|raw| {
serde_json::from_slice(&raw).map_err(|err| Error::new(ErrorKind::Other, err))
});
timer.stop(format!("parse {}", path));
result
}
pub fn read_json<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {
@ -88,6 +103,7 @@ pub fn write_binary<T: Serialize>(path: String, obj: &T) {
println!("Wrote {}", path);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn maybe_read_binary<T: DeserializeOwned>(path: String, timer: &mut Timer) -> Result<T, Error> {
if !path.ends_with(".bin") {
panic!("read_binary needs {} to end with .bin", path);
@ -99,6 +115,23 @@ pub fn maybe_read_binary<T: DeserializeOwned>(path: String, timer: &mut Timer) -
Ok(obj)
}
#[cfg(target_arch = "wasm32")]
pub fn maybe_read_binary<T: DeserializeOwned>(
path: String,
_timer: &mut Timer,
) -> Result<T, Error> {
if let Some(raw) = SYSTEM_DATA.get_file(path.trim_start_matches("../data/system/")) {
let obj: T = bincode::deserialize(raw.contents())
.map_err(|err| Error::new(ErrorKind::Other, err))?;
Ok(obj)
} else {
Err(Error::new(
ErrorKind::Other,
format!("Can't maybe_read_binary {}, it doesn't exist", path),
))
}
}
pub fn read_binary<T: DeserializeOwned>(path: String, timer: &mut Timer) -> T {
match maybe_read_binary(path.clone(), timer) {
Ok(obj) => obj,
@ -162,6 +195,7 @@ pub fn deserialize_multimap<
}
// Just list all things from a directory, return sorted by name, with file extension removed.
#[cfg(not(target_arch = "wasm32"))]
pub fn list_all_objects(dir: String) -> Vec<String> {
let mut results: BTreeSet<String> = BTreeSet::new();
match std::fs::read_dir(dir) {
@ -187,8 +221,22 @@ pub fn list_all_objects(dir: String) -> Vec<String> {
results.into_iter().collect()
}
#[cfg(target_arch = "wasm32")]
pub fn list_all_objects(dir: String) -> Vec<String> {
let mut results = Vec::new();
if let Some(dir) = SYSTEM_DATA.get_dir(dir.trim_start_matches("../data/system/")) {
for f in dir.files() {
results.push(format!("../data/system/{}", f.path().display()));
}
} else {
panic!("Can't list_all_objects in {}", dir);
}
results
}
// Load all serialized things from a directory, return sorted by name, with file extension removed.
// Detects JSON or binary.
#[cfg(not(target_arch = "wasm32"))]
pub fn load_all_objects<T: DeserializeOwned>(dir: String) -> Vec<(String, T)> {
let mut timer = Timer::new(format!("load_all_objects from {}", dir));
let mut tree: BTreeMap<String, T> = BTreeMap::new();
@ -224,6 +272,12 @@ pub fn load_all_objects<T: DeserializeOwned>(dir: String) -> Vec<(String, T)> {
tree.into_iter().collect()
}
#[cfg(target_arch = "wasm32")]
pub fn load_all_objects<T: DeserializeOwned>(_dir: String) -> Vec<(String, T)> {
// TODO
Vec::new()
}
// TODO I'd like to get rid of this and just use Timer.read_file, but external libraries consume
// the reader. :\
pub struct FileWithProgress {

View File

@ -17,8 +17,8 @@ pub use crate::error::Error;
pub use crate::io::{
basename, deserialize_btreemap, deserialize_multimap, file_exists, find_next_file,
find_prev_file, list_all_objects, load_all_objects, maybe_read_binary, maybe_read_json,
read_binary, read_json, serialize_btreemap, serialize_multimap, serialized_size_bytes, to_json,
write_binary, write_json, FileWithProgress,
read_binary, read_json, serialize_btreemap, serialize_multimap, serialized_size_bytes,
slurp_file, to_json, write_binary, write_json, FileWithProgress,
};
pub use crate::logs::Warn;
pub use crate::random::{fork_rng, WeightedUsizeChoice};

View File

@ -151,7 +151,14 @@ impl<'a> Timer<'a> {
// Workaround for borrow checker
fn selfless_println(maybe_sink: &mut Option<Box<dyn TimerSink + 'a>>, line: String) {
println!("{}", line);
#[cfg(not(target_arch = "wasm32"))]
{
println!("{}", line);
}
#[cfg(target_arch = "wasm32")]
{
stdweb::console!(log, "%s", &line);
}
if let Some(ref mut sink) = maybe_sink {
sink.println(line);
}
@ -311,8 +318,10 @@ impl<'a> Timer<'a> {
O: Send,
F: Send + Clone + Copy,
{
// Here's the sequential equivalent, to conveniently compare times
if false {
// Here's the sequential equivalent, to conveniently compare times. Also gotta use this in
// wasm; no threads.
#[cfg(target_arch = "wasm32")]
{
let mut results: Vec<O> = Vec::new();
self.start_iter(timer_name, requests.len());
for req in requests {
@ -322,28 +331,31 @@ impl<'a> Timer<'a> {
return results;
}
scoped_threadpool::Pool::new(num_cpus::get() as u32).scoped(|scope| {
let (tx, rx) = std::sync::mpsc::channel();
let mut results: Vec<Option<O>> = std::iter::repeat_with(|| None)
.take(requests.len())
.collect();
for (idx, req) in requests.into_iter().enumerate() {
let tx = tx.clone();
scope.execute(move || {
// TODO Can we catch panics here, dump a better stacktrace? ezgui runner does
// this
tx.send((idx, cb(req))).unwrap();
});
}
drop(tx);
#[cfg(not(target_arch = "wasm32"))]
{
scoped_threadpool::Pool::new(num_cpus::get() as u32).scoped(|scope| {
let (tx, rx) = std::sync::mpsc::channel();
let mut results: Vec<Option<O>> = std::iter::repeat_with(|| None)
.take(requests.len())
.collect();
for (idx, req) in requests.into_iter().enumerate() {
let tx = tx.clone();
scope.execute(move || {
// TODO Can we catch panics here, dump a better stacktrace? ezgui runner
// does this
tx.send((idx, cb(req))).unwrap();
});
}
drop(tx);
self.start_iter(timer_name, results.len());
for (idx, result) in rx.iter() {
self.next();
results[idx] = Some(result);
}
results.into_iter().map(|x| x.unwrap()).collect()
})
self.start_iter(timer_name, results.len());
for (idx, result) in rx.iter() {
self.next();
results[idx] = Some(result);
}
results.into_iter().map(|x| x.unwrap()).collect()
})
}
}
// Then the caller passes this in as a reader

View File

@ -6,3 +6,4 @@
- std::process::exit
- cargo magic
- conditional compilation backend pattern, not traits with one impl at a time
- scoped_threadpool

View File

@ -92,7 +92,9 @@ impl<'a> EventCtx<'a> {
timer.start_iter("upload textures", num_textures);
for (filename, tex_type) in textures {
timer.next();
let img = image::open(filename).unwrap().to_rgba();
let img = image::load_from_memory(&abstutil::slurp_file(filename).unwrap())
.unwrap()
.to_rgba();
let dims = img.dimensions();
dims_to_textures.entry(dims).or_insert_with(Vec::new).push((
filename.to_string(),

View File

@ -19,7 +19,8 @@ pub fn load_svg(prerender: &Prerender, filename: &str) -> (GeomBatch, Bounds) {
return pair;
}
let svg_tree = usvg::Tree::from_file(&filename, &usvg::Options::default()).unwrap();
let raw = abstutil::slurp_file(&filename).unwrap();
let svg_tree = usvg::Tree::from_data(&raw, &usvg::Options::default()).unwrap();
let mut batch = GeomBatch::new();
match add_svg_inner(&mut batch, svg_tree, HIGH_QUALITY) {
Ok(bounds) => {

View File

@ -39,6 +39,13 @@ fn main() {
opts.dev = true;
flags.sim_flags.rng_seed = Some(42);
}
// No random in wasm
#[cfg(target_arch = "wasm32")]
{
flags.sim_flags.rng_seed = Some(42);
}
if let Some(x) = args.optional("--color_scheme") {
opts.color_scheme = Some(format!("../data/system/{}", x));
}
@ -81,5 +88,11 @@ fn main() {
args.done();
// TODO Montlake map isn't loading, just start here
#[cfg(target_arch = "wasm32")]
{
flags.sim_flags.load = abstutil::path_synthetic_map("signal_single");
}
ezgui::run(settings, |ctx| game::Game::new(flags, opts, mode, ctx));
}

View File

@ -96,6 +96,7 @@ impl SimFlags {
(map, sim, rng)
} else if self.load.starts_with(&abstutil::path_all_raw_maps())
|| self.load.starts_with(&abstutil::path_all_synthetic_maps())
|| self.load.starts_with(&abstutil::path_all_maps())
{
timer.note(format!("Loading map {}", self.load));
@ -105,16 +106,6 @@ impl SimFlags {
let sim = Sim::new(&map, opts, timer);
timer.stop("create sim");
(map, sim, rng)
} else if self.load.starts_with(&abstutil::path_all_maps()) {
timer.note(format!("Loading map {}", self.load));
let map = Map::new(self.load.clone(), false, timer);
timer.start("create sim");
let sim = Sim::new(&map, opts, timer);
timer.stop("create sim");
(map, sim, rng)
} else {
panic!("Don't know how to load {}", self.load);