Skip to content

Commit 4e2e331

Browse files
committed
save and try_save addition; try_load refactor
1 parent b0948ce commit 4e2e331

File tree

9 files changed

+389
-67
lines changed

9 files changed

+389
-67
lines changed

godot-codegen/src/codegen_special_cases.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ const SELECTED_CLASSES: &[&str] = &[
162162
"Resource",
163163
"ResourceFormatLoader",
164164
"ResourceLoader",
165+
"ResourceSaver",
165166
"RigidBody2D",
166167
"SceneTree",
167168
"SceneTreeTimer",

godot-core/src/engine/io/gfile.rs

Lines changed: 56 additions & 54 deletions
Large diffs are not rendered by default.

godot-core/src/engine/io/io_error.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use std::error::Error;
9+
10+
use crate::engine::global::Error as GodotError;
11+
use crate::gen::classes::FileAccess;
12+
use crate::obj::{Gd, NotUniqueError};
13+
14+
type Source = Box<dyn Error + 'static>;
15+
16+
/// Error that can occur while using `gdext` IO utilities.
17+
#[derive(Debug)]
18+
pub struct IoError {
19+
kind: ErrorKind,
20+
source: Option<Source>,
21+
class: Option<String>,
22+
path: Option<String>,
23+
godot_error: Option<GodotError>,
24+
}
25+
26+
impl IoError {
27+
// Path to the file the IO process was handling.
28+
fn path(&self) -> Option<&str> {
29+
self.path.as_deref()
30+
}
31+
32+
// Name of the [Resource](crate::engine::Resource)-inheriting class that the IO process was handling, if any.
33+
fn class(&self) -> Option<&str> {
34+
self.class.as_deref()
35+
}
36+
37+
// Underlying Godot error, if any.
38+
fn godot_error(&self) -> Option<GodotError> {
39+
self.godot_error
40+
}
41+
42+
pub(crate) fn saving(error: GodotError, class: String, path: String) -> Self {
43+
Self {
44+
kind: ErrorKind::ResourceCantSave,
45+
source: None,
46+
class: Some(class),
47+
path: Some(path),
48+
godot_error: Some(error),
49+
}
50+
}
51+
52+
pub(crate) fn loading(class: String, path: String) -> Self {
53+
Self {
54+
kind: ErrorKind::ResourceCantLoad,
55+
source: None,
56+
class: Some(class),
57+
path: Some(path),
58+
godot_error: None,
59+
}
60+
}
61+
62+
pub(crate) fn loading_cast(class: String, path: String) -> Self {
63+
Self {
64+
kind: ErrorKind::ResourceCantCast,
65+
source: None,
66+
class: Some(class),
67+
path: Some(path),
68+
godot_error: None,
69+
}
70+
}
71+
72+
pub(crate) fn check_unique_open_file_access(
73+
file_access: Gd<FileAccess>,
74+
) -> Result<Gd<FileAccess>, Self> {
75+
let path = file_access.get_path();
76+
if !file_access.is_open() {
77+
return Err(Self {
78+
kind: ErrorKind::FileAccessNotOpen,
79+
path: Some(path.to_string()),
80+
source: None,
81+
class: None,
82+
godot_error: None,
83+
});
84+
}
85+
match NotUniqueError::check(file_access) {
86+
Ok(gd) => Ok(gd),
87+
Err(err) => Err(Self {
88+
kind: ErrorKind::FileAccessNotUnique,
89+
path: Some(path.to_string()),
90+
source: Some(err.into()),
91+
class: None,
92+
godot_error: None,
93+
}),
94+
}
95+
}
96+
}
97+
98+
impl std::fmt::Display for IoError {
99+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100+
match (
101+
self.kind,
102+
self.path(),
103+
self.class(),
104+
self.source(),
105+
self.godot_error(),
106+
) {
107+
(ErrorKind::ResourceCantLoad, Some(path), Some(class), _, _) => write!(
108+
f, "can't load resource of class: '{class}' from path: '{path}'"
109+
),
110+
(ErrorKind::ResourceCantCast, Some(path), Some(class), _, _) => write!(
111+
f, "can't cast loaded resource to GodotClass: '{class}' from path: '{path}'"
112+
),
113+
(ErrorKind::ResourceCantSave, Some(path), Some(class), _, Some(godot_error)) => write!(
114+
f, "can't save resource of GodotClass: '{class}' to path: '{path}'; Godot error: {godot_error:?}"
115+
),
116+
(ErrorKind::FileAccessNotOpen, Some(path), _, _, _) => write!(
117+
f, "access to file '{path}' is not open"
118+
),
119+
(ErrorKind::ResourceCantLoad, Some(path), _, Some(source), _) => write!(
120+
f, "file access to '{path}' is not unique: '{source}'"
121+
),
122+
_ => write!(f, "unexpected IO error: {self:?}"),
123+
}
124+
}
125+
}
126+
127+
impl Error for IoError {
128+
fn source(&self) -> Option<&(dyn Error + 'static)> {
129+
if let Some(source) = &self.source {
130+
return Some(source.as_ref());
131+
}
132+
None
133+
}
134+
}
135+
136+
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
137+
enum ErrorKind {
138+
/// Kind of error occuring when loading resource with [try_load](crate::engine::io::try_load) fails while loading object from file.
139+
ResourceCantLoad,
140+
/// Kind of error occuring when loading resource with [try_load](crate::engine::io::try_load) fails while casting object into provided GodotClass.
141+
ResourceCantCast,
142+
/// Kind of error occuring when saving resource with [try_save](crate::engine::io::try_save) fails.
143+
ResourceCantSave,
144+
/// Kind of error occuring when [FileAccess] used to create [GFile](crate::engine::io::GFile) is not open.
145+
FileAccessNotOpen,
146+
/// Kind of error occuring when pointer to [FileAccess] is not an unique reference when creating [GFile](crate::engine::io::GFile).
147+
FileAccessNotUnique,
148+
}

