cotis-modules-debug 0.1.0-alpha

Record, replay, and isolate Cotis layout and UI declaration pipelines
Documentation
//! Recording wrappers around [`cotis::element_configuring`] for stage-1 capture.
//!
//! Each wrapper delegates to the real configuring API and mirrors every operation into a
//! [`LeafedInterfaceDeclaration`].
//! [`DebugOpenElement`] and [`DebugConfiguredParentElement`] record [`Close`](crate::serialize_targets::SerializableInterfaceInstructions::Close)
//! on drop if not closed explicitly.

use cotis::element_configuring::{
    ConfigureElements, ConfigureLeafElements, ConfiguredParentElement, OpenElement,
};
use serde::Serialize;

use crate::serialize_targets::LeafedInterfaceDeclaration;

/// Root debug wrapper used at the start of [`UiFn::draw_in`](cotis::cotis_app::UiFn::draw_in).
///
/// Wrap the layout root `ConfiguredParentElement` and pass a mutable reference to the
/// declaration recorder. All child elements created via [`new_element`](Self::new_element)
/// record [`Open`](crate::serialize_targets::SerializableInterfaceInstructions::Open)
/// before delegating to the inner layout.
pub struct DebugInitialConfiguredParentElement<
    'b,
    'element,
    Configurer: ConfigureElements<ConfigType>,
    ConfigType: Clone + Serialize,
    List: Copy,
> {
    internal: &'b mut ConfiguredParentElement<'element, Configurer, ConfigType>,
    interface_recorder: &'b mut LeafedInterfaceDeclaration<ConfigType, List>,
}

impl<
    'b,
    'element,
    Configurer: ConfigureElements<ConfigType>,
    ConfigType: Clone + Serialize,
    List: Copy,
