1use std::collections::HashMap;
43use std::fmt::Debug;
44use std::marker::PhantomData;
45use std::num::NonZeroU64;
46use std::path::{Path, PathBuf};
47use std::sync::atomic::AtomicU64;
48
49use winit::event_loop::EventLoopProxy;
50
51use crate::engine_handle::BpEvent;
52use crate::shader::{IntermediateOptions, Shader};
53use crate::text::Font;
54use crate::texture::{SamplerType, Texture};
55
56#[cfg(not(target_arch = "wasm32"))]
57use futures::executor::ThreadPool;
58
59#[cfg(target_arch = "wasm32")]
60async fn web_read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, ReadError> {
61 use js_sys::Uint8Array;
62 use wasm_bindgen::JsCast;
63 use wasm_bindgen_futures::JsFuture;
64
65 let path = path.as_ref().as_os_str().to_str().unwrap();
66
67 match web_sys::window() {
68 Some(window) => {
69 let response_value = JsFuture::from(window.fetch_with_str(path)).await.unwrap();
70
71 let response: web_sys::Response = response_value.dyn_into().unwrap();
72
73 if !response.ok() {
74 Err(ReadError::ResponseError(
75 response.status(),
76 response.status_text(),
77 ))?;
78 }
79
80 let data = JsFuture::from(response.array_buffer().unwrap())
81 .await
82 .unwrap();
83 let bytes = Uint8Array::new(&data).to_vec();
84 Ok(bytes)
85 }
86 None => Err(ReadError::WindowError),
87 }
88}
89
90#[cfg(not(target_arch = "wasm32"))]
91pub(crate) async fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, ReadError> {
92 Ok(std::fs::read(path)?)
93}
94
95pub(crate) enum ReadError {
96 IoError(std::io::Error),
97 #[cfg(target_arch = "wasm32")]
98 ResponseError(u16, String),
99 #[cfg(target_arch = "wasm32")]
100 WindowError,
101}
102
103impl Debug for ReadError {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 match self {
106 Self::IoError(e) => write!(f, "IoError({:?})", e),
107 #[cfg(target_arch = "wasm32")]
108 Self::ResponseError(code, text) => write!(f, "ResponseError({:?}, {:?})", code, text),
109 #[cfg(target_arch = "wasm32")]
110 Self::WindowError => write!(f, "WindowError"),
111 }
112 }
113}
114
115impl From<std::io::Error> for ReadError {
116 fn from(value: std::io::Error) -> Self {
117 Self::IoError(value)
118 }
119}
120
121pub(crate) struct Loader {
122 blocked_loading: usize,
123 background_loading: usize,
124 #[cfg(not(target_arch = "wasm32"))]
125 pool: ThreadPool,
126}
127
128impl Loader {
129 pub fn new() -> Self {
130 Self {
131 background_loading: 0,
132 blocked_loading: 0,
133 #[cfg(not(target_arch = "wasm32"))]
134 pool: ThreadPool::new().unwrap(),
135 }
136 }
137
138 pub fn remove_item_loading(&mut self, loading_op: LoadingOp) {
139 match loading_op {
140 LoadingOp::Background => self.background_loading -= 1,
141 LoadingOp::Blocking => self.blocked_loading -= 1,
142 }
143 }
144
145 pub fn get_loading_resources(&self) -> usize {
146 self.background_loading + self.blocked_loading
147 }
148
149 #[cfg(target_arch = "wasm32")]
150 pub fn is_blocked(&self) -> bool {
151 self.blocked_loading > 0
152 }
153
154 pub fn load(&mut self, ip_resource: InProgressResource, proxy: EventLoopProxy<BpEvent>) {
155 match ip_resource.loading_op {
156 LoadingOp::Background => self.background_load(ip_resource, proxy),
157 LoadingOp::Blocking => self.blocking_load(ip_resource, proxy),
158 }
159 }
160
161 #[cfg(not(target_arch = "wasm32"))]
165 pub fn blocking_load(
166 &mut self,
167 ip_resource: InProgressResource,
168 proxy: EventLoopProxy<BpEvent>,
169 ) {
170 let data: Result<Vec<u8>, ReadError> = match std::fs::read(&ip_resource.path) {
171 Ok(d) => Ok(d),
172 Err(e) => Err(e.into()),
173 };
174
175 let resource = Resource::from_result(
176 data,
177 ip_resource.path,
178 ip_resource.id,
179 ip_resource.resource_type,
180 ip_resource.loading_op,
181 );
182 self.blocked_loading += 1;
183 proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
184 }
185
186 #[cfg(target_arch = "wasm32")]
188 pub fn blocking_load(
189 &mut self,
190 ip_resource: InProgressResource,
191 proxy: EventLoopProxy<BpEvent>,
192 ) {
193 use wasm_bindgen_futures::spawn_local;
194 self.blocked_loading += 1;
195 spawn_local(async move {
196 let result = web_read(&ip_resource.path).await;
197 let resource = Resource::from_result(
198 result,
199 ip_resource.path,
200 ip_resource.id,
201 ip_resource.resource_type,
202 ip_resource.loading_op,
203 );
204 proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
205 });
206 }
207
208 pub fn background_load(
210 &mut self,
211 ip_resource: InProgressResource,
212 proxy: EventLoopProxy<BpEvent>,
213 ) {
214 self.background_loading += 1;
215 #[cfg(not(target_arch = "wasm32"))]
216 {
217 self.pool.spawn_ok(async move {
218 let result = read(&ip_resource.path).await;
219 let resource = Resource::from_result(
220 result,
221 ip_resource.path,
222 ip_resource.id,
223 ip_resource.resource_type,
224 ip_resource.loading_op,
225 );
226 proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
227 });
228 }
229
230 #[cfg(target_arch = "wasm32")]
231 {
232 use wasm_bindgen_futures::spawn_local;
233 spawn_local(async move {
234 let result = web_read(&ip_resource.path).await;
235 let resource = Resource::from_result(
236 result,
237 ip_resource.path,
238 ip_resource.id,
239 ip_resource.resource_type,
240 ip_resource.loading_op,
241 );
242 proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
243 });
244 }
245 }
246}
247
248#[derive(Debug)]
249pub(crate) struct Resource {
250 pub(crate) path: PathBuf,
251 pub(crate) data: Vec<u8>,
252 pub(crate) id: NonZeroU64,
253 pub(crate) resource_type: ResourceType,
254 pub(crate) loading_op: LoadingOp,
255}
256
257#[derive(Debug)]
258pub(crate) struct ResourceError {
259 pub(crate) error: ReadError,
260 _path: PathBuf,
261 pub(crate) id: NonZeroU64,
262 pub(crate) resource_type: ResourceType,
263 pub(crate) loading_op: LoadingOp,
264}
265
266impl Resource {
267 pub fn from_result(
268 result: Result<Vec<u8>, ReadError>,
269 path: PathBuf,
270 id: NonZeroU64,
271 resource_type: ResourceType,
272 loading_op: LoadingOp,
273 ) -> Result<Self, ResourceError> {
274 match result {
275 Ok(data) => Ok(Self {
276 path,
277 data,
278 id,
279 resource_type,
280 loading_op,
281 }),
282 Err(e) => Err(ResourceError {
283 error: e,
284 _path: path,
285 id,
286 resource_type,
287 loading_op,
288 }),
289 }
290 }
291}
292
293#[derive(Debug)]
294pub(crate) struct InProgressResource {
295 path: PathBuf,
296 id: NonZeroU64,
297 resource_type: ResourceType,
298 loading_op: LoadingOp,
299}
300
301impl InProgressResource {
302 pub fn new(
303 path: &Path,
304 id: NonZeroU64,
305 resource_type: ResourceType,
306 loading_op: LoadingOp,
307 ) -> Self {
308 Self {
309 path: path.to_owned(),
310 id,
311 resource_type,
312 loading_op,
313 }
314 }
315}
316
317#[derive(Debug)]
318pub(crate) enum ResourceType {
319 Image(SamplerType, SamplerType),
320 Shader(IntermediateOptions),
321 Bytes,
322 Font,
323}
324
325impl PartialEq for ResourceType {
326 fn eq(&self, other: &Self) -> bool {
327 match (self, other) {
328 (Self::Bytes, Self::Bytes) => true,
329 (Self::Font, Self::Font) => true,
330 (Self::Shader(option_1), Self::Shader(option_2)) => {
331 option_1.check_has() == option_2.check_has()
332 }
333 (Self::Image(s1, s2), Self::Image(s3, s4)) => s1 == s3 && s2 == s4,
334 _ => false,
335 }
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
347pub enum LoadingOp {
348 Background,
349 Blocking,
350}
351
352pub(crate) fn generate_id<T>() -> ResourceId<T> {
353 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
354 let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
355 ResourceId(NonZeroU64::new(id).unwrap(), PhantomData::<T>)
356}
357
358#[derive(PartialOrd, Ord)]
360pub struct ResourceId<T>(NonZeroU64, std::marker::PhantomData<T>);
361
362impl<T> ResourceId<T> {
363 pub(crate) fn from_number(number: NonZeroU64) -> Self {
364 Self(number, PhantomData)
365 }
366
367 pub(crate) fn get_id(&self) -> NonZeroU64 {
368 self.0
369 }
370}
371
372impl<T> Clone for ResourceId<T> {
373 fn clone(&self) -> Self {
374 *self
375 }
376}
377
378impl<T> Copy for ResourceId<T> {}
379
380impl<T> std::fmt::Debug for ResourceId<T> {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 f.debug_tuple("ID").field(&self.0).finish()
383 }
384}
385
386impl<T> PartialEq for ResourceId<T> {
387 fn eq(&self, other: &Self) -> bool {
388 self.0 == other.0
389 }
390}
391
392impl<T> Eq for ResourceId<T> {}
393
394impl<T> std::hash::Hash for ResourceId<T> {
395 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
396 self.0.hash(state)
397 }
398}
399
400type ResourceMap<T> = HashMap<ResourceId<T>, T>;
401
402pub(crate) struct ResourceManager {
403 btye_resources: ResourceMap<Vec<u8>>,
404 bindgroup_resources: ResourceMap<Texture>,
405 pipeline_resource: ResourceMap<Shader>,
406 fonts: ResourceMap<Font>,
407}
408
409impl ResourceManager {
410 pub fn new() -> Self {
411 Self {
412 btye_resources: HashMap::new(),
413 bindgroup_resources: HashMap::new(),
414 pipeline_resource: HashMap::new(),
415 fonts: HashMap::new(),
416 }
417 }
418
419 pub fn insert_bytes(&mut self, key: ResourceId<Vec<u8>>, data: Vec<u8>) {
420 self.btye_resources.insert(key, data);
421 }
422
423 pub fn insert_texture(&mut self, key: ResourceId<Texture>, data: Texture) {
424 self.bindgroup_resources.insert(key, data);
425 }
426
427 pub fn insert_pipeline(&mut self, key: ResourceId<Shader>, data: Shader) {
428 self.pipeline_resource.insert(key, data);
429 }
430
431 pub fn insert_font(&mut self, key: ResourceId<Font>, data: Font) {
432 self.fonts.insert(key, data);
433 }
434
435 pub fn get_byte_resource(&self, key: &ResourceId<Vec<u8>>) -> Option<&Vec<u8>> {
436 self.btye_resources.get(key)
437 }
438
439 pub fn get_texture(&self, key: &ResourceId<Texture>) -> Option<&Texture> {
440 self.bindgroup_resources.get(key)
441 }
442
443 pub fn get_pipeline(&self, key: &ResourceId<Shader>) -> Option<&Shader> {
444 self.pipeline_resource.get(key)
445 }
446
447 pub fn get_font(&self, key: &ResourceId<Font>) -> Option<&Font> {
448 self.fonts.get(key)
449 }
450
451 pub fn get_mut_shader(&mut self, key: &ResourceId<Shader>) -> Option<&mut Shader> {
452 self.pipeline_resource.get_mut(key)
453 }
454}