1use core::fmt;
15
16use alloc::format;
17use serde::de::{self, Visitor};
18use serde::{Deserialize, Deserializer, Serialize};
19
20use alloc::vec::Vec;
21
22use crate::ecs::resolver::{
23 resolve_audio_clip_handle, resolve_font_handle, resolve_material_handle, resolve_mesh_handle,
24 resolve_name, resolve_shader_handle, resolve_skinned_mesh_handle, resolve_texture_handle,
25};
26
27macro_rules! resource_handles {
28 ( $( $(#[$m:meta])* $name:ident ),+ $(,)? ) => {
29 $(
30 $(#[$m])*
31 #[derive(
32 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default,
33 Serialize, Deserialize,
34 )]
35 #[serde(transparent)]
36 pub struct $name(
37 pub u32,
39 );
40
41 impl $name {
42 pub fn index(self) -> usize {
44 self.0 as usize
45 }
46 }
47 )+
48 };
49}
50
51resource_handles! {
52 MeshHandle,
54 TextureHandle,
56 MaterialHandle,
58 FontHandle,
60 AudioClipHandle,
62 CubemapTextureHandle,
64 EnvironmentMapHandle,
66 ColorLutHandle,
68 SkinnedMeshHandle,
70 ShaderHandle,
72}
73
74macro_rules! handle_ref_de {
82 (
83 $(#[$extra:meta])*
84 $handle:ident, $article:literal, $noun:literal,
85 $resolve:ident, $ref_fn:ident, $opt_fn:ident $(,)?
86 ) => {
87 fn $ref_fn(name: &str) -> Option<u32> {
88 $resolve(name).or_else(|| resolve_name(name))
89 }
90
91 #[doc = concat!(
92 "`serde` `deserialize_with` helper for an optional ", $noun,
93 " reference field."
94 )]
95 #[doc = concat!(
97 "Mirrors [`de_opt_asset_ref`](crate::ecs::asset_id::de_opt_asset_ref) ",
98 "but resolves to a [`",
99 stringify!($handle),
100 "`]: an integer is an already-resolved handle (the compiled-args / ",
101 "runtime form); a name string is resolved through the installed ",
102 $noun,
103 "-handle resolver; an empty string or null is `None`. Apply with ",
104 "`#[serde(default, deserialize_with = \"concinnity_core::ecs::",
105 stringify!($opt_fn),
106 "\")]`."
107 )]
108 $(#[$extra])*
109 pub fn $opt_fn<'de, D>(d: D) -> Result<Option<$handle>, D::Error>
110 where
111 D: Deserializer<'de>,
112 {
113 if !d.is_human_readable() {
117 return Option::<$handle>::deserialize(d);
118 }
119
120 struct OptVisitor;
121
122 impl Visitor<'_> for OptVisitor {
123 type Value = Option<$handle>;
124
125 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 f.write_str(concat!(
127 $article, " ", $noun,
128 " handle integer, reference name string, or null"
129 ))
130 }
131
132 fn visit_unit<E: de::Error>(self) -> Result<Option<$handle>, E> {
133 Ok(None)
134 }
135 fn visit_none<E: de::Error>(self) -> Result<Option<$handle>, E> {
136 Ok(None)
137 }
138 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Option<$handle>, E> {
139 Ok(Some($handle(v as u32)))
140 }
141 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Option<$handle>, E> {
142 Ok(Some($handle(v as u32)))
143 }
144 fn visit_str<E: de::Error>(self, v: &str) -> Result<Option<$handle>, E> {
145 if v.is_empty() {
146 return Ok(None);
147 }
148 $ref_fn(v).map(|h| Some($handle(h))).ok_or_else(|| {
149 E::custom(format!(
150 concat!(
151 "no ", $noun,
152 "-handle resolver installed to resolve reference {:?}"
153 ),
154 v
155 ))
156 })
157 }
158 fn visit_string<E: de::Error>(
159 self,
160 v: alloc::string::String,
161 ) -> Result<Option<$handle>, E> {
162 self.visit_str(&v)
163 }
164 }
165
166 d.deserialize_any(OptVisitor)
167 }
168 };
169}
170
171handle_ref_de! {
172 TextureHandle, "a", "texture",
173 resolve_texture_handle, resolve_texture_ref, de_opt_texture_handle,
174}
175handle_ref_de! {
176 ShaderHandle, "a", "shader",
177 resolve_shader_handle, resolve_shader_ref, de_opt_shader_handle,
178}
179handle_ref_de! {
180 MaterialHandle, "a", "material",
181 resolve_material_handle, resolve_material_ref, de_opt_material_handle,
182}
183handle_ref_de! {
184 MeshHandle, "a", "mesh",
187 resolve_mesh_handle, resolve_mesh_ref, de_opt_mesh_handle,
188}
189handle_ref_de! {
190 SkinnedMeshHandle, "a", "skinned-mesh",
195 resolve_skinned_mesh_handle, resolve_skinned_mesh_ref, de_opt_skinned_mesh_handle,
196}
197handle_ref_de! {
198 FontHandle, "a", "font",
199 resolve_font_handle, resolve_font_ref, de_opt_font_handle,
200}
201handle_ref_de! {
202 AudioClipHandle, "an", "audio-clip",
203 resolve_audio_clip_handle, resolve_audio_clip_ref, de_opt_audio_clip_handle,
204}
205
206pub fn de_texture_handle<'de, D>(d: D) -> Result<TextureHandle, D::Error>
214where
215 D: Deserializer<'de>,
216{
217 if !d.is_human_readable() {
218 return TextureHandle::deserialize(d);
219 }
220
221 struct HandleVisitor;
222
223 impl Visitor<'_> for HandleVisitor {
224 type Value = TextureHandle;
225
226 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 f.write_str("a texture handle integer or reference name string")
228 }
229
230 fn visit_u64<E: de::Error>(self, v: u64) -> Result<TextureHandle, E> {
231 Ok(TextureHandle(v as u32))
232 }
233 fn visit_i64<E: de::Error>(self, v: i64) -> Result<TextureHandle, E> {
234 Ok(TextureHandle(v as u32))
235 }
236 fn visit_str<E: de::Error>(self, v: &str) -> Result<TextureHandle, E> {
237 resolve_texture_ref(v).map(TextureHandle).ok_or_else(|| {
238 E::custom(format!(
239 "no texture-handle resolver installed to resolve reference {v:?}"
240 ))
241 })
242 }
243 fn visit_string<E: de::Error>(self, v: alloc::string::String) -> Result<TextureHandle, E> {
244 self.visit_str(&v)
245 }
246 }
247
248 d.deserialize_any(HandleVisitor)
249}
250
251pub fn de_audio_clip_handle_vec<'de, D>(d: D) -> Result<Vec<AudioClipHandle>, D::Error>
259where
260 D: Deserializer<'de>,
261{
262 if !d.is_human_readable() {
263 return Vec::<AudioClipHandle>::deserialize(d);
264 }
265
266 struct VecVisitor;
267
268 impl<'de> Visitor<'de> for VecVisitor {
269 type Value = Vec<AudioClipHandle>;
270
271 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 f.write_str("a list of audio-clip handle integers or reference name strings")
273 }
274
275 fn visit_seq<A>(self, mut seq: A) -> Result<Vec<AudioClipHandle>, A::Error>
276 where
277 A: de::SeqAccess<'de>,
278 {
279 let mut out = Vec::new();
280 while let Some(handle) = seq.next_element_seed(OneRef)? {
283 if let Some(handle) = handle {
284 out.push(handle);
285 }
286 }
287 Ok(out)
288 }
289 }
290
291 struct OneRef;
294 impl<'de> de::DeserializeSeed<'de> for OneRef {
295 type Value = Option<AudioClipHandle>;
296 fn deserialize<D2>(self, d: D2) -> Result<Option<AudioClipHandle>, D2::Error>
297 where
298 D2: Deserializer<'de>,
299 {
300 de_opt_audio_clip_handle(d)
301 }
302 }
303
304 d.deserialize_seq(VecVisitor)
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use alloc::string::ToString;
311 use alloc::vec;
312
313 #[test]
314 fn handle_serializes_as_a_bare_u32() {
315 let json = serde_json::to_string(&TextureHandle(7)).unwrap();
317 assert_eq!(json, "7");
318 let back: TextureHandle = serde_json::from_str("7").unwrap();
319 assert_eq!(back, TextureHandle(7));
320 }
321
322 #[test]
323 fn index_is_the_inner_value() {
324 assert_eq!(MeshHandle(0).index(), 0);
325 assert_eq!(MeshHandle(42).index(), 42);
326 }
327
328 #[test]
329 fn per_kind_handles_are_distinct_types_with_independent_values() {
330 let table = ["a", "b", "c"];
333 assert_eq!(table[TextureHandle(1).index()], "b");
334 assert_eq!(table[MeshHandle(2).index()], "c");
335 }
336
337 use crate::test_support::{NoneDeserializer, install_resolvers, len_handle_resolver};
338
339 #[derive(serde::Deserialize)]
340 struct Holder {
341 #[serde(default, deserialize_with = "de_opt_texture_handle")]
342 tex: Option<TextureHandle>,
343 }
344
345 #[test]
346 fn de_opt_texture_handle_reads_an_already_resolved_integer() {
347 let h: Holder = serde_json::from_str("{\"tex\":7}").unwrap();
350 assert_eq!(h.tex, Some(TextureHandle(7)));
351 }
352
353 #[test]
354 fn de_opt_texture_handle_resolves_a_name_through_the_seam() {
355 install_resolvers();
356 let h: Holder = serde_json::from_str("{\"tex\":\"floor\"}").unwrap();
357 assert_eq!(h.tex, Some(TextureHandle(5)));
358 }
359
360 #[test]
361 fn de_opt_texture_handle_treats_empty_null_and_missing_as_none() {
362 assert!(
363 serde_json::from_str::<Holder>("{\"tex\":\"\"}")
364 .unwrap()
365 .tex
366 .is_none()
367 );
368 assert!(
369 serde_json::from_str::<Holder>("{\"tex\":null}")
370 .unwrap()
371 .tex
372 .is_none()
373 );
374 assert!(serde_json::from_str::<Holder>("{}").unwrap().tex.is_none());
375 }
376
377 #[derive(Debug, serde::Deserialize)]
378 struct AudioHolder {
379 #[serde(default, deserialize_with = "de_opt_audio_clip_handle")]
380 clip: Option<AudioClipHandle>,
381 #[serde(default, deserialize_with = "de_audio_clip_handle_vec")]
382 sounds: Vec<AudioClipHandle>,
383 }
384
385 #[test]
386 fn de_opt_audio_clip_handle_reads_integers_and_resolves_names() {
387 install_resolvers();
388 let h: AudioHolder = serde_json::from_str("{\"clip\":7}").unwrap();
390 assert_eq!(h.clip, Some(AudioClipHandle(7)));
391 let h: AudioHolder = serde_json::from_str("{\"clip\":\"theme\"}").unwrap();
392 assert_eq!(h.clip, Some(AudioClipHandle(5)));
393 assert!(
395 serde_json::from_str::<AudioHolder>("{\"clip\":\"\"}")
396 .unwrap()
397 .clip
398 .is_none()
399 );
400 assert!(
401 serde_json::from_str::<AudioHolder>("{}")
402 .unwrap()
403 .clip
404 .is_none()
405 );
406 }
407
408 #[test]
409 fn de_audio_clip_handle_vec_resolves_mixed_and_drops_empties() {
410 install_resolvers();
411 let h: AudioHolder = serde_json::from_str("{\"sounds\":[3,\"door\",\"\"]}").unwrap();
413 assert_eq!(h.sounds, vec![AudioClipHandle(3), AudioClipHandle(4)]);
414 assert!(
416 serde_json::from_str::<AudioHolder>("{}")
417 .unwrap()
418 .sounds
419 .is_empty()
420 );
421 }
422
423 #[derive(serde::Deserialize)]
424 struct TargetHolder {
425 #[serde(default, deserialize_with = "de_opt_skinned_mesh_handle")]
426 target: Option<SkinnedMeshHandle>,
427 }
428
429 #[test]
432 fn handle_helpers_round_trip_through_postcard() {
433 #[derive(serde::Serialize, serde::Deserialize)]
434 struct BakedHolder {
435 #[serde(default, deserialize_with = "de_opt_texture_handle")]
436 tex: Option<TextureHandle>,
437 #[serde(deserialize_with = "de_texture_handle")]
438 stage: TextureHandle,
439 #[serde(default, deserialize_with = "de_opt_mesh_handle")]
440 mesh: Option<MeshHandle>,
441 #[serde(default, deserialize_with = "de_opt_material_handle")]
442 material: Option<MaterialHandle>,
443 #[serde(default, deserialize_with = "de_opt_skinned_mesh_handle")]
444 target: Option<SkinnedMeshHandle>,
445 #[serde(default, deserialize_with = "de_opt_font_handle")]
446 font: Option<FontHandle>,
447 #[serde(default, deserialize_with = "de_opt_audio_clip_handle")]
448 clip: Option<AudioClipHandle>,
449 #[serde(default, deserialize_with = "de_audio_clip_handle_vec")]
450 sounds: Vec<AudioClipHandle>,
451 #[serde(default, deserialize_with = "de_opt_shader_handle")]
452 shader: Option<ShaderHandle>,
453 }
454 let holder = BakedHolder {
455 tex: Some(TextureHandle(3)),
456 stage: TextureHandle(9),
457 mesh: None,
458 material: Some(MaterialHandle(1)),
459 target: Some(SkinnedMeshHandle(2)),
460 font: None,
461 clip: Some(AudioClipHandle(4)),
462 sounds: alloc::vec![AudioClipHandle(5), AudioClipHandle(6)],
463 shader: Some(ShaderHandle(8)),
464 };
465 let bytes = postcard::to_allocvec(&holder).unwrap();
466 let back: BakedHolder = postcard::from_bytes(&bytes).unwrap();
467 assert_eq!(back.tex, holder.tex);
468 assert_eq!(back.stage, holder.stage);
469 assert_eq!(back.mesh, holder.mesh);
470 assert_eq!(back.material, holder.material);
471 assert_eq!(back.target, holder.target);
472 assert_eq!(back.font, holder.font);
473 assert_eq!(back.clip, holder.clip);
474 assert_eq!(back.sounds, holder.sounds);
475 assert_eq!(back.shader, holder.shader);
476 }
477
478 #[test]
479 fn de_opt_skinned_mesh_handle_reads_integers_and_resolves_names() {
480 install_resolvers();
484 let h: TargetHolder = serde_json::from_str("{\"target\":3}").unwrap();
485 assert_eq!(h.target, Some(SkinnedMeshHandle(3)));
486 let h: TargetHolder = serde_json::from_str("{\"target\":\"hero\"}").unwrap();
487 assert_eq!(h.target, Some(SkinnedMeshHandle(4)));
488 assert!(
489 serde_json::from_str::<TargetHolder>("{\"target\":\"\"}")
490 .unwrap()
491 .target
492 .is_none()
493 );
494 assert!(
495 serde_json::from_str::<TargetHolder>("{}")
496 .unwrap()
497 .target
498 .is_none()
499 );
500 }
501
502 macro_rules! opt_handle_cases {
506 ($name:ident, $helper:literal, $helper_fn:path, $handle:ident, $expected:literal) => {
507 #[test]
508 fn $name() {
509 install_resolvers();
510
511 #[derive(Debug, serde::Deserialize)]
512 struct Holder {
513 #[serde(default, deserialize_with = $helper)]
514 r: Option<$handle>,
515 }
516 let parse = |s: &str| serde_json::from_str::<Holder>(s).unwrap().r;
517
518 assert_eq!(parse(r#"{"r":6}"#), Some($handle(6)));
520 assert_eq!(parse(r#"{"r":-1}"#), Some($handle(u32::MAX)));
522 assert_eq!(parse(r#"{"r":"floor"}"#), Some($handle(5)));
524 assert_eq!(parse(r#"{"r":"unknown_x"}"#), Some($handle(9)));
527 assert_eq!(
529 serde_json::from_value::<Holder>(serde_json::json!({"r": "wall"}))
530 .unwrap()
531 .r,
532 Some($handle(4))
533 );
534 assert_eq!(parse(r#"{"r":""}"#), None);
536 assert_eq!(parse(r#"{"r":null}"#), None);
537 assert_eq!(parse("{}"), None);
538 assert_eq!($helper_fn(NoneDeserializer).unwrap(), None);
540 let err = serde_json::from_str::<Holder>(r#"{"r":true}"#)
542 .unwrap_err()
543 .to_string();
544 assert!(err.contains($expected), "{err}");
545 }
546 };
547 }
548
549 opt_handle_cases!(
550 opt_texture_handle_accepts_every_form,
551 "de_opt_texture_handle",
552 de_opt_texture_handle,
553 TextureHandle,
554 "a texture handle integer, reference name string, or null"
555 );
556 opt_handle_cases!(
557 opt_shader_handle_accepts_every_form,
558 "de_opt_shader_handle",
559 de_opt_shader_handle,
560 ShaderHandle,
561 "a shader handle integer, reference name string, or null"
562 );
563 opt_handle_cases!(
564 opt_material_handle_accepts_every_form,
565 "de_opt_material_handle",
566 de_opt_material_handle,
567 MaterialHandle,
568 "a material handle integer, reference name string, or null"
569 );
570 opt_handle_cases!(
571 opt_mesh_handle_accepts_every_form,
572 "de_opt_mesh_handle",
573 de_opt_mesh_handle,
574 MeshHandle,
575 "a mesh handle integer, reference name string, or null"
576 );
577 opt_handle_cases!(
578 opt_skinned_mesh_handle_accepts_every_form,
579 "de_opt_skinned_mesh_handle",
580 de_opt_skinned_mesh_handle,
581 SkinnedMeshHandle,
582 "a skinned-mesh handle integer, reference name string, or null"
583 );
584 opt_handle_cases!(
585 opt_font_handle_accepts_every_form,
586 "de_opt_font_handle",
587 de_opt_font_handle,
588 FontHandle,
589 "a font handle integer, reference name string, or null"
590 );
591 opt_handle_cases!(
592 opt_audio_clip_handle_accepts_every_form,
593 "de_opt_audio_clip_handle",
594 de_opt_audio_clip_handle,
595 AudioClipHandle,
596 "an audio-clip handle integer, reference name string, or null"
597 );
598
599 #[derive(Debug, serde::Deserialize)]
600 struct StageHolder {
601 #[serde(deserialize_with = "de_texture_handle")]
602 stage: TextureHandle,
603 }
604
605 #[test]
606 fn required_texture_handle_accepts_integers_and_names() {
607 install_resolvers();
608 let parse = |s: &str| serde_json::from_str::<StageHolder>(s).unwrap().stage;
609
610 assert_eq!(parse(r#"{"stage":6}"#), TextureHandle(6));
611 assert_eq!(parse(r#"{"stage":-1}"#), TextureHandle(u32::MAX));
612 assert_eq!(parse(r#"{"stage":"floor"}"#), TextureHandle(5));
613 assert_eq!(parse(r#"{"stage":"unknown_x"}"#), TextureHandle(9));
615 assert_eq!(
616 serde_json::from_value::<StageHolder>(serde_json::json!({"stage": "wall"}))
617 .unwrap()
618 .stage,
619 TextureHandle(4)
620 );
621 }
622
623 #[test]
624 fn required_texture_handle_rejects_a_missing_or_wrong_typed_field() {
625 let err = serde_json::from_str::<StageHolder>("{}")
628 .unwrap_err()
629 .to_string();
630 assert!(err.contains("missing field `stage`"), "{err}");
631 let err = serde_json::from_str::<StageHolder>(r#"{"stage":true}"#)
632 .unwrap_err()
633 .to_string();
634 assert!(
635 err.contains("a texture handle integer or reference name string"),
636 "{err}"
637 );
638 }
639
640 #[test]
641 fn audio_clip_handle_vec_rejects_a_non_list() {
642 let err = serde_json::from_str::<AudioHolder>(r#"{"sounds":5}"#)
643 .unwrap_err()
644 .to_string();
645 assert!(
646 err.contains("a list of audio-clip handle integers or reference name strings"),
647 "{err}"
648 );
649 }
650
651 #[test]
652 fn audio_clip_handle_vec_resolves_owned_strings() {
653 install_resolvers();
654 let h: AudioHolder =
656 serde_json::from_value(serde_json::json!({"sounds": ["door", "unknown_x"]})).unwrap();
657 assert_eq!(h.sounds, vec![AudioClipHandle(4), AudioClipHandle(9)]);
658 }
659
660 #[test]
661 fn handles_default_to_zero_and_order_by_index() {
662 assert_eq!(TextureHandle::default(), TextureHandle(0));
664 assert!(MeshHandle(1) < MeshHandle(2));
665 assert_eq!(len_handle_resolver("unknown_floor"), None);
666 }
667}