Skip to content

Commit

Permalink
fix build for wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkirk committed Dec 31, 2020
1 parent 1888b93 commit 4d4b0b3
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 47 deletions.
12 changes: 7 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions game/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ wasm = ["map_gui/wasm", "wasm-bindgen", "widgetry/wasm-backend"]
[dependencies]
aabb-quadtree = "0.1.0"
abstutil = { path = "../abstutil" }
anyhow = "1.0.37"
built = { version = "0.4.3", optional = true, features=["chrono"] }
chrono = "0.4.15"
collisions = { path = "../collisions" }
Expand Down
42 changes: 37 additions & 5 deletions game/src/sandbox/gameplay/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use core::future::Future;
use core::pin::Pin;

use rand_xorshift::XorShiftRng;

use abstutil::{MapName, Timer};
Expand Down Expand Up @@ -81,6 +84,19 @@ pub enum LoadScenario {
Nothing,
Path(String),
Scenario(Scenario),
// wasm futures are not `Send`, since they all ultimately run on the browser's single threaded
// runloop
#[cfg(target_arch = "wasm32")]
Future(Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn Send + FnOnce(&App) -> Scenario>>>>>),
#[cfg(not(target_arch = "wasm32"))]
Future(
Pin<
Box<
dyn Send
+ Future<Output = anyhow::Result<Box<dyn Send + FnOnce(&App) -> Scenario>>>,
>,
>,
),
}

