518 lines
14 KiB
Rust
518 lines
14 KiB
Rust
use std::{any::{Any, TypeId}, marker::PhantomData, ops::{Deref, DerefMut}, sync::{Arc, Condvar, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}};
|
|
|
|
use lyra_ecs::Component;
|
|
use lyra_reflect::Reflect;
|
|
use crate::{loader::LoaderError, lyra_engine, DependencyState};
|
|
use uuid::Uuid;
|
|
|
|
use crate::ResourceStorage;
|
|
|
|
pub fn optionally_add_to_dep<R: ResourceData>(deps: &mut Vec<UntypedResHandle>, handle: &Option<ResHandle<R>>) {
|
|
if let Some(h) = handle {
|
|
deps.push(h.untyped_clone());
|
|
}
|
|
}
|
|
|
|
/// A trait that that each resource type should implement.
|
|
pub trait ResourceData: Send + Sync + Any + 'static {
|
|
fn as_any(&self) -> &dyn Any;
|
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
|
|
|
/// Collect the dependencies of the Resource.
|
|
fn dependencies(&self) -> Vec<UntypedResHandle>;
|
|
|
|
/// Recursively collect the dependencies of the Resource.
|
|
///
|
|
/// If a dependency has a child dependency, it will not show up in this list until its
|
|
/// parent is loaded.
|
|
fn recur_dependencies(&self) -> Vec<UntypedResHandle> {
|
|
let deps = self.dependencies();
|
|
let mut all_deps = deps.clone();
|
|
|
|
for dep in deps.into_iter() {
|
|
let dep = dep.read();
|
|
match &dep.state {
|
|
ResourceState::Ready(data) => {
|
|
let mut deps_dep = data.dependencies();
|
|
all_deps.append(&mut deps_dep);
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
all_deps
|
|
}
|
|
}
|
|
|
|
//impl<T: Send + Sync + Reflect> ResourceData for T { }
|
|
|
|
pub enum ResourceState {
|
|
Loading,
|
|
Error(Arc<LoaderError>),
|
|
Ready(Box<dyn ResourceData>),
|
|
}
|
|
|
|
impl ResourceState {
|
|
/// Returns a boolean indicating if the state of still loading
|
|
pub fn is_loading(&self) -> bool {
|
|
matches!(self, ResourceState::Loading)
|
|
}
|
|
|
|
pub fn is_error(&self) -> bool {
|
|
matches!(self, ResourceState::Error(_))
|
|
}
|
|
|
|
pub fn is_ready(&self) -> bool {
|
|
matches!(self, ResourceState::Ready(_))
|
|
}
|
|
}
|
|
|
|
pub struct ResourceDataRef<'a, T> {
|
|
guard: std::sync::RwLockReadGuard<'a, UntypedResource>,
|
|
_marker: PhantomData<T>,
|
|
}
|
|
|
|
impl<'a, T: 'static> std::ops::Deref for ResourceDataRef<'a, T> {
|
|
type Target = T;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
match &self.guard.state {
|
|
ResourceState::Ready(d) => {
|
|
// for some reason, if I didn't use `.as_ref`, the downcast would fail.
|
|
let d = d.as_ref().as_any();
|
|
d.downcast_ref::<T>().unwrap()
|
|
},
|
|
_ => unreachable!() // ResHandler::data_ref shouldn't allow this to run
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct UntypedResource {
|
|
pub(crate) version: usize,
|
|
pub(crate) state: ResourceState,
|
|
uuid: Uuid,
|
|
pub(crate) path: Option<String>,
|
|
pub(crate) is_watched: bool,
|
|
/// can be used to wait for the resource to load.
|
|
pub(crate) condvar: Arc<(Mutex<bool>, Condvar)>,
|
|
}
|
|
|
|
#[derive(Clone, Component, Reflect)]
|
|
pub struct UntypedResHandle{
|
|
#[reflect(skip)]
|
|
pub(crate) res: Arc<RwLock<UntypedResource>>,
|
|
tyid: TypeId,
|
|
}
|
|
|
|
impl UntypedResHandle {
|
|
pub fn new(res: UntypedResource, tyid: TypeId) -> Self {
|
|
Self {
|
|
res: Arc::new(RwLock::new(res)),
|
|
tyid
|
|
}
|
|
}
|
|
|
|
pub fn read(&self) -> RwLockReadGuard<UntypedResource> {
|
|
self.res.read().unwrap()
|
|
}
|
|
|
|
pub fn write(&self) -> RwLockWriteGuard<UntypedResource> {
|
|
self.res.write().unwrap()
|
|
}
|
|
|
|
/// Returns a boolean indicating if this resource's path is being watched.
|
|
pub fn is_watched(&self) -> bool {
|
|
let d = self.read();
|
|
d.is_watched
|
|
}
|
|
|
|
/// Returns a boolean indicating if this resource is loaded
|
|
pub fn is_loaded(&self) -> bool {
|
|
let d = self.read();
|
|
matches!(d.state, ResourceState::Ready(_))
|
|
}
|
|
|
|
pub fn get_error(&self) -> Option<Arc<LoaderError>> {
|
|
let d = self.read();
|
|
|
|
match &d.state {
|
|
ResourceState::Error(e) => Some(e.clone()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Returns the uuid of the resource.
|
|
pub fn uuid(&self) -> Uuid {
|
|
let d = self.read();
|
|
d.uuid
|
|
}
|
|
|
|
pub fn path(&self) -> Option<String> {
|
|
let d = self.read();
|
|
d.path.clone()
|
|
}
|
|
|
|
/// Retrieves the current version of the resource. This gets incremented when the resource
|
|
/// is reloaded.
|
|
pub fn version(&self) -> usize {
|
|
let d = self.read();
|
|
d.version
|
|
}
|
|
|
|
/// Wait for the resource to be loaded, not including its dependencies
|
|
/// (see[`UntypedResHandle::wait_recurse_dependencies_load`]).
|
|
///
|
|
/// This blocks the thread without consuming CPU time; its backed by a
|
|
/// [`Condvar`](std::sync::Condvar).
|
|
pub fn wait_for_load(&self) {
|
|
let d = self.read();
|
|
|
|
if matches!(d.state, ResourceState::Ready(_)) {
|
|
return;
|
|
}
|
|
|
|
let cv = d.condvar.clone();
|
|
drop(d);
|
|
|
|
let l = cv.0.lock().unwrap();
|
|
let _unused = cv.1.wait(l).unwrap();
|
|
}
|
|
|
|
/// Wait for the entire resource, including its dependencies to be loaded.
|
|
///
|
|
/// This blocks the thread without consuming CPU time; its backed by a
|
|
/// [`Condvar`](std::sync::Condvar).
|
|
pub fn wait_recurse_dependencies_load(&self) {
|
|
self.wait_for_load();
|
|
|
|
let res = self.read();
|
|
match &res.state {
|
|
ResourceState::Ready(data) => {
|
|
// `recur_dependencies` wont return resources that are not loaded in yet
|
|
// if we did not check if the resource was finished loading, we could miss
|
|
// waiting for some resources and finish early.
|
|
while self.recurse_dependency_state().is_loading() {
|
|
for dep in data.recur_dependencies() {
|
|
dep.wait_for_load();
|
|
}
|
|
}
|
|
},
|
|
_ => unreachable!() // the self.wait_for_load ensures that the state is ready
|
|
}
|
|
}
|
|
|
|
/// Recursively get the state of the dependencies.
|
|
///
|
|
/// This doesn't return any resource data, it can be used to check if the resource and its
|
|
/// dependencies are loaded.
|
|
pub fn recurse_dependency_state(&self) -> DependencyState {
|
|
DependencyState::from_res_recurse(self)
|
|
}
|
|
|
|
/// Retrieve a typed handle to the resource.
|
|
///
|
|
/// Returns `None` if the types do not match
|
|
pub fn as_typed<T: ResourceData>(&self) -> Option<ResHandle<T>> {
|
|
self.clone().into_typed()
|
|
}
|
|
|
|
/// Convert `self` into a typed handle.
|
|
///
|
|
/// Returns `None` if the types do not match
|
|
pub fn into_typed<T: ResourceData>(self) -> Option<ResHandle<T>> {
|
|
if self.tyid == TypeId::of::<T>() {
|
|
Some(ResHandle {
|
|
handle: self,
|
|
_marker: PhantomData::<T>,
|
|
})
|
|
} else { None }
|
|
}
|
|
|
|
/// Retrieve the type id of the resource in the handle.
|
|
pub fn resource_type_id(&self) -> TypeId {
|
|
self.tyid
|
|
}
|
|
}
|
|
|
|
/// A handle to a resource.
|
|
///
|
|
/// # Note
|
|
/// This struct has an inner [`RwLock`] to the resource data, so most methods may be blocking.
|
|
/// However, the only times it will be blocking is if another thread is reloading the resource
|
|
/// and has a write lock on the data. This means that most of the time, it is not blocking.
|
|
#[derive(Component, Reflect)]
|
|
pub struct ResHandle<T: ResourceData> {
|
|
pub(crate) handle: UntypedResHandle,
|
|
_marker: PhantomData<T>,
|
|
}
|
|
|
|
impl<T: ResourceData> Clone for ResHandle<T> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
handle: self.handle.clone(),
|
|
_marker: PhantomData::<T>
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: ResourceData> Deref for ResHandle<T> {
|
|
type Target = UntypedResHandle;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.handle
|
|
}
|
|
}
|
|
|
|
impl<T: ResourceData> DerefMut for ResHandle<T> {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.handle
|
|
}
|
|
}
|
|
|
|
impl<T: ResourceData> ResHandle<T> {
|
|
pub fn new_loading(path: Option<&str>) -> Self {
|
|
let res_version = UntypedResource {
|
|
version: 0,
|
|
path: path.map(str::to_string),
|
|
state: ResourceState::Loading,
|
|
uuid: Uuid::new_v4(),
|
|
is_watched: false,
|
|
condvar: Arc::new((Mutex::new(false), Condvar::new())),
|
|
};
|
|
|
|
Self {
|
|
handle: UntypedResHandle::new(res_version, TypeId::of::<T>()),
|
|
_marker: PhantomData::<T>,
|
|
}
|
|
}
|
|
|
|
/// Create the resource with data, its assumed the state is `Ready`
|
|
pub fn new_ready(path: Option<&str>, data: T) -> Self {
|
|
let han = Self::new_loading(path);
|
|
han.set_state(ResourceState::Ready(Box::new(data)));
|
|
han
|
|
}
|
|
|
|
/// Retrieve an untyped clone of the handle
|
|
pub fn untyped_clone(&self) -> UntypedResHandle {
|
|
self.handle.clone()
|
|
}
|
|
|
|
/// Get a reference to the data in the resource
|
|
pub fn data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
|
|
if self.is_loaded() {
|
|
let d = self.handle.read();
|
|
Some(ResourceDataRef {
|
|
guard: d,
|
|
_marker: PhantomData::<T>
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: ResourceData> ResourceStorage for ResHandle<T> {
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
|
|
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
|
|
self.clone()
|
|
}
|
|
|
|
fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
|
|
self
|
|
}
|
|
|
|
fn set_watched(&self, watched: bool) {
|
|
let mut d = self.handle.write();
|
|
d.is_watched = watched;
|
|
}
|
|
|
|
fn version(&self) -> usize {
|
|
self.handle.version()
|
|
}
|
|
|
|
fn uuid(&self) -> Uuid {
|
|
self.handle.uuid()
|
|
}
|
|
|
|
fn path(&self) -> Option<String> {
|
|
self.handle.path()
|
|
}
|
|
|
|
fn is_watched(&self) -> bool {
|
|
self.handle.is_watched()
|
|
}
|
|
|
|
fn is_loaded(&self) -> bool {
|
|
self.handle.is_loaded()
|
|
}
|
|
|
|
fn set_state(&self, new: ResourceState) {
|
|
let mut d = self.handle.write();
|
|
d.state = new;
|
|
}
|
|
|
|
fn clone_untyped(&self) -> UntypedResHandle {
|
|
self.handle.clone()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::{path::PathBuf, str::FromStr, sync::Arc};
|
|
|
|
use async_std::task;
|
|
use instant::Duration;
|
|
use rand::Rng;
|
|
|
|
use crate::{loader::ResourceLoader, ResHandle, ResourceData, ResourceManager};
|
|
|
|
#[allow(dead_code)]
|
|
struct SimpleDepend {
|
|
file_name: String,
|
|
ext: String,
|
|
}
|
|
|
|
impl ResourceData for SimpleDepend {
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
|
|
vec![]
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SlowSimpleDependLoader;
|
|
|
|
impl ResourceLoader for SlowSimpleDependLoader {
|
|
fn extensions(&self) -> &[&str] {
|
|
&["txt", "buf"]
|
|
}
|
|
|
|
fn mime_types(&self) -> &[&str] {
|
|
&[]
|
|
}
|
|
|
|
fn load(&self, _: crate::ResourceManager, path: &str) -> crate::loader::PinedBoxLoaderFuture {
|
|
let path = path.to_string();
|
|
Box::pin(async move {
|
|
let path = PathBuf::from_str(&path).unwrap();
|
|
|
|
let file_name = path.file_name()
|
|
.and_then(|os| os.to_str())
|
|
.unwrap();
|
|
let path_ext = path.extension()
|
|
.and_then(|os| os.to_str())
|
|
.unwrap();
|
|
|
|
let res = rand::thread_rng().gen_range(500..1000);
|
|
|
|
task::sleep(Duration::from_millis(res)).await;
|
|
|
|
let simple = SimpleDepend {
|
|
file_name: file_name.to_string(),
|
|
ext: path_ext.to_string(),
|
|
};
|
|
|
|
Ok(Box::new(simple) as Box<dyn ResourceData>)
|
|
})
|
|
}
|
|
|
|
fn load_bytes(&self, _: crate::ResourceManager, _: Vec<u8>, _: usize, _: usize) -> crate::loader::PinedBoxLoaderFuture {
|
|
unreachable!()
|
|
}
|
|
|
|
fn create_erased_handle(&self) -> std::sync::Arc<dyn crate::ResourceStorage> {
|
|
Arc::from(ResHandle::<SimpleDepend>::new_loading(None))
|
|
}
|
|
}
|
|
|
|
|
|
struct SimpleResource {
|
|
depend_a: ResHandle<SimpleDepend>,
|
|
}
|
|
|
|
impl ResourceData for SimpleResource {
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
|
|
vec![self.depend_a.untyped_clone()]
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SlowSimpleResourceLoader;
|
|
|
|
impl ResourceLoader for SlowSimpleResourceLoader {
|
|
fn extensions(&self) -> &[&str] {
|
|
&["res", "large"]
|
|
}
|
|
|
|
fn mime_types(&self) -> &[&str] {
|
|
&[]
|
|
}
|
|
|
|
fn load(&self, res_man: crate::ResourceManager, _: &str) -> crate::loader::PinedBoxLoaderFuture {
|
|
Box::pin(async move {
|
|
let res = rand::thread_rng().gen_range(500..1000);
|
|
|
|
task::sleep(Duration::from_millis(res)).await;
|
|
|
|
// load dummy dependency that will take a bit
|
|
let depend_path = "depend.txt";
|
|
let depend_han = res_man.request::<SimpleDepend>(depend_path).unwrap();
|
|
|
|
let simple = SimpleResource {
|
|
depend_a: depend_han,
|
|
};
|
|
|
|
Ok(Box::new(simple) as Box<dyn ResourceData>)
|
|
})
|
|
}
|
|
|
|
fn load_bytes(&self, _: crate::ResourceManager, _: Vec<u8>, _: usize, _: usize) -> crate::loader::PinedBoxLoaderFuture {
|
|
unreachable!()
|
|
}
|
|
|
|
fn create_erased_handle(&self) -> std::sync::Arc<dyn crate::ResourceStorage> {
|
|
Arc::from(ResHandle::<SimpleResource>::new_loading(None))
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn recursive() {
|
|
let man = ResourceManager::new();
|
|
man.register_loader::<SlowSimpleDependLoader>();
|
|
man.register_loader::<SlowSimpleResourceLoader>();
|
|
|
|
let res = man.request::<SimpleResource>("massive_asset.res").unwrap();
|
|
|
|
let state = res.recurse_dependency_state();
|
|
assert!(state.is_loading());
|
|
|
|
// this will take a bit
|
|
res.wait_recurse_dependencies_load();
|
|
|
|
let state = res.recurse_dependency_state();
|
|
assert!(!state.is_loading());
|
|
}
|
|
} |