use std::any::{Any, type_name};
use std::cell::RefCell;
use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
use slotmap::SlotMap;
use crate::app::{Handle, HandleId};
struct HandleEntry {
type_name: &'static str,
state: Box<dyn Any>,
retained: u32,
disposed: bool,
}
pub struct RetainedHandle<T> {
value: T,
retained: RetainedHandleId,
}
pub struct RetainedHandleId {
id: HandleId,
dropped: Option<DroppedRetainedHandles>,
}
#[derive(Clone, Default)]
struct DroppedRetainedHandles(Rc<RefCell<Vec<HandleId>>>);
impl DroppedRetainedHandles {
fn push(&self, id: HandleId) {
self.0.borrow_mut().push(id);
}
fn take(&self) -> Vec<HandleId> {
std::mem::take(&mut *self.0.borrow_mut())
}
}
impl<T> RetainedHandle<T> {
pub fn id(&self) -> HandleId {
self.retained.id()
}
}
impl<T: Copy> RetainedHandle<T> {
pub fn get(&self) -> T {
self.value
}
}
impl<T> Deref for RetainedHandle<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl<T: fmt::Debug> fmt::Debug for RetainedHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RetainedHandle({:?})", self.value)
}
}
impl RetainedHandleId {
pub fn id(&self) -> HandleId {
self.id
}
}
impl Drop for RetainedHandleId {
fn drop(&mut self) {
if let Some(dropped) = self.dropped.take() {
dropped.push(self.id);
}
}
}
impl fmt::Debug for RetainedHandleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RetainedHandleId({:?})", self.id)
}
}
pub(crate) struct HandleMap {
entries: SlotMap<HandleId, HandleEntry>,
dropped_retained_handles: DroppedRetainedHandles,
}
impl HandleMap {
pub(crate) fn new() -> HandleMap {
HandleMap {
entries: SlotMap::with_key(),
dropped_retained_handles: DroppedRetainedHandles::default(),
}
}
pub(crate) fn create<T: 'static>(&mut self, state: T) -> Handle<T> {
Handle::new(self.entries.insert(HandleEntry {
type_name: type_name::<T>(),
state: Box::new(state),
retained: 1,
disposed: false,
}))
}
pub(crate) fn destroy(&mut self, id: HandleId) {
let entry = self
.entries
.get_mut(id)
.unwrap_or_else(|| panic!("stale handle: {id:?} was already destroyed"));
assert!(
!entry.disposed,
"stale handle: {id:?} was already destroyed"
);
entry.disposed = true;
self.release_retained(id);
}
pub(crate) fn contains(&self, id: HandleId) -> bool {
self.entries.contains_key(id)
}
pub(crate) fn retain<T: Copy + Into<HandleId>>(&mut self, value: T) -> RetainedHandle<T> {
RetainedHandle {
value,
retained: self.retain_id(value.into()),
}
}
pub(crate) fn retain_id(&mut self, id: HandleId) -> RetainedHandleId {
self.resolve_mut(id).retained += 1;
RetainedHandleId {
id,
dropped: Some(self.dropped_retained_handles.clone()),
}
}
pub(crate) fn release<T>(&mut self, handle: RetainedHandle<T>) {
self.release_id(handle.retained);
}
pub(crate) fn release_id(&mut self, mut handle: RetainedHandleId) {
handle.dropped = None;
self.release_retained(handle.id);
}
fn release_retained(&mut self, id: HandleId) {
let entry = self.resolve_mut(id);
debug_assert!(entry.retained > 0, "release without a retain: {id:?}");
entry.retained -= 1;
if entry.retained == 0 {
self.entries.remove(id);
}
}
pub(crate) fn release_dropped_retained_handles(&mut self) {
loop {
let dropped = self.dropped_retained_handles.take();
if dropped.is_empty() {
return;
}
for id in dropped {
self.release_retained(id);
}
}
}
pub(crate) fn is_disposed(&self, id: HandleId) -> bool {
self.entries.get(id).is_some_and(|entry| entry.disposed)
}
pub(crate) fn handle<T: 'static>(&self, id: HandleId) -> Option<Handle<T>> {
if !self.entries.get(id)?.state.is::<T>() {
return None;
}
Some(Handle::new(id))
}
pub(crate) fn get<T: 'static>(&self, handle: Handle<T>) -> &T {
let entry = self.resolve(handle.id());
entry.state.downcast_ref::<T>().unwrap_or_else(|| {
panic!(
"handle {:?} holds `{}`, not `{}`",
handle.id(),
entry.type_name,
type_name::<T>()
)
})
}
pub(crate) fn get_mut<T: 'static>(&mut self, handle: Handle<T>) -> &mut T {
let id = handle.id();
let entry = self.resolve_mut(id);
downcast_entry_mut(entry, id)
}
pub(crate) fn get_disjoint_mut<A: 'static, B: 'static>(
&mut self,
a: Handle<A>,
b: Handle<B>,
) -> (&mut A, &mut B) {
let [entry_a, entry_b] = self
.entries
.get_disjoint_mut([a.id(), b.id()])
.unwrap_or_else(|| {
panic!(
"handles {:?} and {:?} are not two live entries",
a.id(),
b.id()
)
});
(
downcast_entry_mut(entry_a, a.id()),
downcast_entry_mut(entry_b, b.id()),
)
}
fn resolve(&self, id: HandleId) -> &HandleEntry {
self.entries
.get(id)
.unwrap_or_else(|| panic!("stale handle: {id:?} was destroyed"))
}
fn resolve_mut(&mut self, id: HandleId) -> &mut HandleEntry {
self.entries
.get_mut(id)
.unwrap_or_else(|| panic!("stale handle: {id:?} was destroyed"))
}
}
fn downcast_entry_mut<T: 'static>(entry: &mut HandleEntry, id: HandleId) -> &mut T {
let type_name_in_entry = entry.type_name;
entry.state.downcast_mut::<T>().unwrap_or_else(|| {
panic!(
"handle {id:?} holds `{type_name_in_entry}`, not `{}`",
type_name::<T>()
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_retained_object_outlives_destroy_until_released() {
let mut handles = HandleMap::new();
let handle = handles.create(7u32);
let retained = handles.retain(handle);
handles.destroy(handle.id());
assert!(handles.contains(handle.id()), "kept while retained");
assert!(handles.is_disposed(handle.id()));
assert_eq!(*handles.get(handle), 7, "still readable");
handles.release(retained);
assert!(
!handles.contains(handle.id()),
"removed with the last release"
);
}
#[test]
fn a_dropped_retained_handle_is_released_by_the_sweep() {
let mut handles = HandleMap::new();
let handle = handles.create(7u32);
let retained = handles.retain(handle);
handles.destroy(handle.id());
drop(retained);
assert!(
handles.contains(handle.id()),
"the drop alone does not remove it"
);
handles.release_dropped_retained_handles();
assert!(!handles.contains(handle.id()), "the sweep did");
}
#[test]
fn an_unretained_object_is_removed_by_destroy() {
let mut handles = HandleMap::new();
let handle = handles.create(1u8);
let retained = handles.retain(handle);
handles.release(retained);
assert!(!handles.is_disposed(handle.id()));
handles.destroy(handle.id());
assert!(!handles.contains(handle.id()));
}
#[test]
#[should_panic(expected = "already destroyed")]
fn destroying_twice_panics() {
let mut handles = HandleMap::new();
let handle = handles.create(1u8);
handles.destroy(handle.id());
handles.destroy(handle.id());
}
#[test]
fn retain_id_keeps_the_entry_without_a_typed_handle() {
let mut handles = HandleMap::new();
let handle = handles.create(7u32);
let retained = handles.retain_id(handle.id());
handles.destroy(handle.id());
assert!(handles.contains(handle.id()));
handles.release_id(retained);
assert!(!handles.contains(handle.id()));
}
}