use crate::engine::error::{AccessKind, ExecutionError};
use crate::engine::types::{ComponentID, COMPONENT_CAP};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
pub const DEFAULT_SPIN_LIMIT: u32 = 100_000;
const DIRTY_WORDS: usize = COMPONENT_CAP.div_ceil(64);
pub struct BorrowTracker {
pub(super) states: Box<[AtomicUsize; COMPONENT_CAP]>,
pub(crate) dirty: [AtomicU64; DIRTY_WORDS],
spin_limit: u32,
}
impl BorrowTracker {
pub fn new() -> Self {
Self::with_spin_limit(DEFAULT_SPIN_LIMIT)
}
pub fn with_spin_limit(spin_limit: u32) -> Self {
let states: Box<[AtomicUsize; COMPONENT_CAP]> = unsafe {
let layout = std::alloc::Layout::new::<[AtomicUsize; COMPONENT_CAP]>();
let ptr = std::alloc::alloc_zeroed(layout) as *mut [AtomicUsize; COMPONENT_CAP];
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
Box::from_raw(ptr)
};
Self {
states,
dirty: std::array::from_fn(|_| AtomicU64::new(0)),
spin_limit,
}
}
#[inline]
fn mark_dirty(&self, component_id: ComponentID) {
let word = component_id as usize / 64;
let bit = component_id as usize % 64;
self.dirty[word].fetch_or(1u64 << bit, Ordering::Relaxed);
}
#[inline]
pub fn clear(&self) {
for word_idx in 0..DIRTY_WORDS {
let bits = self.dirty[word_idx].swap(0, Ordering::Relaxed);
if bits == 0 {
continue;
}
let base = word_idx * 64;
let mut remaining = bits;
while remaining != 0 {
let bit = remaining.trailing_zeros() as usize;
let component_id = base + bit;
if component_id < COMPONENT_CAP {
self.states[component_id].store(0, Ordering::Release);
}
remaining &= remaining - 1; }
}
}
pub fn acquire_read(&self, component_id: ComponentID) -> Result<(), ExecutionError> {
let state = &self.states[component_id as usize];
let mut spins = 0u32;
loop {
let current = state.load(Ordering::Acquire);
if current == 1 {
spins += 1;
if spins > self.spin_limit {
return Err(ExecutionError::BorrowConflict {
component_id,
held: AccessKind::Write,
requested: AccessKind::Read,
});
}
if spins.is_multiple_of(1024) {
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
continue;
}
let next = if current == 0 { 2 } else { current + 1 };
if state
.compare_exchange_weak(current, next, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
self.mark_dirty(component_id);
return Ok(());
}
spins += 1;
if spins > self.spin_limit {
let held = if state.load(Ordering::Acquire) == 1 {
AccessKind::Write
} else {
AccessKind::Read
};
return Err(ExecutionError::BorrowConflict {
component_id,
held,
requested: AccessKind::Read,
});
}
if spins.is_multiple_of(1024) {
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
}
}
pub fn release_read(&self, component_id: ComponentID) {
let state = &self.states[component_id as usize];
loop {
let current = state.load(Ordering::Acquire);
debug_assert!(
current >= 2,
"release_read called with state {} for component {}; expected >= 2",
current,
component_id
);
let next = if current == 2 { 0 } else { current - 1 };
if state
.compare_exchange_weak(current, next, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
return;
}
std::hint::spin_loop();
}
}
pub fn acquire_write(&self, component_id: ComponentID) -> Result<(), ExecutionError> {
let state = &self.states[component_id as usize];
let mut spins = 0u32;
loop {
let current = state.load(Ordering::Acquire);
if current != 0 {
spins += 1;
if spins > self.spin_limit {
let held = if current == 1 {
AccessKind::Write
} else {
AccessKind::Read
};
return Err(ExecutionError::BorrowConflict {
component_id,
held,
requested: AccessKind::Write,
});
}
if spins.is_multiple_of(1024) {
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
continue;
}
if state
.compare_exchange_weak(0, 1, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
self.mark_dirty(component_id);
return Ok(());
}
spins += 1;
if spins > self.spin_limit {
let held = if state.load(Ordering::Acquire) == 1 {
AccessKind::Write
} else {
AccessKind::Read
};
return Err(ExecutionError::BorrowConflict {
component_id,
held,
requested: AccessKind::Write,
});
}
if spins.is_multiple_of(1024) {
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
}
}
pub fn release_write(&self, component_id: ComponentID) {
let state = &self.states[component_id as usize];
let previous = state.swap(0, Ordering::AcqRel);
debug_assert!(
previous == 1,
"release_write called with state {} for component {}; expected 1",
previous,
component_id
);
}
}