use async_trait::async_trait;
use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use smol::future;
use smol::io::AsyncRead;
use std::any::TypeId;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use tracing::debug;
use crate::resource::manager::{ResourceLoadResult, ResourceManager};
use crate::resource::path::ResourcePath;
pub mod manager;
pub mod path;
pub mod source;
struct SubResourceRef<P: ?Sized + Send + Sync + 'static> {
type_id: Option<TypeId>,
update_fn: Box<dyn (
Fn(ResourcePath, Arc<Resource<P>>) -> Box<dyn Future<Output = Result<(), ResourceLoadError>> + Send + 'static>
) + Send + Sync + 'static>,
}
impl<P: ?Sized + Send + Sync + 'static> SubResourceRef<P> {
fn from_sub_resource<T: ?Sized + Send + Sync + 'static>(
resource: &Arc<Resource<T>>,
loader: Arc<dyn SubResourceLoader<T, P>>,
) -> Self {
let weak = Arc::downgrade(resource);
Self {
type_id: Some(TypeId::of::<T>()),
update_fn: Box::new(move |id, parent| Box::new({
let async_weak = weak.clone();
let async_loader = loader.clone();
async move {
if let Some(resource) = async_weak.upgrade() {
let parent_value = parent.read();
let (resource_mut, drop_checker) = ResourceMut::from_resource(resource.clone());
async_loader.update(resource_mut, &parent_value).await?;
match drop_checker.recv().await {
Ok(_) => { debug!("Drop-checker received an unexpected message"); }
Err(_) => { }
}
resource.update_sub_resources(&id).await;
}
Ok(())
}
}))
}
}
fn from_callback<F, Fut>(callback: F) -> Self
where
F: (Fn(Arc<Resource<P>>) -> Fut) + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let callback = Arc::new(callback);
Self {
type_id: None,
update_fn: Box::new(move |_, parent| {
let callback = callback.clone();
Box::new(async move {
callback(parent).await;
Ok(())
})
})
}
}
async fn update(&self, id: ResourcePath, parent: Arc<Resource<P>>) -> Result<(), ResourceLoadError> {
Box::into_pin((self.update_fn)(id, parent)).await
}
}
impl<T: ?Sized + Send + Sync + 'static> Debug for SubResourceRef<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut ds = f.debug_struct("SubResource");
match &self.type_id {
Some(type_id) => ds
.field("type", &"SubResource")
.field("type_id", &type_id),
None => ds.field("type", &"Callback"),
}.finish_non_exhaustive()
}
}
pub type ResourceLock<'a, T> = MappedRwLockReadGuard<'a, T>;
pub type ResourceMutLock<'a, T> = MappedRwLockWriteGuard<'a, T>;
#[derive(Debug)]
pub struct Resource<T: ?Sized + Send + Sync + 'static> {
value: RwLock<Box<T>>,
sub_resources: smol::lock::Mutex<Vec<SubResourceRef<T>>>,
update_lock: smol::lock::Mutex<()>,
}
impl<T: ?Sized + Send + Sync + 'static> Resource<T> {
#[inline]
pub(in crate::resource) fn new(value: Box<T>) -> Self {
Self {
value: RwLock::new(value),
sub_resources: smol::lock::Mutex::new(Vec::new()),
update_lock: smol::lock::Mutex::new(()),
}
}
pub(in crate::resource) async fn update_sub_resources(self: &Arc<Self>, id: &ResourcePath) {
let sub_resources = self.sub_resources.lock().await;
for sub_resource in &*sub_resources {
let sub_id = id.clone() + ":<sub-resource>";
match sub_resource.update(sub_id.clone(), self.clone()).await {
Ok(_) => {},
Err(error) => debug!(id = %sub_id, %error, "Failed to update sub-resource"),
}
}
}
#[inline]
pub fn read(&self) -> ResourceLock<'_, T> {
RwLockReadGuard::map(self.value.read(), |b| b.as_ref())
}
pub async fn attach_sub_resource<S: ?Sized + Send + Sync + 'static>(
&self,
loader: impl SubResourceLoader<S, T>,
) -> ResourceLoadResult<S> {
let sub_value = loader.load(&self.read()).await?;
let mut sub_resources = self.sub_resources.lock().await;
let sub_resource = Arc::new(Resource::new(sub_value));
sub_resources.push(SubResourceRef::from_sub_resource(&sub_resource, Arc::new(loader)));
Ok(sub_resource)
}
#[inline]
pub fn attach_sub_resource_blocking<S: ?Sized + Send + Sync + 'static>(
&self,
loader: impl SubResourceLoader<S, T>,
) -> ResourceLoadResult<S> {
future::block_on(self.attach_sub_resource(loader))
}
pub async fn attach_update_callback<F, Fut>(&self, callback: F)
where
F: (Fn(Arc<Resource<T>>) -> Fut) + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let mut sub_resources = self.sub_resources.lock().await;
sub_resources.push(SubResourceRef::from_callback(callback));
}
#[inline]
pub fn attach_update_callback_blocking<F, Fut>(&self, callback: F)
where
F: (Fn(Arc<Resource<T>>) -> Fut) + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
future::block_on(self.attach_update_callback(callback))
}
}
pub struct ResourceMut<T: ?Sized + Send + Sync + 'static> {
inner: Arc<Resource<T>>,
#[allow(dead_code)] drop_notice: smol::channel::Sender<()>,
}
impl<T: ?Sized + Send + Sync + 'static> ResourceMut<T> {
#[inline]
pub(in crate::resource) fn from_resource(
resource: Arc<Resource<T>>
) -> (Self, smol::channel::Receiver<()>) {
let (drop_notice, drop_checker) = smol::channel::bounded(1);
(Self {
inner: resource,
drop_notice,
}, drop_checker)
}
#[inline]
pub fn resource(&self) -> &Arc<Resource<T>> {
&self.inner
}
#[inline]
pub fn read(&self) -> ResourceLock<'_, T> {
RwLockReadGuard::map(self.inner.value.read(), |b| b.as_ref())
}
#[inline]
pub fn write(&self) -> ResourceMutLock<'_, T> {
RwLockWriteGuard::map(self.inner.value.write(), |b| b.as_mut())
}
#[inline]
pub fn replace(&self, value: Box<T>) {
*self.inner.value.write() = value;
}
}
#[derive(Clone, Debug)]
pub enum ResourceLoadError {
NotFound(ResourcePath),
TypeMismatch{
requested: TypeId,
actual: TypeId,
},
ReadError(Arc<dyn Error + Send + Sync + 'static>),
}
impl ResourceLoadError {
#[inline]
pub fn from_mismatch<T: ?Sized + Send + Sync + 'static>(actual: TypeId) -> Self {
Self::TypeMismatch { actual, requested: TypeId::of::<T>() }
}
#[inline]
pub fn from_error(e: impl Error + Send + Sync + 'static) -> Self {
Self::ReadError(Arc::new(e))
}
}
impl<'a> From<&'a str> for ResourceLoadError {
#[inline]
fn from(value: &'a str) -> Self {
Self::ReadError(Box::<dyn Error + Send + Sync>::from(value).into())
}
}
impl From<String> for ResourceLoadError {
#[inline]
fn from(value: String) -> Self {
Self::ReadError(Box::<dyn Error + Send + Sync>::from(value).into())
}
}
impl Display for ResourceLoadError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ResourceLoadError::NotFound(id) =>
write!(f, "No resource found that is associated with ID: '{id}'"),
ResourceLoadError::TypeMismatch { requested, actual } =>
write!(f, "Requested type ({requested:?}) does not match previously loaded type ({actual:?})"),
ResourceLoadError::ReadError(err) =>
std::fmt::Display::fmt(&err, f),
}
}
}
impl Error for ResourceLoadError {}
pub type ResourceReadData = Pin<Box<dyn AsyncRead + Send>>;
#[async_trait]
pub trait ResourceLoader<T: ?Sized + Send + Sync + 'static>: Send + Sync + 'static {
async fn load(
&self,
manager: &Arc<ResourceManager>,
id: ResourcePath,
data: ResourceReadData,
) -> Result<Box<T>, ResourceLoadError>;
async fn update(
&self,
manager: &Arc<ResourceManager>,
id: ResourcePath,
resource: ResourceMut<T>,
data: ResourceReadData,
)
-> Result<(), ResourceLoadError>
{
resource.replace(self.load(manager, id, data).await?);
Ok(())
}
}
#[async_trait]
pub trait SubResourceLoader<T, P>: Send + Sync + 'static
where
T: ?Sized + Send + Sync + 'static,
P: ?Sized + Send + Sync + 'static,
{
async fn load(&self, parent: &P) -> Result<Box<T>, ResourceLoadError>;
async fn update(&self, resource: ResourceMut<T>, parent: &P)
-> Result<(), ResourceLoadError>
{
resource.replace(self.load(parent).await?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::resource::manager::LoadPriority;
use super::*;
use super::manager::tests::*;
#[derive(Clone, Debug)]
enum SubStringLoaderError {
NoSuffix,
NoUpdate,
}
impl Display for SubStringLoaderError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SubStringLoaderError::NoSuffix => f.write_str("No suffix specified"),
SubStringLoaderError::NoUpdate => f.write_str("Updates are not allowed"),
}
}
}
impl Error for SubStringLoaderError {}
struct SubStringLoader {
suffix: Option<String>,
no_update: bool,
}
impl SubStringLoader {
fn new(suffix: impl Into<String>) -> Self {
Self { suffix: Some(suffix.into()), no_update: false }
}
fn err() -> Self {
Self { suffix: None, no_update: true }
}
fn no_update(suffix: impl Into<String>) -> Self {
Self { suffix: Some(suffix.into()), no_update: true }
}
}
#[async_trait]
impl SubResourceLoader<String, String> for SubStringLoader {
async fn load(&self, parent: &String) -> Result<Box<String>, ResourceLoadError> {
match &self.suffix {
Some(suffix) => Ok(Box::new(parent.clone() + "-" + &suffix)),
None => Err(ResourceLoadError::from_error(SubStringLoaderError::NoSuffix)),
}
}
async fn update(&self, resource: ResourceMut<String>, parent: &String) -> Result<(), ResourceLoadError> {
if self.no_update {
Err(ResourceLoadError::from_error(SubStringLoaderError::NoUpdate))
} else {
resource.replace(self.load(parent).await?);
Ok(())
}
}
}
#[test]
fn test_sub_resource_load_error() {
let (manager, data_maps) = create_resource_manager::<1>();
data_maps[0].insert("key", b"value", "h_value");
let fut = manager.get_or_load("key", TestResourceLoader::new("key"), LoadPriority::Immediate);
let value = future::block_on(timeout(fut, Duration::from_secs(1)))
.expect("Resource should load for 'key'");
value.attach_sub_resource_blocking(SubStringLoader::err())
.expect_err("Sub-resource should fail to load");
}
#[test]
fn test_sub_resource_update() {
let (manager, data_maps) = create_resource_manager::<1>();
data_maps[0].insert("key", b"value", "h_value");
let fut = manager.get_or_load("key", TestResourceLoader::new("key"), LoadPriority::Immediate);
let value = future::block_on(timeout(fut, Duration::from_secs(1)))
.expect("Resource should load for 'key'");
let sub_value = value.attach_sub_resource_blocking(SubStringLoader::new("subvalue"))
.expect("Sub-resource should load");
assert_eq!(*sub_value.read(), "value-subvalue".to_owned());
data_maps[0].insert("key", b"new_value", "h_new_value");
future::block_on(timeout(manager.test_ctx().sync_update.wait_count(1), Duration::from_secs(1)));
assert_eq!(*sub_value.read(), "new_value-subvalue");
}
#[test]
fn test_multi_sub_resource() {
let (manager, data_maps) = create_resource_manager::<1>();
data_maps[0].insert("key", b"value", "h_value");
let fut = manager.get_or_load("key", TestResourceLoader::new("key"), LoadPriority::Immediate);
let value = future::block_on(timeout(fut, Duration::from_secs(1)))
.expect("Resource should load for 'key'");
let sub_value_1 = value.attach_sub_resource_blocking(SubStringLoader::no_update("subvalue1"))
.expect("Sub-resource should load");
let sub_value_2 = value.attach_sub_resource_blocking(SubStringLoader::new("subvalue2"))
.expect("Sub-resource should load");
assert_eq!(*sub_value_1.read(), "value-subvalue1".to_owned());
assert_eq!(*sub_value_2.read(), "value-subvalue2".to_owned());
data_maps[0].insert("key", b"new_value", "h_new_value");
future::block_on(timeout(manager.test_ctx().sync_update.wait_count(1), Duration::from_secs(1)));
assert_eq!(*sub_value_1.read(), "value-subvalue1".to_owned());
assert_eq!(*sub_value_2.read(), "new_value-subvalue2".to_owned());
}
#[test]
fn test_chained_sub_resource() {
let (manager, data_maps) = create_resource_manager::<1>();
data_maps[0].insert("key", b"value", "h_value");
let fut = manager.get_or_load("key", TestResourceLoader::new("key"), LoadPriority::Immediate);
let value = future::block_on(timeout(fut, Duration::from_secs(1)))
.expect("Resource should load for 'key'");
let sub_value = value.attach_sub_resource_blocking(SubStringLoader::new("subvalue"))
.expect("Sub-resource should load");
let sub_sub_value = sub_value.attach_sub_resource_blocking(SubStringLoader::new("subsubvalue"))
.expect("Sub-sub-resource should load");
assert_eq!(*sub_sub_value.read(), "value-subvalue-subsubvalue".to_owned());
data_maps[0].insert("key", b"new_value", "h_new_value");
future::block_on(timeout(manager.test_ctx().sync_update.wait_count(1), Duration::from_secs(1)));
assert_eq!(*sub_sub_value.read(), "new_value-subvalue-subsubvalue");
}
}