all_is_cubes/save/
conversion.rs

1//! Conversion between the types in [`super::schema`] and those used in
2//! normal operation.
3
4use alloc::borrow::Cow;
5use alloc::boxed::Box;
6use alloc::sync::Arc;
7use alloc::vec::Vec;
8
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11use super::schema;
12
13mod behavior {
14    use super::*;
15    use crate::behavior::{Behavior, BehaviorSet, BehaviorSetTransaction, Host, Persistence};
16    use crate::transaction::{Merge as _, Transactional as _};
17
18    // TODO: Stop serializing H::Attachment directly or document that it has to be stable.
19
20    impl<H> Serialize for BehaviorSet<H>
21    where
22        H: Host<Attachment: Serialize>,
23    {
24        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
25            schema::BehaviorSetSer::BehaviorSetV1 {
26                behaviors: self
27                    .iter()
28                    .filter_map(|entry| {
29                        let Persistence(behavior) = entry.behavior.persistence()?;
30                        Some(schema::BehaviorSetEntryV1Ser {
31                            attachment: entry.attachment.clone(),
32                            behavior,
33                        })
34                    })
35                    .collect::<Vec<schema::BehaviorSetEntryV1Ser<H::Attachment>>>(),
36            }
37            .serialize(serializer)
38        }
39    }
40
41    impl<'de, H> Deserialize<'de> for BehaviorSet<H>
42    where
43        H: Host<Attachment: serde::de::DeserializeOwned>,
44    {
45        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
46            match schema::BehaviorSetSer::<H::Attachment>::deserialize(deserializer)? {
47                schema::BehaviorSetSer::BehaviorSetV1 { behaviors } => {
48                    let mut set = BehaviorSet::new();
49                    set.transact(|txn, _| {
50                        #[allow(
51                            unreachable_patterns,
52                            reason = "remove this when BehaviorV1Ser is nonempty"
53                        )]
54                        for schema::BehaviorSetEntryV1Ser {
55                            behavior,
56                            attachment,
57                        } in behaviors
58                        {
59                            txn.merge_from(BehaviorSetTransaction::insert(
60                                attachment,
61                                behavior.into(),
62                            ))?;
63                        }
64                        Ok(())
65                    })
66                    .expect("BehaviorSet execute failure");
67                    Ok(set)
68                }
69            }
70        }
71    }
72
73    impl<A> From<schema::BehaviorV1Ser> for Arc<dyn Behavior<A>> {
74        fn from(value: schema::BehaviorV1Ser) -> Self {
75            match value {}
76        }
77    }
78}
79
80mod block {
81    use super::*;
82    use crate::block::{
83        AnimationChange, AnimationHint, Atom, Block, BlockAttributes, BlockCollision, Composite,
84        Modifier, Move, PlacementAction, Primitive, Quote, RotationPlacementRule, TickAction, Zoom,
85        text,
86    };
87    use crate::math::{Rgb, Rgba};
88    use crate::tag;
89    use schema::{BlockSer, ModifierSer};
90
91    impl Serialize for Block {
92        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
93            BlockSer::BlockV1 {
94                primitive: match self.primitive() {
95                    Primitive::Indirect(definition) => schema::PrimitiveSer::IndirectV1 {
96                        definition: definition.clone(),
97                    },
98                    &Primitive::Atom(Atom {
99                        color,
100                        emission,
101                        collision,
102                    }) => schema::PrimitiveSer::AtomV1 {
103                        // attributes on the primitive are no longer used, but still supported
104                        // by deserialization.
105                        attributes: BlockAttributes::DEFAULT_REF.into(),
106                        color: color.into(),
107                        light_emission: emission.into(),
108                        collision: collision.into(),
109                    },
110                    &Primitive::Recur {
111                        ref space,
112                        offset,
113                        resolution,
114                    } => schema::PrimitiveSer::RecurV1 {
115                        // attributes on the primitive are no longer used, but still supported
116                        // by deserialization.
117                        attributes: BlockAttributes::DEFAULT_REF.into(),
118                        space: space.clone(),
119                        offset: offset.into(),
120                        resolution,
121                    },
122                    &Primitive::Air => schema::PrimitiveSer::AirV1,
123                    &Primitive::Text { ref text, offset } => {
124                        schema::PrimitiveSer::TextPrimitiveV1 {
125                            text: text.into(),
126                            offset: offset.into(),
127                        }
128                    }
129                    &Primitive::Raw { .. } => {
130                        return Err(serde::ser::Error::custom("cannot serialize Primitive::Raw"));
131                    }
132                },
133                modifiers: self.modifiers().iter().map(ModifierSer::from).collect(),
134            }
135            .serialize(serializer)
136        }
137    }
138
139    impl<'de> Deserialize<'de> for Block {
140        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
141            Ok(match BlockSer::deserialize(deserializer)? {
142                BlockSer::BlockV1 {
143                    primitive,
144                    modifiers,
145                } => {
146                    let (primitive, attributes) = primitive_from_schema(primitive);
147                    let mut block = Block::from_primitive(primitive);
148
149                    let attr_mod_iter = attributes
150                        .filter(|a| a != BlockAttributes::DEFAULT_REF)
151                        .map(Modifier::from)
152                        .into_iter();
153                    let general_mod_iter = modifiers.into_iter().map(Modifier::from);
154
155                    block.modifiers_mut().extend(attr_mod_iter.chain(general_mod_iter));
156                    block
157                }
158            })
159        }
160    }
161
162    fn primitive_from_schema(value: schema::PrimitiveSer) -> (Primitive, Option<BlockAttributes>) {
163        match value {
164            schema::PrimitiveSer::IndirectV1 { definition } => {
165                (Primitive::Indirect(definition), None)
166            }
167            schema::PrimitiveSer::AtomV1 {
168                attributes,
169                color,
170                light_emission: emission,
171                collision,
172            } => (
173                Primitive::Atom(Atom {
174                    color: Rgba::from(color),
175                    emission: Rgb::from(emission),
176                    collision: collision.into(),
177                }),
178                Some(attributes.into()),
179            ),
180            schema::PrimitiveSer::RecurV1 {
181                attributes,
182                space,
183                offset,
184                resolution,
185            } => (
186                Primitive::Recur {
187                    space,
188                    offset: offset.into(),
189                    resolution,
190                },
191                Some(attributes.into()),
192            ),
193            schema::PrimitiveSer::AirV1 => (Primitive::Air, None),
194            schema::PrimitiveSer::TextPrimitiveV1 { text, offset } => (
195                Primitive::Text {
196                    text: text.into(),
197                    offset: offset.into(),
198                },
199                None,
200            ),
201        }
202    }
203
204    impl<'a> From<&'a BlockAttributes> for schema::BlockAttributesV1Ser {
205        fn from(value: &'a BlockAttributes) -> Self {
206            let &BlockAttributes {
207                ref display_name,
208                selectable,
209                ref inventory,
210                ref ambient_sound,
211                rotation_rule,
212                ref placement_action,
213                ref tick_action,
214                ref activation_action,
215                animation_hint,
216            } = value;
217            schema::BlockAttributesV1Ser {
218                display_name: display_name.clone(),
219                selectable,
220                inventory: inventory.into(),
221                ambient_sound: ambient_sound.into(),
222                rotation_rule: rotation_rule.into(),
223                placement_action: placement_action.as_ref().map(
224                    |&PlacementAction {
225                         ref operation,
226                         in_front,
227                     }| schema::PlacementActionSer {
228                        operation: operation.clone(),
229                        in_front,
230                    },
231                ),
232                tick_action: tick_action.as_ref().map(
233                    |&TickAction {
234                         ref operation,
235                         schedule,
236                     }| schema::TickActionSer {
237                        operation: operation.clone(),
238                        schedule,
239                    },
240                ),
241                activation_action: activation_action.clone(),
242                animation_hint: animation_hint.into(),
243            }
244        }
245    }
246
247    impl From<schema::BlockAttributesV1Ser> for BlockAttributes {
248        fn from(value: schema::BlockAttributesV1Ser) -> Self {
249            let schema::BlockAttributesV1Ser {
250                display_name,
251                selectable,
252                inventory,
253                ambient_sound,
254                rotation_rule,
255                placement_action,
256                tick_action,
257                activation_action,
258                animation_hint,
259            } = value;
260            Self {
261                display_name,
262                selectable,
263                inventory: inventory.into(),
264                ambient_sound: ambient_sound.into(),
265                rotation_rule: rotation_rule.into(),
266                placement_action: placement_action.map(
267                    |schema::PlacementActionSer {
268                         operation,
269                         in_front,
270                     }| PlacementAction {
271                        operation,
272                        in_front,
273                    },
274                ),
275                tick_action: tick_action.map(
276                    |schema::TickActionSer {
277                         operation,
278                         schedule,
279                     }| {
280                        TickAction {
281                            operation,
282                            schedule,
283                        }
284                    },
285                ),
286                activation_action,
287                animation_hint: animation_hint.into(),
288            }
289        }
290    }
291
292    impl From<BlockCollision> for schema::BlockCollisionSer {
293        fn from(value: BlockCollision) -> Self {
294            use schema::BlockCollisionSer as S;
295            match value {
296                BlockCollision::None => S::NoneV1,
297                BlockCollision::Hard => S::HardV1,
298            }
299        }
300    }
301
302    impl From<schema::BlockCollisionSer> for BlockCollision {
303        fn from(value: schema::BlockCollisionSer) -> Self {
304            use schema::BlockCollisionSer as S;
305            match value {
306                S::NoneV1 => BlockCollision::None,
307                S::HardV1 => BlockCollision::Hard,
308            }
309        }
310    }
311
312    impl From<RotationPlacementRule> for schema::RotationPlacementRuleSer {
313        fn from(value: RotationPlacementRule) -> Self {
314            use schema::RotationPlacementRuleSer as S;
315            match value {
316                RotationPlacementRule::Never => S::NeverV1,
317                RotationPlacementRule::Attach { by } => S::AttachV1 { by },
318            }
319        }
320    }
321
322    impl From<schema::RotationPlacementRuleSer> for RotationPlacementRule {
323        fn from(value: schema::RotationPlacementRuleSer) -> Self {
324            use schema::RotationPlacementRuleSer as S;
325            match value {
326                S::NeverV1 => RotationPlacementRule::Never,
327                S::AttachV1 { by } => RotationPlacementRule::Attach { by },
328            }
329        }
330    }
331
332    impl From<AnimationHint> for schema::AnimationHintSer {
333        fn from(value: AnimationHint) -> Self {
334            let AnimationHint {
335                redefinition,
336                replacement,
337            } = value;
338            schema::AnimationHintSer::AnimationHintV1 {
339                redefinition: redefinition.into(),
340                replacement: replacement.into(),
341            }
342        }
343    }
344
345    impl From<schema::AnimationHintSer> for AnimationHint {
346        fn from(value: schema::AnimationHintSer) -> Self {
347            use schema::AnimationHintSer as S;
348            match value {
349                S::AnimationHintV1 {
350                    redefinition,
351                    replacement,
352                } => AnimationHint {
353                    redefinition: redefinition.into(),
354                    replacement: replacement.into(),
355                },
356            }
357        }
358    }
359
360    impl From<AnimationChange> for schema::AnimationChangeV1Ser {
361        fn from(value: AnimationChange) -> Self {
362            use schema::AnimationChangeV1Ser as S;
363            match value {
364                AnimationChange::None => S::None,
365                AnimationChange::ColorSameCategory => S::ColorSameCategory,
366                AnimationChange::Shape => S::Shape,
367            }
368        }
369    }
370
371    impl From<schema::AnimationChangeV1Ser> for AnimationChange {
372        fn from(value: schema::AnimationChangeV1Ser) -> Self {
373            use schema::AnimationChangeV1Ser as S;
374            match value {
375                S::None => AnimationChange::None,
376                S::ColorSameCategory => AnimationChange::ColorSameCategory,
377                S::Shape => AnimationChange::Shape,
378            }
379        }
380    }
381
382    impl<'a> From<&'a Modifier> for ModifierSer<'a> {
383        fn from(value: &'a Modifier) -> Self {
384            match *value {
385                Modifier::Attributes(ref attributes) => ModifierSer::AttributesV1 {
386                    attributes: (&**attributes).into(),
387                },
388                Modifier::Tag(tag::Be(ref tag)) => ModifierSer::TagV1 { tag: tag.clone() },
389                Modifier::Quote(Quote { suppress_ambient }) => {
390                    ModifierSer::QuoteV1 { suppress_ambient }
391                }
392                Modifier::Rotate(rotation) => ModifierSer::RotateV1 { rotation },
393                Modifier::Composite(Composite {
394                    ref source,
395                    operator,
396                    reverse,
397                    disassemblable,
398                }) => ModifierSer::CompositeV1 {
399                    source: source.clone(),
400                    operator,
401                    reverse,
402                    disassemblable,
403                },
404                Modifier::Zoom(ref m) => m.to_serial_schema(),
405                Modifier::Move(ref m) => ModifierSer::Move(m.into()),
406                Modifier::Inventory(ref inventory) => ModifierSer::BlockInventoryV1 {
407                    inventory: Cow::Borrowed(inventory),
408                },
409            }
410        }
411    }
412
413    impl From<ModifierSer<'_>> for Modifier {
414        fn from(value: ModifierSer<'_>) -> Self {
415            match value {
416                ModifierSer::AttributesV1 { attributes } => {
417                    Modifier::Attributes(Arc::new(attributes.into()))
418                }
419                ModifierSer::TagV1 { tag } => Modifier::Tag(tag::Be(tag)),
420                ModifierSer::QuoteV1 { suppress_ambient } => {
421                    Modifier::Quote(Quote { suppress_ambient })
422                }
423                ModifierSer::RotateV1 { rotation } => Modifier::Rotate(rotation),
424                ModifierSer::CompositeV1 {
425                    source,
426                    operator,
427                    reverse,
428                    disassemblable,
429                } => Modifier::Composite(Composite {
430                    source,
431                    operator,
432                    reverse,
433                    disassemblable,
434                }),
435                ModifierSer::ZoomV1 { scale, offset } => {
436                    Modifier::Zoom(Zoom::new(scale, offset.map(i32::from).into()))
437                }
438                ModifierSer::Move(m) => Modifier::Move(m.into()),
439                ModifierSer::BlockInventoryV1 { inventory } => {
440                    Modifier::Inventory(inventory.into_owned())
441                }
442            }
443        }
444    }
445
446    impl From<&Move> for schema::MoveSer {
447        fn from(value: &Move) -> Self {
448            let &Move {
449                direction,
450                distance,
451                velocity,
452                schedule,
453            } = value;
454
455            schema::MoveSer::MoveV1 {
456                direction,
457                distance,
458                velocity,
459                schedule,
460            }
461        }
462    }
463
464    impl From<schema::MoveSer> for Move {
465        fn from(value: schema::MoveSer) -> Self {
466            match value {
467                schema::MoveSer::MoveV1 {
468                    direction,
469                    distance,
470                    velocity,
471                    schedule,
472                } => Move {
473                    direction,
474                    distance,
475                    velocity,
476                    schedule,
477                },
478            }
479        }
480    }
481
482    // Since `Text` has private fields, its `From` impls are in its module.
483
484    impl From<&text::Font> for schema::FontSer {
485        fn from(value: &text::Font) -> Self {
486            match value {
487                text::Font::System16 => schema::FontSer::System16V1,
488                text::Font::Logo => schema::FontSer::LogoV1,
489                text::Font::SmallerBodyText => schema::FontSer::UnstableSmallerBodyTextV1,
490            }
491        }
492    }
493
494    impl From<schema::FontSer> for text::Font {
495        fn from(value: schema::FontSer) -> Self {
496            match value {
497                schema::FontSer::System16V1 => text::Font::System16,
498                schema::FontSer::LogoV1 => text::Font::Logo,
499                schema::FontSer::UnstableSmallerBodyTextV1 => text::Font::SmallerBodyText,
500            }
501        }
502    }
503
504    impl From<text::Positioning> for schema::PositioningSerV1 {
505        fn from(value: text::Positioning) -> Self {
506            let text::Positioning { x, line_y, z } = value;
507            schema::PositioningSerV1 {
508                x: x.into(),
509                line_y: line_y.into(),
510                z: z.into(),
511            }
512        }
513    }
514
515    impl From<schema::PositioningSerV1> for text::Positioning {
516        fn from(value: schema::PositioningSerV1) -> Self {
517            let schema::PositioningSerV1 { x, line_y, z } = value;
518            text::Positioning {
519                x: x.into(),
520                line_y: line_y.into(),
521                z: z.into(),
522            }
523        }
524    }
525
526    impl From<text::PositioningX> for schema::PositioningXSer {
527        fn from(value: text::PositioningX) -> Self {
528            match value {
529                text::PositioningX::Left => schema::PositioningXSer::LeftV1,
530                text::PositioningX::Center => schema::PositioningXSer::CenterV1,
531                text::PositioningX::Right => schema::PositioningXSer::RightV1,
532            }
533        }
534    }
535
536    impl From<schema::PositioningXSer> for text::PositioningX {
537        fn from(value: schema::PositioningXSer) -> Self {
538            match value {
539                schema::PositioningXSer::LeftV1 => text::PositioningX::Left,
540                schema::PositioningXSer::CenterV1 => text::PositioningX::Center,
541                schema::PositioningXSer::RightV1 => text::PositioningX::Right,
542            }
543        }
544    }
545
546    impl From<text::PositioningY> for schema::PositioningYSer {
547        fn from(value: text::PositioningY) -> Self {
548            match value {
549                text::PositioningY::BodyTop => schema::PositioningYSer::BodyTopV1,
550                text::PositioningY::BodyMiddle => schema::PositioningYSer::BodyMiddleV1,
551                text::PositioningY::Baseline => schema::PositioningYSer::BaselineV1,
552                text::PositioningY::BodyBottom => schema::PositioningYSer::BodyBottomV1,
553            }
554        }
555    }
556
557    impl From<schema::PositioningYSer> for text::PositioningY {
558        fn from(value: schema::PositioningYSer) -> Self {
559            match value {
560                schema::PositioningYSer::BodyTopV1 => text::PositioningY::BodyTop,
561                schema::PositioningYSer::BodyMiddleV1 => text::PositioningY::BodyMiddle,
562                schema::PositioningYSer::BaselineV1 => text::PositioningY::Baseline,
563                schema::PositioningYSer::BodyBottomV1 => text::PositioningY::BodyBottom,
564            }
565        }
566    }
567
568    impl From<text::PositioningZ> for schema::PositioningZSer {
569        fn from(value: text::PositioningZ) -> Self {
570            match value {
571                text::PositioningZ::Front => schema::PositioningZSer::FrontV1,
572                text::PositioningZ::Back => schema::PositioningZSer::BackV1,
573            }
574        }
575    }
576
577    impl From<schema::PositioningZSer> for text::PositioningZ {
578        fn from(value: schema::PositioningZSer) -> Self {
579            match value {
580                schema::PositioningZSer::FrontV1 => text::PositioningZ::Front,
581                schema::PositioningZSer::BackV1 => text::PositioningZ::Back,
582            }
583        }
584    }
585}
586
587// `character::Character` and `character::Spawn` serialization are inside their module
588// for the sake of private fields.
589
590mod inv {
591    use super::*;
592    use crate::inv::{self, Inventory, Slot, Tool};
593
594    impl Serialize for Inventory {
595        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
596        where
597            S: Serializer,
598        {
599            schema::InventorySer::InventoryV1 {
600                slots: self.slots.iter().map(|slot| slot.into()).collect(),
601            }
602            .serialize(serializer)
603        }
604    }
605
606    impl<'de> Deserialize<'de> for Inventory {
607        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
608        where
609            D: Deserializer<'de>,
610        {
611            match schema::InventorySer::deserialize(deserializer)? {
612                schema::InventorySer::InventoryV1 { slots } => Ok(Inventory {
613                    slots: slots.into_iter().map(|s| s.into()).collect(),
614                }),
615            }
616        }
617    }
618
619    impl From<Option<schema::InvStackSer>> for Slot {
620        fn from(slot: Option<schema::InvStackSer>) -> Self {
621            match slot {
622                Some(schema::InvStackSer { count, item }) => Slot::Stack(count, item),
623                None => Slot::Empty,
624            }
625        }
626    }
627
628    impl From<&Slot> for Option<schema::InvStackSer> {
629        fn from(slot: &Slot) -> Self {
630            match *slot {
631                Slot::Empty => None,
632                Slot::Stack(count, ref item) => Some(schema::InvStackSer {
633                    count,
634                    item: item.clone(),
635                }),
636            }
637        }
638    }
639
640    impl Serialize for Tool {
641        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
642        where
643            S: Serializer,
644        {
645            match *self {
646                Tool::Activate => schema::ToolSer::ActivateV1 {},
647                Tool::RemoveBlock { keep } => schema::ToolSer::RemoveBlockV1 { keep },
648                Tool::Block(ref block) => schema::ToolSer::BlockV1 {
649                    block: block.clone(),
650                },
651                Tool::InfiniteBlocks(ref block) => schema::ToolSer::InfiniteBlocksV1 {
652                    block: block.clone(),
653                },
654                Tool::CopyFromSpace => schema::ToolSer::CopyFromSpaceV1 {},
655                Tool::EditBlock => schema::ToolSer::EditBlockV1 {},
656                Tool::PushPull => schema::ToolSer::PushPullV1 {},
657                Tool::Jetpack { active } => schema::ToolSer::JetpackV1 { active },
658                Tool::Custom { ref op, ref icon } => schema::ToolSer::CustomV1 {
659                    op: op.clone(),
660                    icon: icon.clone(),
661                },
662            }
663            .serialize(serializer)
664        }
665    }
666
667    impl<'de> Deserialize<'de> for Tool {
668        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
669        where
670            D: Deserializer<'de>,
671        {
672            Ok(match schema::ToolSer::deserialize(deserializer)? {
673                schema::ToolSer::ActivateV1 {} => Tool::Activate,
674                schema::ToolSer::RemoveBlockV1 { keep } => Tool::RemoveBlock { keep },
675                schema::ToolSer::BlockV1 { block } => Tool::Block(block),
676                schema::ToolSer::InfiniteBlocksV1 { block } => Tool::InfiniteBlocks(block),
677                schema::ToolSer::CopyFromSpaceV1 {} => Tool::CopyFromSpace,
678                schema::ToolSer::EditBlockV1 {} => Tool::EditBlock,
679                schema::ToolSer::PushPullV1 {} => Tool::PushPull,
680                schema::ToolSer::JetpackV1 { active } => Tool::Jetpack { active },
681                schema::ToolSer::CustomV1 { op, icon } => Tool::Custom { op, icon },
682            })
683        }
684    }
685
686    impl From<&inv::InvInBlock> for schema::InvInBlockSer {
687        fn from(value: &inv::InvInBlock) -> Self {
688            let inv::InvInBlock {
689                size,
690                icon_scale,
691                icon_resolution,
692                ref icon_rows,
693            } = *value;
694            schema::InvInBlockSer::InvInBlockV1 {
695                size,
696                icon_scale,
697                icon_resolution,
698                icon_rows: icon_rows.iter().map(schema::IconRowSerV1::from).collect(),
699            }
700        }
701    }
702
703    impl From<schema::InvInBlockSer> for inv::InvInBlock {
704        fn from(value: schema::InvInBlockSer) -> Self {
705            match value {
706                schema::InvInBlockSer::InvInBlockV1 {
707                    size,
708                    icon_scale,
709                    icon_resolution,
710                    icon_rows,
711                } => inv::InvInBlock {
712                    size,
713                    icon_scale,
714                    icon_resolution,
715                    icon_rows: icon_rows.into_iter().map(inv::IconRow::from).collect(),
716                },
717            }
718        }
719    }
720
721    impl From<&inv::IconRow> for schema::IconRowSerV1 {
722        fn from(value: &inv::IconRow) -> Self {
723            let inv::IconRow {
724                first_slot,
725                count,
726                origin,
727                stride,
728            } = *value;
729            schema::IconRowSerV1 {
730                first_slot,
731                count,
732                origin: origin.into(),
733                stride: stride.into(),
734            }
735        }
736    }
737
738    impl From<schema::IconRowSerV1> for inv::IconRow {
739        fn from(value: schema::IconRowSerV1) -> Self {
740            let schema::IconRowSerV1 {
741                first_slot,
742                count,
743                origin,
744                stride,
745            } = value;
746            inv::IconRow {
747                first_slot,
748                count,
749                origin: origin.into(),
750                stride: stride.into(),
751            }
752        }
753    }
754}
755
756mod op {
757    use super::*;
758    use crate::op;
759
760    impl Serialize for op::Operation {
761        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
762        where
763            S: Serializer,
764        {
765            match self {
766                op::Operation::Alt(ops) => schema::OperationSer::AltV1 {
767                    ops: Cow::Borrowed(&ops[..]),
768                },
769                op::Operation::Become(block) => schema::OperationSer::BecomeV1 {
770                    block: block.clone(),
771                },
772                op::Operation::DestroyTo(block) => schema::OperationSer::DestroyToV1 {
773                    block: block.clone(),
774                },
775                &op::Operation::Replace {
776                    ref old,
777                    ref new,
778                    conserved,
779                    optional,
780                } => schema::OperationSer::ReplaceV1 {
781                    old: old.clone(),
782                    new: new.clone(),
783                    conserved,
784                    optional,
785                },
786
787                op::Operation::AddModifiers(modifiers) => schema::OperationSer::AddModifiersV1 {
788                    modifiers: modifiers.iter().map(schema::ModifierSer::from).collect(),
789                },
790                op::Operation::StartMove(m) => {
791                    schema::OperationSer::StartMoveV1 { modifier: m.into() }
792                }
793                &op::Operation::MoveInventory {
794                    transfer_into_adjacent,
795                } => schema::OperationSer::MoveInventoryV1 {
796                    transfer_into_adjacent,
797                },
798                &op::Operation::TakeInventory { destroy_if_empty } => {
799                    schema::OperationSer::TakeInventoryV1 { destroy_if_empty }
800                }
801                op::Operation::Neighbors(neighbors) => schema::OperationSer::NeighborsV1 {
802                    // TODO: arrange to be able to borrow here
803                    neighbors: Cow::Owned(
804                        neighbors
805                            .iter()
806                            .map(|&(offset, ref op)| (offset.into(), op.clone()))
807                            .collect(),
808                    ),
809                },
810            }
811            .serialize(serializer)
812        }
813    }
814
815    impl<'de> Deserialize<'de> for op::Operation {
816        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
817        where
818            D: Deserializer<'de>,
819        {
820            Ok(match schema::OperationSer::deserialize(deserializer)? {
821                schema::OperationSer::AltV1 { ops } => op::Operation::Alt(ops.into()),
822                schema::OperationSer::BecomeV1 { block } => op::Operation::Become(block),
823                schema::OperationSer::DestroyToV1 { block } => op::Operation::DestroyTo(block),
824                schema::OperationSer::ReplaceV1 {
825                    old,
826                    new,
827                    conserved,
828                    optional,
829                } => op::Operation::Replace {
830                    old,
831                    new,
832                    conserved,
833                    optional,
834                },
835
836                schema::OperationSer::AddModifiersV1 { modifiers } => op::Operation::AddModifiers(
837                    cow_into_iter(modifiers).map(crate::block::Modifier::from).collect(),
838                ),
839                schema::OperationSer::StartMoveV1 { modifier: m } => {
840                    op::Operation::StartMove(m.into())
841                }
842                schema::OperationSer::MoveInventoryV1 {
843                    transfer_into_adjacent,
844                } => op::Operation::MoveInventory {
845                    transfer_into_adjacent,
846                },
847                schema::OperationSer::TakeInventoryV1 { destroy_if_empty } => {
848                    op::Operation::TakeInventory { destroy_if_empty }
849                }
850                schema::OperationSer::NeighborsV1 { neighbors } => op::Operation::Neighbors(
851                    cow_into_iter(neighbors).map(|(offset, op)| (offset.into(), op)).collect(),
852                ),
853            })
854        }
855    }
856}
857
858mod sound {
859    use super::*;
860    use crate::sound;
861
862    impl Serialize for sound::SoundDef {
863        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
864        where
865            S: Serializer,
866        {
867            schema::SoundDefSer::SynthesizedSoundV1 {
868                duration: self.duration,
869                frequency: self.frequency,
870                amplitude: self.amplitude,
871            }
872            .serialize(serializer)
873        }
874    }
875
876    impl<'de> Deserialize<'de> for sound::SoundDef {
877        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
878        where
879            D: Deserializer<'de>,
880        {
881            match schema::SoundDefSer::deserialize(deserializer)? {
882                schema::SoundDefSer::SynthesizedSoundV1 {
883                    duration,
884                    frequency,
885                    amplitude,
886                } => Ok(sound::SoundDef {
887                    duration,
888                    frequency,
889                    amplitude,
890                }),
891            }
892        }
893    }
894
895    impl From<&sound::Ambient> for schema::AmbientSoundSer {
896        fn from(value: &sound::Ambient) -> Self {
897            let &sound::Ambient {
898                noise_bands: sound::Spectrum(noise_bands),
899            } = value;
900            Self::AmbientSoundV1 { noise_bands }
901        }
902    }
903
904    impl From<schema::AmbientSoundSer> for sound::Ambient {
905        fn from(value: schema::AmbientSoundSer) -> Self {
906            match value {
907                schema::AmbientSoundSer::AmbientSoundV1 { noise_bands } => Self {
908                    noise_bands: sound::Spectrum(noise_bands),
909                },
910            }
911        }
912    }
913}
914
915mod space {
916    use super::*;
917    use crate::math::{Rgb, Vol};
918    use crate::save::compress::{GzSerde, Leu16};
919    use crate::space::{self, LightPhysics, Sky, Space, SpacePhysics};
920
921    impl Serialize for Space {
922        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
923        where
924            S: Serializer,
925        {
926            self.read().serialize(serializer)
927        }
928    }
929
930    impl Serialize for space::Read<'_> {
931        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
932        where
933            S: Serializer,
934        {
935            schema::SpaceSer::SpaceV1 {
936                bounds: self.bounds(),
937                physics: self.physics().into(),
938                blocks: self.block_data().iter().map(|bd| bd.block().clone()).collect(),
939                contents: GzSerde(Cow::Owned(
940                    self.extract(self.bounds(), |e| Leu16::from(e.block_index())).into_elements(),
941                )),
942                light: if matches!(self.physics().light, LightPhysics::None) {
943                    None
944                } else {
945                    Some(GzSerde(Cow::Owned(
946                        self.extract(self.bounds(), |e| {
947                            let mut light = schema::LightSerV1::from(e.light());
948                            if self.in_light_update_queue(e.cube()) {
949                                light.status = schema::LightStatusSerV1::Uninitialized
950                            }
951                            light
952                        })
953                        .into_elements(),
954                    )))
955                },
956                behaviors: Cow::Borrowed(self.behaviors()),
957                spawn: Cow::Borrowed(self.spawn()),
958            }
959            .serialize(serializer)
960        }
961    }
962
963    impl<'de> Deserialize<'de> for Space {
964        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
965        where
966            D: Deserializer<'de>,
967        {
968            match schema::SpaceSer::deserialize(deserializer)? {
969                schema::SpaceSer::SpaceV1 {
970                    bounds,
971                    physics,
972                    blocks,
973                    contents: GzSerde(contents),
974                    light,
975                    behaviors,
976                    spawn,
977                } => {
978                    // Convert data representations
979                    let contents = Vol::from_elements(
980                        bounds,
981                        Vec::from(contents)
982                            .into_iter()
983                            .map(space::BlockIndex::from)
984                            .collect::<Box<[_]>>(),
985                    )
986                    .map_err(serde::de::Error::custom)?;
987                    let light = light
988                        .map(|GzSerde(data)| {
989                            Vol::from_elements(
990                                bounds,
991                                Vec::from(data)
992                                    .into_iter()
993                                    .map(space::PackedLight::from)
994                                    .collect::<Box<[_]>>(),
995                            )
996                        })
997                        .transpose()
998                        .map_err(serde::de::Error::custom)?;
999
1000                    let space = Space::builder(bounds)
1001                        .physics(physics.into())
1002                        // .read_ticket(todo!("TODO(ecs)"))
1003                        .palette_and_contents(blocks, contents, light)
1004                        .map_err(serde::de::Error::custom)?
1005                        .behaviors(behaviors.into_owned())
1006                        .spawn(spawn.into_owned())
1007                        .build();
1008
1009                    Ok(space)
1010                }
1011            }
1012        }
1013    }
1014
1015    impl From<&SpacePhysics> for schema::SpacePhysicsSerV1 {
1016        fn from(value: &SpacePhysics) -> Self {
1017            let &SpacePhysics {
1018                gravity,
1019                ref sky,
1020                ref light,
1021            } = value;
1022            Self {
1023                gravity: gravity.into(),
1024                sky: sky.into(),
1025                light: light.into(),
1026            }
1027        }
1028    }
1029
1030    impl From<schema::SpacePhysicsSerV1> for SpacePhysics {
1031        fn from(value: schema::SpacePhysicsSerV1) -> Self {
1032            let schema::SpacePhysicsSerV1 {
1033                gravity,
1034                sky,
1035                light,
1036            } = value;
1037            Self {
1038                gravity: gravity.into(),
1039                sky: sky.into(),
1040                light: light.into(),
1041            }
1042        }
1043    }
1044
1045    impl From<&Sky> for schema::SkySer {
1046        fn from(value: &Sky) -> Self {
1047            use schema::SkySer as S;
1048            match value {
1049                &Sky::Uniform(color) => S::UniformV1 {
1050                    color: color.into(),
1051                },
1052                &Sky::Octants(colors) => S::OctantsV1 {
1053                    colors: colors.map(Rgb::into),
1054                },
1055            }
1056        }
1057    }
1058
1059    impl From<schema::SkySer> for Sky {
1060        fn from(value: schema::SkySer) -> Self {
1061            use schema::SkySer as S;
1062            match value {
1063                S::UniformV1 { color } => Sky::Uniform(color.into()),
1064                S::OctantsV1 { colors } => Sky::Octants(colors.map(Rgb::from)),
1065            }
1066        }
1067    }
1068
1069    impl From<&LightPhysics> for schema::LightPhysicsSerV1 {
1070        fn from(value: &LightPhysics) -> Self {
1071            use schema::LightPhysicsSerV1 as S;
1072            match value {
1073                LightPhysics::None => S::NoneV1,
1074                &LightPhysics::Rays { maximum_distance } => S::RaysV1 { maximum_distance },
1075            }
1076        }
1077    }
1078
1079    impl From<schema::LightPhysicsSerV1> for LightPhysics {
1080        fn from(value: schema::LightPhysicsSerV1) -> Self {
1081            use schema::LightPhysicsSerV1 as S;
1082            match value {
1083                S::NoneV1 => LightPhysics::None,
1084                S::RaysV1 { maximum_distance } => LightPhysics::Rays { maximum_distance },
1085            }
1086        }
1087    }
1088}
1089
1090mod tag {
1091    use super::*;
1092    use crate::tag::{Tag, TagDef};
1093
1094    impl Serialize for Tag {
1095        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1096        where
1097            S: Serializer,
1098        {
1099            match self {
1100                Tag::Handle(handle) => schema::TagSer::TagHandleV1 {
1101                    handle: handle.clone(),
1102                },
1103            }
1104            .serialize(serializer)
1105        }
1106    }
1107
1108    impl<'de> Deserialize<'de> for Tag {
1109        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1110        where
1111            D: Deserializer<'de>,
1112        {
1113            match schema::TagSer::deserialize(deserializer)? {
1114                schema::TagSer::TagHandleV1 { handle } => Ok(Tag::Handle(handle)),
1115            }
1116        }
1117    }
1118    impl Serialize for TagDef {
1119        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1120        where
1121            S: Serializer,
1122        {
1123            let TagDef = *self;
1124            schema::TagDefSer::TagDefV1 {}.serialize(serializer)
1125        }
1126    }
1127
1128    impl<'de> Deserialize<'de> for TagDef {
1129        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1130        where
1131            D: Deserializer<'de>,
1132        {
1133            match schema::TagDefSer::deserialize(deserializer)? {
1134                schema::TagDefSer::TagDefV1 {} => Ok(TagDef),
1135            }
1136        }
1137    }
1138}
1139
1140mod time {
1141    use super::*;
1142    use crate::time;
1143
1144    impl Serialize for time::Schedule {
1145        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1146            schema::ScheduleSer::ScheduleV1 {
1147                period: self.to_period().unwrap(),
1148            }
1149            .serialize(serializer)
1150        }
1151    }
1152
1153    impl<'de> Deserialize<'de> for time::Schedule {
1154        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1155        where
1156            D: Deserializer<'de>,
1157        {
1158            Ok(match schema::ScheduleSer::deserialize(deserializer)? {
1159                schema::ScheduleSer::ScheduleV1 { period } => time::Schedule::from_period(period),
1160            })
1161        }
1162    }
1163}
1164
1165mod universe {
1166    use super::*;
1167    use crate::block::BlockDef;
1168    use crate::save::schema::MemberEntrySer;
1169    use crate::time;
1170    use crate::universe::{
1171        self, AnyHandle, Handle, HandleSet, Name, PartialUniverse, Universe, tl,
1172    };
1173    use schema::{HandleSer, MemberDe, NameSer};
1174
1175    impl From<&BlockDef> for schema::MemberSer<'_> {
1176        fn from(block_def: &BlockDef) -> Self {
1177            schema::MemberSer::Block {
1178                value: block_def.block().clone(),
1179            }
1180        }
1181    }
1182
1183    impl Serialize for PartialUniverse<'_> {
1184        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1185            let &Self {
1186                read_ticket,
1187                ref handles,
1188            } = self;
1189
1190            let members = handles
1191                .iter()
1192                .map(|handle: &AnyHandle| match handle {
1193                    AnyHandle::BlockDef(member_handle) => {
1194                        let name = member_handle.name();
1195                        let member_repr = schema::MemberSer::from(
1196                            member_handle.read(read_ticket).map_err(|e| {
1197                                serde::ser::Error::custom(format!(
1198                                    "Failed to read universe member {name}: {e}"
1199                                ))
1200                            })?,
1201                        );
1202                        Ok(MemberEntrySer {
1203                            name: member_handle.name(),
1204                            value: member_repr,
1205                        })
1206                    }
1207                    AnyHandle::Character(member_handle) => Ok(MemberEntrySer {
1208                        name: member_handle.name(),
1209                        value: schema::MemberSer::Character {
1210                            value: schema::SerializeHandle(read_ticket, member_handle.clone()),
1211                        },
1212                    }),
1213                    AnyHandle::SoundDef(member_handle) => Ok(MemberEntrySer {
1214                        name: member_handle.name(),
1215                        value: schema::MemberSer::Sound {
1216                            value: schema::SerializeHandle(read_ticket, member_handle.clone()),
1217                        },
1218                    }),
1219                    AnyHandle::Space(member_handle) => Ok(MemberEntrySer {
1220                        name: member_handle.name(),
1221                        value: schema::MemberSer::Space {
1222                            value: schema::SerializeHandle(read_ticket, member_handle.clone()),
1223                        },
1224                    }),
1225                    AnyHandle::TagDef(member_handle) => Ok(MemberEntrySer {
1226                        name: member_handle.name(),
1227                        value: schema::MemberSer::Tag {
1228                            value: schema::SerializeHandle(read_ticket, member_handle.clone()),
1229                        },
1230                    }),
1231                })
1232                .collect::<Result<Vec<MemberEntrySer<schema::MemberSer<'_>>>, S::Error>>()?;
1233            schema::UniverseSer::UniverseV1 { members }.serialize(serializer)
1234        }
1235    }
1236
1237    impl Serialize for Universe {
1238        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1239            // TODO: Serialize resources such as `Clock`, possibly by giving them unique Handles.
1240            PartialUniverse {
1241                handles: HandleSet::all_of(self),
1242                read_ticket: self.read_ticket(),
1243            }
1244            .serialize(serializer)
1245        }
1246    }
1247
1248    impl<'de> Deserialize<'de> for Universe {
1249        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1250            let (data, mut universe) = {
1251                let scope = tl::Scope::install(tl::Context {
1252                    purpose: tl::Purpose::Deserialization,
1253                    universe: Universe::new(),
1254                });
1255                let data = schema::UniverseDe::deserialize(deserializer)?;
1256                let universe = scope.take(tl::Purpose::Deserialization).universe;
1257                (data, universe)
1258            };
1259
1260            match data {
1261                schema::UniverseDe::UniverseV1 { members } => {
1262                    for MemberEntrySer { name, value } in members {
1263                        match value {
1264                            MemberDe::Block { value: block } => universe.insert_deserialized(
1265                                name,
1266                                Box::new(BlockDef::new(universe.read_ticket(), block)),
1267                            ),
1268                            MemberDe::Character { value } => {
1269                                universe.insert_deserialized(name, value)
1270                            }
1271                            MemberDe::Sound { value } => universe.insert_deserialized(name, value),
1272                            MemberDe::Space { value } => universe.insert_deserialized(name, value),
1273                            MemberDe::Tag { value } => universe.insert_deserialized(name, value),
1274                        }
1275                        .map_err(serde::de::Error::custom)?;
1276                    }
1277                }
1278            }
1279
1280            universe.validate_deserialized_members().map_err(serde::de::Error::custom)?;
1281
1282            // Perform a paused step to let things do re-initialization,
1283            // such as `Space` block evaluation, without actually causing any in-game time
1284            // to pass.
1285            universe.step(true, time::Deadline::Asap);
1286
1287            Ok(*universe)
1288        }
1289    }
1290
1291    impl<T: 'static> Serialize for Handle<T> {
1292        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1293            HandleSer::HandleV1 { name: self.name() }.serialize(serializer)
1294        }
1295    }
1296
1297    impl<'de, T: universe::UniverseMember + 'static> Deserialize<'de> for Handle<T> {
1298        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1299            match HandleSer::deserialize(deserializer)? {
1300                HandleSer::HandleV1 { name } => {
1301                    match tl::get_from_context(tl::Purpose::Deserialization, {
1302                        let name = name.clone();
1303                        |context| context.universe.get_or_insert_deserializing(name)
1304                    }) {
1305                        Some(result) => result.map_err(serde::de::Error::custom),
1306                        None => {
1307                            // If there is no `Universe` context, use a “gone” handle.
1308                            // I am unsure whether this has any practical application, but it
1309                            // is at least useful in deserialization tests.
1310                            Ok(Handle::new_gone(name))
1311                        }
1312                    }
1313                }
1314            }
1315        }
1316    }
1317
1318    impl Serialize for Name {
1319        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1320            match self {
1321                Name::Specific(s) => NameSer::Specific(s.clone()),
1322                &Name::Anonym(number) => NameSer::Anonym(number),
1323                Name::Pending => {
1324                    return Err(serde::ser::Error::custom(
1325                        "cannot serialize a pending Handle",
1326                    ));
1327                }
1328            }
1329            .serialize(serializer)
1330        }
1331    }
1332
1333    impl<'de> Deserialize<'de> for Name {
1334        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1335            Ok(match NameSer::deserialize(deserializer)? {
1336                NameSer::Specific(s) => Name::Specific(s),
1337                NameSer::Anonym(n) => Name::Anonym(n),
1338            })
1339        }
1340    }
1341
1342    impl<'ticket, T> Serialize for schema::SerializeHandle<'ticket, T>
1343    where
1344        // Each `Read` implementation must serialize to the serialization of the member.
1345        T: universe::UniverseMember<Read<'ticket>: Serialize>,
1346    {
1347        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1348        where
1349            S: Serializer,
1350        {
1351            let &schema::SerializeHandle(read_ticket, ref handle) = self;
1352            let read = handle.read(read_ticket).map_err(|e| {
1353                serde::ser::Error::custom(format!(
1354                    "Failed to read universe member {name}: {e}",
1355                    name = handle.name()
1356                ))
1357            })?;
1358            read.serialize(serializer)
1359        }
1360    }
1361}
1362
1363/// Create an efficient iterator of owned elements from a [`Cow`].
1364///
1365/// `cow.into_owned().into_iter()` creates a redundant `Vec`, and
1366/// `cow.iter().cloned()` redundantly clones owned items;
1367/// this function avoids both.
1368fn cow_into_iter<T: Clone>(cow: Cow<'_, [T]>) -> impl Iterator<Item = T> + '_ {
1369    use itertools::Either;
1370    match cow {
1371        Cow::Borrowed(slice) => Either::Left(slice.iter().cloned()),
1372        Cow::Owned(vec) => Either::Right(vec.into_iter()),
1373    }
1374}