Skip to content

Commit 6c2d12a

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

File tree

9 files changed

+187
-3
lines changed

9 files changed

+187
-3
lines changed

godot-core/src/tools/autoload.rs

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

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::*;

godot/src/prelude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use super::obj::{
2323
pub use super::register::property::{Export, PhantomVar, Var};
2424
// Re-export macros.
2525
pub use super::register::{godot_api, godot_dyn, Export, GodotClass, GodotConvert, Var};
26-
pub use super::tools::{load, save, try_load, try_save, GFile};
26+
pub use super::tools::{get_autoload, load, save, try_load, try_save, GFile};
2727

2828
// Make trait methods available.
2929
#[rustfmt::skip] // One per line.

itest/godot/SpecialTests.gd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,9 @@ 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 autoload: MyAutoload = fetched
65+
assert_eq(autoload.verify_works(), 787, "Autoload API works as expected")

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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ config_version=5
1212

1313
config/name="IntegrationTests"
1414
run/main_scene="res://TestRunner.tscn"
15-
config/features=PackedStringArray("4.2")
15+
config/features=PackedStringArray("4.5")
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_autoload, try_get_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(focus)]
31+
fn autoload_get() {
32+
let mut autoload = get_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_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(focus)]
50+
fn autoload_try_get_named() {
51+
let autoload = try_get_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(focus)]
58+
fn autoload_try_get_named_inexistent() {
59+
let result = try_get_autoload::<AutoloadClass>("InexistentAutoload");
60+
result.expect_err("non-existent autoload");
61+
}
62+
63+
#[itest(focus)]
64+
fn autoload_try_get_named_bad_type() {
65+
let result = try_get_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)