khora_data/ecs/components/
material_registry.rs1use bincode::config;
25use inventory::collect;
26use khora_core::asset::{AssetHandle, AssetUUID, Material};
27use khora_core::math::LinearRgba;
28
29#[derive(bincode::Encode, bincode::Decode, Debug, Clone)]
31pub struct SerializableMaterialData {
32 pub type_name: String,
34 pub data: Vec<u8>,
36}
37
38pub type MaterialDeserializeFn = fn(&[u8]) -> Result<Box<dyn Material>, String>;
40
41pub struct MaterialRegistration {
45 pub type_name: &'static str,
47 pub serialize: fn(&dyn Material) -> Option<Vec<u8>>,
50 pub deserialize: MaterialDeserializeFn,
52 pub create_default: fn() -> Box<dyn Material>,
54 pub serialize_json: fn(&dyn Material) -> Option<serde_json::Value>,
59 pub deserialize_json: fn(&serde_json::Value) -> Result<Box<dyn Material>, String>,
62}
63
64collect!(MaterialRegistration);
65
66pub fn serialize_material_component(
69 base_color: LinearRgba,
70 material: &dyn Material,
71) -> Option<Vec<u8>> {
72 for reg in inventory::iter::<MaterialRegistration> {
73 if let Some(data) = (reg.serialize)(material) {
74 let serializable = SerializableMaterialData {
75 type_name: reg.type_name.to_string(),
76 data,
77 };
78 return bincode::encode_to_vec(&serializable, config::standard()).ok();
79 }
80 }
81 log::warn!(
83 "No MaterialRegistration found for material type; falling back to base-color-only serialization."
84 );
85 let serializable = SerializableMaterialData {
86 type_name: "__unknown__".to_string(),
87 data: bincode::encode_to_vec(base_color, config::standard()).ok()?,
88 };
89 bincode::encode_to_vec(&serializable, config::standard()).ok()
90}
91
92pub fn deserialize_material_component(
94 data: &[u8],
95) -> Result<(AssetHandle<Box<dyn Material>>, AssetUUID), String> {
96 let (serializable, _): (SerializableMaterialData, _) =
97 bincode::decode_from_slice(data, config::standard()).map_err(|e| e.to_string())?;
98
99 if serializable.type_name == "__unknown__" {
100 let (base_color, _): (LinearRgba, _) =
102 bincode::decode_from_slice(&serializable.data, config::standard())
103 .map_err(|e| e.to_string())?;
104 let mat = khora_core::asset::StandardMaterial {
105 base_color,
106 ..Default::default()
107 };
108 let handle = AssetHandle::new(Box::new(mat) as Box<dyn Material>);
109 return Ok((handle, AssetUUID::new()));
110 }
111
112 for reg in inventory::iter::<MaterialRegistration> {
113 if reg.type_name == serializable.type_name {
114 let material = (reg.deserialize)(&serializable.data)?;
115 let uuid = AssetUUID::new();
116 let handle = AssetHandle::new(material);
117 return Ok((handle, uuid));
118 }
119 }
120
121 Err(format!(
122 "No MaterialRegistration found for type '{}'",
123 serializable.type_name
124 ))
125}
126
127pub fn material_to_json(material: &dyn Material) -> Option<serde_json::Value> {
132 for reg in inventory::iter::<MaterialRegistration> {
133 if let Some(material_json) = (reg.serialize_json)(material) {
134 return Some(serde_json::json!({
135 "type_name": reg.type_name,
136 "material": material_json,
137 }));
138 }
139 }
140 None
141}
142
143pub fn material_from_json(
147 value: &serde_json::Value,
148) -> Result<(AssetHandle<Box<dyn Material>>, AssetUUID), String> {
149 let type_name = value
150 .get("type_name")
151 .and_then(serde_json::Value::as_str)
152 .ok_or_else(|| "material JSON missing string 'type_name'".to_string())?;
153 let material_value = value
154 .get("material")
155 .ok_or_else(|| "material JSON missing 'material' object".to_string())?;
156
157 for reg in inventory::iter::<MaterialRegistration> {
158 if reg.type_name == type_name {
159 let material = (reg.deserialize_json)(material_value)?;
160 return Ok((AssetHandle::new(material), AssetUUID::new()));
161 }
162 }
163
164 Err(format!(
165 "No MaterialRegistration found for type '{type_name}'"
166 ))
167}
168
169use khora_core::asset::{EmissiveMaterial, StandardMaterial, UnlitMaterial, WireframeMaterial};
172
173inventory::submit! {
180 MaterialRegistration {
181 type_name: "StandardMaterial",
182 serialize: |mat| {
183 mat.as_any().downcast_ref::<StandardMaterial>().map(|m| {
184 bincode::encode_to_vec(m, config::standard()).unwrap_or_default()
185 })
186 },
187 deserialize: |data| {
188 let (m, _) = bincode::decode_from_slice::<StandardMaterial, _>(data, config::standard())
189 .map_err(|e| e.to_string())?;
190 Ok(Box::new(m) as Box<dyn Material>)
191 },
192 create_default: || Box::new(StandardMaterial::default()) as Box<dyn Material>,
193 serialize_json: |mat| {
194 mat.as_any()
195 .downcast_ref::<StandardMaterial>()
196 .and_then(|m| serde_json::to_value(m).ok())
197 },
198 deserialize_json: |value| {
199 let m: StandardMaterial =
200 serde_json::from_value(value.clone()).map_err(|e| e.to_string())?;
201 Ok(Box::new(m) as Box<dyn Material>)
202 },
203 }
204}
205
206inventory::submit! {
207 MaterialRegistration {
208 type_name: "UnlitMaterial",
209 serialize: |mat| {
210 mat.as_any().downcast_ref::<UnlitMaterial>().map(|m| {
211 bincode::encode_to_vec(m, config::standard()).unwrap_or_default()
212 })
213 },
214 deserialize: |data| {
215 let (m, _) = bincode::decode_from_slice::<UnlitMaterial, _>(data, config::standard())
216 .map_err(|e| e.to_string())?;
217 Ok(Box::new(m) as Box<dyn Material>)
218 },
219 create_default: || Box::new(UnlitMaterial::default()) as Box<dyn Material>,
220 serialize_json: |mat| {
221 mat.as_any()
222 .downcast_ref::<UnlitMaterial>()
223 .and_then(|m| serde_json::to_value(m).ok())
224 },
225 deserialize_json: |value| {
226 let m: UnlitMaterial =
227 serde_json::from_value(value.clone()).map_err(|e| e.to_string())?;
228 Ok(Box::new(m) as Box<dyn Material>)
229 },
230 }
231}
232
233inventory::submit! {
234 MaterialRegistration {
235 type_name: "EmissiveMaterial",
236 serialize: |mat| {
237 mat.as_any().downcast_ref::<EmissiveMaterial>().map(|m| {
238 bincode::encode_to_vec(m, config::standard()).unwrap_or_default()
239 })
240 },
241 deserialize: |data| {
242 let (m, _) = bincode::decode_from_slice::<EmissiveMaterial, _>(data, config::standard())
243 .map_err(|e| e.to_string())?;
244 Ok(Box::new(m) as Box<dyn Material>)
245 },
246 create_default: || Box::new(EmissiveMaterial::default()) as Box<dyn Material>,
247 serialize_json: |mat| {
248 mat.as_any()
249 .downcast_ref::<EmissiveMaterial>()
250 .and_then(|m| serde_json::to_value(m).ok())
251 },
252 deserialize_json: |value| {
253 let m: EmissiveMaterial =
254 serde_json::from_value(value.clone()).map_err(|e| e.to_string())?;
255 Ok(Box::new(m) as Box<dyn Material>)
256 },
257 }
258}
259
260inventory::submit! {
261 MaterialRegistration {
262 type_name: "WireframeMaterial",
263 serialize: |mat| {
264 mat.as_any().downcast_ref::<WireframeMaterial>().map(|m| {
265 bincode::encode_to_vec(m, config::standard()).unwrap_or_default()
266 })
267 },
268 deserialize: |data| {
269 let (m, _) = bincode::decode_from_slice::<WireframeMaterial, _>(data, config::standard())
270 .map_err(|e| e.to_string())?;
271 Ok(Box::new(m) as Box<dyn Material>)
272 },
273 create_default: || Box::new(WireframeMaterial::default()) as Box<dyn Material>,
274 serialize_json: |mat| {
275 mat.as_any()
276 .downcast_ref::<WireframeMaterial>()
277 .and_then(|m| serde_json::to_value(m).ok())
278 },
279 deserialize_json: |value| {
280 let m: WireframeMaterial =
281 serde_json::from_value(value.clone()).map_err(|e| e.to_string())?;
282 Ok(Box::new(m) as Box<dyn Material>)
283 },
284 }
285}