use crate::error::StorageError;
use crate::storage::mmap::records::{PropertyEntry, PROPERTY_ENTRY_HEADER_SIZE};
use crate::value::Value;
use hashbrown::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
pub struct ArenaAllocator {
current_offset: AtomicU64,
arena_start: u64,
arena_end: u64,
}
impl ArenaAllocator {
pub fn new(arena_start: u64, arena_end: u64, current_offset: u64) -> Self {
Self {
current_offset: AtomicU64::new(current_offset),
arena_start,
arena_end,
}
}
#[inline]
pub fn current_offset(&self) -> u64 {
self.current_offset.load(Ordering::SeqCst)
}
#[inline]
pub fn arena_start(&self) -> u64 {
self.arena_start
}
#[inline]
pub fn arena_end(&self) -> u64 {
self.arena_end
}
#[inline]
pub fn entry_size(value_len: usize) -> usize {
PROPERTY_ENTRY_HEADER_SIZE + value_len
}
pub fn has_space(&self, size: usize) -> bool {
let current = self.current_offset.load(Ordering::SeqCst);
current + size as u64 <= self.arena_end
}
pub fn allocate(&self, size: usize) -> Result<u64, StorageError> {
let size = size as u64;
loop {
let current = self.current_offset.load(Ordering::SeqCst);
let new_offset = current + size;
if new_offset > self.arena_end {
return Err(StorageError::OutOfSpace);
}
match self.current_offset.compare_exchange(
current,
new_offset,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => return Ok(current), Err(_) => continue, }
}
}
pub fn set_arena_end(&mut self, new_end: u64) {
self.arena_end = new_end;
}
}
pub fn serialize_properties<F>(
properties: &HashMap<String, Value>,
mut intern_key: F,
) -> (Vec<u8>, Vec<usize>)
where
F: FnMut(&str) -> u32,
{
let mut data = Vec::new();
let mut next_offsets = Vec::new();
let entries: Vec<_> = properties.iter().collect();
for (key, value) in entries.iter() {
let mut value_data = Vec::new();
value.serialize(&mut value_data);
let key_id = intern_key(key);
let next = u64::MAX;
let entry = PropertyEntry::new(key_id, value.discriminant(), value_data.len() as u32, next);
let next_field_offset = data.len() + 9;
next_offsets.push(next_field_offset);
data.extend_from_slice(&entry.to_bytes());
data.extend_from_slice(&value_data);
}
(data, next_offsets)
}
pub fn link_property_entries(
data: &mut [u8],
next_offsets: &[usize],
base_offset: u64,
entry_sizes: &[usize],
) {
if next_offsets.is_empty() {
return;
}
let mut entry_start = base_offset;
let mut entry_offsets = Vec::with_capacity(next_offsets.len());
for size in entry_sizes {
entry_offsets.push(entry_start);
entry_start += *size as u64;
}
for i in 0..next_offsets.len() {
let next_value = if i + 1 < entry_offsets.len() {
entry_offsets[i + 1]
} else {
u64::MAX };
let offset = next_offsets[i];
data[offset..offset + 8].copy_from_slice(&next_value.to_le_bytes());
}
}
pub fn calculate_entry_sizes(properties: &HashMap<String, Value>) -> Vec<usize> {
properties
.values()
.map(|value| {
let mut buf = Vec::new();
value.serialize(&mut buf);
PROPERTY_ENTRY_HEADER_SIZE + buf.len()
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_allocator_new() {
let allocator = ArenaAllocator::new(1000, 2000, 1000);
assert_eq!(allocator.arena_start(), 1000);
assert_eq!(allocator.arena_end(), 2000);
assert_eq!(allocator.current_offset(), 1000);
}
#[test]
fn test_arena_allocator_has_space() {
let allocator = ArenaAllocator::new(1000, 2000, 1000);
assert!(allocator.has_space(1000));
assert!(allocator.has_space(999));
assert!(!allocator.has_space(1001));
}
#[test]
fn test_arena_allocator_allocate() {
let allocator = ArenaAllocator::new(1000, 2000, 1000);
let offset1 = allocator.allocate(100).unwrap();
assert_eq!(offset1, 1000);
assert_eq!(allocator.current_offset(), 1100);
let offset2 = allocator.allocate(200).unwrap();
assert_eq!(offset2, 1100);
assert_eq!(allocator.current_offset(), 1300);
}
#[test]
fn test_arena_allocator_out_of_space() {
let allocator = ArenaAllocator::new(1000, 1100, 1000);
let offset1 = allocator.allocate(50).unwrap();
assert_eq!(offset1, 1000);
let result = allocator.allocate(100);
assert!(matches!(result, Err(StorageError::OutOfSpace)));
assert_eq!(allocator.current_offset(), 1050);
}
#[test]
fn test_serialize_properties_empty() {
let props: HashMap<String, Value> = HashMap::new();
let (data, next_offsets) = serialize_properties(&props, |_| 0);
assert!(data.is_empty());
assert!(next_offsets.is_empty());
}
#[test]
fn test_serialize_properties_single() {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Alice".to_string()));
let (data, next_offsets) = serialize_properties(&props, |_| 42);
assert_eq!(next_offsets.len(), 1);
assert_eq!(data.len(), 17 + 10);
let key_id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
assert_eq!(key_id, 42);
}
#[test]
fn test_serialize_properties_multiple() {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Bob".to_string()));
props.insert("age".to_string(), Value::Int(30));
let (data, next_offsets) = serialize_properties(&props, |key| match key {
"name" => 1,
"age" => 2,
_ => 0,
});
assert_eq!(next_offsets.len(), 2);
assert!(!data.is_empty());
}
#[test]
fn test_link_property_entries_single() {
let mut data = vec![0u8; 30]; let next_offsets = vec![9]; let entry_sizes = vec![30];
link_property_entries(&mut data, &next_offsets, 1000, &entry_sizes);
let next = u64::from_le_bytes([
data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16],
]);
assert_eq!(next, u64::MAX);
}
#[test]
fn test_link_property_entries_multiple() {
let mut data = vec![0u8; 60]; let next_offsets = vec![9, 39]; let entry_sizes = vec![30, 30];
link_property_entries(&mut data, &next_offsets, 1000, &entry_sizes);
let next1 = u64::from_le_bytes([
data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16],
]);
assert_eq!(next1, 1030);
let next2 = u64::from_le_bytes([
data[39], data[40], data[41], data[42], data[43], data[44], data[45], data[46],
]);
assert_eq!(next2, u64::MAX);
}
#[test]
fn test_link_property_entries_empty() {
let mut data = vec![];
let next_offsets = vec![];
let entry_sizes = vec![];
link_property_entries(&mut data, &next_offsets, 1000, &entry_sizes);
}
#[test]
fn test_calculate_entry_sizes() {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String("Alice".to_string()));
props.insert("age".to_string(), Value::Int(30));
let sizes = calculate_entry_sizes(&props);
assert_eq!(sizes.len(), 2);
for size in &sizes {
assert!(*size > PROPERTY_ENTRY_HEADER_SIZE);
}
}
#[test]
fn test_entry_size() {
assert_eq!(ArenaAllocator::entry_size(0), PROPERTY_ENTRY_HEADER_SIZE);
assert_eq!(
ArenaAllocator::entry_size(10),
PROPERTY_ENTRY_HEADER_SIZE + 10
);
assert_eq!(
ArenaAllocator::entry_size(100),
PROPERTY_ENTRY_HEADER_SIZE + 100
);
}
#[test]
fn test_serialize_all_value_types() {
let mut props = HashMap::new();
props.insert("null".to_string(), Value::Null);
props.insert("bool_true".to_string(), Value::Bool(true));
props.insert("bool_false".to_string(), Value::Bool(false));
props.insert("int".to_string(), Value::Int(42));
props.insert("float".to_string(), Value::Float(3.14));
props.insert("string".to_string(), Value::String("hello".to_string()));
let (data, next_offsets) = serialize_properties(&props, |_| 0);
assert_eq!(next_offsets.len(), 6);
assert!(!data.is_empty());
}
}