moved resource manager into fyrox_resource

This commit is contained in:
Dmitry
2023-04-23 09:59:54 +03:00
parent 91122e2917
commit d42c7aa0cb
25 changed files with 537 additions and 827 deletions
-1
View File
@@ -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"] }
+1 -1
View File
@@ -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},
};
+1
View File
@@ -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"] }
+3
View File
@@ -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;
@@ -31,7 +31,7 @@ impl FileSystemWatcher {
})
}
pub(crate) fn try_get_event(&self) -> Option<Event> {
pub fn try_get_event(&self) -> Option<Event> {
if let Ok(Ok(evt)) = self.receiver.try_recv() {
return Some(evt);
}
+2
View File
@@ -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"] }
@@ -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<T>
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<T> = Sender<ResourceEvent<T>>;
pub type ResourceEventSender = Sender<ResourceEvent>;
/// Event broadcaster is responsible for delivering resource events to "subscribers".
#[derive(Clone)]
pub struct ResourceEventBroadcaster<T>
where
T: Clone,
{
container: Arc<Mutex<Pool<ResourceEventSender<T>>>>,
pub struct ResourceEventBroadcaster {
container: Arc<Mutex<Pool<ResourceEventSender>>>,
}
impl<T> Default for ResourceEventBroadcaster<T>
where
T: Send + Clone + 'static,
{
impl Default for ResourceEventBroadcaster {
fn default() -> Self {
Self::new()
}
}
impl<T> ResourceEventBroadcaster<T>
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<T>) -> Handle<ResourceEventSender<T>> {
pub fn add(&self, sender: ResourceEventSender) -> Handle<ResourceEventSender> {
self.container.lock().spawn(sender)
}
/// Removes an event sender by its handle.
pub fn remove(&self, handle: Handle<ResourceEventSender<T>>) -> ResourceEventSender<T> {
pub fn remove(&self, handle: Handle<ResourceEventSender>) -> ResourceEventSender {
self.container.lock().free(handle)
}
/// Sends an event to all "subscribers" in the broadcaster.
pub fn broadcast(&self, event: ResourceEvent<T>) {
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 {
@@ -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<T, O>
where
T: Clone + 'static,
O: ImportOptions,
{
resources: Vec<TimedEntry<T>>,
default_import_options: O,
pub struct ResourceContainer {
resources: Vec<TimedEntry<UntypedResource>>,
task_pool: Arc<TaskPool>,
loader: Box<dyn ResourceLoader<T, O>>,
loader: FxHashMap<OsString, Box<dyn ResourceLoader>>,
/// Event broadcaster can be used to "subscribe" for events happening inside the container.
pub event_broadcaster: ResourceEventBroadcaster<T>,
pub event_broadcaster: ResourceEventBroadcaster,
}
impl<T, R, E, O> ResourceContainer<T, O>
where
T: Deref<Target = Resource<R, E>> + Clone + Send + Future + From<Resource<R, E>> + 'static,
R: ResourceData,
E: ResourceLoadError,
O: ImportOptions,
{
pub(crate) fn new(task_pool: Arc<TaskPool>, loader: Box<dyn ResourceLoader<T, O>>) -> Self {
impl ResourceContainer {
pub(crate) fn new(task_pool: Arc<TaskPool>) -> 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<L>(&mut self, loader: L)
pub fn set_loader<S: AsRef<str>, L>(
&mut self,
extension: S,
loader: L,
) -> Option<Box<dyn ResourceLoader>>
where
L: 'static + ResourceLoader<T, O>,
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<P: AsRef<Path>>(&self, path: P) -> Option<&T> {
pub fn find<P: AsRef<Path>>(&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<Item = &T> {
pub fn iter(&self) -> impl Iterator<Item = &UntypedResource> {
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<T> {
crate::core::futures::future::join_all(self.resources())
}
/// Returns a set of resource handled by this container.
pub fn resources(&self) -> Vec<T> {
pub fn resources(&self) -> Vec<UntypedResource> {
self.resources.iter().map(|t| t.value.clone()).collect()
}
/// Tries to load a resources at a given path.
pub fn request<P: AsRef<Path>>(&mut self, path: P) -> T {
pub fn request<P: AsRef<Path>>(&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<T> {
pub fn reload_resources(&mut self) -> Vec<UntypedResource> {
let resources = self
.resources
.iter()
@@ -260,13 +213,7 @@ where
.collect::<Vec<_>>();
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<T>) {
pub fn try_restore_optional_resource(&mut self, resource: &mut Option<UntypedResource>) {
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<Option<T>>,
inheritable_resource: &mut InheritableVariable<Option<UntypedResource>>,
) {
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<T, R, E, O> Container for ResourceContainer<T, O>
where
T: Deref<Target = Resource<R, E>> + Clone + Send + Future + From<Resource<R, E>>,
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);
+31 -74
View File
@@ -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<T> ResourceLoadError for T where T: 'static + Debug + Send + Sync {}
impl<T> 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<T>
where
T: ResourceData,
{
state: Option<Arc<Mutex<ResourceState>>>,
state: Option<UntypedResource>,
#[visit(skip)]
phantom: PhantomData<T>,
}
impl<T> Debug for Resource<T>
where
T: ResourceData,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Resource")
}
}
impl<T> PartialEq for Resource<T>
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<T> Eq for Resource<T> where T: ResourceData {}
impl<T> Hash for Resource<T>
where
T: ResourceData,
{
fn hash<H: Hasher>(&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<T> Resource<T>
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<Arc<dyn ResourceLoadError>>) -> 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<Mutex<ResourceState>> {
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<ResourceStateGuard<'_, T>> {
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<T> From<Arc<Mutex<ResourceState>>> for Resource<T>
impl<T> From<UntypedResource> for Resource<T>
where
T: ResourceData,
{
#[inline]
fn from(state: Arc<Mutex<ResourceState>>) -> Self {
fn from(state: UntypedResource) -> Self {
Self {
state: Some(state),
phantom: Default::default(),
@@ -319,12 +288,12 @@ where
}
#[allow(clippy::from_over_into)]
impl<T> Into<Arc<Mutex<ResourceState>>> for Resource<T>
impl<T> Into<UntypedResource> for Resource<T>
where
T: ResourceData,
{
#[inline]
fn into(self) -> Arc<Mutex<ResourceState>> {
fn into(self) -> UntypedResource {
self.state.unwrap()
}
}
@@ -336,22 +305,10 @@ where
type Output = Result<Self, Option<Arc<dyn ResourceLoadError>>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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()))
}
}
@@ -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<Box<dyn Future<Output = ()>>>;
/// Trait for resource loading.
#[cfg(target_arch = "wasm32")]
pub trait ResourceLoader<T, O>
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<T>,
resource: UntypedResource,
event_broadcaster: ResourceEventBroadcaster,
reload: bool,
) -> BoxedLoaderFuture;
}
@@ -38,17 +25,12 @@ pub type BoxedLoaderFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
/// Trait for resource loading.
#[cfg(not(target_arch = "wasm32"))]
pub trait ResourceLoader<T, O>: 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<T>,
resource: UntypedResource,
event_broadcaster: ResourceEventBroadcaster,
reload: bool,
) -> BoxedLoaderFuture;
}
+266
View File
@@ -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<UntypedResource>,
}
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<ContainersStorage>,
watcher: Option<FileSystemWatcher>,
}
/// See module docs.
#[derive(Clone)]
pub struct ResourceManager {
state: Arc<Mutex<ResourceManagerState>>,
}
/// An error that may occur during texture registration.
#[derive(Debug)]
pub enum ResourceRegistrationError {
/// Texture saving has failed.
Texture(Arc<dyn ResourceLoadError>),
/// 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<Arc<dyn ResourceLoadError>> for ResourceRegistrationError {
fn from(e: Arc<dyn ResourceLoadError>) -> 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<P: AsRef<Path>, T: Deref<Target = Resource<D>>, 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<P: AsRef<Path>>(
&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<FileSystemWatcher>) {
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;
}
}
}
}
}
}
}
}
}
@@ -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::<T>(&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
+4
View File
@@ -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 {
+105
View File
@@ -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<Mutex<ResourceState>>);
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<H: Hasher>(&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<T: ResourceData>(data: T) -> Self {
Self(Arc::new(Mutex::new(ResourceState::new_ok(data))))
}
pub fn new_load_error(path: PathBuf, error: Option<Arc<dyn ResourceLoadError>>) -> 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<Self, Option<Arc<dyn ResourceLoadError>>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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())),
}
}
}
+1 -1
View File
@@ -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},
@@ -1,7 +1,7 @@
//! Curve loader.
use crate::{
engine::resource_manager::{
asset::{
container::event::ResourceEventBroadcaster,
loader::{BoxedLoaderFuture, ResourceLoader},
},
+5
View File
@@ -0,0 +1,5 @@
pub mod curve;
pub mod model;
pub mod shader;
pub mod sound;
pub mod texture;
@@ -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,
};
@@ -1,7 +1,7 @@
//! Shader loader.
use crate::{
engine::resource_manager::{
asset::{
container::event::ResourceEventBroadcaster,
loader::{BoxedLoaderFuture, ResourceLoader},
},
@@ -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::{
@@ -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,
};
-533
View File
@@ -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<Texture, TextureImportOptions>,
/// Container for model resources.
pub models: ResourceContainer<Model, ModelImportOptions>,
/// Container for sound buffer resources.
pub sound_buffers: ResourceContainer<SoundBufferResource, SoundBufferImportOptions>,
/// Container for shader resources.
pub shaders: ResourceContainer<Shader, ShaderImportOptions>,
/// Container for curve resources.
pub curves: ResourceContainer<CurveResource, CurveImportOptions>,
}
impl ContainersStorage {
/// Sets a custom texture loader.
pub fn set_texture_loader<L>(&mut self, loader: L)
where
L: 'static + ResourceLoader<Texture, TextureImportOptions>,
{
self.textures.set_loader(loader);
}
/// Sets a custom model loader.
pub fn set_model_loader<L>(&mut self, loader: L)
where
L: 'static + ResourceLoader<Model, ModelImportOptions>,
{
self.models.set_loader(loader);
}
/// Sets a custom sound buffer loader.
pub fn set_sound_buffer_loader<L>(&mut self, loader: L)
where
L: 'static + ResourceLoader<SoundBufferResource, SoundBufferImportOptions>,
{
self.sound_buffers.set_loader(loader);
}
/// Sets a custom shader loader.
pub fn set_shader_loader<L>(&mut self, loader: L)
where
L: 'static + ResourceLoader<Shader, ShaderImportOptions>,
{
self.shaders.set_loader(loader);
}
/// Sets a custom curve loader.
pub fn set_curve_loader<L>(&mut self, loader: L)
where
L: 'static + ResourceLoader<CurveResource, CurveImportOptions>,
{
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<Model>,
curves: Vec<CurveResource>,
shaders: Vec<Shader>,
textures: Vec<Texture>,
sound_buffers: Vec<SoundBufferResource>,
}
impl ResourceWaitContext {
/// Wait until all resources are loaded (or failed to load).
#[must_use]
pub fn is_all_loaded(&self) -> bool {
fn check_container<T, R, E>(container: &[T]) -> bool
where
T: Deref<Target = Resource<R, E>>
+ Clone
+ Send
+ Future
+ From<Resource<R, E>>
+ '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<ContainersStorage>,
watcher: Option<FileSystemWatcher>,
}
/// See module docs.
#[derive(Clone)]
pub struct ResourceManager {
state: Arc<Mutex<ResourceManagerState>>,
}
/// 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<TextureError> 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<SerializationContext>) -> 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<P: AsRef<Path>>(&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<P: AsRef<Path>>(
&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<FileSystemWatcher>) {
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;
}
}
}
}
}
}
}
}
}
-1
View File
@@ -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,