Skip to content

Commit 87c172a

Browse files
author
toasteater
committed
Convenience methods for getting typed nodes
The `path` parameters are restricted to `&str` since: - `impl Trait` syntax interferes with explicit type parameters, which greatly affects ease-of-use. - `&str` should be the most common case, as is evident in the examples. The methods should mostly be used for quick experimentation and porting from GDScript, anyway, so performance when used extensively shouldn't be a concern.
1 parent 0d055a0 commit 87c172a

File tree

9 files changed

+142
-84
lines changed

9 files changed

+142
-84
lines changed

examples/dodge_the_creeps/src/extensions.rs

Lines changed: 0 additions & 27 deletions
This file was deleted.

examples/dodge_the_creeps/src/hud.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::extensions::NodeExt as _;
21
use gdnative::prelude::*;
32

43
#[derive(NativeClass)]
@@ -22,41 +21,41 @@ impl HUD {
2221

2322
#[export]
2423
pub fn show_message(&self, owner: &CanvasLayer, text: String) {
25-
let message_label = unsafe { owner.get_typed_node::<Label, _>("message_label") };
24+
let message_label = unsafe { owner.get_node_as::<Label>("message_label").unwrap() };
2625
message_label.set_text(text);
2726
message_label.show();
2827

29-
let timer = unsafe { owner.get_typed_node::<Timer, _>("message_timer") };
28+
let timer = unsafe { owner.get_node_as::<Timer>("message_timer").unwrap() };
3029
timer.start(0.0);
3130
}
3231

3332
pub fn show_game_over(&self, owner: &CanvasLayer) {
3433
self.show_message(owner, "Game Over".into());
3534

36-
let message_label = unsafe { owner.get_typed_node::<Label, _>("message_label") };
35+
let message_label = unsafe { owner.get_node_as::<Label>("message_label").unwrap() };
3736
message_label.set_text("Dodge the\nCreeps!");
3837
message_label.show();
3938

40-
let button = unsafe { owner.get_typed_node::<Button, _>("start_button") };
39+
let button = unsafe { owner.get_node_as::<Button>("start_button").unwrap() };
4140
button.show();
4241
}
4342

4443
#[export]
4544
pub fn update_score(&self, owner: &CanvasLayer, score: i64) {
46-
let label = unsafe { owner.get_typed_node::<Label, _>("score_label") };
45+
let label = unsafe { owner.get_node_as::<Label>("score_label").unwrap() };
4746
label.set_text(score.to_string());
4847
}
4948

5049
#[export]
5150
fn on_start_button_pressed(&self, owner: &CanvasLayer) {
52-
let button = unsafe { owner.get_typed_node::<Button, _>("start_button") };
51+
let button = unsafe { owner.get_node_as::<Button>("start_button").unwrap() };
5352
button.hide();
5453
owner.emit_signal("start_game", &[]);
5554
}
5655

5756
#[export]
5857
fn on_message_timer_timeout(&self, owner: &CanvasLayer) {
59-
let message_label = unsafe { owner.get_typed_node::<Label, _>("message_label") };
58+
let message_label = unsafe { owner.get_node_as::<Label>("message_label").unwrap() };
6059
message_label.hide()
6160
}
6261
}

examples/dodge_the_creeps/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use gdnative::prelude::*;
22

3-
mod extensions;
43
mod hud;
54
mod main_scene;
65
mod mob;

