Skip to main content

cotis_modules_debug/
serialize_targets.rs

1//! Serializable UI declaration instruction streams and leaf-config type lists.
2//!
3//! This module records the sequence of operations performed while building a Cotis UI
4//! ([`Open`](SerializableInterfaceInstructions::Open), [`Configure`](SerializableInterfaceInstructions::Configure),
5//! [`Close`](SerializableInterfaceInstructions::Close), etc.) as JSON-friendly data. At replay
6//! time, the stream is interpreted to rebuild the element tree without the original UI code.
7//!
8//! Leaf element configs (text, images, etc.) are stored in a type-erased [`LeafConfigData`]
9//! blob. The [`LeafConfigList`](leaf_serialization::LeafConfigList) type parameter on
10//! [`LeafedInterfaceDeclaration`] tells the replay machinery which leaf types to try when
11//! deserializing those blobs.
12//!
13//! # Instruction protocol
14//!
15//! A valid recording for one element subtree looks like:
16//!
17//! ```text
18//! Open → Configure(config)? → LeafConfig(leaf)? → (child subtrees)* → Close
19//! ```
20//!
21//! [`Start`](SerializableInterfaceInstructions::Start) and
22//! [`End`](SerializableInterfaceInstructions::End) are optional session markers written around
23//! a frame; they are persisted to JSON but **ignored during replay**.
24//!
25//! # Stage 2 replay
26//!
27//! [`LeafedInterfaceDeclaration`] implements [`UiFn`] for shared
28//! references, so a loaded declaration can be passed directly to
29//! `CotisApp::compute_frame` as the UI callback.
30
31use std::path::Path;
32
33use cotis::cotis_app::UiFn;
34use cotis::element_configuring::{ConfigureElements, ConfiguredParentElement};
35use serde::de::DeserializeOwned;
36use serde::{Deserialize, Serialize};
37
38use crate::layout_module_debugger::isolated_replay::replay_instructions;
39use crate::serialize_targets::leaf_serialization::{LeafConfigData, LeafConfigListCompatible};
40
41pub mod leaf_serialization;
42
43/// One step in a recorded UI declaration.
44///
45/// Together, a sequence of these instructions describes how an element tree was built.
46/// Only [`Open`](Self::Open), [`Close`](Self::Close), [`Configure`](Self::Configure), and
47/// [`LeafConfig`](Self::LeafConfig) affect replay. [`Start`](Self::Start) and
48/// [`End`](Self::End) are optional frame markers that are ignored on replay.
49#[derive(Serialize, Deserialize, Debug)]
50pub enum SerializableInterfaceInstructions<ConfigType> {
51    /// Optional session/frame start marker (ignored on replay).
52    Start,
53    /// Opens a new element in the tree.
54    Open,
55    /// Sets the parent/element config for the current open element.
56    Configure(ConfigType),
57    /// Sets a leaf-specific config (type-erased JSON blob).
58    LeafConfig(LeafConfigData),
59    /// Closes the current open element.
60    Close,
61    /// Optional session/frame end marker (ignored on replay).
62    End,
63}
64
65/// Append-only builder for a serialized UI declaration instruction stream.
66///
67/// Use the builder methods to record operations in the same order they occur during live UI
68/// construction. The resulting stream can be serialized to JSON via
69/// [`LeafedInterfaceDeclaration::store_in`].
70///
71/// # Examples
72///
73/// ```rust,ignore
74/// let mut decl = SerializedInterfaceDeclaration::<MyConfig>::new();
75/// decl.start();
76/// decl.open();
77/// decl.add_config(my_config);
78/// decl.close();
79/// decl.end();
80/// ```
81#[derive(Serialize, Deserialize, Debug)]
82pub struct SerializedInterfaceDeclaration<ConfigType> {
83    configs: Vec<SerializableInterfaceInstructions<ConfigType>>,
84}
85
86impl<Config> SerializedInterfaceDeclaration<Config> {
87    /// Creates an empty declaration with no recorded instructions.
88    pub fn new() -> Self {
89        Self { configs: vec![] }
90    }
91
92    /// Records a [`Start`](SerializableInterfaceInstructions::Start) marker.
93    pub fn start(&mut self) {
94        self.configs.push(SerializableInterfaceInstructions::Start);
95    }
96
97    /// Records an [`End`](SerializableInterfaceInstructions::End) marker.
98    pub fn end(&mut self) {
99        self.configs.push(SerializableInterfaceInstructions::End);
100    }
101
102    /// Records an [`Open`](SerializableInterfaceInstructions::Open) instruction.
103    pub fn open(&mut self) {
104        self.configs.push(SerializableInterfaceInstructions::Open);
105    }
106
107    /// Records a [`Configure`](SerializableInterfaceInstructions::Configure) instruction.
108    pub fn add_config(&mut self, config: Config) {
109        self.configs
110            .push(SerializableInterfaceInstructions::Configure(config));
111    }
112
113    /// Records a [`Close`](SerializableInterfaceInstructions::Close) instruction.
114    pub fn close(&mut self) {
115        self.configs.push(SerializableInterfaceInstructions::Close);
116    }
117
118    /// Records a [`LeafConfig`](SerializableInterfaceInstructions::LeafConfig) instruction.
119    pub fn add_leaf_config<LeafConfig: Serialize>(&mut self, config: &LeafConfig) {
120        self.configs
121            .push(SerializableInterfaceInstructions::LeafConfig(
122                LeafConfigData::new(config),
123            ));
124    }
125
126    /// Iterates over the recorded instructions in order.
127    pub fn iter_over(&self) -> impl Iterator<Item = &SerializableInterfaceInstructions<Config>> {
128        self.configs.iter()
129    }
130}
131
132impl<Config> Default for SerializedInterfaceDeclaration<Config> {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138/// A serialized UI declaration paired with a leaf-config type list for replay.
139///
140/// The `List` type parameter is a phantom [`LeafConfigList`](leaf_serialization::LeafConfigList)
141/// chain that determines which leaf config types are tried when deserializing
142/// [`LeafConfig`](SerializableInterfaceInstructions::LeafConfig) instructions. It is not
143/// serialized to disk; callers must supply the same list at load time.
144pub struct LeafedInterfaceDeclaration<ConfigType, List: Copy> {
145    serialized: SerializedInterfaceDeclaration<ConfigType>,
146    leaf_types: List,
147}
148
149/// One instruction paired with the leaf type list used for replay dispatch.
150pub struct LeafedInterfaceInstructions<ConfigType, List: Copy> {
151    /// The instruction being replayed.
152    pub instruction: ConfigType,
153    /// Copy of the leaf type list carried through the iterator.
154    pub list: List,
155}
156
157impl<ConfigType, List: Copy> LeafedInterfaceDeclaration<ConfigType, List> {
158    /// Creates a declaration from a serialized stream and a leaf type list.
159    pub fn new(serialized: SerializedInterfaceDeclaration<ConfigType>, leaf_types: List) -> Self {
160        Self {
161            serialized,
162            leaf_types,
163        }
164    }
165
166    /// Records a [`Start`](SerializableInterfaceInstructions::Start) marker.
167    pub fn start(&mut self) {
168        self.serialized.start();
169    }
170
171    /// Records an [`End`](SerializableInterfaceInstructions::End) marker.
172    pub fn end(&mut self) {
173        self.serialized.end();
174    }
175
176    /// Records an [`Open`](SerializableInterfaceInstructions::Open) instruction.
177    pub fn open(&mut self) {
178        self.serialized.open();
179    }
180
181    /// Records a [`Configure`](SerializableInterfaceInstructions::Configure) instruction.
182    pub fn add_config(&mut self, config: ConfigType) {
183        self.serialized.add_config(config);
184    }
185
186    /// Records a [`Close`](SerializableInterfaceInstructions::Close) instruction.
187    pub fn close(&mut self) {
188        self.serialized.close();
189    }
190
191    /// Records a [`LeafConfig`](SerializableInterfaceInstructions::LeafConfig) instruction.
192    pub fn add_leaf_config<LeafConfig: Serialize>(&mut self, config: &LeafConfig) {
193        self.serialized.add_leaf_config(config);
194    }
195
196    /// Iterates instructions paired with the leaf type list for replay.
197    pub fn iter_over(
198        &self,
199    ) -> impl Iterator<
200        Item = LeafedInterfaceInstructions<&SerializableInterfaceInstructions<ConfigType>, List>,
201    > {
202        let list = self.leaf_types;
203        self.serialized
204            .iter_over()
205            .map(move |instruction| LeafedInterfaceInstructions { instruction, list })
206    }
207}
208
209impl<ConfigType: Serialize + DeserializeOwned, List: Copy>
210    LeafedInterfaceDeclaration<ConfigType, List>
211{
212    /// Serializes the instruction stream to a JSON file.
213    ///
214    /// Only the instruction vector is written; the leaf type list is not persisted.
215    ///
216    /// # Errors
217    ///
218    /// Returns an error if JSON serialization or file writing fails.
219    pub fn store_in(&self, file: &Path) -> Result<(), Box<dyn std::error::Error>> {
220        let bytes = serde_json::to_vec(&self.serialized)?;
221        std::fs::write(file, bytes)?;
222        Ok(())
223    }
224
225    /// Loads an instruction stream from a JSON file.
226    ///
227    /// The caller must supply the same [`LeafConfigList`](leaf_serialization::LeafConfigList)
228    /// type chain that was used during recording.
229    ///
230    /// # Errors
231    ///
232    /// Returns an error if file reading or JSON deserialization fails.
233    pub fn load_from(file: &Path, leaf_types: List) -> Result<Self, Box<dyn std::error::Error>> {
234        let bytes = std::fs::read(file)?;
235        let serialized: SerializedInterfaceDeclaration<ConfigType> =
236            serde_json::from_slice(&bytes)?;
237        Ok(Self {
238            serialized,
239            leaf_types,
240        })
241    }
242}
243
244impl<
245    'inside,
246    Configurer: ConfigureElements<ConfigType>,
247    ConfigType: Clone,
248    List: Copy + LeafConfigListCompatible<Configurer, ConfigType>,
249> UiFn<'inside, Configurer, ConfigType> for &LeafedInterfaceDeclaration<ConfigType, List>
250{
251    /// Replays the stored declaration into the layout root (stage 2 of the debug pipeline).
252    ///
253    /// Interprets the instruction stream to rebuild the element tree without debug wrappers.
254    ///
255    /// # Panics
256    ///
257    /// Panics if the instruction stream is invalid:
258    ///
259    /// - `"Bad replay: cannot configure ConfiguredParentElement"` — `Configure` at parent scope.
260    /// - `"Bad replay: cannot configure ConfiguredParentElement with leaf config"` — `LeafConfig`
261    ///   at parent scope.
262    fn draw_in(self, layout: &mut ConfiguredParentElement<'inside, Configurer, ConfigType>) {
263        replay_instructions(&mut self.iter_over(), layout);
264    }
265}