cranpose-core 0.0.60

Core runtime for a Jetpack Compose inspired UI framework in Rust
Documentation
#[cfg(any(test, debug_assertions))]
use super::SlotInvariantError;
use super::{ActiveGroupId, AnchorRegistry, GroupRecord, SlotTable};
use crate::{collections::map::HashMap, AnchorId, ScopeId};
use std::mem;

#[derive(Default)]
pub(crate) struct ScopeIndex {
    by_scope: HashMap<ScopeId, AnchorId>,
}

impl ScopeIndex {
    pub(super) fn new() -> Self {
        Self::default()
    }

    pub(super) fn clear(&mut self) {
        self.by_scope.clear();
    }

    pub(super) fn shrink_to_fit(&mut self) {
        self.by_scope.shrink_to_fit();
    }

    pub(super) fn len(&self) -> usize {
        self.by_scope.len()
    }

    pub(super) fn capacity(&self) -> usize {
        self.by_scope.capacity()
    }

    pub(super) fn heap_bytes(&self) -> usize {
        self.by_scope.capacity() * mem::size_of::<(ScopeId, AnchorId)>()
    }

    pub(super) fn anchor(&self, scope_id: ScopeId) -> Option<AnchorId> {
        self.by_scope.get(&scope_id).copied()
    }

    pub(super) fn entries(&self) -> impl Iterator<Item = (ScopeId, AnchorId)> + '_ {
        self.by_scope
            .iter()
            .map(|(&scope_id, &anchor)| (scope_id, anchor))
    }

    pub(super) fn active_group_index(
        &self,
        scope_id: ScopeId,
        anchors: &AnchorRegistry,
        groups: &[GroupRecord],
    ) -> Option<usize> {
        let anchor = self.anchor(scope_id)?;
        let group_index = anchors.active_index(anchor)?;
        let group = &groups[group_index];
        (group.scope_id == Some(scope_id)).then_some(group_index)
    }

    pub(super) fn assign(&mut self, group: &mut GroupRecord, scope_id: ScopeId) {
        if let Some(existing_anchor) = self.anchor(scope_id) {
            assert_eq!(
                existing_anchor, group.anchor,
                "scope id must resolve to a single active group"
            );
        }

        if let Some(previous) = group.scope_id.replace(scope_id) {
            self.by_scope.remove(&previous);
        }
        self.by_scope.insert(scope_id, group.anchor);
    }

    pub(super) fn remove_groups(&mut self, groups: &[GroupRecord]) {
        for group in groups {
            if let Some(scope_id) = group.scope_id {
                self.by_scope.remove(&scope_id);
            }
        }
    }

    pub(super) fn restore_entries(
        &mut self,
        entries: impl IntoIterator<Item = (ScopeId, AnchorId)>,
    ) {
        for (scope_id, group_anchor) in entries {
            if let Some(existing_anchor) = self.anchor(scope_id) {
                assert_eq!(
                    existing_anchor, group_anchor,
                    "restored scope id must resolve to a single active group"
                );
            }
            self.by_scope.insert(scope_id, group_anchor);
        }
    }

    pub(super) fn assert_restore_entries_available(&self, entries: &[(ScopeId, AnchorId)]) {
        for &(scope_id, group_anchor) in entries {
            if let Some(existing_anchor) = self.anchor(scope_id) {
                assert_eq!(
                    existing_anchor, group_anchor,
                    "restored scope id must resolve to a single active group"
                );
            }
        }
    }

    #[cfg(any(test, debug_assertions))]
    pub(super) fn validate_count(&self, expected: usize) -> Result<(), SlotInvariantError> {
        if self.len() == expected {
            return Ok(());
        }

        Err(SlotInvariantError::ScopeIndexCountMismatch {
            expected,
            actual: self.len(),
        })
    }

    #[cfg(any(test, debug_assertions))]
    pub(super) fn validate_group(&self, group: &GroupRecord) -> Result<(), SlotInvariantError> {
        let Some(scope_id) = group.scope_id else {
            return Ok(());
        };

        let actual = self.anchor(scope_id);
        if actual == Some(group.anchor) {
            return Ok(());
        }

        Err(SlotInvariantError::ScopeIndexMismatch {
            scope_id,
            expected: group.anchor,
            actual,
        })
    }

    #[cfg(test)]
    pub(super) fn insert_for_test(&mut self, scope_id: ScopeId, anchor: AnchorId) {
        self.by_scope.insert(scope_id, anchor);
    }
}

impl SlotTable {
    #[cfg(any(test, debug_assertions))]
    pub(crate) fn scope_index_anchor(&self, scope_id: ScopeId) -> Option<AnchorId> {
        self.scope_index.anchor(scope_id)
    }

    pub(super) fn active_group_for_scope(&self, scope_id: ScopeId) -> Option<ActiveGroupId> {
        let group_index =
            self.scope_index
                .active_group_index(scope_id, &self.anchors, &self.groups)?;
        Some(self.active_group_id_at_index(group_index))
    }

    pub(super) fn assign_active_group_scope(&mut self, group: ActiveGroupId, scope_id: ScopeId) {
        let group_index = self.checked_active_group_index(group);
        self.scope_index
            .assign(&mut self.groups[group_index], scope_id);
    }
}