use std::any::TypeId;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use astrelis_core::alloc::sparse_set::IndexSlot;
use crate::Asset;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HandleId {
pub(crate) slot: IndexSlot,
pub(crate) type_id: TypeId,
}
impl HandleId {
pub fn new(slot: IndexSlot, type_id: TypeId) -> Self {
Self { slot, type_id }
}
pub fn index(&self) -> u32 {
self.slot.index()
}
pub fn generation(&self) -> u32 {
self.slot.generation()
}
pub fn type_id(&self) -> TypeId {
self.type_id
}
}
pub struct Handle<T: Asset> {
pub(crate) id: HandleId,
pub(crate) _marker: PhantomData<T>,
}
impl<T: Asset> std::fmt::Debug for Handle<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Handle")
.field("type", &T::type_name())
.field("index", &self.id.index())
.field("generation", &self.id.generation())
.finish()
}
}
impl<T: Asset> Handle<T> {
pub(crate) fn new(id: HandleId) -> Self {
debug_assert_eq!(id.type_id, TypeId::of::<T>());
Self {
id,
_marker: PhantomData,
}
}
pub fn id(&self) -> HandleId {
self.id
}
pub fn untyped(self) -> UntypedHandle {
UntypedHandle { id: self.id }
}
pub fn type_name(&self) -> &'static str {
T::type_name()
}
}
impl<T: Asset> Clone for Handle<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Asset> Copy for Handle<T> {}
impl<T: Asset> PartialEq for Handle<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T: Asset> Eq for Handle<T> {}
impl<T: Asset> Hash for Handle<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UntypedHandle {
pub(crate) id: HandleId,
}
impl UntypedHandle {
pub fn id(&self) -> HandleId {
self.id
}
pub fn type_id(&self) -> TypeId {
self.id.type_id
}
#[cfg(test)]
#[allow(dead_code)] pub(crate) fn test_handle(index: u32, generation: u32) -> Self {
Self {
id: HandleId::new(IndexSlot::new(index, generation), TypeId::of::<()>()),
}
}
pub fn typed<T: Asset>(self) -> Option<Handle<T>> {
if self.id.type_id == TypeId::of::<T>() {
Some(Handle::new(self.id))
} else {
None
}
}
pub unsafe fn typed_unchecked<T: Asset>(self) -> Handle<T> {
Handle::new(self.id)
}
}
impl<T: Asset> From<Handle<T>> for UntypedHandle {
fn from(handle: Handle<T>) -> Self {
handle.untyped()
}
}
pub struct StrongHandle<T: Asset> {
pub(crate) handle: Handle<T>,
pub(crate) refcount: Arc<AtomicU32>,
}
impl<T: Asset> StrongHandle<T> {
#[cfg(test)]
pub(crate) fn new(handle: Handle<T>, refcount: Arc<AtomicU32>) -> Self {
refcount.fetch_add(1, Ordering::Acquire);
Self { handle, refcount }
}
pub fn handle(&self) -> Handle<T> {
self.handle
}
pub fn id(&self) -> HandleId {
self.handle.id
}
pub fn downgrade(&self) -> WeakHandle<T> {
WeakHandle {
handle: self.handle,
refcount: Arc::downgrade(&self.refcount),
}
}
pub fn ref_count(&self) -> u32 {
self.refcount.load(Ordering::Acquire)
}
}
impl<T: Asset> Clone for StrongHandle<T> {
fn clone(&self) -> Self {
self.refcount.fetch_add(1, Ordering::Relaxed);
Self {
handle: self.handle,
refcount: Arc::clone(&self.refcount),
}
}
}
impl<T: Asset> Drop for StrongHandle<T> {
fn drop(&mut self) {
self.refcount.fetch_sub(1, Ordering::Release);
}
}
impl<T: Asset> std::ops::Deref for StrongHandle<T> {
type Target = Handle<T>;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
pub struct WeakHandle<T: Asset> {
pub(crate) handle: Handle<T>,
pub(crate) refcount: std::sync::Weak<AtomicU32>,
}
impl<T: Asset> WeakHandle<T> {
pub fn handle(&self) -> Handle<T> {
self.handle
}
pub fn id(&self) -> HandleId {
self.handle.id
}
pub fn upgrade(&self) -> Option<StrongHandle<T>> {
self.refcount.upgrade().map(|refcount| {
refcount.fetch_add(1, Ordering::Acquire);
StrongHandle {
handle: self.handle,
refcount,
}
})
}
pub fn is_alive(&self) -> bool {
self.refcount.strong_count() > 0
}
}
impl<T: Asset> Clone for WeakHandle<T> {
fn clone(&self) -> Self {
Self {
handle: self.handle,
refcount: self.refcount.clone(),
}
}
}
pub struct TrackedHandle<T: Asset> {
handle: Handle<T>,
seen_version: u32,
}
impl<T: Asset> TrackedHandle<T> {
pub fn new(handle: Handle<T>) -> Self {
Self {
handle,
seen_version: 0,
}
}
pub fn handle(&self) -> Handle<T> {
self.handle
}
pub fn seen_version(&self) -> u32 {
self.seen_version
}
pub fn check_changed(&mut self, current_version: u32) -> bool {
if current_version > self.seen_version {
self.seen_version = current_version;
true
} else {
false
}
}
pub fn reset(&mut self) {
self.seen_version = 0;
}
}
impl<T: Asset> Clone for TrackedHandle<T> {
fn clone(&self) -> Self {
Self {
handle: self.handle,
seen_version: self.seen_version,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use astrelis_core::alloc::sparse_set::IndexSlot;
use std::sync::atomic::AtomicU32;
#[derive(Debug, Clone)]
struct TestAsset;
impl Asset for TestAsset {
fn type_name() -> &'static str {
"TestAsset"
}
}
fn make_test_handle() -> Handle<TestAsset> {
let slot = IndexSlot::new(1, 42); let type_id = TypeId::of::<TestAsset>();
let handle_id = HandleId::new(slot, type_id);
Handle::new(handle_id)
}
#[test]
fn test_strong_handle_new_and_refcount() {
let handle = make_test_handle();
let refcount = Arc::new(AtomicU32::new(0));
let strong = StrongHandle::new(handle, Arc::clone(&refcount));
assert_eq!(strong.ref_count(), 1);
assert_eq!(refcount.load(Ordering::Relaxed), 1);
let strong2 = strong.clone();
assert_eq!(strong2.ref_count(), 2);
assert_eq!(refcount.load(Ordering::Relaxed), 2);
drop(strong);
assert_eq!(strong2.ref_count(), 1);
assert_eq!(refcount.load(Ordering::Relaxed), 1);
drop(strong2);
assert_eq!(refcount.load(Ordering::Relaxed), 0);
}
#[test]
fn test_strong_handle_downgrade() {
let handle = make_test_handle();
let refcount = Arc::new(AtomicU32::new(0));
let strong = StrongHandle::new(handle, Arc::clone(&refcount));
let weak = strong.downgrade();
assert_eq!(weak.handle(), handle);
assert_eq!(strong.ref_count(), 1);
let upgraded = weak.upgrade();
assert!(upgraded.is_some());
if let Some(upgraded_strong) = upgraded {
assert_eq!(upgraded_strong.ref_count(), 2); drop(upgraded_strong);
}
drop(strong);
assert_eq!(refcount.load(Ordering::Relaxed), 0);
let second_upgrade = weak.upgrade();
assert!(second_upgrade.is_some()); }
}