mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
moved resource manager into fyrox_resource
This commit is contained in:
@@ -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
@@ -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},
|
||||
};
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"] }
|
||||
|
||||
+19
-33
@@ -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 {
|
||||
+67
-126
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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},
|
||||
},
|
||||
@@ -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
-1
@@ -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::{
|
||||
+2
-2
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user