55 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
66 */
77
8+ use std:: cell:: RefCell ;
9+ use std:: collections:: HashMap ;
10+
11+ use sys:: is_main_thread;
12+
813use crate :: builtin:: NodePath ;
914use crate :: classes:: { Engine , Node , SceneTree } ;
1015use crate :: meta:: error:: ConvertError ;
1116use crate :: obj:: { Gd , Inherits , Singleton } ;
17+ use crate :: sys;
1218
13- /// Retrieves an autoload by its name.
19+ /// Retrieves an autoload by name.
1420///
1521/// See [Godot docs] for an explanation of the autoload concept. Godot sometimes uses the term "autoload" interchangeably with "singleton";
1622/// we strictly refer to the former to separate from [`Singleton`][crate::obj::Singleton] objects.
1723///
24+ /// If the autoload can be resolved, it will be cached and returned very quickly the second time.
25+ ///
1826/// [Godot docs]: https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html
1927///
2028/// # Panics
4856/// Autoloads are accessed via the `/root/{name}` path in the scene tree. The name is the one you used to register the autoload in
4957/// `project.godot`. By convention, it often corresponds to the class name, but does not have to.
5058///
59+ /// If the autoload can be resolved, it will be cached and returned very quickly the second time.
60+ ///
5161/// See also [`get_autoload_by_name()`] for simpler function expecting the class name and non-fallible invocation.
5262///
5363/// This function returns `Err` if:
@@ -76,6 +86,16 @@ pub fn try_get_autoload_by_name<T>(autoload_name: &str) -> Result<Gd<T>, Convert
7686where
7787 T : Inherits < Node > ,
7888{
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.
7999 let main_loop = Engine :: singleton ( )
80100 . get_main_loop ( )
81101 . ok_or_else ( || ConvertError :: new ( "main loop not available" ) ) ?;
@@ -90,10 +110,68 @@ where
90110 . get_root ( )
91111 . ok_or_else ( || ConvertError :: new ( "scene tree root not available" ) ) ?;
92112
93- root. try_get_node_as :: < T > ( & autoload_path) . ok_or_else ( || {
94- let class = T :: class_id ( ) ;
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+
131+ thread_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.
142+ fn 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.
153+ fn cast_autoload < T > ( node : Gd < Node > , autoload_name : & str ) -> Result < Gd < T > , ConvertError >
154+ where
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+
95161 ConvertError :: new ( format ! (
96- "autoload `{autoload_name}` not found or has wrong type (expected {class })" ,
162+ "autoload `{autoload_name}` has wrong type (expected {expected}, got {actual })" ,
97163 ) )
98164 } )
99165}
166+
167+ /// Clears the autoload cache (called during shutdown).
168+ ///
169+ /// # Panics
170+ /// Panics if called from a thread other than the main thread.
171+ pub ( 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+ }
0 commit comments