use crate::{
collect_used_resources,
constructor::ResourceConstructorContainer,
core::{
append_extension,
futures::future::join_all,
io::FileLoadError,
log::Log,
make_relative_path, notify,
parking_lot::{Mutex, MutexGuard},
task::TaskPool,
watcher::FileSystemWatcher,
TypeUuidProvider,
},
entry::{TimedEntry, DEFAULT_RESOURCE_LIFETIME},
event::{ResourceEvent, ResourceEventBroadcaster},
io::{FsResourceIo, ResourceIo},
loader::{ResourceLoader, ResourceLoadersContainer},
options::OPTIONS_EXTENSION,
state::{LoadError, ResourceState},
untyped::ResourceKind,
Resource, ResourceData, TypedResourceData, UntypedResource,
};
use fxhash::{FxHashMap, FxHashSet};
use rayon::prelude::*;
use std::borrow::Cow;
use std::ops::{Deref, DerefMut};
use std::{
fmt::{Debug, Display, Formatter},
marker::PhantomData,
path::{Path, PathBuf},
sync::Arc,
};
#[must_use]
#[derive(Default)]
pub struct ResourceWaitContext {
resources: Vec<UntypedResource>,
}
impl ResourceWaitContext {
#[must_use]
pub fn is_all_loaded(&self) -> bool {
let mut loaded_count = 0;
for resource in self.resources.iter() {
if !matches!(resource.0.lock().state, ResourceState::Pending { .. }) {
loaded_count += 1;
}
}
loaded_count == self.resources.len()
}
}
#[derive(Clone)]
pub struct DataSource {
pub extension: Cow<'static, str>,
pub bytes: Cow<'static, [u8]>,
}
impl DataSource {
pub fn new(path: &'static str, data: &'static [u8]) -> Self {
Self {
extension: Cow::Borrowed(
Path::new(path)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or(""),
),
bytes: Cow::Borrowed(data),
}
}
}
#[macro_export]
macro_rules! embedded_data_source {
($path:expr) => {
$crate::manager::DataSource::new($path, include_bytes!($path))
};
}
#[derive(Clone)]
pub struct UntypedBuiltInResource {
pub data_source: Option<DataSource>,
pub resource: UntypedResource,
}
pub struct BuiltInResource<T>
where
T: TypedResourceData,
{
pub data_source: Option<DataSource>,
pub resource: Resource<T>,
}
impl<T: TypedResourceData> Clone for BuiltInResource<T> {
fn clone(&self) -> Self {
Self {
data_source: self.data_source.clone(),
resource: self.resource.clone(),
}
}
}
impl<T: TypedResourceData> BuiltInResource<T> {
pub fn new<F>(data_source: DataSource, make: F) -> Self
where
F: FnOnce(&[u8]) -> Resource<T>,
{
let resource = make(&data_source.bytes);
Self {
resource,
data_source: Some(data_source),
}
}
pub fn new_no_source(resource: Resource<T>) -> Self {
Self {
data_source: None,
resource,
}
}
pub fn resource(&self) -> Resource<T> {
self.resource.clone()
}
}
impl<T: TypedResourceData> From<BuiltInResource<T>> for UntypedBuiltInResource {
fn from(value: BuiltInResource<T>) -> Self {
Self {
data_source: value.data_source,
resource: value.resource.into(),
}
}
}
#[derive(Default, Clone)]
pub struct BuiltInResourcesContainer {
inner: FxHashMap<PathBuf, UntypedBuiltInResource>,
}
impl BuiltInResourcesContainer {
pub fn add<T>(&mut self, resource: BuiltInResource<T>)
where
T: TypedResourceData,
{
self.add_untyped(resource.into())
}
pub fn add_untyped(&mut self, resource: UntypedBuiltInResource) {
self.inner
.insert(resource.resource.kind().path_owned().unwrap(), resource);
}
}
impl Deref for BuiltInResourcesContainer {
type Target = FxHashMap<PathBuf, UntypedBuiltInResource>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for BuiltInResourcesContainer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub struct ResourceManagerState {
pub loaders: ResourceLoadersContainer,
pub event_broadcaster: ResourceEventBroadcaster,
pub constructors_container: ResourceConstructorContainer,
pub built_in_resources: BuiltInResourcesContainer,
pub resource_io: Arc<dyn ResourceIo>,
resources: Vec<TimedEntry<UntypedResource>>,
task_pool: Arc<TaskPool>,
watcher: Option<FileSystemWatcher>,
}
#[derive(Clone)]
pub struct ResourceManager {
state: Arc<Mutex<ResourceManagerState>>,
}
impl Debug for ResourceManager {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "ResourceManager")
}
}
#[derive(Debug)]
pub enum ResourceRegistrationError {
UnableToRegister,
InvalidState,
AlreadyRegistered,
}
impl Display for ResourceRegistrationError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ResourceRegistrationError::UnableToRegister => {
write!(f, "Unable to register the resource!")
}
ResourceRegistrationError::InvalidState => {
write!(f, "A resource was in invalid state!")
}
ResourceRegistrationError::AlreadyRegistered => {
write!(f, "A resource is already registered!")
}
}
}
}
impl ResourceManager {
pub fn new(task_pool: Arc<TaskPool>) -> Self {
Self {
state: Arc::new(Mutex::new(ResourceManagerState::new(task_pool))),
}
}
pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> {
self.state.lock()
}
pub fn resource_io(&self) -> Arc<dyn ResourceIo> {
let state = self.state();
state.resource_io.clone()
}
pub fn task_pool(&self) -> Arc<TaskPool> {
let state = self.state();
state.task_pool()
}
pub fn request<T>(&self, path: impl AsRef<Path>) -> Resource<T>
where
T: TypedResourceData,
{
let untyped = self.state().request(path);
let actual_type_uuid = untyped.type_uuid();
assert_eq!(actual_type_uuid, <T as TypeUuidProvider>::type_uuid());
Resource {
untyped,
phantom: PhantomData::<T>,
}
}
pub fn try_request<T>(&self, path: impl AsRef<Path>) -> Option<Resource<T>>
where
T: TypedResourceData,
{
let untyped = self.state().request(path);
let actual_type_uuid = untyped.type_uuid();
if actual_type_uuid == <T as TypeUuidProvider>::type_uuid() {
Some(Resource {
untyped,
phantom: PhantomData::<T>,
})
} else {
None
}
}
pub fn request_untyped<P>(&self, path: P) -> UntypedResource
where
P: AsRef<Path>,
{
self.state().request(path)
}
pub fn register<P, F>(
&self,
resource: UntypedResource,
path: P,
mut on_register: F,
) -> Result<(), ResourceRegistrationError>
where
P: AsRef<Path>,
F: FnMut(&mut dyn ResourceData, &Path) -> bool,
{
let mut state = self.state();
if let Some(resource) = state.find(path.as_ref()) {
let resource_state = resource.0.lock();
if let ResourceState::Ok(_) = resource_state.state {
return Err(ResourceRegistrationError::AlreadyRegistered);
}
}
state.unregister(path.as_ref());
let mut header = resource.0.lock();
header.kind.make_external(path.as_ref().to_path_buf());
if let ResourceState::Ok(ref mut data) = header.state {
if !on_register(&mut **data, path.as_ref()) {
Err(ResourceRegistrationError::UnableToRegister)
} else {
drop(header);
state.push(resource);
Ok(())
}
} else {
Err(ResourceRegistrationError::InvalidState)
}
}
pub async fn move_resource(
&self,
resource: UntypedResource,
new_path: impl AsRef<Path>,
working_directory: impl AsRef<Path>,
mut filter: impl FnMut(&UntypedResource) -> bool,
) -> Result<(), FileLoadError> {
let new_path = new_path.as_ref().to_owned();
let io = self.state().resource_io.clone();
let existing_path = resource
.kind()
.into_path()
.ok_or_else(|| FileLoadError::Custom("Cannot move embedded resource!".to_string()))?;
let canonical_existing_path = io.canonicalize_path(&existing_path).await?;
let resources = io
.walk_directory(working_directory.as_ref())
.await?
.map(|p| self.request_untyped(p))
.collect::<Vec<_>>();
let resources_to_fix = join_all(resources)
.await
.into_iter()
.filter_map(|r| r.ok())
.filter(|r| r != &resource && filter(r))
.collect::<Vec<_>>();
let mut pairs = resources_to_fix
.par_iter()
.filter_map(|loaded_resource| {
let mut guard = loaded_resource.0.lock();
if let ResourceState::Ok(ref mut data) = guard.state {
let mut used_resources = FxHashSet::default();
(**data).as_reflect(&mut |reflect| {
collect_used_resources(reflect, &mut used_resources);
});
Some((loaded_resource, used_resources))
} else {
None
}
})
.collect::<Vec<_>>();
for (_, used_resources) in pairs.iter_mut() {
let mut used_resources_with_references = FxHashSet::default();
for resource in used_resources.iter() {
if let Some(path) = resource.kind().into_path() {
if let Ok(canonical_resource_path) = io.canonicalize_path(&path).await {
if canonical_resource_path == canonical_existing_path {
used_resources_with_references.insert(resource.clone());
}
}
}
}
*used_resources = used_resources_with_references;
}
for (loaded_resource, used_resources) in pairs {
if !used_resources.is_empty() {
for resource in used_resources {
resource.set_kind(ResourceKind::External(new_path.clone()));
}
let mut header = loaded_resource.0.lock();
if let Some(loaded_resource_path) = header.kind.path_owned() {
if let ResourceState::Ok(ref mut data) = header.state {
match data.save(&loaded_resource_path) {
Ok(_) => Log::info(format!(
"Resource {} was saved successfully!",
header.kind
)),
Err(err) => Log::err(format!(
"Unable to save {} resource. Reason: {:?}",
header.kind, err
)),
};
}
}
}
}
io.move_file(&existing_path, &new_path).await?;
let options_path = append_extension(&existing_path, OPTIONS_EXTENSION);
if io.exists(&options_path).await {
let new_options_path = append_extension(&new_path, OPTIONS_EXTENSION);
io.move_file(&options_path, &new_options_path).await?;
}
Ok(())
}
pub async fn reload_resources(&self) {
let resources = self.state().reload_resources();
join_all(resources).await;
}
}
impl ResourceManagerState {
pub(crate) fn new(task_pool: Arc<TaskPool>) -> Self {
Self {
resources: Default::default(),
task_pool,
loaders: Default::default(),
event_broadcaster: Default::default(),
constructors_container: Default::default(),
watcher: None,
built_in_resources: Default::default(),
resource_io: Arc::new(FsResourceIo),
}
}
pub fn task_pool(&self) -> Arc<TaskPool> {
self.task_pool.clone()
}
pub fn set_resource_io(&mut self, resource_io: Arc<dyn ResourceIo>) {
self.resource_io = resource_io;
}
pub fn set_watcher(&mut self, watcher: Option<FileSystemWatcher>) {
self.watcher = watcher;
}
pub fn count_registered_resources(&self) -> usize {
self.resources.len()
}
pub fn loading_progress(&self) -> usize {
let registered = self.count_registered_resources();
if registered > 0 {
self.count_loaded_resources() * 100 / registered
} else {
100
}
}
pub fn update(&mut self, dt: f32) {
self.resources.retain_mut(|resource| {
if resource.value.use_count() <= 1 {
resource.time_to_live -= dt;
if resource.time_to_live <= 0.0 {
if let Some(path) = resource.0.lock().kind.path_owned() {
Log::info(format!(
"Resource {} destroyed because it is not used anymore!",
path.display()
));
self.event_broadcaster
.broadcast(ResourceEvent::Removed(path));
}
false
} else {
true
}
} else {
resource.time_to_live = DEFAULT_RESOURCE_LIFETIME;
true
}
});
if let Some(watcher) = self.watcher.as_ref() {
if let Some(evt) = watcher.try_get_event() {
if let notify::EventKind::Modify(_) = evt.kind {
for path in evt.paths {
if let Ok(relative_path) = make_relative_path(path) {
if self.try_reload_resource_from_path(&relative_path) {
Log::info(format!(
"File {} was changed, trying to reload a respective resource...",
relative_path.display()
));
break;
}
}
}
}
}
}
}
pub fn push(&mut self, resource: UntypedResource) {
self.event_broadcaster
.broadcast(ResourceEvent::Added(resource.clone()));
self.resources.push(TimedEntry {
value: resource,
time_to_live: DEFAULT_RESOURCE_LIFETIME,
});
}
pub fn find<P: AsRef<Path>>(&self, path: P) -> Option<&UntypedResource> {
for resource in self.resources.iter() {
if let Some(resource_path) = resource.0.lock().kind.path() {
if resource_path == path.as_ref() {
return Some(&resource.value);
}
}
}
None
}
pub fn len(&self) -> usize {
self.resources.len()
}
pub fn is_empty(&self) -> bool {
self.resources.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
self.resources.iter().map(|entry| &entry.value)
}
pub fn destroy_unused_resources(&mut self) {
self.resources
.retain(|resource| resource.value.use_count() > 1);
}
pub fn count_pending_resources(&self) -> usize {
self.resources.iter().fold(0, |counter, resource| {
if let ResourceState::Pending { .. } = resource.0.lock().state {
counter + 1
} else {
counter
}
})
}
pub fn count_loaded_resources(&self) -> usize {
self.resources.iter().fold(0, |counter, resource| {
if let ResourceState::Ok(_) = resource.0.lock().state {
counter + 1
} else {
counter
}
})
}
pub fn resources(&self) -> Vec<UntypedResource> {
self.resources.iter().map(|t| t.value.clone()).collect()
}
pub fn request<P>(&mut self, path: P) -> UntypedResource
where
P: AsRef<Path>,
{
if let Some(built_in_resource) = self.built_in_resources.get(path.as_ref()) {
return built_in_resource.resource.clone();
}
match self.find(path.as_ref()) {
Some(existing) => existing.clone(),
None => {
let path = path.as_ref().to_owned();
let kind = ResourceKind::External(path.clone());
if let Some(loader) = self.find_loader(path.as_ref()) {
let resource = UntypedResource::new_pending(kind, loader.data_type_uuid());
self.spawn_loading_task(path, resource.clone(), loader, false);
self.push(resource.clone());
resource
} else {
let err =
LoadError::new(format!("There's no resource loader for {kind} resource!",));
UntypedResource::new_load_error(kind, err, Default::default())
}
}
}
}
fn find_loader(&self, path: &Path) -> Option<&dyn ResourceLoader> {
path.extension().and_then(|extension| {
self.loaders
.iter()
.find(|loader| loader.supports_extension(&extension.to_string_lossy()))
})
}
fn spawn_loading_task(
&self,
path: PathBuf,
resource: UntypedResource,
loader: &dyn ResourceLoader,
reload: bool,
) {
let event_broadcaster = self.event_broadcaster.clone();
let loader_future = loader.load(path.clone(), self.resource_io.clone());
self.task_pool.spawn_task(async move {
match loader_future.await {
Ok(data) => {
let data = data.0;
Log::info(format!(
"Resource {} was loaded successfully!",
path.display()
));
{
let mut mutex_guard = resource.0.lock();
assert_eq!(mutex_guard.type_uuid, data.type_uuid());
assert!(mutex_guard.kind.is_external());
mutex_guard.state.commit(ResourceState::Ok(data));
}
event_broadcaster.broadcast_loaded_or_reloaded(resource, reload);
}
Err(error) => {
Log::info(format!(
"Resource {} failed to load. Reason: {:?}",
path.display(),
error
));
resource.commit_error(error);
}
}
});
}
pub fn reload_resource(&mut self, resource: UntypedResource) {
let mut header = resource.0.lock();
if !header.state.is_loading() {
if let Some(path) = header.kind.path_owned() {
if let Some(loader) = self.find_loader(&path) {
header.state.switch_to_pending_state();
drop(header);
self.spawn_loading_task(path, resource, loader, true);
} else {
let msg = format!(
"There's no resource loader for {} resource!",
path.display()
);
Log::err(&msg);
resource.commit_error(msg)
}
} else {
Log::err("Cannot reload embedded resource.")
}
}
}
pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
let resources = self
.resources
.iter()
.map(|r| r.value.clone())
.collect::<Vec<_>>();
for resource in resources.iter().cloned() {
self.reload_resource(resource);
}
resources
}
pub fn get_wait_context(&self) -> ResourceWaitContext {
ResourceWaitContext {
resources: self
.resources
.iter()
.map(|e| e.value.clone())
.collect::<Vec<_>>(),
}
}
pub fn try_reload_resource_from_path(&mut self, path: &Path) -> bool {
if let Some(resource) = self.find(path).cloned() {
self.reload_resource(resource);
true
} else {
false
}
}
pub fn unregister(&mut self, path: &Path) {
if let Some(position) = self
.resources
.iter()
.position(|r| r.kind().path() == Some(path))
{
self.resources.remove(position);
}
}
}
#[cfg(test)]
mod test {
use std::error::Error;
use std::{fs::File, time::Duration};
use crate::loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader};
use super::*;
use fyrox_core::uuid::{uuid, Uuid};
use fyrox_core::{
reflect::{FieldInfo, Reflect},
visitor::{Visit, VisitResult, Visitor},
TypeUuidProvider,
};
#[derive(Debug, Default, Reflect, Visit)]
struct Stub {}
impl TypeUuidProvider for Stub {
fn type_uuid() -> Uuid {
uuid!("9d873ff4-3126-47e1-a492-7cd8e7168239")
}
}
impl ResourceData for Stub {
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
Err("Saving is not supported!".to_string().into())
}
fn can_be_saved(&self) -> bool {
false
}
}
impl ResourceLoader for Stub {
fn extensions(&self) -> &[&str] {
&["txt"]
}
fn data_type_uuid(&self) -> Uuid {
<Stub as TypeUuidProvider>::type_uuid()
}
fn load(&self, _path: PathBuf, _io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
Box::pin(async move { Ok(LoaderPayload::new(Stub::default())) })
}
}
fn new_resource_manager() -> ResourceManagerState {
ResourceManagerState::new(Arc::new(Default::default()))
}
#[test]
fn resource_wait_context_is_all_loaded() {
assert!(ResourceWaitContext::default().is_all_loaded());
let path = PathBuf::from("test.txt");
let type_uuid = Uuid::default();
let cx = ResourceWaitContext {
resources: vec![
UntypedResource::new_pending(path.clone().into(), type_uuid),
UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid),
],
};
assert!(!cx.is_all_loaded());
}
#[test]
fn resource_manager_state_new() {
let state = new_resource_manager();
assert!(state.resources.is_empty());
assert!(state.loaders.is_empty());
assert!(state.built_in_resources.is_empty());
assert!(state.constructors_container.is_empty());
assert!(state.watcher.is_none());
assert!(state.is_empty());
}
#[test]
fn resource_manager_state_set_watcher() {
let mut state = new_resource_manager();
assert!(state.watcher.is_none());
let path = PathBuf::from("test.txt");
if File::create(path.clone()).is_ok() {
let watcher = FileSystemWatcher::new(path.clone(), Duration::from_secs(1));
state.set_watcher(watcher.ok());
assert!(state.watcher.is_some());
}
}
#[test]
fn resource_manager_state_push() {
let mut state = new_resource_manager();
assert_eq!(state.count_loaded_resources(), 0);
assert_eq!(state.count_pending_resources(), 0);
assert_eq!(state.count_registered_resources(), 0);
assert_eq!(state.len(), 0);
let path = PathBuf::from("test.txt");
let type_uuid = Uuid::default();
state.push(UntypedResource::new_pending(path.clone().into(), type_uuid));
state.push(UntypedResource::new_load_error(
path.clone().into(),
Default::default(),
type_uuid,
));
state.push(UntypedResource::new_ok(Default::default(), Stub {}));
assert_eq!(state.count_loaded_resources(), 1);
assert_eq!(state.count_pending_resources(), 1);
assert_eq!(state.count_registered_resources(), 3);
assert_eq!(state.len(), 3);
}
#[test]
fn resource_manager_state_loading_progress() {
let mut state = new_resource_manager();
assert_eq!(state.loading_progress(), 100);
let path = PathBuf::from("test.txt");
let type_uuid = Uuid::default();
state.push(UntypedResource::new_pending(path.clone().into(), type_uuid));
state.push(UntypedResource::new_load_error(
path.clone().into(),
Default::default(),
type_uuid,
));
state.push(UntypedResource::new_ok(Default::default(), Stub {}));
assert_eq!(state.loading_progress(), 33);
}
#[test]
fn resource_manager_state_find() {
let mut state = new_resource_manager();
assert!(state.find(Path::new("foo.txt")).is_none());
let path = PathBuf::from("test.txt");
let type_uuid = Uuid::default();
let resource = UntypedResource::new_pending(path.clone().into(), type_uuid);
state.push(resource.clone());
assert_eq!(state.find(path), Some(&resource));
}
#[test]
fn resource_manager_state_resources() {
let mut state = new_resource_manager();
assert_eq!(state.resources(), Vec::new());
let path = PathBuf::from("test.txt");
let type_uuid = Uuid::default();
let r1 = UntypedResource::new_pending(path.clone().into(), type_uuid);
let r2 =
UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid);
let r3 = UntypedResource::new_ok(Default::default(), Stub {});
state.push(r1.clone());
state.push(r2.clone());
state.push(r3.clone());
assert_eq!(state.resources(), vec![r1.clone(), r2.clone(), r3.clone()]);
assert!(state.iter().eq([&r1, &r2, &r3]));
}
#[test]
fn resource_manager_state_destroy_unused_resources() {
let mut state = new_resource_manager();
state.push(UntypedResource::new_pending(
PathBuf::from("test.txt").into(),
Uuid::default(),
));
assert_eq!(state.len(), 1);
state.destroy_unused_resources();
assert_eq!(state.len(), 0);
}
#[test]
fn resource_manager_state_request() {
let mut state = new_resource_manager();
let path = PathBuf::from("test.txt");
let type_uuid = Uuid::default();
let resource =
UntypedResource::new_load_error(path.clone().into(), Default::default(), type_uuid);
state.push(resource.clone());
let res = state.request(path);
assert_eq!(res, resource);
let path = PathBuf::from("foo.txt");
let res = state.request(path.clone());
assert_eq!(res.kind(), ResourceKind::External(path.clone()));
assert_eq!(res.type_uuid(), type_uuid);
assert!(!res.is_loading());
}
#[test]
fn resource_manager_state_try_reload_resource_from_path() {
let mut state = new_resource_manager();
state.loaders.set(Stub {});
let resource = UntypedResource::new_load_error(
PathBuf::from("test.txt").into(),
Default::default(),
Uuid::default(),
);
state.push(resource.clone());
assert!(!state.try_reload_resource_from_path(Path::new("foo.txt")));
assert!(state.try_reload_resource_from_path(Path::new("test.txt")));
assert!(resource.is_loading());
}
#[test]
fn resource_manager_state_get_wait_context() {
let mut state = new_resource_manager();
let resource = UntypedResource::new_ok(Default::default(), Stub {});
state.push(resource.clone());
let cx = state.get_wait_context();
assert!(cx.resources.eq(&vec![resource]));
}
#[test]
fn resource_manager_new() {
let manager = ResourceManager::new(Arc::new(Default::default()));
assert!(manager.state.lock().is_empty());
assert!(manager.state().is_empty());
}
#[test]
fn resource_manager_register() {
let manager = ResourceManager::new(Arc::new(Default::default()));
let path = PathBuf::from("test.txt");
let type_uuid = Uuid::default();
let resource = UntypedResource::new_pending(path.clone().into(), type_uuid);
let res = manager.register(resource.clone(), path.clone(), |_, __| true);
assert!(res.is_err());
let resource = UntypedResource::new_ok(Default::default(), Stub {});
let res = manager.register(resource.clone(), path.clone(), |_, __| true);
assert!(res.is_ok());
}
#[test]
fn resource_manager_request() {
let manager = ResourceManager::new(Arc::new(Default::default()));
let untyped = UntypedResource::new_ok(Default::default(), Stub {});
let res = manager.register(untyped.clone(), PathBuf::from("foo.txt"), |_, __| true);
assert!(res.is_ok());
let res: Resource<Stub> = manager.request(Path::new("foo.txt"));
assert_eq!(
res,
Resource {
untyped,
phantom: PhantomData::<Stub>
}
);
}
#[test]
fn resource_manager_request_untyped() {
let manager = ResourceManager::new(Arc::new(Default::default()));
let resource = UntypedResource::new_ok(Default::default(), Stub {});
let res = manager.register(resource.clone(), PathBuf::from("foo.txt"), |_, __| true);
assert!(res.is_ok());
let res = manager.request_untyped(Path::new("foo.txt"));
assert_eq!(res, resource);
}
#[test]
fn display_for_resource_registration_error() {
assert_eq!(
format!("{}", ResourceRegistrationError::AlreadyRegistered),
"A resource is already registered!"
);
assert_eq!(
format!("{}", ResourceRegistrationError::InvalidState),
"A resource was in invalid state!"
);
assert_eq!(
format!("{}", ResourceRegistrationError::UnableToRegister),
"Unable to register the resource!"
);
}
#[test]
fn debug_for_resource_registration_error() {
assert_eq!(
format!("{:?}", ResourceRegistrationError::AlreadyRegistered),
"AlreadyRegistered"
);
assert_eq!(
format!("{:?}", ResourceRegistrationError::InvalidState),
"InvalidState"
);
assert_eq!(
format!("{:?}", ResourceRegistrationError::UnableToRegister),
"UnableToRegister"
);
}
}