Skip to main content

ferrum_wgpu/assets/
store.rs

1use {
2    crate::assets::{self, Model, ModelDesc, TypeModel},
3    std::{
4        collections::HashMap,
5        marker::PhantomData,
6        sync::{
7            Arc,
8            atomic::{AtomicUsize, Ordering},
9            mpsc::{self, Receiver, Sender},
10        },
11    },
12    wgpu::{BindGroupLayout, Device, Queue},
13};
14
15/// Handle to a model spawned in the [`ModelStore`]; the forge metaphor: the
16/// caller keeps the ingot's id while the bead melts in the background.
17pub struct Ingot<T> {
18    pub id: usize,
19    _marker: PhantomData<T>,
20}
21
22/// Loading state of a model: requested (`Burning`), ready (`Molten`) or
23/// discarded (`Ash`).
24pub(crate) enum Bead<T> {
25    Burning,
26    Molten(T),
27    #[allow(dead_code)]
28    Ash,
29}
30
31/// Owns every model of the scene and the channel through which asynchronously
32/// loaded models arrive.
33pub struct ModelStore {
34    static_models: HashMap<usize, Bead<Model>>,
35    light_models: HashMap<usize, Bead<Model>>,
36    next_id: AtomicUsize,
37    sender: Sender<(usize, Model)>,
38    receiver: Receiver<(usize, Model)>,
39}
40
41impl ModelStore {
42    pub(crate) fn new() -> Self {
43        let (sender, receiver) = mpsc::channel::<(usize, Model)>();
44        Self {
45            static_models: HashMap::new(),
46            light_models: HashMap::new(),
47            next_id: AtomicUsize::new(0),
48            sender,
49            receiver,
50        }
51    }
52
53    /// Registers the model as `Burning` and launches its load. The loading is
54    /// asynchronous, and the model arrives via the channel when it finishes
55    /// (`Bead::Burning` until then). In native: thread + block_on; in wasm
56    /// there are no threads or block_on, so it's queued in the browser's event
57    /// loop with spawn_local (asset fetching is already async).
58    pub(crate) fn spawn(
59        &mut self,
60        device: &Arc<Device>,
61        queue: &Arc<Queue>,
62        layout: &Arc<BindGroupLayout>,
63        model_desc: ModelDesc,
64    ) -> Ingot<Model> {
65        let id: usize = self.next_id.fetch_add(1, Ordering::SeqCst);
66
67        match model_desc.kind {
68            TypeModel::StaticObj => self.static_models.insert(id, Bead::Burning),
69            TypeModel::PointOfLight => self.light_models.insert(id, Bead::Burning),
70        };
71
72        let device: Arc<Device> = Arc::clone(device);
73        let queue: Arc<Queue> = Arc::clone(queue);
74        let layout: Arc<BindGroupLayout> = Arc::clone(layout);
75        let sender: Sender<(usize, Model)> = self.sender.clone();
76        let path: String = model_desc.path.to_string();
77        let instances: Vec<assets::Instance> = model_desc.instances;
78        let kind: TypeModel = model_desc.kind;
79
80        #[cfg(not(target_arch = "wasm32"))]
81        std::thread::spawn(move || {
82            let result: Result<Model, anyhow::Error> = pollster::block_on(assets::load_model(
83                &path, &device, &queue, &layout, instances, kind,
84            ));
85            if let Ok(model) = result {
86                let _ = sender.send((id, model));
87            }
88        });
89
90        #[cfg(target_arch = "wasm32")]
91        wasm_bindgen_futures::spawn_local(async move {
92            let result: Result<Model, anyhow::Error> =
93                assets::load_model(&path, &device, &queue, &layout, instances, kind).await;
94            if let Ok(model) = result {
95                let _ = sender.send((id, model));
96            }
97        });
98
99        Ingot {
100            id,
101            _marker: PhantomData,
102        }
103    }
104
105    /// Drains the channel and turns every finished load into `Molten`.
106    pub(crate) fn collect_loaded(&mut self) {
107        while let Ok((id, model)) = self.receiver.try_recv() {
108            match model.type_model {
109                TypeModel::StaticObj => self.static_models.insert(id, Bead::Molten(model)),
110                TypeModel::PointOfLight => self.light_models.insert(id, Bead::Molten(model)),
111            };
112        }
113    }
114
115    pub(crate) fn static_loaded(&self) -> impl Iterator<Item = &Model> {
116        self.static_models.values().filter_map(|bead| match bead {
117            Bead::Molten(model) => Some(model),
118            _ => None,
119        })
120    }
121
122    pub(crate) fn light_loaded(&self) -> impl Iterator<Item = &Model> {
123        self.light_models.values().filter_map(|bead| match bead {
124            Bead::Molten(model) => Some(model),
125            _ => None,
126        })
127    }
128
129    pub(crate) fn light_model_mut(&mut self, id: &usize) -> Option<&mut Model> {
130        match self.light_models.get_mut(id) {
131            Some(Bead::Molten(model)) => Some(model),
132            _ => None,
133        }
134    }
135}