mittens-engine 0.6.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
Documentation
use crate::engine::ecs::ComponentId;
use crate::engine::ecs::World;
use crate::engine::ecs::component::RenderableComponent;
use crate::engine::graphics::VisualWorld;
use std::collections::HashSet;

pub(crate) const OWNED_LAYOUT_STENCIL_CLIP_LABEL: &str = "__layout_stencil_clip";

#[derive(Debug, Default)]
pub struct ClippingSystem {
    active_stencil_clips: HashSet<ComponentId>,
}

impl ClippingSystem {
    pub fn register_renderable(
        &self,
        world: &World,
        visuals: &mut VisualWorld,
        renderable_component: ComponentId,
    ) {
        self.sync_renderable_stencil_ref(world, visuals, renderable_component);
    }

    pub fn register_stencil_clip(
        &mut self,
        world: &World,
        visuals: &mut VisualWorld,
        component: ComponentId,
    ) {
        self.active_stencil_clips.insert(component);

        let stencil_ref = Self::stencil_ref_for_clip(world, component);
        if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
            visuals.register_stencil_clip(handle, stencil_ref);
        }

        if let Some(scope_root) = Self::stencil_clip_scope_root(world, component) {
            self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
        }
    }

    pub fn unregister_stencil_clip(
        &mut self,
        world: &World,
        visuals: &mut VisualWorld,
        component: ComponentId,
    ) {
        self.active_stencil_clips.remove(&component);

        if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
            visuals.unregister_stencil_clip(handle);
        }

        if let Some(scope_root) = Self::stencil_clip_scope_root(world, component) {
            self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
        }
    }

    pub fn unregister_stencil_clip_for_subtree_node(
        &mut self,
        world: &World,
        visuals: &mut VisualWorld,
        component: ComponentId,
    ) {
        self.active_stencil_clips.remove(&component);
        if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, component) {
            visuals.unregister_stencil_clip(handle);
        }
    }

    pub fn resync_after_renderable_flush(&mut self, world: &World, visuals: &mut VisualWorld) {
        let stencil_clips: Vec<ComponentId> = self.active_stencil_clips(world).collect();

        for stencil_clip in stencil_clips {
            let stencil_ref = Self::stencil_ref_for_clip(world, stencil_clip);
            if let Some(handle) = Self::find_stencil_clip_renderable_handle(world, stencil_clip) {
                let _ = visuals.register_stencil_clip(handle, stencil_ref);
            }
            if let Some(scope_root) = Self::stencil_clip_scope_root(world, stencil_clip) {
                self.sync_stencil_refs_in_subtree(world, visuals, scope_root);
            }
        }
    }

    fn sync_renderable_stencil_ref(
        &self,
        world: &World,
        visuals: &mut VisualWorld,
        renderable_component: ComponentId,
    ) {
        let Some(renderable) =
            world.get_component_by_id_as::<RenderableComponent>(renderable_component)
        else {
            return;
        };
        let Some(handle) = renderable.get_handle() else {
            return;
        };

        let stencil_ref = self
            .stencil_clip_for_renderable_component(world, renderable_component)
            .map(|clip_component| Self::stencil_ref_for_clip(world, clip_component))
            .unwrap_or_else(|| Self::stencil_ref_for_renderable(world, renderable_component));
        let _ = visuals.update_stencil_ref(handle, stencil_ref);
    }

    fn stencil_clip_for_renderable_component(
        &self,
        world: &World,
        renderable_component: ComponentId,
    ) -> Option<ComponentId> {
        self.active_stencil_clips
            .iter()
            .copied()
            .find(|&clip_component| {
                world.get_component_record(clip_component).is_some()
                    && Self::find_stencil_clip_renderable_component(world, clip_component)
                        == Some(renderable_component)
            })
    }

    fn sync_stencil_refs_in_subtree(
        &self,
        world: &World,
        visuals: &mut VisualWorld,
        root: ComponentId,
    ) {
        let mut stack = vec![root];
        while let Some(node) = stack.pop() {
            if world
                .get_component_by_id_as::<RenderableComponent>(node)
                .is_some()
            {
                self.sync_renderable_stencil_ref(world, visuals, node);
            }
            for &child in world.children_of(node) {
                stack.push(child);
            }
        }
    }

    fn active_stencil_clips<'a>(
        &'a mut self,
        world: &World,
    ) -> impl Iterator<Item = ComponentId> + 'a {
        self.active_stencil_clips
            .retain(|&component| world.get_component_record(component).is_some());
        self.active_stencil_clips.iter().copied()
    }

    fn stencil_ref_for_renderable(world: &World, renderable_component: ComponentId) -> u8 {
        use crate::engine::ecs::component::StencilClipComponent;

        let mut depth: u8 = 0;
        let mut cursor = Some(renderable_component);
        while let Some(node) = cursor {
            if world
                .get_component_by_id_as::<StencilClipComponent>(node)
                .is_some()
                || Self::is_layout_clip_scope_root(world, node)
            {
                depth = depth.saturating_add(1);
            }
            cursor = world.parent_of(node);
        }
        depth
    }

    fn stencil_ref_for_clip(world: &World, component: ComponentId) -> u8 {
        use crate::engine::ecs::component::StencilClipComponent;

        let mut depth: u8 = 0;
        let mut cursor = if Self::is_layout_owned_stencil_clip(world, component) {
            world
                .parent_of(component)
                .and_then(|scope_root| world.parent_of(scope_root))
        } else {
            world.parent_of(component)
        };

        while let Some(node) = cursor {
            if world
                .get_component_by_id_as::<StencilClipComponent>(node)
                .is_some()
                || Self::is_layout_clip_scope_root(world, node)
            {
                depth = depth.saturating_add(1);
            }
            cursor = world.parent_of(node);
        }

        depth
    }

    fn is_layout_owned_stencil_clip(world: &World, component: ComponentId) -> bool {
        world.component_label(component) == Some(OWNED_LAYOUT_STENCIL_CLIP_LABEL)
            && world
                .get_component_by_id_as::<crate::engine::ecs::component::StencilClipComponent>(
                    component,
                )
                .is_some()
    }

    fn immediate_owned_layout_stencil_clip(
        world: &World,
        scope_root: ComponentId,
    ) -> Option<ComponentId> {
        world
            .children_of(scope_root)
            .iter()
            .copied()
            .find(|&child| Self::is_layout_owned_stencil_clip(world, child))
    }

    fn layout_bg_node(world: &World, scope_root: ComponentId) -> Option<ComponentId> {
        world
            .children_of(scope_root)
            .iter()
            .copied()
            .find(|&child| world.component_label(child) == Some("__bg"))
    }

    fn subtree_first_renderable(world: &World, root: ComponentId) -> Option<ComponentId> {
        let mut stack = vec![root];
        while let Some(node) = stack.pop() {
            if world
                .get_component_by_id_as::<RenderableComponent>(node)
                .is_some()
            {
                return Some(node);
            }
            for &child in world.children_of(node).iter().rev() {
                stack.push(child);
            }
        }
        None
    }

    fn is_layout_clip_scope_root(world: &World, node: ComponentId) -> bool {
        Self::immediate_owned_layout_stencil_clip(world, node).is_some()
    }

    fn stencil_clip_scope_root(world: &World, component: ComponentId) -> Option<ComponentId> {
        if Self::is_layout_owned_stencil_clip(world, component) {
            return world.parent_of(component);
        }
        let parent = world.parent_of(component)?;
        if world.component_label(parent) == Some("__bg") {
            return world.parent_of(parent);
        }
        Some(parent)
    }

    fn find_stencil_clip_renderable_component(
        world: &World,
        component: ComponentId,
    ) -> Option<ComponentId> {
        let mut cursor = world.parent_of(component);
        while let Some(cid) = cursor {
            if world
                .get_component_by_id_as::<RenderableComponent>(cid)
                .is_some()
            {
                return Some(cid);
            }
            cursor = world.parent_of(cid);
        }

        if Self::is_layout_owned_stencil_clip(world, component) {
            let scope_root = world.parent_of(component)?;
            let bg_id = Self::layout_bg_node(world, scope_root)?;
            return Self::subtree_first_renderable(world, bg_id);
        }

        None
    }

    fn find_stencil_clip_renderable_handle(
        world: &World,
        component: ComponentId,
    ) -> Option<crate::engine::graphics::primitives::InstanceHandle> {
        let renderable_component = Self::find_stencil_clip_renderable_component(world, component)?;
        world
            .get_component_by_id_as::<RenderableComponent>(renderable_component)
            .and_then(|r| r.get_handle())
    }
}