cranpose-core 0.1.9

Core runtime for a Jetpack Compose inspired UI framework in Rust
Documentation
use super::super::ValueSlotId;
use super::SlotTable;
use crate::slot::PayloadAnchor;
use crate::AnchorId;
use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ValueSlotError {
    ForeignTable {
        table_storage_id: usize,
        slot_storage_id: usize,
    },
    InactiveAnchor {
        anchor: PayloadAnchor,
    },
    InactiveOwner {
        anchor: PayloadAnchor,
        owner: AnchorId,
    },
    PayloadIndexOutOfRange {
        anchor: PayloadAnchor,
        owner: AnchorId,
        group_index: usize,
        payload_index: usize,
        payload_len: usize,
        payload_count: usize,
    },
    OwnerMismatch {
        anchor: PayloadAnchor,
        expected: AnchorId,
        actual: AnchorId,
    },
    AnchorMismatch {
        expected: PayloadAnchor,
        actual: PayloadAnchor,
    },
    TypeMismatch {
        anchor: PayloadAnchor,
        expected: &'static str,
        actual: &'static str,
    },
}

impl fmt::Display for ValueSlotError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ValueSlotError::ForeignTable {
                table_storage_id,
                slot_storage_id,
            } => write!(
                f,
                "value slot belongs to storage {slot_storage_id}, not slot table storage {table_storage_id}"
            ),
            ValueSlotError::InactiveAnchor { anchor } => {
                write!(f, "value slot anchor {anchor:?} is not active")
            }
            ValueSlotError::InactiveOwner { anchor, owner } => write!(
                f,
                "value slot anchor {anchor:?} points at inactive owner {owner:?}"
            ),
            ValueSlotError::PayloadIndexOutOfRange {
                anchor,
                owner,
                group_index,
                payload_index,
                payload_len,
                payload_count,
            } => write!(
                f,
                "value slot anchor {anchor:?} points at payload index {payload_index} for owner {owner:?} in group {group_index}, but the group has {payload_len} payloads and the table has {payload_count} payload records"
            ),
            ValueSlotError::OwnerMismatch {
                anchor,
                expected,
                actual,
            } => write!(
                f,
                "value slot anchor {anchor:?} points at owner {expected:?}, but the payload record belongs to {actual:?}"
            ),
            ValueSlotError::AnchorMismatch { expected, actual } => write!(
                f,
                "value slot anchor mismatch: expected {expected:?}, found {actual:?}"
            ),
            ValueSlotError::TypeMismatch {
                anchor,
                expected,
                actual,
            } => write!(
                f,
                "value slot {anchor:?} has type {actual}, not {expected}"
            ),
        }
    }
}

impl std::error::Error for ValueSlotError {}

#[derive(Clone, Copy)]
struct CheckedValueSlot {
    #[cfg(test)]
    group_index: usize,
    #[cfg(test)]
    payload_index: usize,
    absolute_payload_index: usize,
}

impl SlotTable {
    fn checked_value_slot(&self, slot: ValueSlotId) -> Result<CheckedValueSlot, ValueSlotError> {
        if slot.storage_id() != self.storage_id() {
            return Err(ValueSlotError::ForeignTable {
                table_storage_id: self.storage_id(),
                slot_storage_id: slot.storage_id(),
            });
        }
        let (owner, payload_index) = self.payload_anchors.active_location(slot.anchor()).ok_or(
            ValueSlotError::InactiveAnchor {
                anchor: slot.anchor(),
            },
        )?;
        let group_index = self
            .active_group_index(owner)
            .ok_or(ValueSlotError::InactiveOwner {
                anchor: slot.anchor(),
                owner,
            })?;
        let group = &self.groups[group_index];
        let payload_start = group.payload_start as usize;
        let payload_len = group.payload_len as usize;
        let absolute_payload_index = payload_start.saturating_add(payload_index);
        let record = if payload_index < payload_len {
            self.payloads.get(absolute_payload_index)
        } else {
            None
        };
        let Some(record) = record else {
            return Err(ValueSlotError::PayloadIndexOutOfRange {
                anchor: slot.anchor(),
                owner,
                group_index,
                payload_index,
                payload_len,
                payload_count: self.payloads.len(),
            });
        };
        if record.owner != owner {
            return Err(ValueSlotError::OwnerMismatch {
                anchor: slot.anchor(),
                expected: owner,
                actual: record.owner,
            });
        }
        if record.anchor != slot.anchor() {
            return Err(ValueSlotError::AnchorMismatch {
                expected: slot.anchor(),
                actual: record.anchor,
            });
        }
        Ok(CheckedValueSlot {
            #[cfg(test)]
            group_index,
            #[cfg(test)]
            payload_index,
            absolute_payload_index,
        })
    }

    pub(crate) fn try_read_value<T: 'static>(
        &self,
        slot: ValueSlotId,
    ) -> Result<&T, ValueSlotError> {
        let checked = self.checked_value_slot(slot)?;
        let record = &self.payloads[checked.absolute_payload_index];
        record
            .value
            .downcast_ref::<T>()
            .ok_or(ValueSlotError::TypeMismatch {
                anchor: slot.anchor(),
                expected: std::any::type_name::<T>(),
                actual: record.type_name,
            })
    }

    pub(crate) fn read_value<T: 'static>(&self, slot: ValueSlotId) -> &T {
        self.try_read_value(slot)
            .unwrap_or_else(|error| panic!("{error}"))
    }

    pub(crate) fn try_read_value_mut<T: 'static>(
        &mut self,
        slot: ValueSlotId,
    ) -> Result<&mut T, ValueSlotError> {
        let checked = self.checked_value_slot(slot)?;
        let record = &mut self.payloads[checked.absolute_payload_index];
        record
            .value
            .downcast_mut::<T>()
            .ok_or(ValueSlotError::TypeMismatch {
                anchor: slot.anchor(),
                expected: std::any::type_name::<T>(),
                actual: record.type_name,
            })
    }

    pub(crate) fn read_value_mut<T: 'static>(&mut self, slot: ValueSlotId) -> &mut T {
        self.try_read_value_mut(slot)
            .unwrap_or_else(|error| panic!("{error}"))
    }

    #[cfg(test)]
    pub(crate) fn try_write_value<T: 'static>(
        &mut self,
        slot: ValueSlotId,
        value: T,
    ) -> Result<(), ValueSlotError> {
        let checked = self.checked_value_slot(slot)?;
        let kind = self.payloads[checked.absolute_payload_index].kind;
        drop(self.replace_payload_value(checked.group_index, checked.payload_index, kind, value));
        Ok(())
    }

    #[cfg(test)]
    pub(crate) fn write_value<T: 'static>(&mut self, slot: ValueSlotId, value: T) {
        self.try_write_value(slot, value)
            .unwrap_or_else(|error| panic!("{error}"));
    }
}