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}