godot-core/src/engine/io/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
*/
77

88
mod gfile;
9+
mod io_error;
910
mod resources;
1011

1112
pub use gfile::GFile;
12-
pub use resources::{load, try_load};
13+
pub use io_error::*;
14+
pub use resources::{load, save, try_load, try_save};

godot-core/src/engine/io/resources.rs

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
*/
77

88
use crate::builtin::GString;
9-
use crate::gen::classes::{Resource, ResourceLoader};
9+
use crate::engine::global::Error as GodotError;
10+
use crate::gen::classes::{Resource, ResourceLoader, ResourceSaver};
1011
use crate::obj::{Gd, GodotClass, Inherits};
1112

13+
use super::IoError;
14+
1215
/// Loads a resource from the filesystem located at `path`, panicking on error.
1316
///
1417
/// See [`try_load`] for more information.
@@ -29,15 +32,15 @@ where
2932
T: GodotClass + Inherits<Resource>,
3033
{
3134
let path = path.into();
32-
load_impl(&path).unwrap_or_else(|| panic!("failed to load node at path `{path}`"))
35+
load_impl(&path).unwrap_or_else(|err| panic!("failed: {err}"))
3336
}
3437

3538
/// Loads a resource from the filesystem located at `path`.
3639
///
3740
/// The resource is loaded on the method call (unless it's referenced already elsewhere, e.g. in another script or in the scene),
3841
/// which might cause slight delay, especially when loading scenes.
3942
///
40-
/// If the resource cannot be loaded, or is not of type `T` or inherited, this method returns `None`.
43+
/// This function can fail if resource can't be loaded by [`ResourceLoader`] or if the subsequent cast into `T` fails.
4144
///
4245
/// This method is a simplified version of [`ResourceLoader::load()`][crate::engine::ResourceLoader::load],
4346
/// which can be used for more advanced scenarios.
@@ -55,33 +58,125 @@ where
5558
/// ```no_run
5659
/// use godot::prelude::*;
5760
///
58-
/// if let Some(scene) = try_load::<PackedScene>("res://path/to/Main.tscn") {
61+
/// if let Ok(scene) = try_load::<PackedScene>("res://path/to/Main.tscn") {
5962
/// // all good
6063
/// } else {
6164
/// // handle error
6265
/// }
6366
/// ```
64-
// TODO Result to differentiate 2 errors
6567
#[inline]
66-
pub fn try_load<T>(path: impl Into<GString>) -> Option<Gd<T>>
68+
pub fn try_load<T>(path: impl Into<GString>) -> Result<Gd<T>, IoError>
6769
where
6870
T: GodotClass + Inherits<Resource>,
6971
{
7072
load_impl(&path.into())
7173
}
7274

