godot_core/tools/autoload.rs
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
8use std::cell::RefCell;
9use std::collections::HashMap;
10
11use sys::is_main_thread;
12
13use crate::builtin::NodePath;
14use crate::classes::{Engine, Node, SceneTree};
15use crate::meta::error::ConvertError;
16use crate::obj::{Gd, Inherits, Singleton};
17use crate::sys;
18
19/// Retrieves an autoload by name.
20///
21/// See [Godot docs] for an explanation of the autoload concept. Godot sometimes uses the term "autoload" interchangeably with "singleton";
22/// we strictly refer to the former to separate from [`Singleton`][crate::obj::Singleton] objects.
23///
24/// If the autoload can be resolved, it will be cached and returned very quickly the second time.
25///
26/// [Godot docs]: https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html
27///
28/// # Panics
29/// This is a convenience function that calls [`try_get_autoload_by_name()`]. Panics if that fails, e.g. not found or wrong type.
30///
31/// # Example
32/// ```no_run
33/// use godot::prelude::*;
34/// use godot::tools::get_autoload_by_name;
35///
36/// #[derive(GodotClass)]
37/// #[class(init, base=Node)]
38/// struct GlobalStats {
39/// base: Base<Node>,
40/// }
41///
42/// // Assuming "Statistics" is registered as an autoload in `project.godot`,
43/// // this returns the one instance of type Gd<GlobalStats>.
44/// let stats = get_autoload_by_name::<GlobalStats>("Statistics");
45/// ```
46pub fn get_autoload_by_name<T>(autoload_name: &str) -> Gd<T>
47where
48 T: Inherits<Node>,
49{
50 try_get_autoload_by_name::<T>(autoload_name)
51 .unwrap_or_else(|err| panic!("Failed to get autoload `{autoload_name}`: {err}"))
52}
53
54/// Retrieves an autoload by name (fallible).
55///
56/// Autoloads are accessed via the `/root/{name}` path in the scene tree. The name is the one you used to register the autoload in
57/// `project.godot`. By convention, it often corresponds to the class name, but does not have to.
58///
59/// If the autoload can be resolved, it will be cached and returned very quickly the second time.
60///
61/// See also [`get_autoload_by_name()`] for simpler function expecting the class name and non-fallible invocation.
62///
63/// This function returns `Err` if:
64/// - No autoload is registered under `name`.
65/// - The autoload cannot be cast to type `T`.
66/// - There is an error fetching the scene tree.
67///
68/// # Example
69/// ```no_run
70/// use godot::prelude::*;
71/// use godot::tools::try_get_autoload_by_name;
72///
73/// #[derive(GodotClass)]
74/// #[class(init, base=Node)]
75/// struct GlobalStats {
76/// base: Base<Node>,
77/// }
78///
79/// let result = try_get_autoload_by_name::<GlobalStats>("Statistics");
80/// match result {
81/// Ok(autoload) => { /* Use the Gd<GlobalStats>. */ }
82/// Err(err) => eprintln!("Failed to get autoload: {err}"),
83/// }
84/// ```
85pub fn try_get_autoload_by_name<T>(autoload_name: &str) -> Result<Gd<T>, ConvertError>
86where
87 T: Inherits<Node>,
88{
89 ensure_main_thread()?;
90
91 // Check cache first.
92 let cached = AUTOLOAD_CACHE.with(|cache| cache.borrow().get(autoload_name).cloned());
93
94 if let Some(cached_node) = cached {
95 return cast_autoload(cached_node, autoload_name);
96 }
97
98 // Cache miss - fetch from scene tree.
99 let main_loop = Engine::singleton()
100 .get_main_loop()
101 .ok_or_else(|| ConvertError::new("main loop not available"))?;
102
103 let scene_tree = main_loop
104 .try_cast::<SceneTree>()
105 .map_err(|_| ConvertError::new("main loop is not a SceneTree"))?;
106
107 let autoload_path = NodePath::from(&format!("/root/{autoload_name}"));
108
109 let root = scene_tree
110 .get_root()
111 .ok_or_else(|| ConvertError::new("scene tree root not available"))?;
112
113 let autoload_node = root
114 .try_get_node_as::<Node>(&autoload_path)
115 .ok_or_else(|| ConvertError::new(format!("autoload `{autoload_name}` not found")))?;
116
117 // Store in cache as Gd<Node>.
118 AUTOLOAD_CACHE.with(|cache| {
119 cache
120 .borrow_mut()
121 .insert(autoload_name.to_string(), autoload_node.clone());
122 });
123
124 // Cast to requested type.
125 cast_autoload(autoload_node, autoload_name)
126}
127
128// ----------------------------------------------------------------------------------------------------------------------------------------------
129// Cache implementation
130
131thread_local! {
132 /// Cache for autoloads. Maps autoload name to `Gd<Node>`.
133 ///
134 /// Uses `thread_local!` because `Gd<T>` is not `Send`/`Sync`. Since all Godot objects must be accessed
135 /// from the main thread, this is safe. We enforce main-thread access via `ensure_main_thread()`.
136 static AUTOLOAD_CACHE: RefCell<HashMap<String, Gd<Node>>> = RefCell::new(HashMap::new());
137}
138
139/// Verifies that the current thread is the main thread.
140///
141/// Returns an error if called from a thread other than the main thread. This is necessary because `Gd<T>` is not thread-safe.
142fn ensure_main_thread() -> Result<(), ConvertError> {
143 if is_main_thread() {
144 Ok(())
145 } else {
146 Err(ConvertError::new(
147 "Autoloads must be fetched from main thread, as Gd<T> is not thread-safe",
148 ))
149 }
150}
151
152/// Casts an autoload node to the requested type, with descriptive error message on failure.
153fn cast_autoload<T>(node: Gd<Node>, autoload_name: &str) -> Result<Gd<T>, ConvertError>
154where
155 T: Inherits<Node>,
156{
157 node.try_cast::<T>().map_err(|node| {
158 let expected = T::class_id();
159 let actual = node.get_class();
160
161 ConvertError::new(format!(
162 "autoload `{autoload_name}` has wrong type (expected {expected}, got {actual})",
163 ))
164 })
165}
166
167/// Clears the autoload cache (called during shutdown).
168///
169/// # Panics
170/// Panics if called from a thread other than the main thread.
171pub(crate) fn clear_autoload_cache() {
172 ensure_main_thread().expect("clear_autoload_cache() must be called from the main thread");
173
174 AUTOLOAD_CACHE.with(|cache| {
175 cache.borrow_mut().clear();
176 });
177}