1use crate::InputIdentity;
9use crate::bounded_deserialize::{
10 BudgetedCappedSequenceSeed, CappedSequence, RowBudget, consume_ignored_tail,
11};
12use serde::de::{DeserializeSeed, Error as _, IgnoredAny, MapAccess, SeqAccess, Visitor};
13use serde::{Deserialize, Deserializer, Serialize};
14use std::fmt;
15use std::marker::PhantomData;
16
17pub const RAW_SCENE_ATTACHMENT_INVENTORY_V1_ID: &str =
19 "urn:animsmith:raw-scene-attachment-inventory:1";
20pub const RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS: usize = 4_096;
22pub const RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_TEXT_BYTES: usize = 0;
27
28fn deserialize_inventory_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
29where
30 D: Deserializer<'de>,
31 T: Deserialize<'de>,
32{
33 struct BoundedVecVisitor<T>(PhantomData<T>);
34
35 impl<'de, T> Visitor<'de> for BoundedVecVisitor<T>
36 where
37 T: Deserialize<'de>,
38 {
39 type Value = Vec<T>;
40
41 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(
43 formatter,
44 "at most {RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS} raw inventory rows"
45 )
46 }
47
48 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
49 where
50 A: SeqAccess<'de>,
51 {
52 let mut values = Vec::with_capacity(
53 sequence
54 .size_hint()
55 .unwrap_or(0)
56 .min(RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS),
57 );
58 while values.len() < RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS {
59 let Some(value) = sequence.next_element()? else {
60 return Ok(values);
61 };
62 values.push(value);
63 }
64 if sequence.next_element::<serde::de::IgnoredAny>()?.is_some() {
65 return Err(A::Error::custom(
66 "raw scene/attachment inventory exceeded its row bound",
67 ));
68 }
69 Ok(values)
70 }
71 }
72
73 deserializer.deserialize_seq(BoundedVecVisitor(PhantomData))
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "snake_case")]
79pub enum RawSceneAttachmentCoverageV1 {
80 Complete,
82 PrefixOverflow,
84 Unavailable,
86}
87
88impl RawSceneAttachmentCoverageV1 {
89 pub const fn proves_absence(self) -> bool {
91 matches!(self, Self::Complete)
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(deny_unknown_fields)]
98pub struct RawSourceSkeletonEvidenceV1 {
99 coverage: RawSceneAttachmentCoverageV1,
100 source_node_count: u64,
101 source_skin_count: u64,
102}
103
104impl RawSourceSkeletonEvidenceV1 {
105 pub const fn new(
107 coverage: RawSceneAttachmentCoverageV1,
108 source_node_count: u64,
109 source_skin_count: u64,
110 ) -> Self {
111 Self {
112 coverage,
113 source_node_count,
114 source_skin_count,
115 }
116 }
117 pub const fn coverage(&self) -> RawSceneAttachmentCoverageV1 {
119 self.coverage
120 }
121 pub const fn source_node_count(&self) -> u64 {
123 self.source_node_count
124 }
125 pub const fn source_skin_count(&self) -> u64 {
127 self.source_skin_count
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133#[serde(deny_unknown_fields)]
134pub struct RawSceneRootRowV1 {
135 source_scene_index: u64,
136 #[serde(deserialize_with = "deserialize_inventory_vec")]
137 root_node_indices: Vec<u64>,
138}
139impl RawSceneRootRowV1 {
140 pub fn new(source_scene_index: u64, root_node_indices: Vec<u64>) -> Self {
142 Self {
143 source_scene_index,
144 root_node_indices,
145 }
146 }
147 pub const fn source_scene_index(&self) -> u64 {
149 self.source_scene_index
150 }
151 pub fn root_node_indices(&self) -> &[u64] {
153 &self.root_node_indices
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
159#[serde(deny_unknown_fields)]
160pub struct RawNodeMeshAttachmentRowV1 {
161 source_node_index: u64,
162 source_mesh_index: u64,
163}
164impl RawNodeMeshAttachmentRowV1 {
165 pub const fn new(source_node_index: u64, source_mesh_index: u64) -> Self {
167 Self {
168 source_node_index,
169 source_mesh_index,
170 }
171 }
172 pub const fn source_node_index(&self) -> u64 {
174 self.source_node_index
175 }
176 pub const fn source_mesh_index(&self) -> u64 {
178 self.source_mesh_index
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
184#[serde(rename_all = "snake_case")]
185pub enum RawPrimitiveTopologyV1 {
186 Points,
188 Lines,
190 LineLoop,
192 LineStrip,
194 Triangles,
196 TriangleStrip,
198 TriangleFan,
200 Other {
202 mode: u64,
204 },
205}
206impl RawPrimitiveTopologyV1 {
207 pub const fn from_gltf_mode(mode: u64) -> Self {
209 match mode {
210 0 => Self::Points,
211 1 => Self::Lines,
212 2 => Self::LineLoop,
213 3 => Self::LineStrip,
214 4 => Self::Triangles,
215 5 => Self::TriangleStrip,
216 6 => Self::TriangleFan,
217 _ => Self::Other { mode },
218 }
219 }
220 pub const fn gltf_mode(self) -> u64 {
222 match self {
223 Self::Points => 0,
224 Self::Lines => 1,
225 Self::LineLoop => 2,
226 Self::LineStrip => 3,
227 Self::Triangles => 4,
228 Self::TriangleStrip => 5,
229 Self::TriangleFan => 6,
230 Self::Other { mode } => mode,
231 }
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
237#[serde(deny_unknown_fields)]
238pub struct RawMeshPrimitiveRowV1 {
239 source_mesh_index: u64,
240 source_primitive_index: u64,
241 topology: RawPrimitiveTopologyV1,
242 indices_accessor_index: Option<u64>,
243}
244impl RawMeshPrimitiveRowV1 {
245 pub const fn new(
247 source_mesh_index: u64,
248 source_primitive_index: u64,
249 topology: RawPrimitiveTopologyV1,
250 indices_accessor_index: Option<u64>,
251 ) -> Self {
252 Self {
253 source_mesh_index,
254 source_primitive_index,
255 topology,
256 indices_accessor_index,
257 }
258 }
259 pub const fn source_mesh_index(&self) -> u64 {
261 self.source_mesh_index
262 }
263 pub const fn source_primitive_index(&self) -> u64 {
265 self.source_primitive_index
266 }
267 pub const fn topology(&self) -> RawPrimitiveTopologyV1 {
269 self.topology
270 }
271 pub const fn indices_accessor_index(&self) -> Option<u64> {
273 self.indices_accessor_index
274 }
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
279#[serde(deny_unknown_fields)]
280pub struct RawSceneRootRowsV1 {
281 coverage: RawSceneAttachmentCoverageV1,
282 #[serde(deserialize_with = "deserialize_inventory_vec")]
283 rows: Vec<RawSceneRootRowV1>,
284}
285impl RawSceneRootRowsV1 {
286 pub fn new(coverage: RawSceneAttachmentCoverageV1, rows: Vec<RawSceneRootRowV1>) -> Self {
288 Self { coverage, rows }
289 }
290 pub const fn coverage(&self) -> RawSceneAttachmentCoverageV1 {
292 self.coverage
293 }
294 pub fn rows(&self) -> &[RawSceneRootRowV1] {
296 &self.rows
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
302#[serde(deny_unknown_fields)]
303pub struct RawNodeMeshAttachmentRowsV1 {
304 coverage: RawSceneAttachmentCoverageV1,
305 #[serde(deserialize_with = "deserialize_inventory_vec")]
306 rows: Vec<RawNodeMeshAttachmentRowV1>,
307}
308impl RawNodeMeshAttachmentRowsV1 {
309 pub fn new(
311 coverage: RawSceneAttachmentCoverageV1,
312 rows: Vec<RawNodeMeshAttachmentRowV1>,
313 ) -> Self {
314 Self { coverage, rows }
315 }
316 pub const fn coverage(&self) -> RawSceneAttachmentCoverageV1 {
318 self.coverage
319 }
320 pub fn rows(&self) -> &[RawNodeMeshAttachmentRowV1] {
322 &self.rows
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
328#[serde(deny_unknown_fields)]
329pub struct RawMeshPrimitiveRowsV1 {
330 coverage: RawSceneAttachmentCoverageV1,
331 #[serde(deserialize_with = "deserialize_inventory_vec")]
332 rows: Vec<RawMeshPrimitiveRowV1>,
333}
334impl RawMeshPrimitiveRowsV1 {
335 pub fn new(coverage: RawSceneAttachmentCoverageV1, rows: Vec<RawMeshPrimitiveRowV1>) -> Self {
337 Self { coverage, rows }
338 }
339 pub const fn coverage(&self) -> RawSceneAttachmentCoverageV1 {
341 self.coverage
342 }
343 pub fn rows(&self) -> &[RawMeshPrimitiveRowV1] {
345 &self.rows
346 }
347}
348
349#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
351#[serde(deny_unknown_fields)]
352pub struct RawSceneAttachmentInventoryV1 {
353 schema: &'static str,
354 identity: InputIdentity,
355 primary_input: InputIdentity,
356 source_skeleton: RawSourceSkeletonEvidenceV1,
357 scenes: RawSceneRootRowsV1,
358 node_mesh_attachments: RawNodeMeshAttachmentRowsV1,
359 mesh_primitives: RawMeshPrimitiveRowsV1,
360}
361
362fn set_once<T, E>(slot: &mut Option<T>, value: T, field: &'static str) -> Result<(), E>
363where
364 E: serde::de::Error,
365{
366 if slot.replace(value).is_some() {
367 return Err(E::duplicate_field(field));
368 }
369 Ok(())
370}
371
372fn required<T, E>(value: Option<T>, field: &'static str) -> Result<T, E>
373where
374 E: serde::de::Error,
375{
376 value.ok_or_else(|| E::missing_field(field))
377}
378
379enum BudgetedSceneRow {
380 Value(RawSceneRootRowV1),
381 Skipped,
382}
383
384struct BudgetedSceneRowSeed<'a> {
385 budget: &'a mut RowBudget,
386}
387
388impl<'de> DeserializeSeed<'de> for BudgetedSceneRowSeed<'_> {
389 type Value = BudgetedSceneRow;
390
391 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
392 where
393 D: Deserializer<'de>,
394 {
395 if !self.budget.admit() {
396 return IgnoredAny::deserialize(deserializer).map(|_| BudgetedSceneRow::Skipped);
397 }
398
399 #[derive(Deserialize)]
400 #[serde(field_identifier, rename_all = "snake_case")]
401 enum Field {
402 SourceSceneIndex,
403 RootNodeIndices,
404 }
405
406 struct SceneRowVisitor<'a> {
407 budget: &'a mut RowBudget,
408 }
409
410 impl<'de> Visitor<'de> for SceneRowVisitor<'_> {
411 type Value = RawSceneRootRowV1;
412
413 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
414 formatter.write_str("one raw scene-root row")
415 }
416
417 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
418 where
419 A: MapAccess<'de>,
420 {
421 let mut source_scene_index = None;
422 let mut root_node_indices = None;
423 while let Some(field) = map.next_key()? {
424 match field {
425 Field::SourceSceneIndex => set_once(
426 &mut source_scene_index,
427 map.next_value()?,
428 "source_scene_index",
429 )?,
430 Field::RootNodeIndices => {
431 if root_node_indices.is_some() {
432 return Err(A::Error::duplicate_field("root_node_indices"));
433 }
434 root_node_indices =
435 Some(map.next_value_seed(BudgetedCappedSequenceSeed {
436 budget: self.budget,
437 local_limit: RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS,
438 element: PhantomData,
439 })?);
440 }
441 }
442 }
443 let roots: CappedSequence<u64> = required(root_node_indices, "root_node_indices")?;
444 if roots.overflowed {
445 return Err(A::Error::custom(
446 "raw scene/attachment inventory exceeded its row bound",
447 ));
448 }
449 Ok(RawSceneRootRowV1 {
450 source_scene_index: required(source_scene_index, "source_scene_index")?,
451 root_node_indices: roots.values,
452 })
453 }
454 }
455
456 deserializer
457 .deserialize_struct(
458 "RawSceneRootRowV1",
459 &["source_scene_index", "root_node_indices"],
460 SceneRowVisitor {
461 budget: self.budget,
462 },
463 )
464 .map(BudgetedSceneRow::Value)
465 }
466}
467
468struct BudgetedSceneRowsSeed<'a> {
469 budget: &'a mut RowBudget,
470}
471
472impl<'de> DeserializeSeed<'de> for BudgetedSceneRowsSeed<'_> {
473 type Value = CappedSequence<RawSceneRootRowV1>;
474
475 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
476 where
477 D: Deserializer<'de>,
478 {
479 struct RowsVisitor<'a> {
480 budget: &'a mut RowBudget,
481 }
482
483 impl<'de> Visitor<'de> for RowsVisitor<'_> {
484 type Value = CappedSequence<RawSceneRootRowV1>;
485
486 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
487 formatter.write_str("a bounded sequence of raw scene-root rows")
488 }
489
490 fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
491 where
492 A: SeqAccess<'de>,
493 {
494 let mut values = Vec::with_capacity(
495 sequence
496 .size_hint()
497 .unwrap_or(0)
498 .min(RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS),
499 );
500 let mut seen = 0usize;
501 while seen < RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS {
502 let Some(row) = sequence.next_element_seed(BudgetedSceneRowSeed {
503 budget: self.budget,
504 })?
505 else {
506 return Ok(CappedSequence {
507 values,
508 overflowed: false,
509 });
510 };
511 seen += 1;
512 match row {
513 BudgetedSceneRow::Value(row) => values.push(row),
514 BudgetedSceneRow::Skipped => {
515 let overflowed = consume_ignored_tail(
516 &mut sequence,
517 seen,
518 RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS,
519 )?;
520 return Ok(CappedSequence { values, overflowed });
521 }
522 }
523 }
524 let overflowed = consume_ignored_tail(
525 &mut sequence,
526 seen,
527 RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS,
528 )?;
529 Ok(CappedSequence { values, overflowed })
530 }
531 }
532
533 deserializer.deserialize_seq(RowsVisitor {
534 budget: self.budget,
535 })
536 }
537}
538
539struct RawSceneRowsSetSeed<'a> {
540 budget: &'a mut RowBudget,
541}
542
543impl<'de> DeserializeSeed<'de> for RawSceneRowsSetSeed<'_> {
544 type Value = RawSceneRootRowsV1;
545
546 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
547 where
548 D: Deserializer<'de>,
549 {
550 #[derive(Deserialize)]
551 #[serde(field_identifier, rename_all = "snake_case")]
552 enum Field {
553 Coverage,
554 Rows,
555 }
556 struct SetVisitor<'a> {
557 budget: &'a mut RowBudget,
558 }
559 impl<'de> Visitor<'de> for SetVisitor<'_> {
560 type Value = RawSceneRootRowsV1;
561 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
562 formatter.write_str("coverage-qualified raw scene-root rows")
563 }
564 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
565 where
566 A: MapAccess<'de>,
567 {
568 let mut coverage = None;
569 let mut rows = None;
570 while let Some(field) = map.next_key()? {
571 match field {
572 Field::Coverage => set_once(&mut coverage, map.next_value()?, "coverage")?,
573 Field::Rows => {
574 if rows.is_some() {
575 return Err(A::Error::duplicate_field("rows"));
576 }
577 rows = Some(map.next_value_seed(BudgetedSceneRowsSeed {
578 budget: self.budget,
579 })?);
580 }
581 }
582 }
583 let rows: CappedSequence<_> = required(rows, "rows")?;
584 if rows.overflowed {
585 return Err(A::Error::custom(
586 "raw scene/attachment inventory exceeded its row bound",
587 ));
588 }
589 Ok(RawSceneRootRowsV1 {
590 coverage: required(coverage, "coverage")?,
591 rows: rows.values,
592 })
593 }
594 }
595 deserializer.deserialize_struct(
596 "RawSceneRootRowsV1",
597 &["coverage", "rows"],
598 SetVisitor {
599 budget: self.budget,
600 },
601 )
602 }
603}
604
605trait RawRowsSet: Sized {
606 type Row;
607 const NAME: &'static str;
608 fn from_parts(coverage: RawSceneAttachmentCoverageV1, rows: Vec<Self::Row>) -> Self;
609}
610
611impl RawRowsSet for RawNodeMeshAttachmentRowsV1 {
612 type Row = RawNodeMeshAttachmentRowV1;
613 const NAME: &'static str = "RawNodeMeshAttachmentRowsV1";
614 fn from_parts(coverage: RawSceneAttachmentCoverageV1, rows: Vec<Self::Row>) -> Self {
615 Self { coverage, rows }
616 }
617}
618
619impl RawRowsSet for RawMeshPrimitiveRowsV1 {
620 type Row = RawMeshPrimitiveRowV1;
621 const NAME: &'static str = "RawMeshPrimitiveRowsV1";
622 fn from_parts(coverage: RawSceneAttachmentCoverageV1, rows: Vec<Self::Row>) -> Self {
623 Self { coverage, rows }
624 }
625}
626
627struct RawRowsSetSeed<'a, S> {
628 budget: &'a mut RowBudget,
629 set: PhantomData<fn() -> S>,
630}
631
632impl<'de, S> DeserializeSeed<'de> for RawRowsSetSeed<'_, S>
633where
634 S: RawRowsSet,
635 S::Row: Deserialize<'de>,
636{
637 type Value = S;
638
639 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
640 where
641 D: Deserializer<'de>,
642 {
643 #[derive(Deserialize)]
644 #[serde(field_identifier, rename_all = "snake_case")]
645 enum Field {
646 Coverage,
647 Rows,
648 }
649 struct SetVisitor<'a, S> {
650 budget: &'a mut RowBudget,
651 set: PhantomData<fn() -> S>,
652 }
653 impl<'de, S> Visitor<'de> for SetVisitor<'_, S>
654 where
655 S: RawRowsSet,
656 S::Row: Deserialize<'de>,
657 {
658 type Value = S;
659 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
660 write!(formatter, "coverage-qualified {} rows", S::NAME)
661 }
662 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
663 where
664 A: MapAccess<'de>,
665 {
666 let mut coverage = None;
667 let mut rows = None;
668 while let Some(field) = map.next_key()? {
669 match field {
670 Field::Coverage => set_once(&mut coverage, map.next_value()?, "coverage")?,
671 Field::Rows => {
672 if rows.is_some() {
673 return Err(A::Error::duplicate_field("rows"));
674 }
675 rows = Some(map.next_value_seed(BudgetedCappedSequenceSeed {
676 budget: self.budget,
677 local_limit: RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS,
678 element: PhantomData,
679 })?);
680 }
681 }
682 }
683 let rows: CappedSequence<S::Row> = required(rows, "rows")?;
684 if rows.overflowed {
685 return Err(A::Error::custom(
686 "raw scene/attachment inventory exceeded its row bound",
687 ));
688 }
689 Ok(S::from_parts(required(coverage, "coverage")?, rows.values))
690 }
691 }
692 deserializer.deserialize_struct(
693 S::NAME,
694 &["coverage", "rows"],
695 SetVisitor {
696 budget: self.budget,
697 set: PhantomData,
698 },
699 )
700 }
701}
702
703impl<'de> Deserialize<'de> for RawSceneAttachmentInventoryV1 {
704 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
705 where
706 D: Deserializer<'de>,
707 {
708 #[derive(Deserialize)]
709 #[serde(field_identifier, rename_all = "snake_case")]
710 enum Field {
711 Schema,
712 Identity,
713 PrimaryInput,
714 SourceSkeleton,
715 Scenes,
716 NodeMeshAttachments,
717 MeshPrimitives,
718 }
719 struct InventoryVisitor;
720 impl<'de> Visitor<'de> for InventoryVisitor {
721 type Value = (
722 String,
723 InputIdentity,
724 InputIdentity,
725 RawSourceSkeletonEvidenceV1,
726 RawSceneRootRowsV1,
727 RawNodeMeshAttachmentRowsV1,
728 RawMeshPrimitiveRowsV1,
729 usize,
730 bool,
731 );
732 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
733 formatter.write_str("a bounded raw scene/attachment inventory")
734 }
735 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
736 where
737 A: MapAccess<'de>,
738 {
739 let mut budget = RowBudget::new(RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS);
740 let mut schema = None;
741 let mut identity = None;
742 let mut primary_input = None;
743 let mut source_skeleton = None;
744 let mut scenes = None;
745 let mut node_mesh_attachments = None;
746 let mut mesh_primitives = None;
747 while let Some(field) = map.next_key()? {
748 match field {
749 Field::Schema => set_once(&mut schema, map.next_value()?, "schema")?,
750 Field::Identity => set_once(&mut identity, map.next_value()?, "identity")?,
751 Field::PrimaryInput => {
752 set_once(&mut primary_input, map.next_value()?, "primary_input")?
753 }
754 Field::SourceSkeleton => {
755 set_once(&mut source_skeleton, map.next_value()?, "source_skeleton")?
756 }
757 Field::Scenes => {
758 if scenes.is_some() {
759 return Err(A::Error::duplicate_field("scenes"));
760 }
761 scenes = Some(map.next_value_seed(RawSceneRowsSetSeed {
762 budget: &mut budget,
763 })?);
764 }
765 Field::NodeMeshAttachments => {
766 if node_mesh_attachments.is_some() {
767 return Err(A::Error::duplicate_field("node_mesh_attachments"));
768 }
769 node_mesh_attachments = Some(map.next_value_seed(RawRowsSetSeed {
770 budget: &mut budget,
771 set: PhantomData,
772 })?);
773 }
774 Field::MeshPrimitives => {
775 if mesh_primitives.is_some() {
776 return Err(A::Error::duplicate_field("mesh_primitives"));
777 }
778 mesh_primitives = Some(map.next_value_seed(RawRowsSetSeed {
779 budget: &mut budget,
780 set: PhantomData,
781 })?);
782 }
783 }
784 }
785 Ok((
786 required(schema, "schema")?,
787 required(identity, "identity")?,
788 required(primary_input, "primary_input")?,
789 required(source_skeleton, "source_skeleton")?,
790 required(scenes, "scenes")?,
791 required(node_mesh_attachments, "node_mesh_attachments")?,
792 required(mesh_primitives, "mesh_primitives")?,
793 budget.found(),
794 budget.overflowed(),
795 ))
796 }
797 }
798 let (
799 schema,
800 identity,
801 primary_input,
802 source_skeleton,
803 scenes,
804 node_mesh_attachments,
805 mesh_primitives,
806 found,
807 overflowed,
808 ) = deserializer.deserialize_struct(
809 "RawSceneAttachmentInventoryV1",
810 &[
811 "schema",
812 "identity",
813 "primary_input",
814 "source_skeleton",
815 "scenes",
816 "node_mesh_attachments",
817 "mesh_primitives",
818 ],
819 InventoryVisitor,
820 )?;
821 if overflowed {
822 return Err(D::Error::custom(
823 RawSceneAttachmentInventoryError::TooManyRows {
824 found,
825 limit: RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS,
826 },
827 ));
828 }
829 if schema != RAW_SCENE_ATTACHMENT_INVENTORY_V1_ID {
830 return Err(serde::de::Error::custom(format!(
831 "raw scene/attachment inventory schema must be {RAW_SCENE_ATTACHMENT_INVENTORY_V1_ID:?}"
832 )));
833 }
834 let value = Self {
835 schema: RAW_SCENE_ATTACHMENT_INVENTORY_V1_ID,
836 identity,
837 primary_input,
838 source_skeleton,
839 scenes,
840 node_mesh_attachments,
841 mesh_primitives,
842 };
843 value.validate().map_err(serde::de::Error::custom)?;
844 let expected = inventory_identity(
845 &value.primary_input,
846 value.source_skeleton,
847 &value.scenes,
848 &value.node_mesh_attachments,
849 &value.mesh_primitives,
850 )
851 .map_err(serde::de::Error::custom)?;
852 if value.identity != expected {
853 return Err(serde::de::Error::custom(
854 "raw scene/attachment inventory identity does not match its contents",
855 ));
856 }
857 Ok(value)
858 }
859}
860impl RawSceneAttachmentInventoryV1 {
861 pub fn new(
863 primary_input: InputIdentity,
864 source_skeleton: RawSourceSkeletonEvidenceV1,
865 scenes: RawSceneRootRowsV1,
866 node_mesh_attachments: RawNodeMeshAttachmentRowsV1,
867 mesh_primitives: RawMeshPrimitiveRowsV1,
868 ) -> Result<Self, RawSceneAttachmentInventoryError> {
869 let identity = inventory_identity(
870 &primary_input,
871 source_skeleton,
872 &scenes,
873 &node_mesh_attachments,
874 &mesh_primitives,
875 )?;
876 let value = Self {
877 schema: RAW_SCENE_ATTACHMENT_INVENTORY_V1_ID,
878 identity,
879 primary_input,
880 source_skeleton,
881 scenes,
882 node_mesh_attachments,
883 mesh_primitives,
884 };
885 value.validate()?;
886 Ok(value)
887 }
888 pub const fn contract_id(&self) -> &'static str {
890 self.schema
891 }
892 pub const fn identity(&self) -> &InputIdentity {
894 &self.identity
895 }
896 pub const fn primary_input(&self) -> &InputIdentity {
898 &self.primary_input
899 }
900 pub const fn source_skeleton(&self) -> &RawSourceSkeletonEvidenceV1 {
902 &self.source_skeleton
903 }
904 pub const fn scenes(&self) -> &RawSceneRootRowsV1 {
906 &self.scenes
907 }
908 pub const fn node_mesh_attachments(&self) -> &RawNodeMeshAttachmentRowsV1 {
910 &self.node_mesh_attachments
911 }
912 pub const fn mesh_primitives(&self) -> &RawMeshPrimitiveRowsV1 {
914 &self.mesh_primitives
915 }
916 fn validate(&self) -> Result<(), RawSceneAttachmentInventoryError> {
917 validate_unavailable_rows("scenes", self.scenes.coverage, self.scenes.rows.len())?;
918 validate_unavailable_rows(
919 "node_mesh_attachments",
920 self.node_mesh_attachments.coverage,
921 self.node_mesh_attachments.rows.len(),
922 )?;
923 validate_unavailable_rows(
924 "mesh_primitives",
925 self.mesh_primitives.coverage,
926 self.mesh_primitives.rows.len(),
927 )?;
928 if self.source_skeleton.coverage == RawSceneAttachmentCoverageV1::Unavailable
929 && (self.source_skeleton.source_node_count != 0
930 || self.source_skeleton.source_skin_count != 0)
931 {
932 return Err(RawSceneAttachmentInventoryError::UnavailableSkeletonHasCounts);
933 }
934 let rows = self
935 .scenes
936 .rows
937 .iter()
938 .try_fold(0usize, |total, row| {
939 total
940 .checked_add(1)?
941 .checked_add(row.root_node_indices.len())
942 })
943 .and_then(|rows| rows.checked_add(self.node_mesh_attachments.rows.len()))
944 .and_then(|rows| rows.checked_add(self.mesh_primitives.rows.len()))
945 .ok_or(RawSceneAttachmentInventoryError::AggregateRowsOverflow)?;
946 if rows > RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS {
947 return Err(RawSceneAttachmentInventoryError::TooManyRows {
948 found: rows,
949 limit: RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS,
950 });
951 }
952 for (expected, row) in self.scenes.rows.iter().enumerate() {
953 if row.source_scene_index != expected as u64 {
954 return Err(RawSceneAttachmentInventoryError::NonCanonicalSceneOrder {
955 expected: expected as u64,
956 found: row.source_scene_index,
957 });
958 }
959 }
960 if self.source_skeleton.coverage == RawSceneAttachmentCoverageV1::Complete {
961 for scene in &self.scenes.rows {
962 for &node in &scene.root_node_indices {
963 if node >= self.source_skeleton.source_node_count {
964 return Err(RawSceneAttachmentInventoryError::SceneRootNodeOutOfRange {
965 node,
966 node_count: self.source_skeleton.source_node_count,
967 });
968 }
969 }
970 }
971 for attachment in &self.node_mesh_attachments.rows {
972 if attachment.source_node_index >= self.source_skeleton.source_node_count {
973 return Err(RawSceneAttachmentInventoryError::AttachmentNodeOutOfRange {
974 node: attachment.source_node_index,
975 node_count: self.source_skeleton.source_node_count,
976 });
977 }
978 }
979 }
980 if self
981 .node_mesh_attachments
982 .rows
983 .windows(2)
984 .any(|rows| rows[0].source_node_index >= rows[1].source_node_index)
985 {
986 return Err(RawSceneAttachmentInventoryError::NonCanonicalAttachmentOrder);
987 }
988 if self.mesh_primitives.rows.windows(2).any(|rows| {
989 (rows[0].source_mesh_index, rows[0].source_primitive_index)
990 >= (rows[1].source_mesh_index, rows[1].source_primitive_index)
991 }) {
992 return Err(RawSceneAttachmentInventoryError::NonCanonicalPrimitiveOrder);
993 }
994 if self.mesh_primitives.coverage == RawSceneAttachmentCoverageV1::Complete {
995 let mut current_mesh = None;
996 let mut expected_primitive = 0;
997 for primitive in &self.mesh_primitives.rows {
998 if current_mesh != Some(primitive.source_mesh_index) {
999 current_mesh = Some(primitive.source_mesh_index);
1000 expected_primitive = 0;
1001 }
1002 if primitive.source_primitive_index != expected_primitive {
1003 return Err(
1004 RawSceneAttachmentInventoryError::NonContiguousPrimitiveOrdinal {
1005 mesh: primitive.source_mesh_index,
1006 expected: expected_primitive,
1007 found: primitive.source_primitive_index,
1008 },
1009 );
1010 }
1011 expected_primitive = expected_primitive.saturating_add(1);
1012 }
1013 }
1014 Ok(())
1015 }
1016}
1017
1018fn validate_unavailable_rows(
1019 domain: &'static str,
1020 coverage: RawSceneAttachmentCoverageV1,
1021 rows: usize,
1022) -> Result<(), RawSceneAttachmentInventoryError> {
1023 if coverage == RawSceneAttachmentCoverageV1::Unavailable && rows != 0 {
1024 return Err(RawSceneAttachmentInventoryError::UnavailableHasRows { domain, rows });
1025 }
1026 Ok(())
1027}
1028
1029#[derive(Serialize)]
1030struct InventoryIdentityFields<'a> {
1031 schema: &'static str,
1032 primary_input: &'a InputIdentity,
1033 source_skeleton: RawSourceSkeletonEvidenceV1,
1034 scenes: &'a RawSceneRootRowsV1,
1035 node_mesh_attachments: &'a RawNodeMeshAttachmentRowsV1,
1036 mesh_primitives: &'a RawMeshPrimitiveRowsV1,
1037}
1038
1039fn inventory_identity(
1040 primary_input: &InputIdentity,
1041 source_skeleton: RawSourceSkeletonEvidenceV1,
1042 scenes: &RawSceneRootRowsV1,
1043 node_mesh_attachments: &RawNodeMeshAttachmentRowsV1,
1044 mesh_primitives: &RawMeshPrimitiveRowsV1,
1045) -> Result<InputIdentity, RawSceneAttachmentInventoryError> {
1046 let bytes = serde_json::to_vec(&InventoryIdentityFields {
1047 schema: RAW_SCENE_ATTACHMENT_INVENTORY_V1_ID,
1048 primary_input,
1049 source_skeleton,
1050 scenes,
1051 node_mesh_attachments,
1052 mesh_primitives,
1053 })
1054 .map_err(|error| RawSceneAttachmentInventoryError::IdentityEncoding {
1055 message: error.to_string(),
1056 })?;
1057 Ok(InputIdentity::from_bytes(&bytes))
1058}
1059
1060#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1062#[non_exhaustive]
1063pub enum RawSceneAttachmentInventoryError {
1064 #[error("raw scene/attachment inventory identity encoding failed: {message}")]
1066 IdentityEncoding {
1067 message: String,
1069 },
1070 #[error("raw scene/attachment unavailable {domain} retained {rows} row(s)")]
1072 UnavailableHasRows {
1073 domain: &'static str,
1075 rows: usize,
1077 },
1078 #[error("raw scene/attachment unavailable source skeleton retained nonzero counts")]
1080 UnavailableSkeletonHasCounts,
1081 #[error("raw scene/attachment aggregate row count overflowed")]
1083 AggregateRowsOverflow,
1084 #[error("raw scene/attachment inventory retained {found} rows, exceeding V1 limit {limit}")]
1086 TooManyRows {
1087 found: usize,
1089 limit: usize,
1091 },
1092 #[error("raw scene rows are not in canonical source order: expected {expected}, found {found}")]
1094 NonCanonicalSceneOrder {
1095 expected: u64,
1097 found: u64,
1099 },
1100 #[error("raw node-to-mesh attachment rows are not in canonical source-node order")]
1102 NonCanonicalAttachmentOrder,
1103 #[error("raw scene root node {node} is outside source node count {node_count}")]
1105 SceneRootNodeOutOfRange {
1106 node: u64,
1108 node_count: u64,
1110 },
1111 #[error("raw attachment node {node} is outside source node count {node_count}")]
1113 AttachmentNodeOutOfRange {
1114 node: u64,
1116 node_count: u64,
1118 },
1119 #[error("raw mesh primitive rows are not in canonical mesh/primitive order")]
1121 NonCanonicalPrimitiveOrder,
1122 #[error(
1124 "raw mesh {mesh} primitive order is not contiguous: expected {expected}, found {found}"
1125 )]
1126 NonContiguousPrimitiveOrdinal {
1127 mesh: u64,
1129 expected: u64,
1131 found: u64,
1133 },
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use super::*;
1139 #[test]
1140 fn empty_complete_sets_prove_absence() {
1141 let inventory = RawSceneAttachmentInventoryV1::new(
1142 InputIdentity::from_bytes(b"raw"),
1143 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Complete, 0, 0),
1144 RawSceneRootRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1145 RawNodeMeshAttachmentRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1146 RawMeshPrimitiveRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1147 )
1148 .unwrap();
1149 assert!(inventory.mesh_primitives().coverage().proves_absence());
1150 }
1151 #[test]
1152 fn rejects_noncanonical_primitives() {
1153 let error = RawSceneAttachmentInventoryV1::new(
1154 InputIdentity::from_bytes(b"raw"),
1155 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Complete, 0, 0),
1156 RawSceneRootRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1157 RawNodeMeshAttachmentRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1158 RawMeshPrimitiveRowsV1::new(
1159 RawSceneAttachmentCoverageV1::Complete,
1160 vec![
1161 RawMeshPrimitiveRowV1::new(1, 0, RawPrimitiveTopologyV1::Triangles, None),
1162 RawMeshPrimitiveRowV1::new(0, 0, RawPrimitiveTopologyV1::Points, Some(2)),
1163 ],
1164 ),
1165 )
1166 .unwrap_err();
1167 assert_eq!(
1168 error,
1169 RawSceneAttachmentInventoryError::NonCanonicalPrimitiveOrder
1170 );
1171 }
1172
1173 #[test]
1174 fn rejects_unavailable_rows_and_skeleton_counts() {
1175 let identity = InputIdentity::from_bytes(b"raw");
1176 let error = RawSceneAttachmentInventoryV1::new(
1177 identity.clone(),
1178 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Unavailable, 0, 0),
1179 RawSceneRootRowsV1::new(
1180 RawSceneAttachmentCoverageV1::Unavailable,
1181 vec![RawSceneRootRowV1::new(0, vec![])],
1182 ),
1183 RawNodeMeshAttachmentRowsV1::new(RawSceneAttachmentCoverageV1::Unavailable, vec![]),
1184 RawMeshPrimitiveRowsV1::new(RawSceneAttachmentCoverageV1::Unavailable, vec![]),
1185 )
1186 .unwrap_err();
1187 assert!(matches!(
1188 error,
1189 RawSceneAttachmentInventoryError::UnavailableHasRows {
1190 domain: "scenes",
1191 rows: 1
1192 }
1193 ));
1194
1195 let error = RawSceneAttachmentInventoryV1::new(
1196 identity,
1197 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Unavailable, 1, 0),
1198 RawSceneRootRowsV1::new(RawSceneAttachmentCoverageV1::Unavailable, vec![]),
1199 RawNodeMeshAttachmentRowsV1::new(RawSceneAttachmentCoverageV1::Unavailable, vec![]),
1200 RawMeshPrimitiveRowsV1::new(RawSceneAttachmentCoverageV1::Unavailable, vec![]),
1201 )
1202 .unwrap_err();
1203 assert_eq!(
1204 error,
1205 RawSceneAttachmentInventoryError::UnavailableSkeletonHasCounts
1206 );
1207 }
1208
1209 #[test]
1210 fn deserialization_stops_at_each_raw_inventory_n_plus_one_boundary() {
1211 let roots = serde_json::json!({
1212 "source_scene_index": 0,
1213 "root_node_indices": vec![0_u64; RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS + 1]
1214 });
1215 assert!(serde_json::from_value::<RawSceneRootRowV1>(roots).is_err());
1216
1217 let attachments = serde_json::json!({
1218 "coverage": "prefix_overflow",
1219 "rows": (0..=RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS)
1220 .map(|index| serde_json::json!({
1221 "source_node_index": index,
1222 "source_mesh_index": 0
1223 }))
1224 .collect::<Vec<_>>()
1225 });
1226 assert!(serde_json::from_value::<RawNodeMeshAttachmentRowsV1>(attachments).is_err());
1227 }
1228
1229 #[test]
1230 fn inventory_deserialization_enforces_nested_scene_roots_against_aggregate_budget() {
1231 let inventory = RawSceneAttachmentInventoryV1::new(
1232 InputIdentity::from_bytes(b"raw"),
1233 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Complete, 1, 0),
1234 RawSceneRootRowsV1::new(
1235 RawSceneAttachmentCoverageV1::Complete,
1236 vec![RawSceneRootRowV1::new(
1237 0,
1238 vec![0; RAW_SCENE_ATTACHMENT_INVENTORY_V1_MAX_ROWS - 1],
1239 )],
1240 ),
1241 RawNodeMeshAttachmentRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1242 RawMeshPrimitiveRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1243 )
1244 .expect("one scene row plus N-1 roots consumes the exact aggregate budget");
1245 let exact = serde_json::to_string(&inventory).unwrap();
1246 let decoded: RawSceneAttachmentInventoryV1 = serde_json::from_str(&exact).unwrap();
1247 assert_eq!(decoded, inventory);
1248
1249 let mut overflow: serde_json::Value = serde_json::from_str(&exact).unwrap();
1250 overflow["scenes"]["rows"][0]["root_node_indices"]
1251 .as_array_mut()
1252 .unwrap()
1253 .push(serde_json::Value::Null);
1254 let error = serde_json::from_str::<RawSceneAttachmentInventoryV1>(
1255 &serde_json::to_string(&overflow).unwrap(),
1256 )
1257 .unwrap_err()
1258 .to_string();
1259 assert!(
1260 error.contains("retained 4097 rows"),
1261 "aggregate N+1 must win before the hostile tail value is decoded: {error}"
1262 );
1263 }
1264
1265 #[test]
1266 fn rejects_complete_skeleton_node_references_outside_its_evidence() {
1267 let identity = InputIdentity::from_bytes(b"raw");
1268 let error = RawSceneAttachmentInventoryV1::new(
1269 identity.clone(),
1270 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Complete, 1, 0),
1271 RawSceneRootRowsV1::new(
1272 RawSceneAttachmentCoverageV1::Complete,
1273 vec![RawSceneRootRowV1::new(0, vec![1])],
1274 ),
1275 RawNodeMeshAttachmentRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1276 RawMeshPrimitiveRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1277 )
1278 .unwrap_err();
1279 assert!(matches!(
1280 error,
1281 RawSceneAttachmentInventoryError::SceneRootNodeOutOfRange {
1282 node: 1,
1283 node_count: 1
1284 }
1285 ));
1286
1287 let error = RawSceneAttachmentInventoryV1::new(
1288 identity,
1289 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Complete, 1, 0),
1290 RawSceneRootRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1291 RawNodeMeshAttachmentRowsV1::new(
1292 RawSceneAttachmentCoverageV1::Complete,
1293 vec![RawNodeMeshAttachmentRowV1::new(1, 0)],
1294 ),
1295 RawMeshPrimitiveRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1296 )
1297 .unwrap_err();
1298 assert!(matches!(
1299 error,
1300 RawSceneAttachmentInventoryError::AttachmentNodeOutOfRange {
1301 node: 1,
1302 node_count: 1
1303 }
1304 ));
1305 }
1306
1307 #[test]
1308 fn complete_primitive_rows_must_be_contiguous_per_mesh() {
1309 let error = RawSceneAttachmentInventoryV1::new(
1310 InputIdentity::from_bytes(b"raw"),
1311 RawSourceSkeletonEvidenceV1::new(RawSceneAttachmentCoverageV1::Complete, 0, 0),
1312 RawSceneRootRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1313 RawNodeMeshAttachmentRowsV1::new(RawSceneAttachmentCoverageV1::Complete, vec![]),
1314 RawMeshPrimitiveRowsV1::new(
1315 RawSceneAttachmentCoverageV1::Complete,
1316 vec![RawMeshPrimitiveRowV1::new(
1317 0,
1318 1,
1319 RawPrimitiveTopologyV1::Triangles,
1320 None,
1321 )],
1322 ),
1323 )
1324 .unwrap_err();
1325 assert!(matches!(
1326 error,
1327 RawSceneAttachmentInventoryError::NonContiguousPrimitiveOrdinal {
1328 mesh: 0,
1329 expected: 0,
1330 found: 1,
1331 }
1332 ));
1333 }
1334}