Skip to content

Commit e07f667

Browse files
authored
Merge pull request #683 from godot-rust/qol/engine-extensions
Replace `NodeExt` + `PackedSceneExt` traits with `impl` blocks, extending classes directly
2 parents 397946a + ca0db1e commit e07f667

File tree

9 files changed

+125
-122
lines changed

9 files changed

+125
-122
lines changed

.github/workflows/full-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ jobs:
109109
# Note: Windows uses '--target x86_64-pc-windows-msvc' by default as Cargo argument.
110110
include:
111111
- name: macos
112-
os: macos-11
112+
os: macos-latest # arm64
113113

114114
- name: windows
115115
os: windows-latest

godot-codegen/src/generator/classes.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
8989
None => (quote! { crate::obj::NoBase }, None),
9090
};
9191

92-
let (constructor, godot_default_impl) = make_constructor_and_default(class, ctx);
92+
let (constructor, construct_doc, godot_default_impl) = make_constructor_and_default(class, ctx);
93+
let construct_doc = construct_doc.replace("Self", &class_name.rust_ty.to_string());
9394
let api_level = class.api_level;
9495
let init_level = api_level.to_init_level();
9596

@@ -164,6 +165,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
164165
use super::*;
165166

166167
#[doc = #class_doc]
168+
#[doc = #construct_doc]
167169
#[derive(Debug)]
168170
#[repr(C)]
169171
pub struct #class_name {
@@ -296,13 +298,16 @@ fn make_class_module_file(classes_and_modules: Vec<GeneratedClassModule>) -> Tok
296298
}
297299
}
298300

299-
fn make_constructor_and_default(class: &Class, ctx: &Context) -> (TokenStream, TokenStream) {
301+
fn make_constructor_and_default(
302+
class: &Class,
303+
ctx: &Context,
304+
) -> (TokenStream, &'static str, TokenStream) {
300305
let godot_class_name = &class.name().godot_ty;
301306
let godot_class_stringname = make_string_name(godot_class_name);
302307
// Note: this could use class_name() but is not yet done due to upcoming lazy-load refactoring.
303308
//let class_name_obj = quote! { <Self as crate::obj::GodotClass>::class_name() };
304309

305-
let (constructor, has_godot_default_impl);
310+
let (constructor, construct_doc, has_godot_default_impl);
306311
if ctx.is_singleton(godot_class_name) {
307312
// Note: we cannot return &'static mut Self, as this would be very easy to mutably alias.
308313
// &'static Self would be possible, but we would lose the whole mutability information (even if that is best-effort and
@@ -318,15 +323,28 @@ fn make_constructor_and_default(class: &Class, ctx: &Context) -> (TokenStream, T
318323
}
319324
}
320325
};
326+
construct_doc = "# Singleton\n\n\
327+
This class is a singleton. You can get the one instance using [`Self::singleton()`][Self::singleton].";
321328
has_godot_default_impl = false;
322329
} else if !class.is_instantiable {
323-
// Abstract base classes or non-singleton classes without constructor
330+
// Abstract base classes or non-singleton classes without constructor.
324331
constructor = TokenStream::new();
332+
construct_doc = "# Not instantiable\n\nThis class cannot be constructed. Obtain `Gd<Self>` instances via Godot APIs.";
325333
has_godot_default_impl = false;
326334
} else {
327335
// Manually managed classes (Object, Node, ...) as well as ref-counted ones (RefCounted, Resource, ...).
328336
// The constructors are provided as associated methods in NewGd::new_gd() and NewAlloc::new_alloc().
329337
constructor = TokenStream::new();
338+
339+
if class.is_refcounted {
340+
construct_doc = "# Construction\n\n\
341+
This class is reference-counted. You can create a new instance using [`Self::new_gd()`][crate::obj::NewGd::new_gd]."
342+
} else {
343+
construct_doc = "# Construction\n\n\
344+
This class is manually managed. You can create a new instance using [`Self::new_alloc()`][crate::obj::NewAlloc::new_alloc].\n\n\
345+
Do not forget to call [`free()`][crate::obj::Gd::free] or hand over ownership to Godot."
346+
}
347+
330348
has_godot_default_impl = true;
331349
}
332350

@@ -343,7 +361,7 @@ fn make_constructor_and_default(class: &Class, ctx: &Context) -> (TokenStream, T
343361
TokenStream::new()
344362
};
345363