> DebugInitialConfiguredParentElement<'b, 'element, Configurer, ConfigType, List>
{
    /// Creates a root wrapper that records into `interface_recorder`.
    pub fn new(
        internal: &'b mut ConfiguredParentElement<'element, Configurer, ConfigType>,
        interface_recorder: &'b mut LeafedInterfaceDeclaration<ConfigType, List>,
    ) -> Self {
        Self {
            internal,
            interface_recorder,
        }
    }

    /// Opens a new child element, recording `Open` in the declaration stream.
    ///
    /// Returns a [`DebugOpenElement`] that can be used to configure the new element.
    pub fn new_element(&'_ mut self) -> DebugOpenElement<'_, Configurer, ConfigType, List>
    where
        Self: Sized,
    {
        self.interface_recorder.open();

        let new_element = self.internal.new_element();
        DebugOpenElement {
            internal: Some(new_element),
            interface_recorder: Some(&mut self.interface_recorder),
        }
    }

    /// Closes the root element and records `Close` in the declaration stream.
    ///
    /// It is not strictly necessary to call this method; closing also occurs when the
    /// wrapper is dropped after all child [`DebugOpenElement`] handles are released.
    pub fn close_element(self) {
        self.interface_recorder.close();
    }
}

/// Debug wrapper for a parent element created via [`DebugOpenElement::make_parent`].
///
/// Records `Open` for each child via [`new_element`](Self::new_element). Records
/// `Close` on drop.
pub struct DebugConfiguredParentElement<
    'element,
    Configurer: ConfigureElements<ConfigType>,
    ConfigType: Clone + Serialize,
    List: Copy,
> {
    internal: ConfiguredParentElement<'element, Configurer, ConfigType>,
    interface_recorder: &'element mut LeafedInterfaceDeclaration<ConfigType, List>,
}

impl<'element, Configurer: ConfigureElements<ConfigType>, ConfigType: Clone + Serialize, List: Copy>
    DebugConfiguredParentElement<'element, Configurer, ConfigType, List>
{
    /// Creates a parent wrapper (typically via [`DebugOpenElement::make_parent`]).
    pub fn new(
        internal: ConfiguredParentElement<'element, Configurer, ConfigType>,
        interface_recorder: &'element mut LeafedInterfaceDeclaration<ConfigType, List>,
    ) -> Self {
        Self {
            internal,
            interface_recorder,
        }
    }

    /// Opens a new child element, recording `Open` in the declaration stream.
    pub fn new_element(&'_ mut self) -> DebugOpenElement<'_, Configurer, ConfigType, List>
    where
        Self: Sized,
    {
        self.interface_recorder.open();

        let new_element = self.internal.new_element();
        DebugOpenElement {
            internal: Some(new_element),
            interface_recorder: Some(&mut self.interface_recorder),
        }
    }

    /// Closes this parent element.
    ///
    /// `Close` is also recorded when this wrapper is dropped.
    pub fn close_element(self) {
        drop(self)
    }
}

/// Debug wrapper for an open element being configured.
///
/// Use [`set_config`](Self::set_config) for parent configs and
/// [`set_leaf_config`](Self::set_leaf_config) for leaf configs. Converting to a parent via
/// [`make_parent`](Self::make_parent) does not record an extra instruction.
pub struct DebugOpenElement<
    'element,
    Configurer: ConfigureElements<ConfigType>,
    ConfigType: Clone + Serialize,
    List: Copy,
> {
    internal: Option<OpenElement<'element, Configurer, ConfigType>>,
    interface_recorder: Option<&'element mut LeafedInterfaceDeclaration<ConfigType, List>>,
}

impl<'element, ConfigType: Clone + Serialize, Configurer: ConfigureElements<ConfigType>, List: Copy>
    DebugOpenElement<'element, Configurer, ConfigType, List>
{
    /// Sets the element config, recording `Configure` and applying it to the live element.
    pub fn set_config(&mut self, config: ConfigType) {
        self.interface_recorder
            .as_mut()
            .unwrap()
            .add_config(config.clone());
        self.internal.as_mut().unwrap().set_config(config);
    }

    /// Converts this open element into a parent that can accept children.
    ///
    /// Returns a [`DebugConfiguredParentElement`] wrapping the inner parent element.
    pub fn make_parent(
        mut self,
    ) -> DebugConfiguredParentElement<'element, Configurer, ConfigType, List> {
        DebugConfiguredParentElement {
            internal: self.internal.take().unwrap().make_parent(),
            interface_recorder: self.interface_recorder.take().unwrap(),
        }
    }

    /// Closes this open element.
    ///
    /// `Close` is also recorded when this wrapper is dropped.
    pub fn close_element(self) {
        drop(self)
    }

    /// Sets a leaf-specific config, recording `LeafConfig` and applying it to the live element.
    ///
    /// Consumes `self` because the inner [`OpenElement::set_leaf_config`] also consumes
    /// the open handle.
    pub fn set_leaf_config<LeafConfig>(mut self, config: LeafConfig)
    where
        Configurer: ConfigureLeafElements<LeafConfig>,
        LeafConfig: Serialize,
    {
        self.interface_recorder
            .as_mut()
            .unwrap()
            .add_leaf_config(&config);
        self.internal.take().unwrap().set_leaf_config(config);
    }
}

/// Records `Close` when a [`DebugOpenElement`] is dropped without an explicit close.
impl<'element, Configurer: ConfigureElements<ConfigType>, ConfigType: Clone + Serialize, List: Copy>
    Drop for DebugOpenElement<'element, Configurer, ConfigType, List>
{
    fn drop(&mut self) {
        match &mut self.interface_recorder {
            None => {}
            Some(recorder) => {
                recorder.close();
            }
        }
    }
}

/// Records `Close` when a [`DebugConfiguredParentElement`] is dropped.
impl<'element, ConfigType: Clone + Serialize, Configurer: ConfigureElements<ConfigType>, List: Copy>
    Drop for DebugConfiguredParentElement<'element, Configurer, ConfigType, List>
{
    fn drop(&mut self) {
        self.interface_recorder.close();
    }
}