Skip to content

Commit de635ab

Browse files
committed
Add API to fetch autoloads by name
1 parent 21a85e2 commit de635ab

8 files changed

Lines changed: 188 additions & 1 deletion

File tree

godot-core/src/tools/autoload.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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::classes::{Engine, Node, SceneTree};
10+
use crate::meta::error::ConvertError;
11+
use crate::obj::{Gd, Inherits, Singleton};
12+
13+
/// Retrieves an autoload by its name.
14+
///
15+
/// See [Godot docs] for an explanation of the autoload concept. Godot sometimes uses the term "autoload" interchangeably with "singleton";
16+
/// we strictly refer to the former to separate from [`Singleton`][crate::obj::Singleton] objects.
17+
///
18+
/// [Godot docs]: https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html
19+
///
20+
/// # Panics
21+
/// This is a convenience function that calls [`try_get_named_autoload()`]. Panics if that fails, e.g. not found or wrong type.
22+
///
23+
/// # Example
24+
/// ```no_run
25+
/// use godot::prelude::*;
26+
/// use godot::tools::get_named_autoload;
27+
///
28+
/// #[derive(GodotClass)]
29+
/// #[class(init, base=Node)]
30+
/// struct GlobalStats {
31+
/// base: Base<Node>,
32+
/// }
33+
///
34+
/// // Assuming "Statistics" is registered as an autoload in `project.godot`,
35+
/// // this returns the one instance of type Gd<GlobalStats>.
36+
/// let stats = get_named_autoload::<GlobalStats>("Statistics");
37+
/// ```
38+
pub fn get_named_autoload<T>(autoload_name: &str) -> Gd<T>
39+
where
40+
T: Inherits<Node>,
41+
{
42+
try_get_named_autoload::<T>(autoload_name)
43+
.unwrap_or_else(|err| panic!("Failed to get autoload `{autoload_name}`: {err}"))
44+
}
45+
46+
/// Retrieves an autoload by name (fallible).
47+
///
48+
/// Autoloads are accessed via the `/root/{name}` path in the scene tree. The name is the one you used to register the autoload in
49+
/// `project.godot`. By convention, it often corresponds to the class name, but does not have to.
50+
///
51+
/// See also [`get_named_autoload()`] for simpler function expecting the class name and non-fallible invocation.
52+
///
53+
/// This function returns `Err` if:
54+
/// - No autoload is registered under `name`.
55+
/// - The autoload cannot be cast to type `T`.
56+
/// - There is an error fetching the scene tree.
57+
///
58+
/// # Example
59+
/// ```no_run
60+
/// use godot::prelude::*;
61+
/// use godot::tools::try_get_named_autoload;
62+
///
63+
/// #[derive(GodotClass)]
64+
/// #[class(init, base=Node)]
65+
/// struct GlobalStats {
66+
/// base: Base<Node>,
67+
/// }
68+
///
69+
/// let result = try_get_named_autoload::<GlobalStats>("Statistics");
70+
/// match result {
71+
/// Ok(autoload) => { /* Use the Gd<GlobalStats>. */ }
72+
/// Err(err) => eprintln!("Failed to get autoload: {err}"),
73+
/// }
74+
/// ```
75+
pub fn try_get_named_autoload<T>(autoload_name: &str) -> Result<Gd<T>, ConvertError>
76+
where
77+
T: Inherits<Node>,
78+
{
79+
let main_loop = Engine::singleton()
80+
.get_main_loop()
81+
.ok_or_else(|| ConvertError::new("main loop not available"))?;
82+
83+
let scene_tree = main_loop
84+
.try_cast::<SceneTree>()
85+
.map_err(|_| ConvertError::new("main loop is not a SceneTree"))?;
86+
87+
let autoload_path = NodePath::from(&format!("/root/{autoload_name}"));
88+
89+
let root = scene_tree
90+
.get_root()
91+
.ok_or_else(|| ConvertError::new("scene tree root not available"))?;
92+
93+
root.try_get_node_as::<T>(&autoload_path).ok_or_else(|| {
94+
let class = T::class_id();
95+
ConvertError::new(format!(
96+
"autoload `{autoload_name}` not found or has wrong type (expected {class})",
97+
))
98+
})
99+
}

godot-core/src/tools/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
//! Contains functionality that extends existing Godot classes and functions, to make them more versatile
1111
//! or better integrated with Rust.
1212
13+
mod autoload;
1314
mod gfile;
1415
mod save_load;
1516
mod translate;
1617

