use std::cell::RefCell;
use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RetentionPolicy {
Discard,
PromoteToPool,
PromoteToHeap,
PromoteToScratch(&'static str),
}
impl Default for RetentionPolicy {
fn default() -> Self {
Self::Discard
}
}
impl RetentionPolicy {
pub fn promotes(&self) -> bool {
!matches!(self, Self::Discard)
}
pub fn destination(&self) -> &'static str {
match self {
Self::Discard => "discard",
Self::PromoteToPool => "pool",
Self::PromoteToHeap => "heap",
Self::PromoteToScratch(name) => name,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Importance {
Ephemeral,
Reusable,
Persistent,
Scratch(&'static str),
}
impl Importance {
pub fn to_policy(self) -> RetentionPolicy {
match self {
Self::Ephemeral => RetentionPolicy::Discard,
Self::Reusable => RetentionPolicy::PromoteToPool,
Self::Persistent => RetentionPolicy::PromoteToHeap,
Self::Scratch(name) => RetentionPolicy::PromoteToScratch(name),
}
}
}
impl From<Importance> for RetentionPolicy {
fn from(importance: Importance) -> Self {
importance.to_policy()
}
}
impl Default for Importance {
fn default() -> Self {
Self::Ephemeral
}
}
#[derive(Debug, Clone)]
pub struct RetainedMeta {
pub policy: RetentionPolicy,
pub size: usize,
pub tag: Option<&'static str>,
pub type_name: &'static str,
}
pub(crate) struct RetainedAllocation {
pub ptr: *mut u8,
pub meta: RetainedMeta,
pub promote_fn: Box<dyn FnOnce(*mut u8) -> PromotedAllocation>,
}
unsafe impl Send for RetainedAllocation {}
#[derive(Debug)]
pub enum PromotedAllocation {
Pool {
ptr: *mut u8,
size: usize,
tag: Option<&'static str>,
type_name: &'static str,
},
Heap {
ptr: *mut u8,
size: usize,
tag: Option<&'static str>,
type_name: &'static str,
},
Scratch {
pool_name: &'static str,
ptr: *mut u8,
size: usize,
tag: Option<&'static str>,
type_name: &'static str,
},
Failed {
reason: PromotionFailure,
meta: RetainedMeta,
},
}
impl PromotedAllocation {
pub fn is_success(&self) -> bool {
!matches!(self, Self::Failed { .. })
}
pub fn size(&self) -> usize {
match self {
Self::Pool { size, .. } => *size,
Self::Heap { size, .. } => *size,
Self::Scratch { size, .. } => *size,
Self::Failed { meta, .. } => meta.size,
}
}
pub fn tag(&self) -> Option<&'static str> {
match self {
Self::Pool { tag, .. } => *tag,
Self::Heap { tag, .. } => *tag,
Self::Scratch { tag, .. } => *tag,
Self::Failed { meta, .. } => meta.tag,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Self::Pool { type_name, .. } => type_name,
Self::Heap { type_name, .. } => type_name,
Self::Scratch { type_name, .. } => type_name,
Self::Failed { meta, .. } => meta.type_name,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PromotionFailure {
BudgetExceeded,
ScratchPoolNotFound,
ScratchPoolFull,
TooLarge,
InternalError,
}
impl std::fmt::Display for PromotionFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BudgetExceeded => write!(f, "budget exceeded"),
Self::ScratchPoolNotFound => write!(f, "scratch pool not found"),
Self::ScratchPoolFull => write!(f, "scratch pool full"),
Self::TooLarge => write!(f, "allocation too large"),
Self::InternalError => write!(f, "internal error"),
}
}
}
pub struct FrameRetained<'a, T> {
ptr: *mut T,
id: usize,
_marker: PhantomData<&'a mut T>,
}
impl<'a, T> FrameRetained<'a, T> {
pub(crate) fn new(ptr: *mut T, id: usize) -> Self {
Self {
ptr,
id,
_marker: PhantomData,
}
}
pub fn get(&self) -> &T {
unsafe { &*self.ptr }
}
pub fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
pub fn as_ptr(&self) -> *const T {
self.ptr
}
pub fn as_mut_ptr(&mut self) -> *mut T {
self.ptr
}
pub fn id(&self) -> usize {
self.id
}
}
impl<'a, T> std::ops::Deref for FrameRetained<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
impl<'a, T> std::ops::DerefMut for FrameRetained<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.get_mut()
}
}
pub(crate) struct RetentionRegistry {
allocations: Vec<RetainedAllocation>,
next_id: usize,
}
impl RetentionRegistry {
pub fn new() -> Self {
Self {
allocations: Vec::with_capacity(64),
next_id: 0,
}
}
pub fn register(&mut self, alloc: RetainedAllocation) -> usize {
let id = self.next_id;
self.next_id += 1;
self.allocations.push(alloc);
id
}
pub fn take_all(&mut self) -> Vec<RetainedAllocation> {
self.next_id = 0;
std::mem::take(&mut self.allocations)
}
pub fn clear(&mut self) {
self.allocations.clear();
self.next_id = 0;
}
pub fn len(&self) -> usize {
self.allocations.len()
}
pub fn is_empty(&self) -> bool {
self.allocations.is_empty()
}
}
impl Default for RetentionRegistry {
fn default() -> Self {
Self::new()
}
}
thread_local! {
pub(crate) static RETENTION_REGISTRY: RefCell<RetentionRegistry> =
RefCell::new(RetentionRegistry::new());
}
pub(crate) fn register_retained(alloc: RetainedAllocation) -> usize {
RETENTION_REGISTRY.with(|r| r.borrow_mut().register(alloc))
}
pub(crate) fn take_retained() -> Vec<RetainedAllocation> {
RETENTION_REGISTRY.with(|r| r.borrow_mut().take_all())
}
pub(crate) fn clear_retained() {
RETENTION_REGISTRY.with(|r| r.borrow_mut().clear());
}
pub(crate) fn retained_count() -> usize {
RETENTION_REGISTRY.with(|r| r.borrow().len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_retention_policy_default() {
assert_eq!(RetentionPolicy::default(), RetentionPolicy::Discard);
}
#[test]
fn test_importance_to_policy() {
assert_eq!(Importance::Ephemeral.to_policy(), RetentionPolicy::Discard);
assert_eq!(Importance::Reusable.to_policy(), RetentionPolicy::PromoteToPool);
assert_eq!(Importance::Persistent.to_policy(), RetentionPolicy::PromoteToHeap);
}
#[test]
fn test_policy_promotes() {
assert!(!RetentionPolicy::Discard.promotes());
assert!(RetentionPolicy::PromoteToPool.promotes());
assert!(RetentionPolicy::PromoteToHeap.promotes());
assert!(RetentionPolicy::PromoteToScratch("test").promotes());
}
#[test]
fn test_retention_registry() {
clear_retained();
let meta = RetainedMeta {
policy: RetentionPolicy::PromoteToPool,
size: 64,
tag: None,
type_name: "TestType",
};
let alloc = RetainedAllocation {
ptr: std::ptr::null_mut(),
meta,
promote_fn: Box::new(|_| PromotedAllocation::Pool {
ptr: std::ptr::null_mut(),
size: 64,
tag: None,
type_name: "TestType",
}),
};
let id = register_retained(alloc);
assert_eq!(id, 0);
assert_eq!(retained_count(), 1);
let taken = take_retained();
assert_eq!(taken.len(), 1);
assert_eq!(retained_count(), 0);
}
}