ursus_core/assets/registry/
material.rs1use crate::assets::upload::GpuUploadRequest;
2use std::collections::{HashMap, HashSet};
3use std::sync::mpsc::Sender;
4use ursus_materials::pack::aos::pack;
5use ursus_materials::{Material, MaterialHandle, ShadingStrategy, StrategyRegistry};
6
7#[derive(Default)]
9pub(crate) struct MaterialHandleAllocator {
10 next: u32,
11}
12
13impl MaterialHandleAllocator {
14 pub(crate) fn alloc(&mut self) -> MaterialHandle {
15 let id = self.next;
16 self.next += 1;
17 MaterialHandle(id)
18 }
19}
20
21#[derive(Default)]
30pub struct MaterialRegistry {
31 handles: MaterialHandleAllocator,
32 materials: HashMap<MaterialHandle, Material>,
33 dirty: HashSet<MaterialHandle>,
34}
35
36impl MaterialRegistry {
37 pub fn insert(&mut self, material: Material) -> MaterialHandle {
40 let handle = self.handles.alloc();
41 self.materials.insert(handle, material);
42 self.dirty.insert(handle);
43 handle
44 }
45
46 pub fn modify(&mut self, handle: MaterialHandle, f: impl FnOnce(&mut Material)) -> bool {
49 let Some(material) = self.materials.get_mut(&handle) else {
50 return false;
51 };
52 f(material);
53 self.dirty.insert(handle);
54 true
55 }
56
57 pub fn get(&self, handle: MaterialHandle) -> Option<&Material> {
58 self.materials.get(&handle)
59 }
60
61 pub(crate) fn drain_dirty(&mut self) -> Vec<(MaterialHandle, Material)> {
65 self.dirty.drain().filter_map(|handle| self.materials.get(&handle).map(|m| (handle, m.clone()))).collect()
66 }
67}
68
69#[derive(Default)]
71pub struct MaterialAssetRegistry {
72 materials: MaterialRegistry,
73 strategies: StrategyRegistry,
74}
75
76impl MaterialAssetRegistry {
77 pub fn insert(&mut self, material: Material) -> MaterialHandle {
78 if self.strategies.by_name(material.strategy).is_none() {
79 panic!(
80 "MaterialAssetRegistry::insert: material '{}' references unknown strategy '{}' - \
81 register it via register_strategy() before creating materials that use it",
82 material.name, material.strategy
83 );
84 }
85 self.materials.insert(material)
86 }
87
88 pub fn modify(&mut self, handle: MaterialHandle, f: impl FnOnce(&mut Material)) -> bool {
89 self.materials.modify(handle, f)
90 }
91
92 pub fn get(&self, handle: MaterialHandle) -> Option<&Material> {
93 self.materials.get(handle)
94 }
95
96 pub fn register_strategy(&mut self, strategy: impl ShadingStrategy + 'static) {
97 self.strategies.register(strategy);
98 }
99
100 pub(crate) fn flush_dirty(&mut self, upload_tx: &Sender<GpuUploadRequest>) {
103 for (handle, material) in self.materials.drain_dirty() {
104 let Some(strategy) = self.strategies.by_name(material.strategy) else {
105 log::error!(
106 "MaterialAssetRegistry: material '{}' references unknown strategy '{}'",
107 material.name,
108 material.strategy
109 );
110 continue;
111 };
112
113 if let Err(e) = strategy.validate(&material) {
114 log::error!("MaterialAssetRegistry: material '{}' failed validation: {e}", material.name);
115 continue;
116 }
117
118 let values = match strategy.resolve(&material) {
119 Ok(values) => values,
120 Err(e) => {
121 log::error!("MaterialAssetRegistry: failed to resolve material '{}': {e}", material.name);
122 continue;
123 }
124 };
125
126 let Some(bytes) = pack(strategy.fields(), &values) else {
127 log::error!(
128 "MaterialAssetRegistry: failed to pack material '{}' - value/field mismatch",
129 material.name
130 );
131 continue;
132 };
133
134 upload_tx.send(GpuUploadRequest::Material { handle, bytes }).ok();
135 }
136 }
137}