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