Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/io/wasm.rs
9447 views
1
use crate::io::{
2
get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader,
3
};
4
use alloc::{borrow::ToOwned, boxed::Box, format};
5
use js_sys::{Uint8Array, JSON};
6
use std::{
7
borrow::Cow,
8
path::{Path, PathBuf},
9
};
10
use tracing::error;
11
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
12
use wasm_bindgen_futures::JsFuture;
13
use web_sys::Response;
14
15
/// Represents the global object in the JavaScript context
16
#[wasm_bindgen]
17
extern "C" {
18
/// The [Global](https://developer.mozilla.org/en-US/docs/Glossary/Global_object) object.
19
type Global;
20
21
/// The [window](https://developer.mozilla.org/en-US/docs/Web/API/Window) global object.
22
#[wasm_bindgen(method, getter, js_name = Window)]
23
fn window(this: &Global) -> JsValue;
24
25
/// The [WorkerGlobalScope](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope) global object.
26
#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
27
fn worker(this: &Global) -> JsValue;
28
}
29
30
/// Reader implementation for loading assets via HTTP in Wasm.
31
pub struct HttpWasmAssetReader {
32
root_path: PathBuf,
33
request_mapper: Option<Box<dyn Fn(&str) -> Cow<str> + Send + Sync + 'static>>,
34
}
35
36
impl HttpWasmAssetReader {
37
/// Creates a new `WasmAssetReader`. The path provided will be used to build URLs to query for assets.
38
pub fn new<P: AsRef<Path>>(path: P) -> Self {
39
Self {
40
root_path: path.as_ref().to_owned(),
41
request_mapper: None,
42
}
43
}
44
45
/// Sets a mapper function to modify the request URL for each asset fetch. This can be used to
46
/// add query parameters or modify the path in any way.
47
pub fn with_request_mapper<F>(mut self, mapper: F) -> Self
48
where
49
F: Fn(&str) -> Cow<str> + Send + Sync + 'static,
50
{
51
self.request_mapper = Some(Box::new(mapper));
52
self
53
}
54
}
55
56
fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_ {
57
move |value| {
58
let message = match JSON::stringify(&value) {
59
Ok(js_str) => format!("Failed to {context}: {js_str}"),
60
Err(_) => {
61
format!("Failed to {context} and also failed to stringify the JSValue of the error")
62
}
63
};
64
65
std::io::Error::other(message)
66
}
67
}
68
69
impl HttpWasmAssetReader {
70
// Also used by [`WebAssetReader`](crate::web::WebAssetReader)
71
pub(crate) async fn fetch_bytes(
72
&self,
73
path: PathBuf,
74
) -> Result<impl Reader + use<>, AssetReaderError> {
75
let path = path.to_str().unwrap();
76
let fetch_path = self
77
.request_mapper
78
.as_ref()
79
.map_or_else(|| Cow::Borrowed(path), |mapper| mapper(path));
80
81
// The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available.
82
let global: Global = js_sys::global().unchecked_into();
83
let promise = if !global.window().is_undefined() {
84
let window: web_sys::Window = global.unchecked_into();
85
window.fetch_with_str(&fetch_path)
86
} else if !global.worker().is_undefined() {
87
let worker: web_sys::WorkerGlobalScope = global.unchecked_into();
88
worker.fetch_with_str(&fetch_path)
89
} else {
90
let error = std::io::Error::other("Unsupported JavaScript global context");
91
return Err(AssetReaderError::Io(error.into()));
92
};
93
let resp_value = JsFuture::from(promise)
94
.await
95
.map_err(js_value_to_err("fetch path"))?;
96
let resp = resp_value
97
.dyn_into::<Response>()
98
.map_err(js_value_to_err("convert fetch to Response"))?;
99
match resp.status() {
100
200 => {
101
let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap();
102
let bytes = Uint8Array::new(&data).to_vec();
103
let reader = VecReader::new(bytes);
104
Ok(reader)
105
}
106
// Some web servers, including itch.io's CDN, return 403 when a requested file isn't present.
107
// TODO: remove handling of 403 as not found when it's easier to configure
108
// see https://github.com/bevyengine/bevy/pull/19268#pullrequestreview-2882410105
109
403 | 404 => Err(AssetReaderError::NotFound((*fetch_path).into())),
110
status => Err(AssetReaderError::HttpError(status)),
111
}
112
}
113
}
114
115
impl AssetReader for HttpWasmAssetReader {
116
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
117
let path = self.root_path.join(path);
118
self.fetch_bytes(path).await
119
}
120
121
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
122
let meta_path = get_meta_path(&self.root_path.join(path));
123
self.fetch_bytes(meta_path).await
124
}
125
126
async fn read_directory<'a>(
127
&'a self,
128
_path: &'a Path,
129
) -> Result<Box<PathStream>, AssetReaderError> {
130
let stream: Box<PathStream> = Box::new(EmptyPathStream);
131
error!("Reading directories is not supported with the HttpWasmAssetReader");
132
Ok(stream)
133
}
134
135
async fn is_directory<'a>(&'a self, _path: &'a Path) -> Result<bool, AssetReaderError> {
136
error!("Reading directories is not supported with the HttpWasmAssetReader");
137
Ok(false)
138
}
139
}
140
141