use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug)]
pub(crate) struct ShareCharge {
path_bytes: u32,
base_count: usize,
allocated: AtomicBool,
}
impl ShareCharge {
pub(crate) fn full() -> Self {
Self {
path_bytes: 0,
base_count: 0,
allocated: AtomicBool::new(false),
}
}
pub(crate) fn derive(&self, parent_count: usize, count: usize, path_bytes: usize) -> Self {
if !self.allocated.load(Ordering::Relaxed) {
return Self::full();
}
let base_count = if self.path_bytes == 0 {
parent_count
} else {
self.base_count
};
if count >= base_count.saturating_mul(2) {
return Self::full();
}
Self {
path_bytes: u32::try_from(path_bytes.max(1)).unwrap_or(u32::MAX),
base_count,
allocated: AtomicBool::new(false),
}
}
pub(crate) fn size_extra(&self, full_bytes: usize) -> usize {
if self.path_bytes == 0 {
full_bytes
} else {
self.path_bytes as usize
}
}
pub(crate) fn on_alloc(&self, full_bytes: usize) -> usize {
self.allocated.store(true, Ordering::Relaxed);
self.size_extra(full_bytes)
}
}
impl Clone for ShareCharge {
fn clone(&self) -> Self {
Self::full()
}
}
const ARC_HEADER: usize = 16;
pub(crate) fn vector_path_bytes<T>(len: usize) -> usize {
const NODE: usize = ARC_HEADER + 32 + 32 * 8;
let mut levels = 1;
let mut capacity = 32usize;
while capacity < len {
capacity = capacity.saturating_mul(32);
levels += 1;
}
levels * NODE + ARC_HEADER + std::mem::size_of::<T>()
}
pub(crate) fn hamt_path_bytes<E>(len: usize) -> usize {
const NODE: usize = ARC_HEADER + 40 + 64 * 8;
let mut levels = 1;
let mut capacity = 64usize;
while capacity < len {
capacity = capacity.saturating_mul(64);
levels += 1;
}
(levels + 1) * NODE + ARC_HEADER + std::mem::size_of::<E>()
}
pub(crate) fn rb_path_bytes<E>(len: usize) -> usize {
const NODE: usize = ARC_HEADER + 32;
let log2 = (usize::BITS - len.saturating_add(1).leading_zeros()) as usize;
(2 * log2 + 3) * NODE + ARC_HEADER + std::mem::size_of::<E>()
}
#[cfg(test)]
#[must_use]
pub(crate) fn alloc_scope() -> impl Sized {
#[cfg(feature = "no-gc")]
{
cljrs_gc::alloc_ctx::ScratchGuard::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unallocated_parent_yields_full_charge() {
let parent = ShareCharge::full();
let child = parent.derive(100, 101, 500);
assert_eq!(child.size_extra(10_000), 10_000);
}
#[test]
fn allocated_parent_yields_path_charge() {
let parent = ShareCharge::full();
assert_eq!(parent.on_alloc(10_000), 10_000);
let child = parent.derive(100, 101, 500);
assert_eq!(child.on_alloc(10_100), 500);
let grandchild = child.derive(101, 102, 500);
assert_eq!(grandchild.size_extra(10_200), 500);
}
#[test]
fn doubling_the_base_count_recharges_in_full() {
let mut v = ShareCharge::full();
v.on_alloc(0);
let mut full_at = Vec::new();
for count in 11..=100 {
let next = v.derive(count - 1, count, 1);
if next.on_alloc(count * 1000) == count * 1000 {
full_at.push(count);
}
v = next;
}
assert_eq!(full_at, vec![20, 40, 80]);
}
#[test]
fn clone_is_charged_in_full() {
let parent = ShareCharge::full();
parent.on_alloc(0);
let child = parent.derive(100, 101, 500);
assert_eq!(child.clone().size_extra(10_100), 10_100);
}
}