18+
pub use autoload::*;
1719
pub use gfile::*;
1820
pub use save_load::*;
1921
pub use translate::*;

itest/godot/SpecialTests.gd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,14 @@ func test_collision_object_2d_input_event():
5757

5858
window.queue_free()
5959

60+
func test_autoload():
61+
var fetched = Engine.get_main_loop().get_root().get_node_or_null("/root/MyAutoload")
62+
assert_that(fetched != null, "MyAutoload should be loaded")
63+
64+
var by_class: AutoloadClass = fetched
65+
assert_eq(by_class.verify_works(), 787, "Autoload typed by class")
66+
67+
# Autoload in GDScript can be referenced by class name or autoload name, however autoload as a type is only available in Godot 4.3+.
68+
# See https://github.com/godot-rust/gdext/pull/1381#issuecomment-3446111511.
69+
# var by_name: MyAutoload = fetched
70+
# assert_eq(by_name.verify_works(), 787, "Autoload typed by name")

itest/godot/TestRunner.tscn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[gd_scene load_steps=2 format=3 uid="uid://dgcj68l8n6wpb"]
22

3-
[ext_resource type="Script" path="res://TestRunner.gd" id="1_wdbrf"]
3+
[ext_resource type="Script" uid="uid://dcsm6ho05dipr" path="res://TestRunner.gd" id="1_wdbrf"]
44

55
[node name="TestRunner" type="Node"]
66
script = ExtResource("1_wdbrf")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[gd_scene format=3 uid="uid://csf04mj3dj8bn"]
2+
3+
[node name="AutoloadNode" type="AutoloadClass"]

itest/godot/project.godot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ run/main_scene="res://TestRunner.tscn"
1515
config/features=PackedStringArray("4.2")
1616
run/flush_stdout_on_print=true
1717

18+
[autoload]
19+
20+
MyAutoload="*res://gdscript_tests/AutoloadScene.tscn"
21+
1822
[debug]
1923

2024
gdscript/warnings/shadowed_variable=0
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 godot::classes::Node;
9+
use godot::prelude::*;
10+
use godot::tools::{get_named_autoload, try_get_named_autoload};
11+
12+
use crate::framework::itest;
13+
14+
#[derive(GodotClass)]
15+
#[class(init, base=Node)]
16+
struct AutoloadClass {
17+
base: Base<Node>,
18+
#[var]
19+
property: i32,
20+
}
21+
22+
#[godot_api]
23+
impl AutoloadClass {
24+
#[func]
25+
fn verify_works(&self) -> i32 {
26+
787
27+
}
28+
}
29+
30+
#[itest]
31+
fn autoload_get() {
32+
let mut autoload = get_named_autoload::<AutoloadClass>("MyAutoload");
33+
{
34+
let mut guard = autoload.bind_mut();
35+
assert_eq!(guard.verify_works(), 787);
36+
assert_eq!(guard.property, 0, "still has default value");
37+
38+
guard.property = 42;
39+
}
40+
41+
// Fetch same autoload anew.
42+
let autoload2 = get_named_autoload::<AutoloadClass>("MyAutoload");
43+
assert_eq!(autoload2.bind().property, 42);
44+
45+
// Reset for other tests.
46+
autoload.bind_mut().property = 0;
47+
}
48+
49+
#[itest]
50+
fn autoload_try_get_named() {
51+
let autoload = try_get_named_autoload::<AutoloadClass>("MyAutoload").expect("fetch autoload");
52+
53+
assert_eq!(autoload.bind().verify_works(), 787);
54+
assert_eq!(autoload.bind().property, 0, "still has default value");
55+
}
56+
57+
#[itest]
58+
fn autoload_try_get_named_inexistent() {
59+
let result = try_get_named_autoload::<AutoloadClass>("InexistentAutoload");
60+
result.expect_err("non-existent autoload");
61+
}
62+
63+
#[itest]
64+
fn autoload_try_get_named_bad_type() {
65+
let result = try_get_named_autoload::<Node2D>("MyAutoload");
66+
result.expect_err("autoload of incompatible node type");
67+
}

itest/rust/src/engine_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
mod async_test;
9+
mod autoload_test;
910
mod codegen_enums_test;
1011
mod codegen_test;
1112
mod engine_enum_test;

0 commit comments

Comments
 (0)