346-
(constructor, godot_default_impl)
364+
(constructor, construct_doc, godot_default_impl)
347365
}
348366

349367
fn make_deref_impl(class_name: &TyName, base_ty: &TokenStream) -> TokenStream {

godot-codegen/src/special_cases/special_cases.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ pub fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ct
4040
// Already covered by manual APIs
4141
//| ("Object", "to_string")
4242
| ("Object", "get_instance_id")
43+
44+
// Removed because it is a worse version of Node::get_node_or_null(): it _seems_ like it's fallible due to Option<T> return type,
45+
// however Godot will emit an error message if the node is absent. In the future with non-null types, this may be re-introduced.
46+
// Alternatively, both get_node/get_node_or_null could become generic and use the get_node_as/try_get_node_as impl (removing those).
47+
| ("Node", "get_node")
4348

4449
// Removed in https://github.com/godotengine/godot/pull/88418, but they cannot reasonably be used before, either.
4550
| ("GDExtension", "open_library")

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
use crate::builtin::GString;
99
use crate::engine::global::Error as GodotError;
1010
use crate::gen::classes::{Resource, ResourceLoader, ResourceSaver};
11-
use crate::obj::{Gd, GodotClass, Inherits};
11+
use crate::obj::{Gd, Inherits};
1212

1313
use super::IoError;
1414

15-
/// Loads a resource from the filesystem located at `path`, panicking on error.
15+
/// ⚠️ Loads a resource from the filesystem located at `path`, panicking on error.
1616
///
1717
/// See [`try_load`] for more information.
1818
///
@@ -29,24 +29,24 @@ use super::IoError;
2929
#[inline]
3030
pub fn load<T>(path: impl Into<GString>) -> Gd<T>
3131
where
32-
T: GodotClass + Inherits<Resource>,
32+
T: Inherits<Resource>,
3333
{
3434
let path = path.into();
3535
load_impl(&path).unwrap_or_else(|err| panic!("failed: {err}"))
3636
}
3737

3838
/// Loads a resource from the filesystem located at `path`.
3939
///
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.
40+
/// The resource is loaded during the method call, unless it is already referenced elsewhere, e.g. in another script or in the scene.
41+
/// This might cause slight delay, especially when loading scenes.
4242
///
4343
/// This function can fail if resource can't be loaded by [`ResourceLoader`] or if the subsequent cast into `T` fails.
4444
///
4545
/// This method is a simplified version of [`ResourceLoader::load()`][crate::engine::ResourceLoader::load],
4646
/// which can be used for more advanced scenarios.
4747
///
48-
/// # Note:
49-
/// Resource paths can be obtained by right-clicking on a resource in the Godot editor (_FileSystem_ dock) and choosing "Copy Path",
48+
/// # Note
49+
/// Resource paths can be obtained by right-clicking on a resource in the Godot editor (_FileSystem_ dock) and choosing _Copy Path_,
5050
/// or by dragging the file from the _FileSystem_ dock into the script.
5151
///
5252
/// The path must be absolute (typically starting with `res://`), a local path will fail.
@@ -67,12 +67,12 @@ where
6767
#[inline]
6868
pub fn try_load<T>(path: impl Into<GString>) -> Result<Gd<T>, IoError>
6969
where
70-
T: GodotClass + Inherits<Resource>,
70+
T: Inherits<Resource>,
7171
{
7272
load_impl(&path.into())
7373
}
7474

75-
/// Saves a [`Resource`]-inheriting [`GodotClass`] `obj` into file located at `path`.
75+
/// ⚠️ Saves a [`Resource`]-inheriting object into the file located at `path`.
7676
///
7777
/// See [`try_save`] for more information.
7878
///
@@ -84,20 +84,20 @@ where
8484
/// use godot::prelude::*;
8585
/// use godot::engine::save;
8686
///
87-
/// save(Resource::new_gd(), "res://base_resource.tres")
87+
/// save(Resource::new_gd(), "res://BaseResource.tres")
8888
/// ```
8989
/// use godot::
9090
#[inline]
9191
pub fn save<T>(obj: Gd<T>, path: impl Into<GString>)
9292
where
93-
T: GodotClass + Inherits<Resource>,
93+
T: Inherits<Resource>,
9494
{
9595
let path = path.into();
9696
save_impl(obj, &path)
9797
.unwrap_or_else(|err| panic!("failed to save resource at path '{}': {}", &path, err));
9898
}
9999

100-
/// Saves a [Resource]-inheriting [GodotClass] `obj` into file located at `path`.
100+
/// Saves a [`Resource`]-inheriting object into the file located at `path`.
101101
///
102102
/// This function can fail if [`ResourceSaver`] can't save the resource to file, as it is a simplified version of
103103
/// [`ResourceSaver::save()`][crate::engine::ResourceSaver::save]. The underlying method can be used for more advances scenarios.
@@ -127,7 +127,7 @@ where
127127
#[inline]
128128
pub fn try_save<T>(obj: Gd<T>, path: impl Into<GString>) -> Result<(), IoError>
129129
where
130-
T: GodotClass + Inherits<Resource>,
130+
T: Inherits<Resource>,
131131
{
132132
save_impl(obj, &path.into())
133133
}
@@ -139,7 +139,7 @@ where
139139
// Note that more optimizations than that likely make no sense, as loading is quite expensive
140140
fn load_impl<T>(path: &GString) -> Result<Gd<T>, IoError>
141141
where
142-
T: GodotClass + Inherits<Resource>,
142+
T: Inherits<Resource>,
143143
{
144144
// TODO unclone GString
145145
match ResourceLoader::singleton()
@@ -163,7 +163,7 @@ where
163163

164164
fn save_impl<T>(obj: Gd<T>, path: &GString) -> Result<(), IoError>
165165
where
166-
T: GodotClass + Inherits<Resource>,
166+
T: Inherits<Resource>,
167167
{
168168
// TODO unclone GString
169169
let res = ResourceSaver::singleton()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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::NodePath;
9+
use crate::engine::{Node, PackedScene};
10+
use crate::obj::{Gd, Inherits};
11+
12+
/// Manual extensions for the `Node` class.
13+
impl Node {
14+
/// ⚠️ Retrieves the node at path `path`, panicking if not found or bad type.
15+
///
16+
/// # Panics
17+
/// If the node is not found, or if it does not have type `T` or inherited.
18+
pub fn get_node_as<T>(&self, path: impl Into<NodePath>) -> Gd<T>
19+
where
20+
T: Inherits<Node>,
21+
{
22+
let path = path.into();
23+
let copy = path.clone(); // TODO avoid copy
24+
25+
self.try_get_node_as(path).unwrap_or_else(|| {
26+
panic!(
27+
"There is no node of type {ty} at path `{copy}`",
28+
ty = T::class_name()
29+
)
30+
})
31+
}
32+
33+
/// Retrieves the node at path `path` (fallible).
34+
///
35+
/// If the node is not found, or if it does not have type `T` or inherited,
36+
/// `None` will be returned.
37+
pub fn try_get_node_as<T>(&self, path: impl Into<NodePath>) -> Option<Gd<T>>
38+
where
39+
T: Inherits<Node>,
40+
{
41+
let path = path.into();
42+
43+
// TODO differentiate errors (not found, bad type) with Result
44+
self.get_node_or_null(path)
45+
.and_then(|node| node.try_cast::<T>().ok())
46+
}
47+
}
48+
49+
// ----------------------------------------------------------------------------------------------------------------------------------------------
50+
51+
/// Manual extensions for the `PackedScene` class.
52+
impl PackedScene {
53+
/// ⚠️ Instantiates the scene as type `T`, panicking if not found or bad type.
54+
///
55+
/// # Panics
56+
/// If the scene is not type `T` or inherited.
57+
pub fn instantiate_as<T>(&self) -> Gd<T>
58+
where
59+
T: Inherits<Node>,
60+
{
61+
self.try_instantiate_as::<T>()
62+
.unwrap_or_else(|| panic!("Failed to instantiate {to}", to = T::class_name()))
63+
}
64+
65+
/// Instantiates the scene as type `T` (fallible).
66+
///
67+
/// If the scene is not type `T` or inherited.
68+
pub fn try_instantiate_as<T>(&self) -> Option<Gd<T>>
69+
where
70+
T: Inherits<Node>,
71+
{
72+
self.instantiate().and_then(|gd| gd.try_cast::<T>().ok())
73+
}
74+
}

0 commit comments

Comments
 (0)