From 247bfcbf4781a07dd3e967be460dc2c41112ecd1 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 20 Mar 2024 22:15:45 -0400 Subject: [PATCH] conditional compilation of async compat Problems with the previous code: - `async-compat` is necessary for headless mode (cli server rendering) to load remote scenes from the API without blowing up. Without it, the code complains about the lack of a Tokio runtime. - Unfortunately, `async-compat` doesn't work with WASM and the entire web build is broken by its presence. This change introduces conditional compilation so `async-compat` is only leveraged in headless mode. --- studio/Cargo.toml | 2 + studio/src/remote/reader.rs | 136 ++++++++++++++++++++---------------- 2 files changed, 77 insertions(+), 61 deletions(-) diff --git a/studio/Cargo.toml b/studio/Cargo.toml index 6ee14ad..122f3ff 100644 --- a/studio/Cargo.toml +++ b/studio/Cargo.toml @@ -15,6 +15,7 @@ path = "src/main.rs" [[bin]] name = "studio-headless" path = "src/main-headless.rs" +required-features = ["headless_cli"] [dependencies] bevy-inspector-egui = "0.22.0" @@ -69,6 +70,7 @@ wasm = [ "bevy_rapier3d/wasm-bindgen", ] desktop = [] +headless_cli = [] [dependencies.bevy] workspace = true diff --git a/studio/src/remote/reader.rs b/studio/src/remote/reader.rs index 812516c..8918650 100644 --- a/studio/src/remote/reader.rs +++ b/studio/src/remote/reader.rs @@ -16,6 +16,71 @@ pub struct RemoteAssetReader; impl RemoteAssetReader { const API_BASE_URL: &'static str = "https://api.fakeyou.com/v1/media_files/file"; const BUCKET_BASE_URL: &'static str = "https://storage.googleapis.com/vocodes-public"; + + async fn load<'a>( + &'a self, + path: &'a Path, + ) -> Result>, AssetReaderError> { + let token = path + .ext_file_prefix() + .ok_or_else(|| { + AssetReaderError::Io(std::io::Error::new( + ErrorKind::Other, + format!("Failed to parse token from '{path:?}'"), + )) + })? + .to_str() + .unwrap(); + + info!("Loading asset from media token: '{token}'"); + + let response = reqwest::get(format!("{}/{token}", &Self::API_BASE_URL)) + .await + .map_err(IntoError::::into)?; + + let response = match response.status() { + StatusCode::OK => response + .json::() + .await + .map_err(IntoError::::into), + other => Err(AssetReaderError::Io(std::io::Error::new( + ErrorKind::Other, + format!("Status Code from endpoint: {other}"), + ))), + }?; + + let url = format!( + "{}{}", + &Self::BUCKET_BASE_URL, + &response.media_file.public_bucket_path + ); + + let response = reqwest::get(url) + .await + .map_err(IntoError::::into)?; + + let bytes = match response.status() { + // TODO: This is not thread-safe, but some variation of this + // would be better than the current solution once Bevy + // WASM becomes multi-threaded: + // StatusCode::OK => Ok(response.bytes_stream().map(|result| { + // result.map_err(|err| std::io::Error::new(ErrorKind::Other, format!("{err}"))) + // })), + StatusCode::OK => Ok(response + .bytes() + .await + .map_err(IntoError::::into)? + .into_iter() + .collect::>()), + other => Err(AssetReaderError::Io(std::io::Error::new( + ErrorKind::Other, + format!("Status Code from bucket URL: {other}"), + ))), + }?; + + let reader: Box = Box::new(VecReader::new(bytes)); + Ok(reader) + } } impl AssetReader for RemoteAssetReader { @@ -23,67 +88,16 @@ impl AssetReader for RemoteAssetReader { &'a self, path: &'a Path, ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(Compat::new(async move { - let token = path - .ext_file_prefix() - .ok_or_else(|| { - AssetReaderError::Io(std::io::Error::new( - ErrorKind::Other, - format!("Failed to parse token from '{path:?}'"), - )) - })? - .to_str() - .unwrap(); - - info!("Loading asset from media token: '{token}'"); - - let response = reqwest::get(format!("{}/{token}", &Self::API_BASE_URL)) - .await - .map_err(IntoError::::into)?; - - let response = match response.status() { - StatusCode::OK => response - .json::() - .await - .map_err(IntoError::::into), - other => Err(AssetReaderError::Io(std::io::Error::new( - ErrorKind::Other, - format!("Status Code from endpoint: {other}"), - ))), - }?; - - let url = format!( - "{}{}", - &Self::BUCKET_BASE_URL, - &response.media_file.public_bucket_path - ); - - let response = reqwest::get(url) - .await - .map_err(IntoError::::into)?; - - let bytes = match response.status() { - // TODO: This is not thread-safe, but some variation of this - // would be better than the current solution once Bevy - // WASM becomes multi-threaded: - // StatusCode::OK => Ok(response.bytes_stream().map(|result| { - // result.map_err(|err| std::io::Error::new(ErrorKind::Other, format!("{err}"))) - // })), - StatusCode::OK => Ok(response - .bytes() - .await - .map_err(IntoError::::into)? - .into_iter() - .collect::>()), - other => Err(AssetReaderError::Io(std::io::Error::new( - ErrorKind::Other, - format!("Status Code from bucket URL: {other}"), - ))), - }?; - - let reader: Box = Box::new(VecReader::new(bytes)); - Ok(reader) - })) + let unboxed = async move { + self.load(path).await + }; + if cfg!(headless_cli) { + // NB: Headless mode (server rendering) is running without a Tokio runtime. + // The reqwest HTTP client requires async-compat to function at runtime. + Box::pin(Compat::new(unboxed)) + } else { + Box::pin(unboxed) + } } fn read_meta<'a>(