examples/dodge_the_creeps/src/main_scene.rs

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use crate::extensions::NodeExt as _;
21
use crate::hud;
32
use crate::mob;
43
use crate::player;
5-
use gdnative::api::{Area2D, PathFollow2D, Position2D, RigidBody2D};
4+
use gdnative::api::{PathFollow2D, Position2D, RigidBody2D};
65
use gdnative::prelude::*;
76
use rand::*;
87
use std::f64::consts::PI;
@@ -27,55 +26,50 @@ impl Main {
2726

2827
#[export]
2928
fn game_over(&self, owner: &Node) {
30-
let score_timer = unsafe { owner.get_typed_node::<Timer, _>("score_timer") };
31-
let mob_timer = unsafe { owner.get_typed_node::<Timer, _>("mob_timer") };
29+
let score_timer = unsafe { owner.get_node_as::<Timer>("score_timer").unwrap() };
30+
let mob_timer = unsafe { owner.get_node_as::<Timer>("mob_timer").unwrap() };
3231

3332
score_timer.stop();
3433
mob_timer.stop();
3534

36-
let hud_node = unsafe { owner.get_typed_node::<CanvasLayer, _>("hud") };
37-
hud_node
38-
.cast_instance::<hud::HUD>()
39-
.and_then(|hud| hud.map(|x, o| x.show_game_over(&*o)).ok())
35+
let hud = unsafe { owner.get_node_as_instance::<hud::HUD>("hud").unwrap() };
36+
hud.map(|x, o| x.show_game_over(&*o))
37+
.ok()
4038
.unwrap_or_else(|| godot_print!("Unable to get hud"));
4139
}
4240

4341
#[export]
4442
fn new_game(&mut self, owner: &Node) {
45-
let start_position = unsafe { owner.get_typed_node::<Position2D, _>("start_position") };
46-
let player = unsafe { owner.get_typed_node::<Area2D, _>("player") };
47-
let start_timer = unsafe { owner.get_typed_node::<Timer, _>("start_timer") };
43+
let start_position = unsafe { owner.get_node_as::<Position2D>("start_position").unwrap() };
44+
let player = unsafe {
45+
owner
46+
.get_node_as_instance::<player::Player>("player")
47+
.unwrap()
48+
};
49+
let start_timer = unsafe { owner.get_node_as::<Timer>("start_timer").unwrap() };
4850

4951
self.score = 0;
5052

5153
player
52-
.cast_instance::<player::Player>()
53-
.and_then(|player| {
54-
player
55-
.map(|x, o| x.start(&*o, start_position.position()))
56-
.ok()
57-
})
54+
.map(|x, o| x.start(&*o, start_position.position()))
55+
.ok()
5856
.unwrap_or_else(|| godot_print!("Unable to get player"));
5957

6058
start_timer.start(0.0);
6159

62-
let hud_node = unsafe { owner.get_typed_node::<CanvasLayer, _>("hud") };
63-
hud_node
64-
.cast_instance::<hud::HUD>()
65-
.and_then(|hud| {
66-
hud.map(|x, o| {
67-
x.update_score(&*o, self.score);
68-
x.show_message(&*o, "Get Ready".into());
69-
})
70-
.ok()
71-
})
72-
.unwrap_or_else(|| godot_print!("Unable to get hud"));
60+
let hud = unsafe { owner.get_node_as_instance::<hud::HUD>("hud").unwrap() };
61+
hud.map(|x, o| {
62+
x.update_score(&*o, self.score);
63+
x.show_message(&*o, "Get Ready".into());
64+
})
65+
.ok()
66+
.unwrap_or_else(|| godot_print!("Unable to get hud"));
7367
}
7468

7569
#[export]
7670
fn on_start_timer_timeout(&self, owner: &Node) {
77-
let mob_timer = unsafe { owner.get_typed_node::<Timer, _>("mob_timer") };
78-
let score_timer = unsafe { owner.get_typed_node::<Timer, _>("score_timer") };
71+
let mob_timer = unsafe { owner.get_node_as::<Timer>("mob_timer").unwrap() };
72+
let score_timer = unsafe { owner.get_node_as::<Timer>("score_timer").unwrap() };
7973
mob_timer.start(0.0);
8074
score_timer.start(0.0);
8175
}
@@ -84,17 +78,19 @@ impl Main {
8478
fn on_score_timer_timeout(&mut self, owner: &Node) {
8579
self.score += 1;
8680

87-
let hud_node = unsafe { owner.get_typed_node::<CanvasLayer, _>("hud") };
88-
hud_node
89-
.cast_instance::<hud::HUD>()
90-
.and_then(|hud| hud.map(|x, o| x.update_score(&*o, self.score)).ok())
81+
let hud = unsafe { owner.get_node_as_instance::<hud::HUD>("hud").unwrap() };
82+
hud.map(|x, o| x.update_score(&*o, self.score))
83+
.ok()
9184
.unwrap_or_else(|| godot_print!("Unable to get hud"));
9285
}
9386

9487
#[export]
9588
fn on_mob_timer_timeout(&self, owner: &Node) {
96-
let mob_spawn_location =
97-
unsafe { owner.get_typed_node::<PathFollow2D, _>("mob_path/mob_spawn_locations") };
89+
let mob_spawn_location = unsafe {
90+
owner
91+
.get_node_as::<PathFollow2D>("mob_path/mob_spawn_locations")
92+
.unwrap()
93+
};
9894

9995
let mob_scene: Ref<RigidBody2D, _> = instance_scene(&self.mob);
10096

@@ -123,8 +119,7 @@ impl Main {
123119
mob_owner
124120
.set_linear_velocity(mob_owner.linear_velocity().rotated(Angle { radians: d }));
125121

126-
let hud_node = unsafe { owner.get_typed_node::<CanvasLayer, _>("hud") };
127-
let hud = hud_node.cast_instance::<hud::HUD>().unwrap();
122+
let hud = unsafe { owner.get_node_as_instance::<hud::HUD>("hud").unwrap() };
128123

129124
hud.map(|_, o| {
130125
o.connect(

examples/dodge_the_creeps/src/mob.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::extensions::NodeExt;
21
use gdnative::api::{AnimatedSprite, RigidBody2D};
32
use gdnative::prelude::*;
43
use rand::seq::SliceRandom;
@@ -44,8 +43,11 @@ impl Mob {
4443
#[export]
4544
fn _ready(&mut self, owner: &RigidBody2D) {
4645
let mut rng = rand::thread_rng();
47-
let animated_sprite =
48-
unsafe { owner.get_typed_node::<AnimatedSprite, _>("animated_sprite") };
46+
let animated_sprite = unsafe {
47+
owner
48+
.get_node_as::<AnimatedSprite>("animated_sprite")
49+
.unwrap()
50+
};
4951
animated_sprite.set_animation(MOB_TYPES.choose(&mut rng).unwrap().to_str())
5052
}
5153

examples/dodge_the_creeps/src/player.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::extensions::NodeExt as _;
21
use gdnative::api::{AnimatedSprite, Area2D, CollisionShape2D, PhysicsBody2D};
32
use gdnative::prelude::*;
43

@@ -39,8 +38,11 @@ impl Player {
3938

4039
#[export]
4140
fn _process(&mut self, owner: &Area2D, delta: f32) {
42-
let animated_sprite =
43-
unsafe { owner.get_typed_node::<AnimatedSprite, _>("animated_sprite") };
41+
let animated_sprite = unsafe {
42+
owner
43+
.get_node_as::<AnimatedSprite>("animated_sprite")
44+
.unwrap()
45+
};
4446

4547
let input = Input::godot_singleton();
4648
let mut velocity = Vector2::new(0.0, 0.0);
@@ -90,8 +92,11 @@ impl Player {
9092
owner.hide();
9193
owner.emit_signal("hit", &[]);
9294

93-
let collision_shape =
94-
unsafe { owner.get_typed_node::<CollisionShape2D, _>("collision_shape_2d") };
95+
let collision_shape = unsafe {
96+
owner
97+
.get_node_as::<CollisionShape2D>("collision_shape_2d")
98+
.unwrap()
99+
};
95100

96101
collision_shape.set_deferred("disabled", true);
97102
}
@@ -101,8 +106,11 @@ impl Player {
101106
owner.set_global_position(pos);
102107
owner.show();
103108

104-
let collision_shape =
105-
unsafe { owner.get_typed_node::<CollisionShape2D, _>("collision_shape_2d") };
109+
let collision_shape = unsafe {
110+
owner
111+
.get_node_as::<CollisionShape2D>("collision_shape_2d")
112+
.unwrap()
113+
};
106114

107115
collision_shape.set_disabled(false);
108116
}

gdnative-bindings/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
mod generated;
99
pub use generated::*;
1010

11+
pub mod utils;
12+
1113
pub(crate) mod icalls;

gdnative-bindings/src/utils.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! Utility functions and extension traits that depend on generated bindings
2+
3+
use super::generated::{Engine, Node, SceneTree};
4+
use gdnative_core::nativescript::{NativeClass, RefInstance};
5+
use gdnative_core::object::SubClass;
6+
use gdnative_core::thread_access::Shared;
7+
use gdnative_core::TRef;
8+
9+
/// Convenience method to obtain a reference to an "auto-load" node, that is a child of the root
10+
/// node. Returns `None` if the node does not exist or is not of the correct type.
11+
///
12+
/// # Safety
13+
///
14+
/// This method accesses the scene tree. As a result, any calls to this function must
15+
/// follow the official [thread-safety guidelines][thread-safety]. `assume_safe`
16+
/// invariants must be observed for the resulting node during `'a`, if any.
17+
///
18+
/// [thread-safety]: https://docs.godotengine.org/en/stable/tutorials/threads/thread_safe_apis.html
19+
pub unsafe fn autoload<'a, T>(name: &str) -> Option<TRef<'a, T>>
20+
where
21+
T: SubClass<Node>,
22+
{
23+
Engine::godot_singleton()
24+
.get_main_loop()?
25+
.assume_safe()
26+
.cast::<SceneTree>()?
27+
.root()?
28+
.assume_safe()
29+
.get_node(name)?
30+
.assume_safe()
31+
.cast::<T>()
32+
}
33+
34+
pub trait NodeExt {
35+
/// Convenience method to obtain a reference to a node at `path` relative to `self`,
36+
/// and cast it to the desired type. Returns `None` if the node does not exist or is
37+
/// not of the correct type.
38+
///
39+
/// # Safety
40+
///
41+
/// This method accesses the scene tree. As a result, any calls to this function must
42+
/// follow the official [thread-safety guidelines][thread-safety]. `assume_safe`
43+
/// invariants must be observed for the resulting node during `'a`, if any.
44+
///
45+
/// [thread-safety]: https://docs.godotengine.org/en/stable/tutorials/threads/thread_safe_apis.html
46+
unsafe fn get_node_as<'a, T>(&self, path: &str) -> Option<TRef<'a, T>>
47+
where
48+
T: SubClass<Node>;
49+
50+
/// Convenience method to obtain a reference to a node at `path` relative to `self`,
51+
/// and cast it to an instance of the desired `NativeClass` type. Returns `None` if
52+
/// the node does not exist or is not of the correct type.
53+
///
54+
/// # Safety
55+
///
56+
/// This method accesses the scene tree. As a result, any calls to this function must
57+
/// follow the official [thread-safety guidelines][thread-safety]. `assume_safe`
58+
/// invariants must be observed for the resulting node during `'a`, if any.
59+
///
60+
/// [thread-safety]: https://docs.godotengine.org/en/stable/tutorials/threads/thread_safe_apis.html
61+
unsafe fn get_node_as_instance<'a, T>(&self, path: &str) -> Option<RefInstance<'a, T, Shared>>
62+
where
63+
T: NativeClass,
64+
T::Base: SubClass<Node>,
65+
{
66+
self.get_node_as::<T::Base>(path)?.cast_instance()
67+
}
68+
}
69+
70+
impl<'n, N: SubClass<Node>> NodeExt for &'n N {
71+
unsafe fn get_node_as<'a, T>(&self, path: &str) -> Option<TRef<'a, T>>
72+
where
73+
T: SubClass<Node>,
74+
{
75+
self.upcast().get_node(path)?.assume_safe().cast()
76+
}
77+
}

gdnative/src/prelude.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ pub use gdnative_bindings::{
3838
KinematicBody, KinematicBody2D, Label, Node, Node2D, Object, PackedScene, Reference,
3939
ResourceLoader, SceneTree, Shader, Spatial, Sprite, Texture, Timer, Tween, Viewport,
4040
};
41+
42+
#[cfg(feature = "bindings")]
43+
pub use gdnative_bindings::utils::*;

0 commit comments

Comments
 (0)