use crate::builtin::GString;
use crate::classes::{Resource, ResourceLoader, ResourceSaver};
use crate::global::{Error as GodotError, suppress_godot_errors};
use crate::meta::error::IoError;
use crate::meta::{AsArg, arg_into_ref};
use crate::obj::{Gd, Inherits, Singleton};
#[inline]
pub fn load<T>(path: impl AsArg<GString>) -> Gd<T>
where
T: Inherits<Resource>,
{
arg_into_ref!(path);
load_impl(path).unwrap_or_else(|err| panic!("failed to load resource at '{path}': {err}"))
}
#[inline]
pub fn try_load<T>(path: impl AsArg<GString>) -> Result<Gd<T>, IoError>
where
T: Inherits<Resource>,
{
arg_into_ref!(path);
load_impl(path)
}
#[inline]
#[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
pub async fn load_threaded<T>(path: impl AsArg<GString>) -> Gd<T>
where
T: Inherits<Resource>,
{
arg_into_ref!(path);
load_threaded_impl(path)
.await
.unwrap_or_else(|err| panic!("failed to load resource at '{path}': {err}"))
}
#[inline]
#[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
pub async fn try_load_threaded<T>(path: impl AsArg<GString>) -> Result<Gd<T>, IoError>
where
T: Inherits<Resource>,
{
arg_into_ref!(path);
load_threaded_impl(path).await
}
#[inline]
pub fn save<T>(obj: &Gd<T>, path: impl AsArg<GString>)
where
T: Inherits<Resource>,
{
arg_into_ref!(path);
save_impl(obj, path)
.unwrap_or_else(|err| panic!("failed to save resource at path '{path}': {err}"));
}
#[inline]
pub fn try_save<T>(obj: &Gd<T>, path: impl AsArg<GString>) -> Result<(), IoError>
where
T: Inherits<Resource>,
{
arg_into_ref!(path);
save_impl(obj, path)
}
fn load_impl<T>(path: &GString) -> Result<Gd<T>, IoError>
where
T: Inherits<Resource>,
{
let _guard = suppress_godot_errors();
let loaded = ResourceLoader::singleton()
.load_ex(path)
.type_hint(&T::class_id().to_gstring())
.done();
match loaded {
Some(res) => match res.try_cast::<T>() {
Ok(obj) => Ok(obj),
Err(_) => Err(IoError::loading_cast(
T::class_id().to_string(),
path.to_string(),
)),
},
None => Err(IoError::loading(
T::class_id().to_string(),
path.to_string(),
)),
}
}
#[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
async fn load_threaded_impl<T>(path: &GString) -> Result<Gd<T>, IoError>
where
T: Inherits<Resource>,
{
use crate::classes::resource_loader::ThreadLoadStatus;
use crate::classes::{Engine, SceneTree};
let mut resource_loader = ResourceLoader::singleton();
let tree = Engine::singleton()
.get_main_loop()
.ok_or_else(|| {
IoError::loading_precondition(
T::class_id().to_string(),
path.to_string(),
"SceneTree is ready",
)
})?
.try_cast::<SceneTree>()
.map_err(|_| {
IoError::loading_precondition(
T::class_id().to_string(),
path.to_string(),
"MainLoop is SceneTree",
)
})?;
resource_loader
.load_threaded_request_ex(path)
.type_hint(&T::class_id().to_gstring())
.done();
loop {
match resource_loader.load_threaded_get_status(path) {
ThreadLoadStatus::IN_PROGRESS => {
tree.signals().process_frame().to_future().await;
}
ThreadLoadStatus::LOADED => {
break resource_loader
.load_threaded_get(path)
.unwrap()
.try_cast::<T>()
.map_err(|_| {
IoError::loading_cast(T::class_id().to_string(), path.to_string())
});
}
ThreadLoadStatus::INVALID_RESOURCE | ThreadLoadStatus::FAILED => {
resource_loader.load_threaded_get(path);
break Err(IoError::loading(
T::class_id().to_string(),
path.to_string(),
));
}
_ => unreachable!(),
}
}
}
fn save_impl<T>(obj: &Gd<T>, path: &GString) -> Result<(), IoError>
where
T: Inherits<Resource>,
{
let _guard = suppress_godot_errors();
let res = ResourceSaver::singleton().save_ex(obj).path(path).done();
if res == GodotError::OK {
Ok(())
} else {
Err(IoError::saving(
res,
T::class_id().to_string(),
path.to_string(),
))
}
}