cotis-modules-debug 0.1.0-alpha

Record, replay, and isolate Cotis layout and UI declaration pipelines
Documentation
//! Isolated layout-module replay without a full [`CotisApp`](cotis::cotis_app::CotisApp).
//!
//! [`LayoutModuleReplay`] runs a stored [`LeafedInterfaceDeclaration`] through a layout
//! manager in a single frame, returning layout output directly. For replay inside a normal
//! app loop, prefer [`UiFn`](cotis::cotis_app::UiFn) on [`&LeafedInterfaceDeclaration`](crate::serialize_targets::LeafedInterfaceDeclaration).

use std::iter::Peekable;

use cotis::element_configuring::{ConfigureElements, ConfiguredParentElement, OpenElement};
use cotis::layout::LayoutFrameManager;
use cotis::layout::{LayoutManager, LayoutManagerCompatible};

use crate::serialize_targets::leaf_serialization::LeafConfigListCompatible;
use crate::serialize_targets::{
    LeafedInterfaceDeclaration, LeafedInterfaceInstructions, SerializableInterfaceInstructions,
};

/// Replays a stored UI declaration through a layout manager in one isolated frame.
///
/// Unlike the [`UiFn`](cotis::cotis_app::UiFn) path (stage 2 of the debug pipeline), this type
/// does not require [`CotisApp`](cotis::cotis_app::CotisApp). It calls `init`, `prepare`, and
/// a single `begin_frame` on the wrapped layout module, then interprets the declaration
/// instructions to rebuild the element tree.
pub struct LayoutModuleReplay<Module, ConfigType, List: Copy> {
    internal: Module,
    declaration: LeafedInterfaceDeclaration<ConfigType, List>,
}

impl<Module, ConfigType, List: Copy> LayoutModuleReplay<Module, ConfigType, List> {
    /// Creates a replay runner from a layout module and a stored declaration.
    pub fn new(
        internal: Module,
        declaration: LeafedInterfaceDeclaration<ConfigType, List>,
    ) -> Self {
        Self {
            internal,
            declaration,
        }
    }

    /// Runs one isolated layout frame and returns the layout module's output.
    ///
    /// This initializes the layout module, replays all declaration instructions against the
    /// frame root, closes the root element, and collects the frame's layout output.
    ///
    /// # Panics
    ///
    /// Panics if the stored instruction stream is invalid for replay:
    ///
    /// - `"Bad replay: cannot configure ConfiguredParentElement"` when a `Configure`
    ///   instruction appears while configuring a parent scope.
    /// - `"Bad replay: cannot configure ConfiguredParentElement with leaf config"` when a
    ///   `LeafConfig` instruction appears while configuring a parent scope.
    pub fn replay<'frame, RenderCommandType, Renderer>(
        &mut self,
        renderer: &mut Renderer,
    ) -> Vec<RenderCommandType>
    where
        ConfigType: 'frame + Clone,
        RenderCommandType: 'frame,
        Module: LayoutManager<'frame, ConfigType, RenderCommandType>
            + LayoutManagerCompatible<Renderer>,
        for<'layout> List: LeafConfigListCompatible<Module::ElementConfigurer<'layout>, ConfigType>,
    {
        self.internal.init(renderer);
        self.internal.prepare(renderer);
        let mut frame = self.internal.begin_frame();
        let mut root = frame.begin();
        let mut iter = self.declaration.iter_over();

        replay_instructions(&mut iter, &mut root);

        root.close_element();
        frame.end().collect()
    }
}

/// Replays a declaration instruction stream into an existing layout root.
pub(crate) fn replay_instructions<'a, ConfigType, Configurer, List>(
    inst_iter: &mut impl Iterator<
        Item = LeafedInterfaceInstructions<&'a SerializableInterfaceInstructions<ConfigType>, List>,
    >,
    layout: &mut ConfiguredParentElement<'_, Configurer, ConfigType>,
) where
    ConfigType: Clone + 'a,
    Configurer: ConfigureElements<ConfigType>,
    List: Copy + LeafConfigListCompatible<Configurer, ConfigType>,
{
    replay_instructions_recursive_parent(&mut inst_iter.peekable(), layout);
}

fn replay_instructions_recursive_parent<'a, ConfigType, Configurer, List>(
    inst_iter: &mut Peekable<
        impl Iterator<
            Item = LeafedInterfaceInstructions<
                &'a SerializableInterfaceInstructions<ConfigType>,
                List,
            >,
        >,
    >,
    layout: &mut ConfiguredParentElement<'_, Configurer, ConfigType>,
) where
    ConfigType: Clone + 'a,
    Configurer: ConfigureElements<ConfigType>,
    List: Copy + LeafConfigListCompatible<Configurer, ConfigType>,
{
    while let Some(leafed) = inst_iter.next() {
        match leafed.instruction {
            SerializableInterfaceInstructions::Open => {
                let element = layout.new_element();
                replay_instructions_recursive_open(inst_iter, element);
            }
            SerializableInterfaceInstructions::Close => {
                break;
            }
            SerializableInterfaceInstructions::Configure(_) => {
                panic!("Bad replay: cannot configure ConfiguredParentElement");
            }
            SerializableInterfaceInstructions::LeafConfig(_) => {
                panic!("Bad replay: cannot configure ConfiguredParentElement with leaf config");
            }
            _ => {}
        }
    }
}

fn replay_instructions_recursive_open<'a, ConfigType, Configurer, List>(
    inst_iter: &mut Peekable<
        impl Iterator<
            Item = LeafedInterfaceInstructions<
                &'a SerializableInterfaceInstructions<ConfigType>,
                List,
            >,
        >,
    >,
    layout: OpenElement<'_, Configurer, ConfigType>,
) where
    ConfigType: Clone + 'a,
    Configurer: ConfigureElements<ConfigType>,
    List: Copy + LeafConfigListCompatible<Configurer, ConfigType>,
{
    let mut layout_opt = Some(layout);
    let mut parent_opt: Option<ConfiguredParentElement<'_, Configurer, ConfigType>> = None;

    while let Some(leafed) = inst_iter.next() {
        match leafed.instruction {
            SerializableInterfaceInstructions::Configure(c) => {
                if let Some(ref mut open) = layout_opt {
                    open.set_config(c.clone());
                }
            }
            SerializableInterfaceInstructions::LeafConfig(data) => {
                if let Some(open) = layout_opt.take() {
                    List::configure_open(data, open);
                }
            }
            SerializableInterfaceInstructions::Open => {
                if parent_opt.is_none()
                    && let Some(open) = layout_opt.take()
                {
                    parent_opt = Some(open.make_parent());
                }
                if let Some(ref mut parent) = parent_opt {
                    let child_element = parent.new_element();
                    replay_instructions_recursive_open(inst_iter, child_element);
                }
            }
            SerializableInterfaceInstructions::Close => {
                if let Some(parent) = parent_opt.take() {
                    parent.close_element();
                } else if let Some(open) = layout_opt.take() {
                    open.close_element();
                }
                break;
            }
            _ => {}
        }
    }
}