Skip to main content

oxide_asset/
server.rs

1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::sync::mpsc::{self, Receiver, TryRecvError};
4
5use crate::{Assets, Handle, HandleAllocator};
6
7#[derive(thiserror::Error, Debug)]
8pub enum AssetServerError {
9    #[error("{0}")]
10    Message(String),
11    #[error("asset type mismatch during async load completion")]
12    TypeMismatch,
13    #[error("asset loading thread disconnected")]
14    ChannelDisconnected,
15}
16
17struct PendingAsset {
18    type_id: TypeId,
19    receiver: Receiver<Result<Box<dyn Any + Send>, AssetServerError>>,
20}
21
22pub struct AssetServer {
23    allocator: HandleAllocator,
24    pending: HashMap<u64, PendingAsset>,
25}
26
27impl Default for AssetServer {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl AssetServer {
34    pub fn new() -> Self {
35        Self {
36            allocator: HandleAllocator::new(),
37            pending: HashMap::new(),
38        }
39    }
40
41    pub fn allocate_handle<T>(&mut self) -> Handle<T> {
42        self.allocator.allocate::<T>()
43    }
44
45    pub fn load_async<T, F>(&mut self, loader: F) -> Handle<T>
46    where
47        T: Send + 'static,
48        F: FnOnce() -> Result<T, AssetServerError> + Send + 'static,
49    {
50        let handle = self.allocate_handle::<T>();
51        let id = handle.id();
52        let (sender, receiver) = mpsc::channel();
53
54        std::thread::spawn(move || {
55            let result = loader().map(|asset| Box::new(asset) as Box<dyn Any + Send>);
56            let _ = sender.send(result);
57        });
58
59        self.pending.insert(
60            id,
61            PendingAsset {
62                type_id: TypeId::of::<T>(),
63                receiver,
64            },
65        );
66
67        handle
68    }
69
70    /// Polls for completed async assets and returns ready `(Handle<T>, T)` pairs.
71    pub fn poll_ready<T: Send + 'static>(
72        &mut self,
73    ) -> Vec<Result<(Handle<T>, T), AssetServerError>> {
74        let mut completed = Vec::new();
75        let pending_ids: Vec<u64> = self
76            .pending
77            .iter()
78            .filter_map(|(id, pending)| (pending.type_id == TypeId::of::<T>()).then_some(*id))
79            .collect();
80
81        for id in pending_ids {
82            let status = match self.pending.get(&id) {
83                Some(pending) => pending.receiver.try_recv(),
84                None => continue,
85            };
86
87            match status {
88                Ok(result) => {
89                    let _ = self.pending.remove(&id);
90                    match result {
91                        Ok(boxed_asset) => match boxed_asset.downcast::<T>() {
92                            Ok(asset) => {
93                                let handle = Handle::new(id);
94                                completed.push(Ok((handle, *asset)));
95                            }
96                            Err(_) => completed.push(Err(AssetServerError::TypeMismatch)),
97                        },
98                        Err(err) => completed.push(Err(err)),
99                    }
100                }
101                Err(TryRecvError::Empty) => {}
102                Err(TryRecvError::Disconnected) => {
103                    let _ = self.pending.remove(&id);
104                    completed.push(Err(AssetServerError::ChannelDisconnected));
105                }
106            }
107        }
108
109        completed
110    }
111
112    pub fn poll_loaded<T: Send + 'static>(
113        &mut self,
114        assets: &mut Assets<T>,
115    ) -> Vec<Result<Handle<T>, AssetServerError>> {
116        let mut completed_handles = Vec::new();
117        for result in self.poll_ready::<T>() {
118            match result {
119                Ok((handle, asset)) => {
120                    assets.insert(handle, asset);
121                    completed_handles.push(Ok(handle));
122                }
123                Err(err) => completed_handles.push(Err(err)),
124            }
125        }
126        completed_handles
127    }
128
129    pub fn is_loading<T: 'static>(&self, handle: &Handle<T>) -> bool {
130        self.pending.contains_key(&handle.id())
131    }
132
133    pub fn pending_count(&self) -> usize {
134        self.pending.len()
135    }
136}