impl GameplayMode {
Expand Down Expand Up @@ -118,11 +134,27 @@ impl GameplayMode {
} else if name == "home_to_work" {
LoadScenario::Scenario(ScenarioGenerator::proletariat_robot(map, &mut rng, timer))
} else if name == "census" {
let config = popdat::Config::default();
LoadScenario::Scenario(
popdat::generate_scenario("typical monday", config, map, &mut rng)
.expect("unable to build census scenario"),
)
let map_area = map.get_boundary_polygon().clone();
let map_bounds = map.get_gps_bounds().clone();
let mut rng = sim::fork_rng(&mut rng);

LoadScenario::Future(Box::pin(async move {
let areas = popdat::CensusArea::fetch_all_for_map(&map_area, &map_bounds).await?;

let scenario_from_app: Box<dyn Send + FnOnce(&App) -> Scenario> =
Box::new(move |app: &App| {
let config = popdat::Config::default();
popdat::generate_scenario(
"typical monday",
areas,
config,
&app.primary.map,
&mut rng,
)
});

Ok(scenario_from_app)
}))
} else {
LoadScenario::Path(abstutil::path_scenario(map.get_name(), &name))
}
Expand Down
22 changes: 22 additions & 0 deletions game/src/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,28 @@ impl State<App> for SandboxLoader {
self.stage = Some(LoadStage::GotScenario(scenario));
continue;
}
gameplay::LoadScenario::Future(future) => {
use map_gui::load::FutureLoader;
return Transition::Push(FutureLoader::<App, Scenario>::new(
ctx,
Box::pin(future),
"Loading Scenario",
Box::new(|_, _, scenario| {
// TODO show error/retry alert?
let scenario =
scenario.expect("failed to load scenario from future");
Transition::Multi(vec![
Transition::Pop,
Transition::ModifyState(Box::new(|state, _, app| {
let loader =
state.downcast_mut::<SandboxLoader>().unwrap();
app.primary.scenario = Some(scenario.clone());
loader.stage = Some(LoadStage::GotScenario(scenario));
})),
])
}),
));
}
gameplay::LoadScenario::Path(path) => {
// Reuse the cached scenario, if possible.
if let Some(ref scenario) = app.primary.scenario {
Expand Down
10 changes: 6 additions & 4 deletions map_gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ authors = ["Dustin Carlino <[email protected]>"]
edition = "2018"

[features]
native = ["reqwest"]
wasm = ["futures", "futures-channel", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys"]
native = ["reqwest", "tokio"]
wasm = ["js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys"]
# Just a marker to not use localhost URLs
wasm_s3 = []
# A marker to use a named release from S3 instead of dev for updating files
Expand All @@ -15,11 +15,12 @@ release_s3 = []
[dependencies]
aabb-quadtree = "0.1.0"
abstutil = { path = "../abstutil" }
anyhow = "1.0.37"
colorous = "1.0.3"
contour = "0.3.0"
flate2 = "1.0.19"
futures = { version = "0.3.8", optional = true }
futures-channel = { version = "0.3.8", optional = true }
futures = { version = "0.3.8" }
futures-channel = { version = "0.3.8"}
geojson = "0.21.0"
geom = { path = "../geom" }
instant = "0.1.7"
Expand All @@ -29,6 +30,7 @@ map_model = { path = "../map_model" }
reqwest = { version = "0.10.8", optional = true, default-features=false, features=["blocking", "rustls-tls"] }
serde = "1.0.116"
sim = { path = "../sim" }
tokio = { version ="0.2", features=["rt-core"], optional = true }
wasm-bindgen = { version = "0.2.68", optional = true }
wasm-bindgen-futures = { version = "0.4.18", optional = true }
webbrowser = "0.5.5"
Expand Down
125 changes: 124 additions & 1 deletion map_gui/src/load.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
//! Loading large resources (like maps, scenarios, and prebaked data) requires different strategies
//! on native and web. Both cases are wrapped up as a State that runs a callback when done.
use std::future::Future;
use std::pin::Pin;

use futures_channel::oneshot;
use instant::Instant;
use serde::de::DeserializeOwned;
#[cfg(not(target_arch = "wasm32"))]
use tokio::runtime::Runtime;

use abstutil::{MapName, Timer};
use widgetry::{Color, EventCtx, GfxCtx, State, Transition};
use geom::Duration;
use widgetry::{Color, EventCtx, GfxCtx, Line, Panel, State, Text, Transition, UpdateType};

use crate::tools::PopupMsg;
use crate::AppLike;
Expand Down Expand Up @@ -244,3 +252,118 @@ mod wasm_loader {
}
}
}

pub struct FutureLoader<A, T>
where
A: AppLike,
{
loading_title: String,
started: Instant,
panel: Panel,
receiver: oneshot::Receiver<anyhow::Result<Box<dyn Send + FnOnce(&A) -> T>>>,
on_load: Option<Box<dyn FnOnce(&mut EventCtx, &mut A, anyhow::Result<T>) -> Transition<A>>>,

// If Runtime is dropped, any active tasks will be canceled, so we retain it here even
// though we never access it. It might make more sense for Runtime to live on App if we're
// going to be doing more background spawning.
#[cfg(not(target_arch = "wasm32"))]
#[allow(dead_code)]
runtime: Runtime,
}

impl<A, T> FutureLoader<A, T>
where
A: 'static + AppLike,
T: 'static,
{
#[cfg(target_arch = "wasm32")]
pub fn new(
ctx: &mut EventCtx,
future: Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn Send + FnOnce(&A) -> T>>>>>,
loading_title: &str,
on_load: Box<dyn FnOnce(&mut EventCtx, &mut A, anyhow::Result<T>) -> Transition<A>>,
) -> Box<dyn State<A>> {
let (tx, receiver) = oneshot::channel();
wasm_bindgen_futures::spawn_local(async move {
tx.send(future.await).ok().unwrap();
});
Box::new(FutureLoader {
loading_title: loading_title.to_string(),
started: Instant::now(),
panel: ctx.make_loading_screen(Text::from(Line(loading_title))),
receiver,
on_load: Some(on_load),
})
}

#[cfg(not(target_arch = "wasm32"))]
pub fn new(
ctx: &mut EventCtx,
future: Pin<
Box<dyn Send + Future<Output = anyhow::Result<Box<dyn Send + FnOnce(&A) -> T>>>>,
>,
loading_title: &str,
on_load: Box<dyn FnOnce(&mut EventCtx, &mut A, anyhow::Result<T>) -> Transition<A>>,
) -> Box<dyn State<A>> {
let runtime = Runtime::new().unwrap();
let (tx, receiver) = oneshot::channel();
runtime.spawn(async move {
tx.send(future.await).ok().unwrap();
});

Box::new(FutureLoader {
loading_title: loading_title.to_string(),
started: Instant::now(),
panel: ctx.make_loading_screen(Text::from(Line(loading_title))),
receiver,
on_load: Some(on_load),
runtime,
})
}
}

impl<A, T> State<A> for FutureLoader<A, T>
where
A: 'static + AppLike,
T: 'static,
{
fn event(&mut self, ctx: &mut EventCtx, app: &mut A) -> Transition<A> {
match self.receiver.try_recv() {
Err(e) => {
error!("channel failed: {:?}", e);
let on_load = self.on_load.take().unwrap();
return on_load(ctx, app, Err(anyhow::anyhow!("channel canceled")));
}
Ok(None) => {
self.panel = ctx.make_loading_screen(Text::from_multiline(vec![
Line(&self.loading_title),
Line(format!(
"Time spent: {}",
Duration::realtime_elapsed(self.started)
)),
]));

// Until the response is received, just ask winit to regularly call event(), so we
// can keep polling the channel.
ctx.request_update(UpdateType::Game);
return Transition::Keep;
}
Ok(Some(Err(e))) => {
error!("error in fetching data");
let on_load = self.on_load.take().unwrap();
return on_load(ctx, app, Err(e));
}
Ok(Some(Ok(builder))) => {
debug!("future complete");
let t = builder(app);
let on_load = self.on_load.take().unwrap();
return on_load(ctx, app, Ok(t));
}
}
}

fn draw(&self, g: &mut GfxCtx, _: &A) {
g.clear(Color::BLACK);
self.panel.draw(g);
}
}
1 change: 0 additions & 1 deletion popdat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ log = "0.4.11"
map_model = { path = "../map_model" }
rand = "0.7.0"
rand_xorshift = "0.2.0"
tokio = "0.2.24"
geo-booleanop = "0.3.2"
serde_json = "1.0.60"
sim = { path = "../sim" }
Loading

0 comments on commit 4d4b0b3

Please sign in to comment.