75+
/// Saves a [`Resource`]-inheriting [`GodotClass`] `obj` into file located at `path`.
76+
///
77+
/// See [`try_save`] for more information.
78+
///
79+
/// # Panics
80+
/// If the resouce cannot be saved.
81+
///
82+
/// # Example
83+
/// ```no_run
84+
/// use godot::prelude::*;
85+
/// use godot::engine::save;
86+
///
87+
/// save(Resource::new(), "res://base_resource.tres")
88+
/// ```
89+
/// use godot::
90+
#[inline]
91+
pub fn save<T>(obj: Gd<T>, path: impl Into<GString>)
92+
where
93+
T: GodotClass + Inherits<Resource>,
94+
{
95+
let path = path.into();
96+
save_impl(obj, &path)
97+
.unwrap_or_else(|err| panic!("failed to save resource at path '{}': {}", &path, err));
98+
}
99+
100+
/// Saves a [Resource]-inheriting [GodotClass] `obj` into file located at `path`.
101+
///
102+
/// This function can fail if [`ResourceSaver`] can't save the resource to file, as it is a simplified version of
103+
/// [`ResourceSaver::save()`][crate::engine::ResourceSaver::save]. The underlying method can be used for more advances scenarios.
104+
///
105+
/// # Note
106+
/// Target path must be presented in Godot-recognized format, mainly the ones beginning with `res://` and `user://`. Saving
107+
/// to `res://` is possible only when working with unexported project - after its export only `user://` is viable.
108+
///
109+
/// # Example
110+
/// ```no_run
111+
/// use godot::prelude::*;
112+
/// use godot::engine::try_save;
113+
///
114+
/// #[derive(GodotClass)]
115+
/// #[class(base=Resource, init)]
116+
/// struct SavedGame {
117+
/// // exported properties are saved in `.tres` files
118+
/// #[export]
119+
/// level: u32
120+
/// };
121+
///
122+
/// let savestate = SavedGame::new_gd();
123+
/// let res = try_save(savestate, "user://save.tres");
124+
///
125+
/// assert!(res.is_ok());
126+
/// ```
127+
#[inline]
128+
pub fn try_save<T>(obj: Gd<T>, path: impl Into<GString>) -> Result<(), IoError>
129+
where
130+
T: GodotClass + Inherits<Resource>,
131+
{
132+
save_impl(obj, &path.into())
133+
}
134+
73135
// ----------------------------------------------------------------------------------------------------------------------------------------------
74136
// Implementation of this file
75137

76138
// Separate function, to avoid constructing string twice
77139
// Note that more optimizations than that likely make no sense, as loading is quite expensive
78-
fn load_impl<T>(path: &GString) -> Option<Gd<T>>
140+
fn load_impl<T>(path: &GString) -> Result<Gd<T>, IoError>
79141
where
80142
T: GodotClass + Inherits<Resource>,
81143
{
82-
ResourceLoader::singleton()
144+
// TODO unclone GString
145+
match ResourceLoader::singleton()
83146
.load_ex(path.clone())
84147
.type_hint(T::class_name().to_godot_string())
85-
.done() // TODO unclone
86-
.and_then(|res| res.try_cast::<T>().ok())
148+
.done()
149+
{
150+
Some(res) => match res.try_cast::<T>() {
151+
Ok(obj) => Ok(obj),
152+
Err(_) => Err(IoError::loading_cast(
153+
T::class_name().to_string(),
154+
path.to_string(),
155+
)),
156+
},
157+
None => Err(IoError::loading(
158+
T::class_name().to_string(),
159+
path.to_string(),
160+
)),
161+
}
162+
}
163+
164+
fn save_impl<T>(obj: Gd<T>, path: &GString) -> Result<(), IoError>
165+
where
166+
T: GodotClass + Inherits<Resource>,
167+
{
168+
// TODO unclone GString
169+
let res = ResourceSaver::singleton()
170+
.save_ex(obj.upcast())
171+
.path(path.clone())
172+
.done();
173+
174+
if res == GodotError::OK {
175+
return Ok(());
176+
}
177+
Err(IoError::saving(
178+
res,
179+
T::class_name().to_string(),
180+
path.to_string(),
181+
))
87182
}

godot-core/src/obj/gd.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,8 @@ impl NotUniqueError {
739739
}
740740
}
741741

742+
impl std::error::Error for NotUniqueError {}
743+
742744
impl std::fmt::Display for NotUniqueError {
743745
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
744746
write!(

itest/rust/src/engine_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
mod gfile_test;
99
mod native_structures_test;
1010
mod node_test;
11+
mod save_load_test;
1112
mod utilities_test;

0 commit comments

Comments
 (0)