use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use crossbeam_queue::SegQueue;
static NEXT_HANDLE_ID: AtomicUsize = AtomicUsize::new(1);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HandleId(pub usize);
impl Default for HandleId {
fn default() -> Self {
Self::new()
}
}
impl HandleId {
pub fn new() -> Self {
HandleId(NEXT_HANDLE_ID.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Clone)]
pub struct AssetDropQueue(Arc<SegQueue<usize>>);
impl AssetDropQueue {
fn new() -> Self {
Self(Arc::new(SegQueue::new()))
}
fn push(&self, id: usize) {
self.0.push(id);
}
fn pop(&self) -> Option<usize> {
self.0.pop()
}
}
impl std::fmt::Debug for AssetDropQueue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("AssetDropQueue(..)")
}
}
pub struct HandleIdTracker {
pub id: usize,
drop_queue: AssetDropQueue,
}
impl Drop for HandleIdTracker {
fn drop(&mut self) {
self.drop_queue.push(self.id);
}
}
pub struct Handle<T> {
pub id: HandleId,
pub tracker: Option<Arc<HandleIdTracker>>,
_marker: PhantomData<T>,
}
impl<T> Default for Handle<T> {
fn default() -> Self {
Self::weak(HandleId::new())
}
}
impl<T> Handle<T> {
pub fn new(id: HandleId, drop_queue: AssetDropQueue) -> Self {
Self {
id,
tracker: Some(Arc::new(HandleIdTracker {
id: id.0,
drop_queue,
})),
_marker: PhantomData,
}
}
pub fn weak(id: HandleId) -> Self {
Self {
id,
tracker: None,
_marker: PhantomData,
}
}
pub fn is_weak(&self) -> bool {
self.tracker.is_none()
}
pub fn make_strong(&mut self, drop_queue: AssetDropQueue) {
if self.tracker.is_none() {
self.tracker = Some(Arc::new(HandleIdTracker {
id: self.id.0,
drop_queue,
}));
}
}
}
impl<T> Clone for Handle<T> {
fn clone(&self) -> Self {
Self {
id: self.id,
tracker: self.tracker.clone(),
_marker: PhantomData,
}
}
}
impl<T> PartialEq for Handle<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T> Eq for Handle<T> {}
impl<T> Hash for Handle<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl<T: 'static + Send + Sync> crate::component::Component for Handle<T> {}
impl<T> std::fmt::Debug for Handle<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_weak() {
write!(f, "WeakHandle({:?})", self.id)
} else {
write!(f, "StrongHandle({:?})", self.id)
}
}
}
pub struct Assets<T> {
pub data: HashMap<HandleId, T>,
drop_queue: AssetDropQueue,
}
impl<T> Default for Assets<T> {
fn default() -> Self {
Self {
data: HashMap::new(),
drop_queue: AssetDropQueue::new(),
}
}
}
impl<T> Assets<T> {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, asset: T) -> Handle<T> {
let id = HandleId::new();
self.data.insert(id, asset);
Handle::new(id, self.drop_queue.clone())
}
pub fn get(&self, handle: &Handle<T>) -> Option<&T> {
self.data.get(&handle.id)
}
pub fn get_mut(&mut self, handle: &Handle<T>) -> Option<&mut T> {
self.data.get_mut(&handle.id)
}
pub fn insert(&mut self, handle: &Handle<T>, asset: T) {
self.data.insert(handle.id, asset);
}
pub fn remove(&mut self, handle: &Handle<T>) -> Option<T> {
self.data.remove(&handle.id)
}
pub fn process_drops(&mut self) {
while let Some(dropped_id) = self.drop_queue.pop() {
self.data.remove(&HandleId(dropped_id));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn asset_drop_queue_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AssetDropQueue>();
assert_send_sync::<HandleIdTracker>();
assert_send_sync::<Handle<u32>>();
}
#[test]
fn strong_handle_drop_collects_on_next_process_drops() {
let mut assets = Assets::<u32>::new();
let handle = assets.add(7);
let id = handle.id;
assets.process_drops();
assert_eq!(assets.data.get(&id), Some(&7), "a live handle collects nothing");
drop(handle);
assert_eq!(
assets.data.get(&id),
Some(&7),
"dropping only reports the id — the entry survives until it is processed"
);
assets.process_drops();
assert!(!assets.data.contains_key(&id), "the reported id is erased");
}
#[test]
fn clones_share_one_tracker() {
let mut assets = Assets::<u32>::new();
let handle = assets.add(1);
let id = handle.id;
let clone = handle.clone();
drop(handle);
assets.process_drops();
assert_eq!(assets.data.get(&id), Some(&1), "one clone is still alive");
drop(clone);
assets.process_drops();
assert!(!assets.data.contains_key(&id));
}
#[test]
fn weak_handle_never_collects() {
let mut assets = Assets::<u32>::new();
let handle = Handle::<u32>::weak(HandleId::new());
assets.insert(&handle, 3);
let id = handle.id;
drop(handle);
assets.process_drops();
assert_eq!(assets.data.get(&id), Some(&3));
}
#[test]
fn make_strong_binds_to_this_maps_queue() {
let mut assets = Assets::<u32>::new();
let mut handle = Handle::<u32>::weak(HandleId::new());
assets.insert(&handle, 5);
let id = handle.id;
handle.make_strong(assets.drop_queue.clone());
assert!(!handle.is_weak());
drop(handle);
assets.process_drops();
assert!(
!assets.data.contains_key(&id),
"the upgraded handle reported to this map"
);
}
}