Skip to content

Commit 5effee3

Browse files
authored
Merge pull request #535 from StatisMike/feature/save_trysave
`save` and `try_save` functions and IO-utilites refactor in godot_core::engine refactor
2 parents 68801cb + b118232 commit 5effee3

File tree

11 files changed

+604
-230
lines changed

11 files changed

+604
-230
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/gfile.rs renamed to godot-core/src/engine/io/gfile.rs

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

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

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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+
/// Error that can occur while using `gdext` IO utilities.
15+
#[derive(Debug)]
16+
pub struct IoError {
17+
data: ErrorData,
18+
}
19+
20+
impl std::fmt::Display for IoError {
21+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22+
match &self.data {
23+
ErrorData::Load(err) => err.fmt(f),
24+
ErrorData::Save(err) => err.fmt(f),
25+
ErrorData::GFile(err) => err.fmt(f),
26+
}
27+
}
28+
}
29+
30+
impl Error for IoError {
31+
fn source(&self) -> Option<&(dyn Error + 'static)> {
32+
if let ErrorData::GFile(GFileError {
33+
kind: GFileErrorKind::NotUniqueRef(err),
34+
..
35+
}) = &self.data
36+
{
37+
return Some(err);
38+
}
39+
None
40+
}
41+
}
42+
43+
impl IoError {
44+
pub(crate) fn saving(error: GodotError, class: String, path: String) -> Self {
45+
Self {
46+
data: ErrorData::Save(SaverError {
47+
class,
48+
path,
49+
godot_error: error,
50+
}),
51+
}
52+
}
53+
54+
pub(crate) fn loading(class: String, path: String) -> Self {
55+
Self {
56+
data: ErrorData::Load(LoaderError {
57+
kind: LoaderErrorKind::Load,
58+
class,
59+
path,
60+
}),
61+
}
62+
}
63+
64+
pub(crate) fn loading_cast(class: String, path: String) -> Self {
65+
Self {
66+
data: ErrorData::Load(LoaderError {
67+
kind: LoaderErrorKind::Cast,
68+
class,
69+
path,
70+
}),
71+
}
72+
}
73+
74+
pub(crate) fn check_unique_open_file_access(
75+
file_access: Gd<FileAccess>,
76+
) -> Result<Gd<FileAccess>, Self> {
77+
let path = file_access.get_path();
78+
79+
if !file_access.is_open() {
80+
return Err(Self {
81+
data: ErrorData::GFile(GFileError {
82+
kind: GFileErrorKind::NotOpen,
83+
path: path.to_string(),
84+
}),
85+
});
86+
}
87+
88+
match NotUniqueError::check(file_access) {
89+
Ok(gd) => Ok(gd),
90+
Err(err) => Err(Self {
91+
data: ErrorData::GFile(GFileError {
92+
kind: GFileErrorKind::NotUniqueRef(err),
93+
path: path.to_string(),
94+
}),
95+
}),
96+
}
97+
}
98+
}
99+
100+
#[derive(Debug)]
101+
enum ErrorData {
102+
Load(LoaderError),
103+
Save(SaverError),
104+
GFile(GFileError),
105+
}
106+
107+
#[derive(Debug)]
108+
struct LoaderError {
109+
kind: LoaderErrorKind,
110+
class: String,
111+
path: String,
112+
}
113+
114+
#[derive(Debug)]
115+
enum LoaderErrorKind {
116+
Load,
117+
Cast,
118+
}
119+
120+
impl std::fmt::Display for LoaderError {
121+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122+
let class = &self.class;
123+
let path = &self.path;
124+
125+
match &self.kind {
126+
LoaderErrorKind::Load => write!(
127+
f,
128+
"can't load resource of class: '{class}' from path: '{path}'"
129+
),
130+
LoaderErrorKind::Cast => write!(
131+
f,
132+
"can't cast loaded resource to class: '{class}' from path: '{path}'"
133+
),
134+
}
135+
}
136+
}
137+
138+
#[derive(Debug)]
139+
struct SaverError {
140+
class: String,
141+
path: String,
142+
godot_error: GodotError,
143+
}
144+
145+
impl std::fmt::Display for SaverError {
146+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147+
let class = &self.class;
148+
let path = &self.path;
149+
let godot_error = &self.godot_error;
150+
151+
write!(f, "can't save resource of class: '{class}' to path: '{path}'; Godot error: {godot_error:?}")
152+
}
153+
}
154+
155+
#[derive(Debug)]
156+
struct GFileError {
157+
kind: GFileErrorKind,
158+
path: String,
159+
}
160+
161+
#[derive(Debug)]
162+
enum GFileErrorKind {
163+
NotUniqueRef(NotUniqueError),
164+
NotOpen,
165+
}
166+
167+
impl std::fmt::Display for GFileError {
168+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169+
let path = &self.path;
170+
171+
match &self.kind {
172+
GFileErrorKind::NotUniqueRef(err) => {
173+
write!(f, "access to file '{path}' is not unique: '{err}'")
174+
}
175+
GFileErrorKind::NotOpen => write!(f, "access to file '{path}' is not open"),
176+
}
177+
}
178+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
mod gfile;
9+
mod io_error;
10+
mod resources;
11+
12+
pub use gfile::GFile;
13+
pub use io_error::*;
14+
pub use resources::{load, save, try_load, try_save};

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

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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 crate::builtin::GString;
9+
use crate::engine::global::Error as GodotError;
10+
use crate::gen::classes::{Resource, ResourceLoader, ResourceSaver};
11+
use crate::obj::{Gd, GodotClass, Inherits};
12+
13+
use super::IoError;
14+
15+
/// Loads a resource from the filesystem located at `path`, panicking on error.
16+
///
17+
/// See [`try_load`] for more information.
18+
///
19+
/// # Example
20+
///
21+
/// ```no_run
22+
/// use godot::prelude::*;
23+
///
24+
/// let scene = load::<PackedScene>("res://path/to/Main.tscn");
25+
/// ```
26+
///
27+
/// # Panics
28+
/// If the resource cannot be loaded, or is not of type `T` or inherited.
29+
#[inline]
30+
pub fn load<T>(path: impl Into<GString>) -> Gd<T>
31+
where
32+
T: GodotClass + Inherits<Resource>,
33+
{
34+
let path = path.into();
35+
load_impl(&path).unwrap_or_else(|err| panic!("failed: {err}"))
36+
}
37+
38+
/// Loads a resource from the filesystem located at `path`.
39+
///
40+
/// The resource is loaded on the method call (unless it's referenced already elsewhere, e.g. in another script or in the scene),
41+
/// which might cause slight delay, especially when loading scenes.
42+
///
43+
/// This function can fail if resource can't be loaded by [`ResourceLoader`] or if the subsequent cast into `T` fails.
44+
///
45+
/// This method is a simplified version of [`ResourceLoader::load()`][crate::engine::ResourceLoader::load],
46+
/// which can be used for more advanced scenarios.
47+
///
48+
/// # Note:
49+
/// Resource paths can be obtained by right-clicking on a resource in the Godot editor (_FileSystem_ dock) and choosing "Copy Path",
50+
/// or by dragging the file from the _FileSystem_ dock into the script.
51+
///
52+
/// The path must be absolute (typically starting with `res://`), a local path will fail.
53+
///
54+
/// # Example
55+
/// Loads a scene called `Main` located in the `path/to` subdirectory of the Godot project and caches it in a variable.
56+
/// The resource is directly stored with type `PackedScene`.
57+
///
58+
/// ```no_run
59+
/// use godot::prelude::*;
60+
///
61+
/// if let Ok(scene) = try_load::<PackedScene>("res://path/to/Main.tscn") {
62+
/// // all good
63+
/// } else {
64+
/// // handle error
65+
/// }
66+
/// ```
67+
#[inline]
68+
pub fn try_load<T>(path: impl Into<GString>) -> Result<Gd<T>, IoError>
69+
where
70+
T: GodotClass + Inherits<Resource>,
71+
{
72+
load_impl(&path.into())
73+
}
74+
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 save_state = SavedGame::new_gd();
123+
/// let res = try_save(save_state, "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+
135+
// ----------------------------------------------------------------------------------------------------------------------------------------------
136+
// Implementation of this file
137+
138+
// Separate function, to avoid constructing string twice
139+
// Note that more optimizations than that likely make no sense, as loading is quite expensive
140+
fn load_impl<T>(path: &GString) -> Result<Gd<T>, IoError>
141+
where
142+
T: GodotClass + Inherits<Resource>,
143+
{
144+
// TODO unclone GString
145+
match ResourceLoader::singleton()
146+
.load_ex(path.clone())
147+
.type_hint(T::class_name().to_gstring())
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+
))
182+
}

0 commit comments

Comments
 (0)