diff --git a/Cargo.toml b/Cargo.toml index 2fdd14f1c..035b72155 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,6 @@ ron = "0.8.0" fxhash = "0.2.1" strum = "0.24.0" strum_macros = "0.24.0" -notify = "5" clap = { version = "4", features = ["derive"] } spade = "2.1.0" winit = { version = "0.28.1", features = ["serde"] } diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 1677dc250..eebf40327 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -74,6 +74,7 @@ use crate::{ utils::path_fixer::PathFixer, world::{graph::selection::GraphSelection, WorldViewer}, }; +use fyrox::core::watcher::FileSystemWatcher; use fyrox::{ core::{ algebra::{Matrix3, Vector2}, @@ -121,7 +122,6 @@ use fyrox::{ into_gui_texture, log::{Log, MessageKind}, translate_cursor_icon, translate_event, - watcher::FileSystemWatcher, }, window::{Icon, WindowAttributes}, }; diff --git a/fyrox-core/Cargo.toml b/fyrox-core/Cargo.toml index 4a96f891e..0180041d3 100644 --- a/fyrox-core/Cargo.toml +++ b/fyrox-core/Cargo.toml @@ -33,6 +33,7 @@ parking_lot = "0.12.0" fxhash = "0.2.1" bitflags = "1.3.2" once_cell = "1.17.1" +notify = "5" [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3.53", features = ["Request", "Window", "Response", "AudioContext", "AudioBuffer", "AudioContextOptions", "AudioNode", "AudioBufferSourceNode", "AudioDestinationNode"] } diff --git a/fyrox-core/src/lib.rs b/fyrox-core/src/lib.rs index aa7651ad9..4c3b97685 100644 --- a/fyrox-core/src/lib.rs +++ b/fyrox-core/src/lib.rs @@ -43,10 +43,13 @@ pub mod sparse; pub mod sstorage; pub mod variable; pub mod visitor; +pub mod watcher; pub use futures; pub use instant; +pub use notify; + #[cfg(target_arch = "wasm32")] pub use js_sys; use std::iter::FromIterator; diff --git a/src/utils/watcher.rs b/fyrox-core/src/watcher.rs similarity index 95% rename from src/utils/watcher.rs rename to fyrox-core/src/watcher.rs index e2b254e0b..f10cb9951 100644 --- a/src/utils/watcher.rs +++ b/fyrox-core/src/watcher.rs @@ -31,7 +31,7 @@ impl FileSystemWatcher { }) } - pub(crate) fn try_get_event(&self) -> Option { + pub fn try_get_event(&self) -> Option { if let Ok(Ok(evt)) = self.receiver.try_recv() { return Some(evt); } diff --git a/fyrox-resource/Cargo.toml b/fyrox-resource/Cargo.toml index 2edc3108f..6e621bbb0 100644 --- a/fyrox-resource/Cargo.toml +++ b/fyrox-resource/Cargo.toml @@ -17,3 +17,5 @@ rust-version = "1.56" [dependencies] fyrox-core = { path = "../fyrox-core", version = "0.23.0" } fxhash = "0.2.1" +ron = "0.8.0" +serde = { version = "1", features = ["derive"] } diff --git a/src/engine/resource_manager/container/entry.rs b/fyrox-resource/src/container/entry.rs similarity index 100% rename from src/engine/resource_manager/container/entry.rs rename to fyrox-resource/src/container/entry.rs diff --git a/src/engine/resource_manager/container/event.rs b/fyrox-resource/src/container/event.rs similarity index 63% rename from src/engine/resource_manager/container/event.rs rename to fyrox-resource/src/container/event.rs index a059096aa..a518f60e4 100644 --- a/src/engine/resource_manager/container/event.rs +++ b/fyrox-resource/src/container/event.rs @@ -1,12 +1,10 @@ //! Resource event handling. -use crate::{ - core::{ - parking_lot::Mutex, - pool::{Handle, Pool}, - }, - utils::log::Log, +use crate::core::{ + parking_lot::Mutex, + pool::{Handle, Pool}, }; +use crate::UntypedResource; use std::{ path::PathBuf, sync::{mpsc::Sender, Arc}, @@ -14,48 +12,36 @@ use std::{ /// A resource event. #[derive(Clone)] -pub enum ResourceEvent -where - T: Clone, -{ +pub enum ResourceEvent { /// Occurs when a resource was fully loaded without any errors. - Loaded(T), + Loaded(UntypedResource), /// Occurs when a resource was already fully loaded, but was reloaded by an explicit request. - Reloaded(T), + Reloaded(UntypedResource), /// Occurs when a resource was just added to a resource container. - Added(T), + Added(UntypedResource), /// Occurs when a resource was removed from a resource container. Removed(PathBuf), } /// Type alias for event sender. -pub type ResourceEventSender = Sender>; +pub type ResourceEventSender = Sender; /// Event broadcaster is responsible for delivering resource events to "subscribers". #[derive(Clone)] -pub struct ResourceEventBroadcaster -where - T: Clone, -{ - container: Arc>>>, +pub struct ResourceEventBroadcaster { + container: Arc>>, } -impl Default for ResourceEventBroadcaster -where - T: Send + Clone + 'static, -{ +impl Default for ResourceEventBroadcaster { fn default() -> Self { Self::new() } } -impl ResourceEventBroadcaster -where - T: Send + Clone + 'static, -{ +impl ResourceEventBroadcaster { /// Creates new empty event broadcaster. pub fn new() -> Self { Self { @@ -64,31 +50,31 @@ where } /// Adds an event sender to the broadcaster and returns its handle. - pub fn add(&self, sender: ResourceEventSender) -> Handle> { + pub fn add(&self, sender: ResourceEventSender) -> Handle { self.container.lock().spawn(sender) } /// Removes an event sender by its handle. - pub fn remove(&self, handle: Handle>) -> ResourceEventSender { + pub fn remove(&self, handle: Handle) -> ResourceEventSender { self.container.lock().free(handle) } /// Sends an event to all "subscribers" in the broadcaster. - pub fn broadcast(&self, event: ResourceEvent) { + pub fn broadcast(&self, event: ResourceEvent) { let container = self.container.lock(); for sender in container.iter() { - Log::verify(sender.send(event.clone())); + let _ = sender.send(event.clone()); } } /// Sends a [`ResourceEvent::Loaded`] event to all "subscribers" in the broadcaster. - pub fn broadcast_loaded(&self, resource: T) { + pub fn broadcast_loaded(&self, resource: UntypedResource) { self.broadcast(ResourceEvent::Loaded(resource)) } /// Sends either a [`ResourceEvent::Loaded`] event or a [`ResourceEvent::Reloaded`] to all /// "subscribers" in the broadcaster depending on the `reload` parameter. - pub fn broadcast_loaded_or_reloaded(&self, resource: T, reload: bool) { + pub fn broadcast_loaded_or_reloaded(&self, resource: UntypedResource, reload: bool) { self.broadcast(if reload { ResourceEvent::Reloaded(resource) } else { diff --git a/src/engine/resource_manager/container/mod.rs b/fyrox-resource/src/container/mod.rs similarity index 55% rename from src/engine/resource_manager/container/mod.rs rename to fyrox-resource/src/container/mod.rs index 594397d58..5ec49368a 100644 --- a/src/engine/resource_manager/container/mod.rs +++ b/fyrox-resource/src/container/mod.rs @@ -2,20 +2,19 @@ //! resources. use crate::{ - asset::{Resource, ResourceData, ResourceLoadError}, - core::{futures::future::JoinAll, variable::InheritableVariable, VecExtensions}, - engine::resource_manager::{ - container::{ - entry::{TimedEntry, DEFAULT_RESOURCE_LIFETIME}, - event::{ResourceEvent, ResourceEventBroadcaster}, - }, - loader::ResourceLoader, - options::ImportOptions, - task::TaskPool, + container::{ + entry::{TimedEntry, DEFAULT_RESOURCE_LIFETIME}, + event::{ResourceEvent, ResourceEventBroadcaster}, }, - utils::log::Log, + core::{variable::InheritableVariable, VecExtensions}, + loader::ResourceLoader, + state::ResourceState, + task::TaskPool, + UntypedResource, }; -use std::{future::Future, ops::Deref, path::Path, sync::Arc}; +use fxhash::FxHashMap; +use std::ffi::OsString; +use std::{path::Path, sync::Arc}; pub mod entry; pub mod event; @@ -28,47 +27,40 @@ pub(crate) trait Container { /// track resources life time and remove unused timed-out resources. It also provides useful /// methods to search resources, count loaded or pending, wait until all resources are loading, /// etc. -pub struct ResourceContainer -where - T: Clone + 'static, - O: ImportOptions, -{ - resources: Vec>, - default_import_options: O, +pub struct ResourceContainer { + resources: Vec>, task_pool: Arc, - loader: Box>, + loader: FxHashMap>, /// Event broadcaster can be used to "subscribe" for events happening inside the container. - pub event_broadcaster: ResourceEventBroadcaster, + pub event_broadcaster: ResourceEventBroadcaster, } -impl ResourceContainer -where - T: Deref> + Clone + Send + Future + From> + 'static, - R: ResourceData, - E: ResourceLoadError, - O: ImportOptions, -{ - pub(crate) fn new(task_pool: Arc, loader: Box>) -> Self { +impl ResourceContainer { + pub(crate) fn new(task_pool: Arc) -> Self { Self { resources: Default::default(), - default_import_options: Default::default(), task_pool, - loader, + loader: Default::default(), event_broadcaster: ResourceEventBroadcaster::new(), } } /// Sets the loader to load resources with. - pub fn set_loader(&mut self, loader: L) + pub fn set_loader, L>( + &mut self, + extension: S, + loader: L, + ) -> Option> where - L: 'static + ResourceLoader, + L: 'static + ResourceLoader, { - self.loader = Box::new(loader); + self.loader + .insert(OsString::from(extension.as_ref()), Box::new(loader)) } /// Adds a new resource in the container. - pub fn push(&mut self, resource: T) { + pub fn push(&mut self, resource: UntypedResource) { self.event_broadcaster .broadcast(ResourceEvent::Added(resource.clone())); @@ -83,9 +75,9 @@ where /// # Complexity /// /// O(n) - pub fn find>(&self, path: P) -> Option<&T> { + pub fn find>(&self, path: P) -> Option<&UntypedResource> { for resource in self.resources.iter() { - if resource.state().path() == path.as_ref() { + if resource.0.lock().path() == path.as_ref() { return Some(&resource.value); } } @@ -98,15 +90,16 @@ where // One usage means that the resource has single owner, and that owner // is this container. Such resources have limited life time, if the time // runs out before it gets shared again, the resource will be deleted. - if resource.use_count() <= 1 { + if resource.value.use_count() <= 1 { resource.time_to_live -= dt; if resource.time_to_live <= 0.0 { - let path = resource.state().path().to_path_buf(); + let path = resource.0.lock().path().to_path_buf(); - Log::info(format!( + // TODO: Use logger when it will be moved to fyrox_core. + println!( "Resource {} destroyed because it is not used anymore!", path.display() - )); + ); self.event_broadcaster .broadcast(ResourceEvent::Removed(path)); @@ -137,7 +130,7 @@ where } /// Creates an iterator over resources in the container. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.resources.iter().map(|entry| &entry.value) } @@ -150,7 +143,7 @@ where /// Returns total amount of resources that still loading. pub fn count_pending_resources(&self) -> usize { self.resources.iter().fold(0, |counter, resource| { - if let ResourceState::Pending { .. } = *resource.state() { + if let ResourceState::Pending { .. } = *resource.0.lock() { counter + 1 } else { counter @@ -161,7 +154,7 @@ where /// Returns total amount of completely loaded resources. pub fn count_loaded_resources(&self) -> usize { self.resources.iter().fold(0, |counter, resource| { - if let ResourceState::Ok(_) = *resource.state() { + if let ResourceState::Ok(_) = *resource.0.lock() { counter + 1 } else { counter @@ -169,90 +162,50 @@ where }) } - /// Sets default import options. Keep in mind, that actual import options could defined by a - /// special file with additional extension `.options`. - pub fn set_default_import_options(&mut self, options: O) { - self.default_import_options = options; - } - - /// Locks current thread until every resource is loaded (or failed to load). - /// - /// # Platform specific - /// - /// WASM: WebAssembly uses simple loop to wait for all resources, which means - /// full load of single CPU core. - pub fn wait(&self) { - #[cfg(target_arch = "wasm32")] - { - // In case of WebAssembly, spin until everything is loaded. - loop { - let mut loaded_count = 0; - for resource in self.resources.iter() { - if !matches!(*resource.value.state(), ResourceState::Pending { .. }) { - loaded_count += 1; - } - } - if loaded_count == self.resources.len() { - break; - } - } - } - - #[cfg(not(target_arch = "wasm32"))] - { - crate::core::futures::executor::block_on(self.wait_concurrent()); - } - } - - /// Waits until all resources are loaded (or failed to load) in concurrent manner. - pub fn wait_concurrent(&self) -> JoinAll { - crate::core::futures::future::join_all(self.resources()) - } - /// Returns a set of resource handled by this container. - pub fn resources(&self) -> Vec { + pub fn resources(&self) -> Vec { self.resources.iter().map(|t| t.value.clone()).collect() } /// Tries to load a resources at a given path. - pub fn request>(&mut self, path: P) -> T { + pub fn request>(&mut self, path: P) -> UntypedResource { match self.find(path.as_ref()) { Some(existing) => existing.clone(), None => { - let resource = T::from(Resource::new(ResourceState::new_pending( - path.as_ref().to_owned(), - ))); + let resource = UntypedResource::new_pending(path.as_ref().to_owned()); + self.push(resource.clone()); - self.task_pool.spawn_task(self.loader.load( - resource.clone(), - self.default_import_options.clone(), - self.event_broadcaster.clone(), - false, - )); + self.try_spawn_loading_task(path.as_ref(), resource.clone()); resource } } } - /// Reloads a single resource. - pub fn reload_resource(&mut self, resource: T) { - if !resource.is_loading() { - resource.state().switch_to_pending_state(); + fn try_spawn_loading_task(&mut self, path: &Path, resource: UntypedResource) { + if let Some(loader) = path.extension().and_then(|ext| self.loader.get(ext)) { + self.task_pool + .spawn_task(loader.load(resource, self.event_broadcaster.clone(), false)); + } + } - self.task_pool.spawn_task(self.loader.load( - resource, - self.default_import_options.clone(), - self.event_broadcaster.clone(), - true, - )); + /// Reloads a single resource. + pub fn reload_resource(&mut self, resource: UntypedResource) { + let state = resource.0.lock(); + + if !state.is_loading() { + let path = state.path().to_path_buf(); + state.switch_to_pending_state(); + drop(state); + + self.try_spawn_loading_task(&path, resource); } } /// Reloads all resources in the container. Returns a list of resources that will be reloaded. /// You can use the list to wait until all resources are loading. - pub fn reload_resources(&mut self) -> Vec { + pub fn reload_resources(&mut self) -> Vec { let resources = self .resources .iter() @@ -260,13 +213,7 @@ where .collect::>(); for resource in resources.iter().cloned() { - resource.state().switch_to_pending_state(); - self.task_pool.spawn_task(self.loader.load( - resource, - self.default_import_options.clone(), - self.event_broadcaster.clone(), - true, - )); + self.reload_resource(resource); } resources @@ -275,8 +222,8 @@ where /// Tries to restore resource by making an attempt to request resource with path from existing /// resource instance. This method is used to restore "shallow" resources after scene /// deserialization. - pub fn try_restore_resource(&mut self, resource: &mut T) { - let path = resource.state().path().to_path_buf(); + pub fn try_restore_resource(&mut self, resource: &mut UntypedResource) { + let path = resource.0.lock().path().to_path_buf(); let new_resource = self.request(path); *resource = new_resource; } @@ -284,9 +231,9 @@ where /// Tries to restore resource by making an attempt to request resource with path from existing /// resource instance. This method is used to restore "shallow" resources after scene /// deserialization. - pub fn try_restore_optional_resource(&mut self, resource: &mut Option) { + pub fn try_restore_optional_resource(&mut self, resource: &mut Option) { if let Some(shallow_resource) = resource.as_mut() { - let path = shallow_resource.state().path().to_path_buf(); + let path = shallow_resource.0.lock().path().to_path_buf(); let new_resource = self.request(path); *shallow_resource = new_resource; } @@ -297,22 +244,16 @@ where /// deserialization. pub fn try_restore_inheritable_resource( &mut self, - inheritable_resource: &mut InheritableVariable>, + inheritable_resource: &mut InheritableVariable>, ) { if let Some(shallow_resource) = inheritable_resource.get_value_mut_silent().as_mut() { - let new_resource = self.request(shallow_resource.state().path()); + let new_resource = self.request(shallow_resource.0.lock().path()); *shallow_resource = new_resource; } } } -impl Container for ResourceContainer -where - T: Deref> + Clone + Send + Future + From>, - R: ResourceData, - E: ResourceLoadError, - O: ImportOptions, -{ +impl Container for ResourceContainer { fn try_reload_resource_from_path(&mut self, path: &Path) -> bool { if let Some(resource) = self.find(path).cloned() { self.reload_resource(resource); diff --git a/fyrox-resource/src/lib.rs b/fyrox-resource/src/lib.rs index e7c877112..30e17ee82 100644 --- a/fyrox-resource/src/lib.rs +++ b/fyrox-resource/src/lib.rs @@ -4,19 +4,20 @@ use crate::{ core::{ - parking_lot::{Mutex, MutexGuard}, + parking_lot::MutexGuard, reflect::FieldValue, uuid::{uuid, Uuid}, visitor::prelude::*, }, state::ResourceState, }; +use std::fmt::Display; use std::{ any::Any, borrow::Cow, fmt::{Debug, Formatter}, future::Future, - hash::{Hash, Hasher}, + hash::Hash, marker::PhantomData, ops::{Deref, DerefMut}, path::{Path, PathBuf}, @@ -25,10 +26,17 @@ use std::{ task::{Context, Poll}, }; +use crate::untyped::UntypedResource; pub use fyrox_core as core; pub mod constructor; -mod state; +pub mod container; +pub mod loader; +pub mod manager; +pub mod options; +pub mod state; +pub mod task; +pub mod untyped; pub const TEXTURE_RESOURCE_UUID: Uuid = uuid!("02c23a44-55fa-411a-bc39-eb7a5eadf15c"); pub const SOUND_BUFFER_RESOURCE_UUID: Uuid = uuid!("f6a077b7-c8ff-4473-a95b-0289441ea9d8"); @@ -51,9 +59,9 @@ pub trait ResourceData: 'static + Debug + Visit + Send { } /// A trait for resource load error. -pub trait ResourceLoadError: 'static + Debug + Send + Sync {} +pub trait ResourceLoadError: 'static + Debug + Display + Send + Sync {} -impl ResourceLoadError for T where T: 'static + Debug + Send + Sync {} +impl ResourceLoadError for T where T: 'static + Display + Debug + Send + Sync {} pub struct ResourceStateGuard<'a, T> where @@ -138,83 +146,44 @@ impl Default for ResourceState { } } -/// See module docs. -#[derive(Visit)] +#[derive(Visit, PartialEq, Eq, Hash, Debug)] pub struct Resource where T: ResourceData, { - state: Option>>, + state: Option, #[visit(skip)] phantom: PhantomData, } -impl Debug for Resource -where - T: ResourceData, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Resource") - } -} - -impl PartialEq for Resource -where - T: ResourceData, -{ - fn eq(&self, other: &Self) -> bool { - match (self.state.as_ref(), other.state.as_ref()) { - (Some(state), Some(other_state)) => std::ptr::eq(&**state, &**other_state), - (None, None) => true, - _ => false, - } - } -} - -impl Eq for Resource where T: ResourceData {} - -impl Hash for Resource -where - T: ResourceData, -{ - fn hash(&self, state: &mut H) { - match self.state.as_ref() { - None => state.write_u64(0), - Some(resource_state) => state.write_u64(&**resource_state as *const _ as u64), - } - } -} - impl Resource where T: ResourceData, { pub fn new_pending(path: PathBuf) -> Self { Self { - state: Some(Arc::new(Mutex::new(ResourceState::new_pending(path)))), + state: Some(UntypedResource::new_pending(path)), phantom: PhantomData, } } pub fn new_ok(data: T) -> Self { Self { - state: Some(Arc::new(Mutex::new(ResourceState::new_ok(data)))), + state: Some(UntypedResource::new_ok(data)), phantom: PhantomData, } } pub fn new_load_error(path: PathBuf, error: Option>) -> Self { Self { - state: Some(Arc::new(Mutex::new(ResourceState::new_load_error( - path, error, - )))), + state: Some(UntypedResource::new_load_error(path, error)), phantom: PhantomData, } } /// Converts self to internal value. #[inline] - pub fn into_inner(self) -> Arc> { + pub fn into_inner(self) -> UntypedResource { self.state.unwrap() } @@ -230,7 +199,7 @@ where /// Tries to lock internal mutex provides access to the state. #[inline] pub fn try_acquire_state(&self) -> Option> { - if let Some(guard) = self.state.as_ref().unwrap().try_lock() { + if let Some(guard) = self.state.as_ref().unwrap().0.try_lock() { Some(ResourceStateGuard { guard, phantom: Default::default(), @@ -241,7 +210,7 @@ where } fn state_inner(&self) -> MutexGuard<'_, ResourceState> { - self.state.as_ref().unwrap().lock() + self.state.as_ref().unwrap().0.lock() } /// Returns true if the resource is still loading. @@ -252,13 +221,13 @@ where /// Returns exact amount of users of the resource. #[inline] pub fn use_count(&self) -> usize { - Arc::strong_count(self.state.as_ref().unwrap()) + self.state.as_ref().unwrap().use_count() } /// Returns a pointer as numeric value which can be used as a hash. #[inline] pub fn key(&self) -> usize { - (&**self.state.as_ref().unwrap() as *const _) as usize + self.state.as_ref().unwrap().key() } /// Allows you to obtain reference to the resource data. @@ -305,12 +274,12 @@ where } } -impl From>> for Resource +impl From for Resource where T: ResourceData, { #[inline] - fn from(state: Arc>) -> Self { + fn from(state: UntypedResource) -> Self { Self { state: Some(state), phantom: Default::default(), @@ -319,12 +288,12 @@ where } #[allow(clippy::from_over_into)] -impl Into>> for Resource +impl Into for Resource where T: ResourceData, { #[inline] - fn into(self) -> Arc> { + fn into(self) -> UntypedResource { self.state.unwrap() } } @@ -336,22 +305,10 @@ where type Output = Result>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let state = self.as_ref().state.clone(); - match *state.unwrap().lock() { - ResourceState::Pending { ref mut wakers, .. } => { - // Collect wakers, so we'll be able to wake task when worker thread finish loading. - let cx_waker = cx.waker(); - if let Some(pos) = wakers.iter().position(|waker| waker.will_wake(cx_waker)) { - wakers[pos] = cx_waker.clone(); - } else { - wakers.push(cx_waker.clone()) - } - - Poll::Pending - } - ResourceState::LoadError { ref error, .. } => Poll::Ready(Err(error.clone())), - ResourceState::Ok(_) => Poll::Ready(Ok(self.clone())), - } + let inner = self.state.as_ref().unwrap().clone(); + std::pin::Pin::new(&mut inner) + .poll(cx) + .map(|r| r.map(|_| self.clone())) } } diff --git a/src/engine/resource_manager/loader/mod.rs b/fyrox-resource/src/loader.rs similarity index 57% rename from src/engine/resource_manager/loader/mod.rs rename to fyrox-resource/src/loader.rs index b174a5e83..a7cf2eee4 100644 --- a/src/engine/resource_manager/loader/mod.rs +++ b/fyrox-resource/src/loader.rs @@ -1,33 +1,20 @@ //! Resource loader. It manages resource loading. -use crate::engine::resource_manager::{ - container::event::ResourceEventBroadcaster, options::ImportOptions, -}; +use crate::{container::event::ResourceEventBroadcaster, UntypedResource}; use std::{future::Future, pin::Pin}; -pub mod curve; -pub mod model; -pub mod shader; -pub mod sound; -pub mod texture; - /// Future type for resource loading. See 'ResourceLoader'. #[cfg(target_arch = "wasm32")] pub type BoxedLoaderFuture = Pin>>; /// Trait for resource loading. #[cfg(target_arch = "wasm32")] -pub trait ResourceLoader -where - T: Clone, - O: ImportOptions, -{ +pub trait ResourceLoader { /// Loads or reloads a resource. fn load( &self, - resource: T, - default_import_options: O, - event_broadcaster: ResourceEventBroadcaster, + resource: UntypedResource, + event_broadcaster: ResourceEventBroadcaster, reload: bool, ) -> BoxedLoaderFuture; } @@ -38,17 +25,12 @@ pub type BoxedLoaderFuture = Pin + Send>>; /// Trait for resource loading. #[cfg(not(target_arch = "wasm32"))] -pub trait ResourceLoader: Send -where - T: Clone, - O: ImportOptions, -{ +pub trait ResourceLoader: Send { /// Loads or reloads a resource. fn load( &self, - resource: T, - default_import_options: O, - event_broadcaster: ResourceEventBroadcaster, + resource: UntypedResource, + event_broadcaster: ResourceEventBroadcaster, reload: bool, ) -> BoxedLoaderFuture; } diff --git a/fyrox-resource/src/manager.rs b/fyrox-resource/src/manager.rs new file mode 100644 index 000000000..98750f0a4 --- /dev/null +++ b/fyrox-resource/src/manager.rs @@ -0,0 +1,266 @@ +//! Resource manager controls loading and lifetime of resource in the engine. + +use crate::{ + container::{Container, ResourceContainer}, + state::ResourceState, + task::TaskPool, + Resource, ResourceData, ResourceLoadError, UntypedResource, +}; +use fyrox_core::{ + futures::future::join_all, + make_relative_path, notify, + parking_lot::{Mutex, MutexGuard}, + watcher::FileSystemWatcher, +}; +use std::{ + fmt::{Debug, Display, Formatter}, + ops::Deref, + path::Path, + sync::Arc, +}; + +/// Storage of resource containers. +pub struct ContainersStorage { + pub resources: ResourceContainer, +} + +impl ContainersStorage { + /// Wait until all resources are loaded (or failed to load). + pub fn get_wait_context(&self) -> ResourceWaitContext { + ResourceWaitContext { + resources: self.resources.resources(), + } + } +} + +/// A set of resources that can be waited for. +#[must_use] +#[derive(Default)] +pub struct ResourceWaitContext { + resources: Vec, +} + +impl ResourceWaitContext { + /// Wait until all resources are loaded (or failed to load). + #[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(), ResourceState::Pending { .. }) { + loaded_count += 1; + } + } + loaded_count == self.resources.len() + } +} + +/// See module docs. +pub struct ResourceManagerState { + containers_storage: Option, + watcher: Option, +} + +/// See module docs. +#[derive(Clone)] +pub struct ResourceManager { + state: Arc>, +} + +/// An error that may occur during texture registration. +#[derive(Debug)] +pub enum ResourceRegistrationError { + /// Texture saving has failed. + Texture(Arc), + /// Texture was in invalid state (Pending, LoadErr) + InvalidState, + /// Texture is already registered. + AlreadyRegistered, +} + +impl Display for ResourceRegistrationError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ResourceRegistrationError::Texture(v) => Display::fmt(v, f), + ResourceRegistrationError::InvalidState => { + write!(f, "A texture was in invalid state!") + } + ResourceRegistrationError::AlreadyRegistered => { + write!(f, "A texture is already registered!") + } + } + } +} + +impl From> for ResourceRegistrationError { + fn from(e: Arc) -> Self { + Self::Texture(e) + } +} + +impl ResourceManager { + /// Creates a resource manager with default settings and loaders. + pub fn new() -> Self { + let resource_manager = Self { + state: Arc::new(Mutex::new(ResourceManagerState::new())), + }; + + let task_pool = Arc::new(TaskPool::new()); + + resource_manager.state().containers_storage = Some(ContainersStorage { + resources: ResourceContainer::new(task_pool.clone()), + }); + + resource_manager + } + + /// Returns a guarded reference to internal state of resource manager. + pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> { + self.state.lock() + } + + pub fn request, T: Deref>, D: ResourceData>( + &self, + path: P, + ) -> T { + self.state().containers_mut().resources.request(path) + } + + /// Saves given texture in the specified path and registers it in resource manager, so + /// it will be accessible through it later. + pub fn register>( + &self, + resource: UntypedResource, + path: P, + ) -> Result<(), ResourceRegistrationError> { + let mut state = self.state(); + if state.containers().resources.find(path.as_ref()).is_some() { + Err(ResourceRegistrationError::AlreadyRegistered) + } else { + let mut texture_state = resource.0.lock(); + match &mut *texture_state { + ResourceState::Ok(data) => { + data.set_path(path.as_ref().to_path_buf()); + if let Err(e) = data.save() { + Err(ResourceRegistrationError::Texture(e)) + } else { + std::mem::drop(texture_state); + state.containers_mut().resources.push(resource); + Ok(()) + } + } + _ => Err(ResourceRegistrationError::InvalidState), + } + } + } + + /// Reloads all loaded resources. Normally it should never be called, because it is **very** heavy + /// method! This method is asynchronous, it uses all available CPU power to reload resources as + /// fast as possible. + pub async fn reload_resources(&self) { + let resources = self.state().containers_mut().resources.reload_resources(); + join_all(resources).await; + } +} + +impl ResourceManagerState { + pub(crate) fn new() -> Self { + Self { + containers_storage: None, + watcher: None, + } + } + + /// Sets resource watcher which will track any modifications in file system and forcing + /// the manager to reload changed resources. By default there is no watcher, since it + /// may be an undesired effect to reload resources at runtime. This is very useful thing + /// for fast iterative development. + pub fn set_watcher(&mut self, watcher: Option) { + self.watcher = watcher; + } + + /// Returns a reference to resource containers storage. + pub fn containers(&self) -> &ContainersStorage { + self.containers_storage + .as_ref() + .expect("Corrupted resource manager!") + } + + /// Returns a reference to resource containers storage. + pub fn containers_mut(&mut self) -> &mut ContainersStorage { + self.containers_storage + .as_mut() + .expect("Corrupted resource manager!") + } + + /// Returns total amount of resources in pending state. + pub fn count_pending_resources(&self) -> usize { + let containers = self.containers(); + containers.resources.count_pending_resources() + } + + /// Returns total amount of loaded resources. + pub fn count_loaded_resources(&self) -> usize { + let containers = self.containers(); + containers.resources.count_loaded_resources() + } + + /// Returns total amount of registered resources. + pub fn count_registered_resources(&self) -> usize { + let containers = self.containers(); + containers.resources.len() + } + + /// Returns percentage of loading progress. This method is useful to show progress on + /// loading screen in your game. This method could be used alone if your game depends + /// only on external resources, or if your game doing some heavy calculations this value + /// can be combined with progress of your tasks. + pub fn loading_progress(&self) -> usize { + let registered = self.count_registered_resources(); + if registered > 0 { + self.count_loaded_resources() * 100 / registered + } else { + 100 + } + } + + /// Immediately destroys all unused resources. + pub fn destroy_unused_resources(&mut self) { + let containers = self.containers_mut(); + containers.resources.destroy_unused(); + } + + /// Update resource containers and do hot-reloading. + /// + /// Resources are removed if they're not used + /// or reloaded if they have changed in disk. + /// + /// Normally, this is called from `Engine::update()`. + /// You should only call this manually if you don't use that method. + pub fn update(&mut self, dt: f32) { + let containers = self.containers_mut(); + containers.resources.update(dt); + + 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) { + let containers = self.containers_mut(); + for container in [&mut containers.resources as &mut dyn Container] { + if container.try_reload_resource_from_path(&relative_path) { + // TODO: Use logger when it will be moved to fyrox_core. + println!( + "File {} was changed, trying to reload a respective resource...", + relative_path.display() + ); + + break; + } + } + } + } + } + } + } + } +} diff --git a/src/engine/resource_manager/options.rs b/fyrox-resource/src/options.rs similarity index 64% rename from src/engine/resource_manager/options.rs rename to fyrox-resource/src/options.rs index eacb61f8c..efe9de8ac 100644 --- a/src/engine/resource_manager/options.rs +++ b/fyrox-resource/src/options.rs @@ -1,12 +1,9 @@ //! Resource import options common traits. -use crate::utils::log::{Log, MessageKind}; -use fyrox_core::{append_extension, io}; +use crate::core::{append_extension, io}; use ron::ser::PrettyConfig; -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::fs::File; -use std::path::Path; +use serde::{de::DeserializeOwned, Serialize}; +use std::{fs::File, path::Path}; /// A trait for resource import options. It provides generic functionality shared over all types of import options. pub trait ImportOptions: Serialize + DeserializeOwned + Default + Clone { @@ -33,28 +30,24 @@ where Ok(bytes) => match ron::de::from_bytes::(&bytes) { Ok(options) => Some(options), Err(e) => { - Log::writeln( - MessageKind::Error, - format!( - "Malformed options file {} for {} resource! Reason: {:?}", - settings_path.display(), - resource_path.display(), - e - ), + // TODO: Use logger when it will be moved to fyrox_core. + eprintln!( + "Malformed options file {} for {} resource! Reason: {:?}", + settings_path.display(), + resource_path.display(), + e ); None } }, Err(e) => { - Log::writeln( - MessageKind::Information, - format!( - "Unable to load options file {} for {} resource, fallback to defaults! Reason: {:?}", - settings_path.display(), - resource_path.display(), - e - ), + // TODO: Use logger when it will be moved to fyrox_core. + eprintln!( + "Unable to load options file {} for {} resource, fallback to defaults! Reason: {:?}", + settings_path.display(), + resource_path.display(), + e ); None diff --git a/fyrox-resource/src/state.rs b/fyrox-resource/src/state.rs index 061585d8d..ff7d64882 100644 --- a/fyrox-resource/src/state.rs +++ b/fyrox-resource/src/state.rs @@ -168,6 +168,10 @@ impl ResourceState { Self::Ok(Box::new(data)) } + pub fn is_loading(&self) -> bool { + matches!(self, ResourceState::Pending { .. }) + } + /// Switches the internal state of the resource to [`ResourceState::Pending`]. pub fn switch_to_pending_state(&mut self) { match self { diff --git a/src/engine/resource_manager/task.rs b/fyrox-resource/src/task.rs similarity index 100% rename from src/engine/resource_manager/task.rs rename to fyrox-resource/src/task.rs diff --git a/fyrox-resource/src/untyped.rs b/fyrox-resource/src/untyped.rs new file mode 100644 index 000000000..5b12de6d9 --- /dev/null +++ b/fyrox-resource/src/untyped.rs @@ -0,0 +1,105 @@ +use crate::{state::ResourceState, ResourceData, ResourceLoadError}; +use fyrox_core::{parking_lot::Mutex, visitor::prelude::*}; +use std::{ + fmt::{Debug, Formatter}, + future::Future, + hash::{Hash, Hasher}, + path::PathBuf, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +#[derive(Clone)] +pub struct UntypedResource(pub Arc>); + +impl Visit for UntypedResource { + // Delegating implementation. + fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { + self.0.visit(name, visitor) + } +} + +impl Default for UntypedResource { + fn default() -> Self { + Self(Arc::new(Mutex::new(ResourceState::new_pending( + Default::default(), + )))) + } +} + +impl Debug for UntypedResource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Resource") + } +} + +impl PartialEq for UntypedResource { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(&*self.0, &*other.0) + } +} + +impl Eq for UntypedResource {} + +impl Hash for UntypedResource { + fn hash(&self, state: &mut H) { + state.write_u64(&*self.0 as *const _ as u64) + } +} + +impl UntypedResource { + pub fn new_pending(path: PathBuf) -> Self { + Self(Arc::new(Mutex::new(ResourceState::new_pending(path)))) + } + + pub fn new_ok(data: T) -> Self { + Self(Arc::new(Mutex::new(ResourceState::new_ok(data)))) + } + + pub fn new_load_error(path: PathBuf, error: Option>) -> Self { + Self(Arc::new(Mutex::new(ResourceState::new_load_error( + path, error, + )))) + } + + /// Returns true if the resource is still loading. + pub fn is_loading(&self) -> bool { + matches!(*self.0.lock(), ResourceState::Pending { .. }) + } + + /// Returns exact amount of users of the resource. + #[inline] + pub fn use_count(&self) -> usize { + Arc::strong_count(&self.0) + } + + /// Returns a pointer as numeric value which can be used as a hash. + #[inline] + pub fn key(&self) -> usize { + (&*self.0 as *const _) as usize + } +} + +impl Future for UntypedResource { + type Output = Result>>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state = self.0.clone(); + match *state.lock() { + ResourceState::Pending { ref mut wakers, .. } => { + // Collect wakers, so we'll be able to wake task when worker thread finish loading. + let cx_waker = cx.waker(); + if let Some(pos) = wakers.iter().position(|waker| waker.will_wake(cx_waker)) { + wakers[pos] = cx_waker.clone(); + } else { + wakers.push(cx_waker.clone()) + } + + Poll::Pending + } + ResourceState::LoadError { ref error, .. } => Poll::Ready(Err(error.clone())), + ResourceState::Ok(_) => Poll::Ready(Ok(self.clone())), + } + } +} diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 2860c2803..718570ffb 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -5,7 +5,7 @@ pub mod error; pub mod executor; -pub mod resource_manager; +pub mod resource_loaders; use crate::{ core::{algebra::Vector2, futures::executor::block_on, instant, pool::Handle}, diff --git a/src/engine/resource_manager/loader/curve.rs b/src/engine/resource_loaders/curve.rs similarity index 97% rename from src/engine/resource_manager/loader/curve.rs rename to src/engine/resource_loaders/curve.rs index 52d6d1d2e..6210e7aad 100644 --- a/src/engine/resource_manager/loader/curve.rs +++ b/src/engine/resource_loaders/curve.rs @@ -1,7 +1,7 @@ //! Curve loader. use crate::{ - engine::resource_manager::{ + asset::{ container::event::ResourceEventBroadcaster, loader::{BoxedLoaderFuture, ResourceLoader}, }, diff --git a/src/engine/resource_loaders/mod.rs b/src/engine/resource_loaders/mod.rs new file mode 100644 index 000000000..a0ac09461 --- /dev/null +++ b/src/engine/resource_loaders/mod.rs @@ -0,0 +1,5 @@ +pub mod curve; +pub mod model; +pub mod shader; +pub mod sound; +pub mod texture; diff --git a/src/engine/resource_manager/loader/model.rs b/src/engine/resource_loaders/model.rs similarity index 95% rename from src/engine/resource_manager/loader/model.rs rename to src/engine/resource_loaders/model.rs index 7853ea180..9e3c17bed 100644 --- a/src/engine/resource_manager/loader/model.rs +++ b/src/engine/resource_loaders/model.rs @@ -1,13 +1,13 @@ //! Model loader. -use crate::engine::SerializationContext; use crate::{ - engine::resource_manager::{ + asset::{ container::event::ResourceEventBroadcaster, loader::{BoxedLoaderFuture, ResourceLoader}, + manager::ResourceManager, options::try_get_import_settings, - ResourceManager, }, + engine::SerializationContext, resource::model::{Model, ModelData, ModelImportOptions}, utils::log::Log, }; diff --git a/src/engine/resource_manager/loader/shader.rs b/src/engine/resource_loaders/shader.rs similarity index 97% rename from src/engine/resource_manager/loader/shader.rs rename to src/engine/resource_loaders/shader.rs index 94221a472..f770a7fa9 100644 --- a/src/engine/resource_manager/loader/shader.rs +++ b/src/engine/resource_loaders/shader.rs @@ -1,7 +1,7 @@ //! Shader loader. use crate::{ - engine::resource_manager::{ + asset::{ container::event::ResourceEventBroadcaster, loader::{BoxedLoaderFuture, ResourceLoader}, }, diff --git a/src/engine/resource_manager/loader/sound.rs b/src/engine/resource_loaders/sound.rs similarity index 98% rename from src/engine/resource_manager/loader/sound.rs rename to src/engine/resource_loaders/sound.rs index 131267a85..9c029c60d 100644 --- a/src/engine/resource_manager/loader/sound.rs +++ b/src/engine/resource_loaders/sound.rs @@ -1,12 +1,12 @@ //! Sound buffer loader. use crate::{ - core::reflect::prelude::*, - engine::resource_manager::{ + asset::{ container::event::ResourceEventBroadcaster, loader::{BoxedLoaderFuture, ResourceLoader}, options::{try_get_import_settings, ImportOptions}, }, + core::reflect::prelude::*, utils::log::Log, }; use fyrox_sound::buffer::{ diff --git a/src/engine/resource_manager/loader/texture.rs b/src/engine/resource_loaders/texture.rs similarity index 98% rename from src/engine/resource_manager/loader/texture.rs rename to src/engine/resource_loaders/texture.rs index b7244775a..31b804218 100644 --- a/src/engine/resource_manager/loader/texture.rs +++ b/src/engine/resource_loaders/texture.rs @@ -1,12 +1,12 @@ //! Texture loader. use crate::{ - core::instant, - engine::resource_manager::{ + asset::{ container::event::ResourceEventBroadcaster, loader::{BoxedLoaderFuture, ResourceLoader}, options::try_get_import_settings, }, + core::instant, resource::texture::{Texture, TextureData, TextureImportOptions}, utils::log::Log, }; diff --git a/src/engine/resource_manager/mod.rs b/src/engine/resource_manager/mod.rs deleted file mode 100644 index 60f0a5d9c..000000000 --- a/src/engine/resource_manager/mod.rs +++ /dev/null @@ -1,533 +0,0 @@ -//! Resource manager controls loading and lifetime of resource in the engine. - -use crate::{ - asset::{Resource, ResourceData, ResourceLoadError}, - core::{ - futures::future::join_all, - make_relative_path, - parking_lot::{Mutex, MutexGuard}, - }, - engine::{ - resource_manager::{ - container::{Container, ResourceContainer}, - loader::{ - curve::CurveLoader, - model::ModelLoader, - shader::ShaderLoader, - sound::{SoundBufferImportOptions, SoundBufferLoader}, - texture::TextureLoader, - ResourceLoader, - }, - task::TaskPool, - }, - SerializationContext, - }, - material::shader::{Shader, ShaderImportOptions}, - resource::{ - curve::{CurveImportOptions, CurveResource}, - model::{Model, ModelImportOptions}, - texture::{Texture, TextureError, TextureImportOptions, TextureState}, - }, - utils::{log::Log, watcher::FileSystemWatcher}, -}; -use fyrox_sound::buffer::SoundBufferResource; -use std::{ - fmt::{Debug, Display, Formatter}, - future::Future, - ops::Deref, - path::Path, - sync::Arc, -}; - -pub mod container; -pub mod loader; -pub mod options; -mod task; - -/// Storage of resource containers. -pub struct ContainersStorage { - /// Container for texture resources. - pub textures: ResourceContainer, - - /// Container for model resources. - pub models: ResourceContainer, - - /// Container for sound buffer resources. - pub sound_buffers: ResourceContainer, - - /// Container for shader resources. - pub shaders: ResourceContainer, - - /// Container for curve resources. - pub curves: ResourceContainer, -} - -impl ContainersStorage { - /// Sets a custom texture loader. - pub fn set_texture_loader(&mut self, loader: L) - where - L: 'static + ResourceLoader, - { - self.textures.set_loader(loader); - } - - /// Sets a custom model loader. - pub fn set_model_loader(&mut self, loader: L) - where - L: 'static + ResourceLoader, - { - self.models.set_loader(loader); - } - - /// Sets a custom sound buffer loader. - pub fn set_sound_buffer_loader(&mut self, loader: L) - where - L: 'static + ResourceLoader, - { - self.sound_buffers.set_loader(loader); - } - - /// Sets a custom shader loader. - pub fn set_shader_loader(&mut self, loader: L) - where - L: 'static + ResourceLoader, - { - self.shaders.set_loader(loader); - } - - /// Sets a custom curve loader. - pub fn set_curve_loader(&mut self, loader: L) - where - L: 'static + ResourceLoader, - { - self.curves.set_loader(loader); - } - - /// Wait until all resources are loaded (or failed to load). - pub fn get_wait_context(&self) -> ResourceWaitContext { - ResourceWaitContext { - models: self.models.resources(), - curves: self.curves.resources(), - shaders: self.shaders.resources(), - textures: self.textures.resources(), - sound_buffers: self.sound_buffers.resources(), - } - } -} - -/// A set of resources that can be waited for. -#[must_use] -#[derive(Default)] -pub struct ResourceWaitContext { - models: Vec, - curves: Vec, - shaders: Vec, - textures: Vec, - sound_buffers: Vec, -} - -impl ResourceWaitContext { - /// Wait until all resources are loaded (or failed to load). - #[must_use] - pub fn is_all_loaded(&self) -> bool { - fn check_container(container: &[T]) -> bool - where - T: Deref> - + Clone - + Send - + Future - + From> - + 'static, - R: ResourceData, - E: ResourceLoadError, - { - let mut loaded_count = 0; - for resource in container.iter() { - if !matches!(*resource.state(), ResourceState::Pending { .. }) { - loaded_count += 1; - } - } - loaded_count == container.len() - } - - check_container(&self.models) - && check_container(&self.curves) - && check_container(&self.shaders) - && check_container(&self.textures) - && check_container(&self.sound_buffers) - } -} - -/// See module docs. -pub struct ResourceManagerState { - containers_storage: Option, - watcher: Option, -} - -/// See module docs. -#[derive(Clone)] -pub struct ResourceManager { - state: Arc>, -} - -/// An error that may occur during texture registration. -#[derive(Debug)] -pub enum TextureRegistrationError { - /// Texture saving has failed. - Texture(TextureError), - /// Texture was in invalid state (Pending, LoadErr) - InvalidState, - /// Texture is already registered. - AlreadyRegistered, -} - -impl Display for TextureRegistrationError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - TextureRegistrationError::Texture(v) => Display::fmt(v, f), - TextureRegistrationError::InvalidState => { - write!(f, "A texture was in invalid state!") - } - TextureRegistrationError::AlreadyRegistered => { - write!(f, "A texture is already registered!") - } - } - } -} - -impl From for TextureRegistrationError { - fn from(e: TextureError) -> Self { - Self::Texture(e) - } -} - -impl ResourceManager { - /// Creates a resource manager with default settings and loaders. - pub fn new(serialization_context: Arc) -> Self { - let resource_manager = Self { - state: Arc::new(Mutex::new(ResourceManagerState::new())), - }; - - let task_pool = Arc::new(TaskPool::new()); - - resource_manager.state().containers_storage = Some(ContainersStorage { - textures: ResourceContainer::new(task_pool.clone(), Box::new(TextureLoader)), - models: ResourceContainer::new( - task_pool.clone(), - Box::new(ModelLoader { - resource_manager: resource_manager.clone(), - serialization_context, - }), - ), - sound_buffers: ResourceContainer::new(task_pool.clone(), Box::new(SoundBufferLoader)), - shaders: ResourceContainer::new(task_pool.clone(), Box::new(ShaderLoader)), - curves: ResourceContainer::new(task_pool, Box::new(CurveLoader)), - }); - - resource_manager - } - - /// Returns a guarded reference to internal state of resource manager. - pub fn state(&self) -> MutexGuard<'_, ResourceManagerState> { - self.state.lock() - } - - /// Tries to load texture from given path or get instance of existing, if any. This method is asynchronous, - /// it immediately returns a texture which can be shared across multiple places, the loading may fail, but it is - /// internal state of the texture. The engine does not care if texture failed to load, it just won't use - /// such texture during the rendering. If you need to access internals of the texture you have to get state first - /// and then use pattern matching to get TextureData which contains actual texture data. - /// - /// # Import options - /// - /// It is possible to define custom import options. Using import options you could set desired compression quality, - /// filtering, wrapping, etc. Import options should be defined in a separate file with the same name as the source - /// texture, but with additional extension `options`. For example you have a `foo.jpg` texture, a file with import - /// options should be called `foo.jpg.options`. It's content may look something like this: - /// - /// ```text - /// ( - /// minification_filter: Linear, - /// magnification_filter: Linear, - /// s_wrap_mode: Repeat, - /// t_wrap_mode: ClampToEdge, - /// anisotropy: 8.0, - /// compression: NoCompression, - /// ) - /// ``` - /// - /// Usually there is no need to change this file manually, it can be modified from the editor using the Asset Browser. - /// When there is no import options file, the engine will use texture import options defined in the resource manager. - /// See [set_default_import_options](ResourceContainer::set_default_import_options) for more info. - /// - /// # Async/.await - /// - /// Each Texture implements Future trait and can be used in async contexts. - /// - /// # Supported formats - /// - /// To load images and decode them, Fyrox uses image create which supports following image formats: png, tga, bmp, dds, - /// jpg, gif, tiff, dxt. - pub fn request_texture>(&self, path: P) -> Texture { - self.state().containers_mut().textures.request(path) - } - - /// Saves given texture in the specified path and registers it in resource manager, so - /// it will be accessible through it later. - pub fn register_texture>( - &self, - texture: Texture, - path: P, - ) -> Result<(), TextureRegistrationError> { - let mut state = self.state(); - if state.containers().textures.find(path.as_ref()).is_some() { - Err(TextureRegistrationError::AlreadyRegistered) - } else { - let mut texture_state = texture.state(); - match &mut *texture_state { - TextureState::Ok(texture_data) => { - texture_data.set_path(path); - if let Err(e) = texture_data.save() { - Err(TextureRegistrationError::Texture(e)) - } else { - std::mem::drop(texture_state); - state.containers_mut().textures.push(texture); - Ok(()) - } - } - _ => Err(TextureRegistrationError::InvalidState), - } - } - } - - /// Tries to load new model resource from given path or get instance of existing, if any. - /// This method is asynchronous, it immediately returns a model which can be shared across - /// multiple places, the loading may fail, but it is internal state of the model. If you need - /// to access internals of the texture you have to get state first and then use pattern matching - /// to get ModelData which contains actual model data. - /// - /// # Async/.await - /// - /// Each model implements Future trait and can be used in async contexts. - /// - /// # Supported formats - /// - /// Currently only FBX (common format in game industry for storing complex 3d models) - /// and RGS (native Fyroxed format) formats are supported. - pub fn request_model>(&self, path: P) -> Model { - self.state().containers_mut().models.request(path) - } - - /// Tries to load new sound buffer from given path or get instance of existing, if any. - /// This method is **blocking**, so it will block current thread until sound buffer is - /// loading. On failure it returns None and prints failure reason to log. - /// - /// # Supported formats - /// - /// Currently only WAV and OGG are supported. - pub fn request_sound_buffer>(&self, path: P) -> SoundBufferResource { - self.state().containers_mut().sound_buffers.request(path) - } - - /// Tries to load a new shader resource from given path or get instance of existing, if any. - /// This method is asynchronous, it immediately returns a shader which can be shared across - /// multiple places, the loading may fail, but it is internal state of the shader. - /// - /// # Async/.await - /// - /// Each shader implements Future trait and can be used in async contexts. - pub fn request_shader>(&self, path: P) -> Shader { - self.state().containers_mut().shaders.request(path) - } - - /// Tries to load a new curve resource from given path or get instance of existing, if any. - /// This method is asynchronous, it immediately returns a curve which can be shared across - /// multiple places, the loading may fail, but it is internal state of the curve resource. - /// - /// # Async/.await - /// - /// Each curve implements Future trait and can be used in async contexts. - pub fn request_curve>(&self, path: P) -> CurveResource { - self.state().containers_mut().curves.request(path) - } - - /// Reloads every loaded texture. This method is asynchronous, internally it uses thread pool - /// to run reload on separate thread per texture. - pub async fn reload_textures(&self) { - let resources = self.state().containers_mut().textures.reload_resources(); - join_all(resources).await; - } - - /// Reloads every loaded model. This method is asynchronous, internally it uses thread pool - /// to run reload on separate thread per model. - pub async fn reload_models(&self) { - let resources = self.state().containers_mut().models.reload_resources(); - join_all(resources).await; - } - - /// Reloads every loaded shader. This method is asynchronous, internally it uses thread pool - /// to run reload on separate thread per shader. - pub async fn reload_shaders(&self) { - let resources = self.state().containers_mut().shaders.reload_resources(); - join_all(resources).await; - } - - /// Reloads every loaded curve resource. This method is asynchronous, internally it uses thread pool - /// to run reload on separate thread per resource. - pub async fn reload_curve_resources(&self) { - let resources = self.state().containers_mut().curves.reload_resources(); - join_all(resources).await; - } - - /// Reloads every loaded sound buffer. This method is asynchronous, internally it uses thread pool - /// to run reload on separate thread per sound buffer. - pub async fn reload_sound_buffers(&self) { - let resources = self - .state() - .containers_mut() - .sound_buffers - .reload_resources(); - join_all(resources).await; - } - - /// Reloads all loaded resources. Normally it should never be called, because it is **very** heavy - /// method! This method is asynchronous, it uses all available CPU power to reload resources as - /// fast as possible. - pub async fn reload_resources(&self) { - crate::core::futures::join!( - self.reload_textures(), - self.reload_models(), - self.reload_sound_buffers(), - self.reload_shaders(), - self.reload_curve_resources(), - ); - } -} - -impl ResourceManagerState { - pub(in crate::engine) fn new() -> Self { - Self { - containers_storage: None, - watcher: None, - } - } - - /// Sets resource watcher which will track any modifications in file system and forcing - /// the manager to reload changed resources. By default there is no watcher, since it - /// may be an undesired effect to reload resources at runtime. This is very useful thing - /// for fast iterative development. - pub fn set_watcher(&mut self, watcher: Option) { - self.watcher = watcher; - } - - /// Returns a reference to resource containers storage. - pub fn containers(&self) -> &ContainersStorage { - self.containers_storage - .as_ref() - .expect("Corrupted resource manager!") - } - - /// Returns a reference to resource containers storage. - pub fn containers_mut(&mut self) -> &mut ContainersStorage { - self.containers_storage - .as_mut() - .expect("Corrupted resource manager!") - } - - /// Returns total amount of resources in pending state. - pub fn count_pending_resources(&self) -> usize { - let containers = self.containers(); - containers.textures.count_pending_resources() - + containers.sound_buffers.count_pending_resources() - + containers.models.count_pending_resources() - + containers.shaders.count_pending_resources() - + containers.curves.count_pending_resources() - } - - /// Returns total amount of loaded resources. - pub fn count_loaded_resources(&self) -> usize { - let containers = self.containers(); - containers.textures.count_loaded_resources() - + containers.sound_buffers.count_loaded_resources() - + containers.models.count_loaded_resources() - + containers.shaders.count_loaded_resources() - + containers.curves.count_loaded_resources() - } - - /// Returns total amount of registered resources. - pub fn count_registered_resources(&self) -> usize { - let containers = self.containers(); - containers.textures.len() - + containers.sound_buffers.len() - + containers.models.len() - + containers.shaders.len() - + containers.curves.len() - } - - /// Returns percentage of loading progress. This method is useful to show progress on - /// loading screen in your game. This method could be used alone if your game depends - /// only on external resources, or if your game doing some heavy calculations this value - /// can be combined with progress of your tasks. - pub fn loading_progress(&self) -> usize { - let registered = self.count_registered_resources(); - if registered > 0 { - self.count_loaded_resources() * 100 / registered - } else { - 100 - } - } - - /// Immediately destroys all unused resources. - pub fn destroy_unused_resources(&mut self) { - let containers = self.containers_mut(); - containers.sound_buffers.destroy_unused(); - containers.models.destroy_unused(); - containers.textures.destroy_unused(); - containers.shaders.destroy_unused(); - containers.curves.destroy_unused(); - } - - /// Update resource containers and do hot-reloading. - /// - /// Resources are removed if they're not used - /// or reloaded if they have changed in disk. - /// - /// Normally, this is called from `Engine::update()`. - /// You should only call this manually if you don't use that method. - pub fn update(&mut self, dt: f32) { - let containers = self.containers_mut(); - containers.textures.update(dt); - containers.models.update(dt); - containers.sound_buffers.update(dt); - containers.shaders.update(dt); - containers.curves.update(dt); - - 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) { - let containers = self.containers_mut(); - for container in [ - &mut containers.textures as &mut dyn Container, - &mut containers.models as &mut dyn Container, - &mut containers.sound_buffers as &mut dyn Container, - &mut containers.shaders as &mut dyn Container, - &mut containers.curves as &mut dyn Container, - ] { - if container.try_reload_resource_from_path(&relative_path) { - Log::info(format!( - "File {} was changed, trying to reload a respective resource...", - relative_path.display() - )); - - break; - } - } - } - } - } - } - } - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 4279fadc7..db73c1816 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -10,7 +10,6 @@ pub mod log; pub mod navmesh; pub mod raw_mesh; pub mod uvgen; -pub mod watcher; use crate::{ core::algebra::Vector2,