// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 //! Source block types for the offload engine. //! //! Blocks can be provided to the offload engine in three forms: //! - External: BlockId + SequenceHash, block is held elsewhere //! - Strong: RAII ImmutableBlock reference //! - Weak: WeakBlock that may have been evicted use std::marker::PhantomData; use crate::{BlockId, SequenceHash}; use kvbm_logical::blocks::{BlockMetadata, ImmutableBlock, WeakBlock}; /// External block reference with sequence hash for registration. /// /// Used when the caller holds the actual block but wants to provide /// the offload engine with enough information to register blocks /// in the destination tier after transfer. #[derive(Debug, Clone, Copy)] pub struct ExternalBlock { /// The block ID in the source tier pub block_id: BlockId, /// The sequence hash for registration in destination tier pub sequence_hash: SequenceHash, _marker: PhantomData, } impl ExternalBlock { /// Create a new external block reference. pub fn new(block_id: BlockId, sequence_hash: SequenceHash) -> Self { Self { block_id, sequence_hash, _marker: PhantomData, } } } /// Represents a single block source for offloading. /// /// The source type determines how the block is resolved: /// - `External`: Caller holds the block, we have ID + SequenceHash for registration /// - `Strong`: We hold a strong RAII reference /// - `Weak`: We hold a weak reference that may need upgrading #[derive(Debug)] pub enum SourceBlock { /// External block reference with ID and sequence hash External(ExternalBlock), /// Strong RAII reference to an immutable block Strong(ImmutableBlock), /// Weak reference that may have been evicted Weak(WeakBlock), } impl SourceBlock { /// Get the block ID if available without upgrading. /// /// For External and Strong variants, returns Some(id). /// For Weak variant, returns None (would need upgrade to get ID). pub fn block_id(&self) -> Option { match self { SourceBlock::External(ext) => Some(ext.block_id), SourceBlock::Strong(block) => Some(block.block_id()), SourceBlock::Weak(_) => None, } } /// Get the sequence hash if available without upgrading. /// /// All variants can provide sequence_hash without upgrading: /// - External: stored in ExternalBlock /// - Strong: from ImmutableBlock /// - Weak: WeakBlock stores sequence_hash directly pub fn sequence_hash(&self) -> Option { match self { SourceBlock::External(ext) => Some(ext.sequence_hash), SourceBlock::Strong(block) => Some(block.sequence_hash()), SourceBlock::Weak(weak) => Some(weak.sequence_hash()), } } /// Check if this is an external block reference. pub fn is_external(&self) -> bool { matches!(self, SourceBlock::External(_)) } /// Check if this is a strong reference. pub fn is_strong(&self) -> bool { matches!(self, SourceBlock::Strong(_)) } /// Check if this is a weak reference. pub fn is_weak(&self) -> bool { matches!(self, SourceBlock::Weak(_)) } } impl From> for SourceBlock { fn from(ext: ExternalBlock) -> Self { SourceBlock::External(ext) } } impl From> for SourceBlock { fn from(block: ImmutableBlock) -> Self { SourceBlock::Strong(block) } } impl From> for SourceBlock { fn from(block: WeakBlock) -> Self { SourceBlock::Weak(block) } } /// Collection of source blocks for batch operations. /// /// Blocks are grouped by their source type for efficient processing. /// All blocks in a SourceBlocks must be of the same type. #[derive(Debug)] pub enum SourceBlocks { /// External block references with IDs and sequence hashes External(Vec>), /// Strong RAII references Strong(Vec>), /// Weak references that may need upgrading Weak(Vec>), } impl SourceBlocks { /// Create an empty collection of external blocks. pub fn empty_external() -> Self { SourceBlocks::External(Vec::new()) } /// Create an empty collection of strong blocks. pub fn empty_strong() -> Self { SourceBlocks::Strong(Vec::new()) } /// Create an empty collection of weak blocks. pub fn empty_weak() -> Self { SourceBlocks::Weak(Vec::new()) } /// Get the number of blocks in this collection. pub fn len(&self) -> usize { match self { SourceBlocks::External(blocks) => blocks.len(), SourceBlocks::Strong(blocks) => blocks.len(), SourceBlocks::Weak(blocks) => blocks.len(), } } /// Check if the collection is empty. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Get external blocks, or None for other types. pub fn external_blocks(&self) -> Option<&[ExternalBlock]> { match self { SourceBlocks::External(blocks) => Some(blocks), _ => None, } } /// Get strong blocks, or None for other types. pub fn strong_blocks(&self) -> Option<&[ImmutableBlock]> { match self { SourceBlocks::Strong(blocks) => Some(blocks), _ => None, } } /// Get weak blocks, or None for other types. pub fn weak_blocks(&self) -> Option<&[WeakBlock]> { match self { SourceBlocks::Weak(blocks) => Some(blocks), _ => None, } } /// Check if this is external blocks. pub fn is_external(&self) -> bool { matches!(self, SourceBlocks::External(_)) } /// Check if this is strong blocks. pub fn is_strong(&self) -> bool { matches!(self, SourceBlocks::Strong(_)) } /// Check if this is weak blocks. pub fn is_weak(&self) -> bool { matches!(self, SourceBlocks::Weak(_)) } } impl From>> for SourceBlocks { fn from(blocks: Vec>) -> Self { SourceBlocks::External(blocks) } } impl From>> for SourceBlocks { fn from(blocks: Vec>) -> Self { SourceBlocks::Strong(blocks) } } impl From>> for SourceBlocks { fn from(blocks: Vec>) -> Self { SourceBlocks::Weak(blocks) } } // Allow converting a single SourceBlock into SourceBlocks impl From> for SourceBlocks { fn from(block: SourceBlock) -> Self { match block { SourceBlock::External(ext) => SourceBlocks::External(vec![ext]), SourceBlock::Strong(b) => SourceBlocks::Strong(vec![b]), SourceBlock::Weak(b) => SourceBlocks::Weak(vec![b]), } } } #[cfg(test)] mod tests { use super::*; use kvbm_common::tokens::TokenBlockSequence; use kvbm_logical::KvbmSequenceHashProvider; /// Create a test sequence hash at a given position. fn test_seq_hash(position: usize) -> SequenceHash { let tokens_per_block = 4; let total_tokens = (position + 1) * tokens_per_block; let tokens: Vec = (0..total_tokens as u32).collect(); let seq = TokenBlockSequence::from_slice(&tokens, tokens_per_block as u32, Some(1337)); seq.blocks()[position].kvbm_sequence_hash() } #[test] fn test_external_block_creation() { let hash = test_seq_hash(0); let ext: ExternalBlock<()> = ExternalBlock::new(42, hash); assert_eq!(ext.block_id, 42); assert_eq!(ext.sequence_hash, hash); } #[test] fn test_source_blocks_from_vec_external() { let ext1: ExternalBlock<()> = ExternalBlock::new(1, test_seq_hash(0)); let ext2: ExternalBlock<()> = ExternalBlock::new(2, test_seq_hash(1)); let ext3: ExternalBlock<()> = ExternalBlock::new(3, test_seq_hash(2)); let blocks: SourceBlocks<()> = vec![ext1, ext2, ext3].into(); assert!(blocks.is_external()); assert_eq!(blocks.len(), 3); let external = blocks.external_blocks().unwrap(); assert_eq!(external[0].block_id, 1); assert_eq!(external[1].block_id, 2); assert_eq!(external[2].block_id, 3); } #[test] fn test_source_blocks_empty() { let blocks: SourceBlocks<()> = SourceBlocks::empty_external(); assert!(blocks.is_empty()); assert!(blocks.is_external()); } #[test] fn test_source_block_accessors() { let hash = test_seq_hash(5); let ext: ExternalBlock<()> = ExternalBlock::new(42, hash); let block: SourceBlock<()> = ext.into(); assert_eq!(block.block_id(), Some(42)); assert_eq!(block.sequence_hash(), Some(hash)); assert!(block.is_external()); } }