saddle-core 0.2.0-rc.19

Shared contracts for Saddle components
Documentation
use core::alloc::{Layout, LayoutError};

const FREE: u64 = 0;
const REQUEST: u64 = 1;
const FRAMEWORK_QUEUED: u64 = 2;

const SCHEMA_IDENTITY: [u8; 32] = [
    0xfb, 0xed, 0x6a, 0xcd, 0x3e, 0xd0, 0xb3, 0x2a, 0x17, 0xe2, 0x9b, 0x7c, 0xb6, 0x85, 0x89, 0xb7,
    0x71, 0x19, 0x07, 0xdd, 0x91, 0x3d, 0xd5, 0x7f, 0x07, 0x22, 0xf3, 0x1a, 0x76, 0x1d, 0x1b, 0xf0,
];

/// Stable physical state for one Admission-owned Observability block.
///
/// Fields are private and only ownership transitions are exposed. This type
/// is not a raw-facts carrier or a second layout construction surface.
///
/// ```compile_fail
/// use saddle_core::ObservabilityBlockState;
/// let _ = ObservabilityBlockState {
///     generation: 0,
///     owner: 0,
///     request_slot: 0,
///     request_generation: 0,
///     audit_generation: 0,
/// };
/// ```
#[doc(hidden)]
#[repr(C, align(8))]
#[derive(Clone, Copy)]
pub struct ObservabilityBlockState {
    generation: u64,
    owner: u64,
    request_slot: u64,
    request_generation: u64,
    audit_generation: u64,
}

impl core::fmt::Debug for ObservabilityBlockState {
    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        formatter.write_str("ObservabilityBlockState(<opaque>)")
    }
}

const _: () = assert!(core::mem::size_of::<ObservabilityBlockState>() == 40);
const _: () = assert!(core::mem::align_of::<ObservabilityBlockState>() == 8);

impl ObservabilityBlockState {
    fn free() -> Self {
        Self {
            generation: 0,
            owner: FREE,
            request_slot: 0,
            request_generation: 0,
            audit_generation: 0,
        }
    }

    pub fn is_free(&self) -> bool {
        self.owner == FREE
    }

    pub fn is_request_owned(&self) -> bool {
        self.owner == REQUEST
    }

    pub fn is_framework_queued(&self) -> bool {
        self.owner == FRAMEWORK_QUEUED
    }

    pub fn next_request_generation(&self) -> Option<u64> {
        self.is_free()
            .then(|| self.generation.checked_add(1))
            .flatten()
    }

    pub fn acquire_request(
        &mut self,
        slot: usize,
        generation: u64,
        audit_generation: u64,
    ) -> Option<u64> {
        if !self.is_free() {
            return None;
        }
        let block_generation = self.generation.checked_add(1)?;
        let request_slot = u64::try_from(slot).ok()?;
        self.generation = block_generation;
        self.owner = REQUEST;
        self.request_slot = request_slot;
        self.request_generation = generation;
        self.audit_generation = audit_generation;
        Some(block_generation)
    }

    pub fn request_matches(
        &self,
        block_generation: u64,
        slot: usize,
        generation: u64,
        audit_generation: u64,
    ) -> bool {
        self.generation == block_generation
            && self.owner == REQUEST
            && u64::try_from(slot) == Ok(self.request_slot)
            && self.request_generation == generation
            && self.audit_generation == audit_generation
    }

    pub fn commit_request_to_framework(
        &mut self,
        block_generation: u64,
        slot: usize,
        generation: u64,
        audit_generation: u64,
    ) -> bool {
        if !self.request_matches(block_generation, slot, generation, audit_generation) {
            return false;
        }
        self.clear_owner(FRAMEWORK_QUEUED);
        true
    }

    pub fn release_framework(&mut self, block_generation: u64) -> bool {
        if self.generation != block_generation || !self.is_framework_queued() {
            return false;
        }
        self.clear_owner(FREE);
        true
    }

    pub fn rollback_request(
        &mut self,
        block_generation: u64,
        slot: usize,
        generation: u64,
        audit_generation: u64,
    ) -> bool {
        if !self.request_matches(block_generation, slot, generation, audit_generation) {
            return false;
        }
        self.clear_owner(FREE);
        true
    }

    fn clear_owner(&mut self, owner: u64) {
        self.owner = owner;
        self.request_slot = 0;
        self.request_generation = 0;
        self.audit_generation = 0;
    }
}

/// The single array-layout calculation for the stable block state.
#[doc(hidden)]
pub fn observability_block_state_array_layout(blocks: usize) -> Result<Layout, LayoutError> {
    Layout::array::<ObservabilityBlockState>(blocks)
}

/// The single initialization path for a fixed block-state array.
#[doc(hidden)]
pub fn allocate_observability_block_states(
    blocks: usize,
) -> Result<Box<[ObservabilityBlockState]>, std::collections::TryReserveError> {
    let mut states = Vec::new();
    states.try_reserve_exact(blocks)?;
    states.resize(blocks, ObservabilityBlockState::free());
    Ok(states.into_boxed_slice())
}

#[doc(hidden)]
pub const fn observability_block_state_schema_identity() -> [u8; 32] {
    SCHEMA_IDENTITY
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn layout_and_transitions_are_stable() {
        assert_eq!(core::mem::size_of::<ObservabilityBlockState>(), 40);
        assert_eq!(core::mem::align_of::<ObservabilityBlockState>(), 8);
        assert_eq!(
            observability_block_state_array_layout(4).unwrap().size(),
            160
        );
        assert_ne!(observability_block_state_schema_identity(), [0; 32]);

        let mut states = allocate_observability_block_states(1).unwrap();
        let state = &mut states[0];
        assert_eq!(state.acquire_request(3, 7, 11), Some(1));
        assert!(state.request_matches(1, 3, 7, 11));
        assert!(state.commit_request_to_framework(1, 3, 7, 11));
        assert!(state.release_framework(1));
        assert!(state.is_free());
        assert_eq!(state.acquire_request(4, 8, 12), Some(2));
        assert!(state.rollback_request(2, 4, 8, 12));
    }
}