use std::{
any::{type_name, Any},
fmt,
marker::PhantomData,
ops::Deref,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};
use crate::{
asset::{NotHotReloaded, Storable},
utils::RwLock,
SharedString,
};
#[cfg(feature = "hot-reloading")]
use crate::utils::RwLockReadGuard;
pub(crate) struct StaticInner<T> {
id: SharedString,
value: T,
}
impl<T> StaticInner<T> {
#[inline]
fn new(value: T, id: SharedString) -> Self {
Self { id, value }
}
}
#[allow(dead_code)]
pub(crate) struct DynamicInner<T> {
id: SharedString,
value: RwLock<T>,
reload_global: AtomicBool,
reload: AtomicReloadId,
}
#[cfg(feature = "hot-reloading")]
impl<T> DynamicInner<T> {
#[inline]
fn new(value: T, id: SharedString) -> Self {
Self {
id,
value: RwLock::new(value),
reload: AtomicReloadId::new(),
reload_global: AtomicBool::new(false),
}
}
pub fn write(&self, value: T) {
let mut data = self.value.write();
*data = value;
self.reload.increment();
self.reload_global.store(true, Ordering::Release);
}
}
pub struct CacheEntry(Box<dyn Any + Send + Sync>);
impl CacheEntry {
#[inline]
pub fn new<T: Storable>(asset: T, id: SharedString, _mutable: impl FnOnce() -> bool) -> Self {
#[cfg(not(feature = "hot-reloading"))]
let inner = Box::new(StaticInner::new(asset, id));
#[cfg(feature = "hot-reloading")]
let inner: Box<dyn Any + Send + Sync> = if T::HOT_RELOADED && _mutable() {
Box::new(DynamicInner::new(asset, id))
} else {
Box::new(StaticInner::new(asset, id))
};
CacheEntry(inner)
}
#[inline]
pub(crate) fn inner(&self) -> UntypedHandle {
UntypedHandle(self.0.as_ref())
}
#[inline]
pub fn into_inner<T: Storable>(self) -> (T, SharedString) {
#[allow(unused_mut)]
let mut this = self.0;
#[cfg(feature = "hot-reloading")]
if T::HOT_RELOADED {
match this.downcast::<DynamicInner<T>>() {
Ok(inner) => return (inner.value.into_inner(), inner.id),
Err(t) => this = t,
}
}
if let Ok(inner) = this.downcast::<StaticInner<T>>() {
return (inner.value, inner.id);
}
wrong_handle_type()
}
}
impl fmt::Debug for CacheEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CacheEntry").finish()
}
}
#[derive(Clone, Copy)]
pub(crate) struct UntypedHandle<'a>(&'a (dyn Any + Send + Sync));
impl<'a> UntypedHandle<'a> {
#[inline]
pub(crate) unsafe fn extend_lifetime<'b>(self) -> UntypedHandle<'b> {
let inner = &*(self.0 as *const (dyn Any + Send + Sync));
UntypedHandle(inner)
}
#[inline]
pub fn try_downcast<T: Storable>(self) -> Option<Handle<'a, T>> {
Handle::new(self)
}
#[inline]
pub fn downcast<T: Storable>(self) -> Handle<'a, T> {
match self.try_downcast() {
Some(h) => h,
None => wrong_handle_type(),
}
}
}
impl fmt::Debug for UntypedHandle<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UntypedHandle").finish()
}
}
enum HandleInner<'a, T> {
Static(&'a StaticInner<T>),
#[cfg(feature = "hot-reloading")]
Dynamic(&'a DynamicInner<T>),
}
impl<T> Clone for HandleInner<'_, T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for HandleInner<'_, T> {}
pub struct Handle<'a, T> {
inner: HandleInner<'a, T>,
}
impl<'a, T> Handle<'a, T> {
fn new(inner: UntypedHandle<'a>) -> Option<Self>
where
T: Storable,
{
#[allow(clippy::never_loop)]
let inner = loop {
#[cfg(feature = "hot-reloading")]
if T::HOT_RELOADED {
if let Some(inner) = inner.0.downcast_ref::<DynamicInner<T>>() {
break HandleInner::Dynamic(inner);
}
}
if let Some(inner) = inner.0.downcast_ref::<StaticInner<T>>() {
break HandleInner::Static(inner);
}
return None;
};
Some(Handle { inner })
}
#[inline]
fn either<U>(
&self,
on_static: impl FnOnce(&'a StaticInner<T>) -> U,
_on_dynamic: impl FnOnce(&'a DynamicInner<T>) -> U,
) -> U {
match self.inner {
HandleInner::Static(s) => on_static(s),
#[cfg(feature = "hot-reloading")]
HandleInner::Dynamic(s) => _on_dynamic(s),
}
}
#[inline]
#[cfg(feature = "hot-reloading")]
pub(crate) fn as_dynamic(&self) -> &DynamicInner<T> {
self.either(|_| wrong_handle_type(), |this| this)
}
#[inline]
pub fn read(&self) -> AssetGuard<'a, T> {
let inner = match self.inner {
HandleInner::Static(this) => GuardInner::Ref(&this.value),
#[cfg(feature = "hot-reloading")]
HandleInner::Dynamic(this) => GuardInner::Guard(this.value.read()),
};
AssetGuard { inner }
}
#[inline]
pub fn id(&self) -> &'a SharedString {
self.either(|s| &s.id, |d| &d.id)
}
#[inline]
pub fn reload_watcher(&self) -> ReloadWatcher<'a> {
ReloadWatcher::new(self.either(|_| None, |d| Some(&d.reload)))
}
#[inline]
pub fn last_reload_id(&self) -> ReloadId {
self.either(|_| ReloadId(0), |this| this.reload.load())
}
#[inline]
pub fn reloaded_global(&self) -> bool {
self.either(
|_| false,
|this| this.reload_global.swap(false, Ordering::Acquire),
)
}
#[inline]
pub fn same_handle(&self, other: &Self) -> bool {
self.either(
|s1| other.either(|s2| std::ptr::eq(s1, s2), |_| false),
|d1| other.either(|_| false, |d2| std::ptr::eq(d1, d2)),
)
}
}
impl<'a, A> Handle<'a, A>
where
A: NotHotReloaded,
{
#[inline]
#[allow(clippy::let_unit_value)]
pub fn get(&self) -> &'a A {
let _ = A::_CHECK_NOT_HOT_RELOADED;
self.either(
|this| &this.value,
|_| {
panic!(
"`{}` implements `NotHotReloaded` but do not disable hot-reloading",
type_name::<A>()
)
},
)
}
}
impl<A> Handle<'_, A>
where
A: Copy,
{
#[inline]
pub fn copied(self) -> A {
*self.read()
}
}
impl<A> Handle<'_, A>
where
A: Clone,
{
#[inline]
pub fn cloned(self) -> A {
self.read().clone()
}
}
impl<A> Clone for Handle<'_, A> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<A> Copy for Handle<'_, A> {}
impl<T, U> PartialEq<Handle<'_, U>> for Handle<'_, T>
where
T: PartialEq<U>,
{
#[inline]
fn eq(&self, other: &Handle<U>) -> bool {
self.read().eq(&other.read())
}
}
impl<A> Eq for Handle<'_, A> where A: Eq {}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<A> serde::Serialize for Handle<'_, A>
where
A: serde::Serialize,
{
#[inline]
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.read().serialize(s)
}
}
impl<A> fmt::Debug for Handle<'_, A>
where
A: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Handle")
.field("value", &*self.read())
.finish()
}
}
pub enum GuardInner<'a, T> {
Ref(&'a T),
#[cfg(feature = "hot-reloading")]
Guard(RwLockReadGuard<'a, T>),
}
pub struct AssetGuard<'a, A> {
inner: GuardInner<'a, A>,
}
impl<A> Deref for AssetGuard<'_, A> {
type Target = A;
#[inline]
fn deref(&self) -> &A {
match &self.inner {
GuardInner::Ref(r) => r,
#[cfg(feature = "hot-reloading")]
GuardInner::Guard(g) => g,
}
}
}
impl<A, U> AsRef<U> for AssetGuard<'_, A>
where
A: AsRef<U>,
{
#[inline]
fn as_ref(&self) -> &U {
(**self).as_ref()
}
}
impl<A> fmt::Display for AssetGuard<'_, A>
where
A: fmt::Display,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl<A> fmt::Debug for AssetGuard<'_, A>
where
A: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(feature = "hot-reloading")]
#[derive(Debug, Clone, Copy)]
struct ReloadWatcherInner<'a> {
reload_id: &'a AtomicReloadId,
last_reload_id: ReloadId,
}
#[cfg(feature = "hot-reloading")]
impl<'a> ReloadWatcherInner<'a> {
#[inline]
fn new(reload_id: &'a AtomicReloadId) -> Self {
Self {
reload_id,
last_reload_id: reload_id.load(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ReloadWatcher<'a> {
#[cfg(feature = "hot-reloading")]
inner: Option<ReloadWatcherInner<'a>>,
_private: PhantomData<&'a ()>,
}
impl<'a> ReloadWatcher<'a> {
#[inline]
fn new(_reload_id: Option<&'a AtomicReloadId>) -> Self {
#[cfg(feature = "hot-reloading")]
let inner = _reload_id.map(ReloadWatcherInner::new);
Self {
#[cfg(feature = "hot-reloading")]
inner,
_private: PhantomData,
}
}
#[inline]
pub fn reloaded(&mut self) -> bool {
#[cfg(feature = "hot-reloading")]
if let Some(inner) = &mut self.inner {
let new_id = inner.reload_id.load();
return inner.last_reload_id.update(new_id);
}
false
}
#[inline]
pub fn last_reload_id(&self) -> ReloadId {
#[cfg(feature = "hot-reloading")]
if let Some(inner) = &self.inner {
return inner.reload_id.load();
}
ReloadId(0)
}
}
impl Default for ReloadWatcher<'_> {
#[inline]
fn default() -> Self {
Self::new(None)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ReloadId(usize);
impl ReloadId {
#[inline]
pub fn update(&mut self, new: ReloadId) -> bool {
let newer = new > *self;
if newer {
*self = new;
}
newer
}
}
#[derive(Debug)]
pub struct AtomicReloadId(AtomicUsize);
impl AtomicReloadId {
#[inline]
pub const fn new() -> Self {
Self(AtomicUsize::new(0))
}
#[inline]
pub fn with_value(value: ReloadId) -> Self {
Self(AtomicUsize::new(value.0))
}
#[inline]
pub fn update(&self, new: ReloadId) -> bool {
new > self.fetch_max(new)
}
#[inline]
pub fn load(&self) -> ReloadId {
ReloadId(self.0.load(Ordering::Acquire))
}
#[inline]
pub fn store(&self, new: ReloadId) {
self.0.store(new.0, Ordering::Release)
}
#[inline]
#[cfg(feature = "hot-reloading")]
fn increment(&self) {
self.0.fetch_add(1, Ordering::Release);
}
#[inline]
pub fn swap(&self, new: ReloadId) -> ReloadId {
ReloadId(self.0.swap(new.0, Ordering::AcqRel))
}
#[inline]
pub fn fetch_max(&self, new: ReloadId) -> ReloadId {
ReloadId(self.0.fetch_max(new.0, Ordering::AcqRel))
}
}
#[cold]
#[track_caller]
fn wrong_handle_type() -> ! {
panic!("wrong handle type");
}