Skip to content

Conversation

@Bromeon
Copy link
Member

@Bromeon Bromeon commented Oct 24, 2025

Closes #126.

Currently no caching yet; we can implement this in a separate PR.

Previously, custom errors were formatted with Debug impl, which wrapped the error
message in Some("..."). Now properly unwraps the Option and uses Display impl.
@Bromeon Bromeon added feature Adds functionality to the library c: engine Godot classes (nodes, resources, ...) labels Oct 24, 2025
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1381

Copy link
Contributor

@Yarwin Yarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine to me, with one caveat – after implementing caching we should check children of the root and pick autoload by class, not the name – making one assumption (there is a node T added as the autoload) instead of two (there is node T named {name} added as the autload). More flexible and less error-prone – traversing scene tree might be expensive when traversed multiple times on runtime, but doing it only once is negligible.

@ValorZard
Copy link

How does this deal with name collision?
When I did this stuff before, I'd get the problem that defining my Node "MySingleton" would cause name collisions in GDScript since it wouldn't let me instantiate an autoload with the same name as its base Node, so I would have to rename my autoload to "MyAutoload"

@ValorZard
Copy link

Example:
I had the rust defined Node "AsyncSingleton"
https://github.com/ValorZard/godot-iroh-gossip-example/blob/master/rust/src/async_singleton.rs
But, that means I couldn't instantiate an autoload as AsyncSingleton because of namespace collisions, so, instead I named my autoload AsyncEventBus
https://github.com/ValorZard/godot-iroh-gossip-example/blob/master/godot/async_event_bus.tscn
https://github.com/ValorZard/godot-iroh-gossip-example/blob/master/godot/test_scene.gd

@Yarwin
Copy link
Contributor

Yarwin commented Oct 25, 2025

I actually raised this issue on discord.

Tested it by myself and indeed it doesn't work properly 😅. Sample code:

#[derive(GodotClass)]
#[class(init, base = Node)]
struct MyClass {
    base: Base<Node>,
}

#[godot_api]
impl INode for MyClass {
    fn ready(&mut self) {
        let autoload = get_autoload::<MyRustAutoload>();
        godot_print!("my autoload: {autoload}");
    }
}

#[derive(GodotClass)]
#[class(init, base = Node)]
struct MyRustAutoload {
    base: Base<Node>,
}

#[godot_api]
impl MyRustAutoload {
    #[func]
    fn foo(&self) -> u32 {
        44
    }
}

Godot Editor won't allow us to create autoload with said name:

image

If we force it via project settings GDScript can't get said value directly as any other autload:

image

Additionally it just doesn't work in gdscript with static typing:

func _ready() -> void:
	var some_autoload = get_node("/root/MyRustAutoload")
	print(some_autoload.foo())
	print("all fine")

	# Error: Internal script error! Opcode: 28 (please report).
	var my_rust_autoload: MyRustAutoload = get_node("/root/MyRustAutoload")
	...

Copy link
Contributor

@Yarwin Yarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per @ValorZard comment – implement with caching & retrieve autoloads by traversing children of the root node.

@ValorZard
Copy link

Is there a way for a rust defined autoload to just not put itself into the Godot namespace so we don’t have name collision?

@Bromeon
Copy link
Member Author

Bromeon commented Oct 25, 2025

Thanks for the feedback!

Godot Editor won't allow us to create autoload with said name:

This is very interesting. I edited the project file as a text file, and it seemed to be no problem:

image

But yes, adding it via UI caused the error message.

But all the tests passed in my case. It seems more a UI limitation than anything else.


Additionally it just doesn't work in gdscript with static typing:

This also worked for me:

func test_autoload():
	var fetched = Engine.get_main_loop().get_root().get_node_or_null("/root/MyAutoload")
	assert_that(fetched != null, "MyAutoload should be loaded")

	var autoload: MyAutoload = fetched
	assert_eq(autoload.verify_works(), 787, "Autoload API works as expected")

I'll see what can be done. That means a name for the autoload is always required, the parameter-less get_autoload() makes not much sense.

Some people might want to statically associate one autoload with a type, but technically there's nothing speaking against having multiple autoloads for the same class, right?

@Yarwin
Copy link
Contributor

Yarwin commented Oct 25, 2025

Is there a way for a rust defined autoload to just not put itself into the Godot namespace so we don’t have name collision?

"Global" on/off. https://docs.godotengine.org/en/latest/tutorials/scripting/singletons_autoload.html (that's all it does, adds it to Script Server). I would avoid that though – it would forbid usage of one of the advertised use cases: When you should use an Autoload

Some people might want to statically associate one autoload with a type, but technically there's nothing speaking against having multiple autoloads for the same class, right?

Technically no, you can even create new node of the said autoload class – in practice I haven't seen it yet.

I'll see what can be done. That means a name for the autoload is always required, the parameter-less get_autoload() makes not much sense.

We can always move this functionality to external library, which would be invoked by user in build.rs (this is what ttencate suggested recently for generating wrappers for GDScript classes) 🤔. Personally I think "only one instance for autoload (also called a Singleton in Godot) is fine limitation.

@Bromeon
Copy link
Member Author

Bromeon commented Oct 25, 2025

So it looks like

	var by_class: AutoloadClass = fetched
	assert_eq(by_class.verify_works(), 787, "Autoload typed by class")

always works, however the following:

	var by_name: MyAutoload = fetched
	assert_eq(by_name.verify_works(), 787, "Autoload typed by name")

only works from Godot 4.3+ onwards. For Godot 4.2, I get:

SCRIPT ERROR: Parse Error: Could not parse singleton "MyAutoload" from "res://gdscript_tests/AutoloadScene.tscn".
          at: GDScript::reload (res://SpecialTests.gd:68)
ERROR: Failed to load script "res://SpecialTests.gd" with error "Parse error".

/// // this returns the one instance of type Gd<GlobalStats>.
/// let stats = get_named_autoload::<GlobalStats>("Statistics");
/// ```
pub fn get_named_autoload<T>(autoload_name: &str) -> Gd<T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that in current form it doesn't bring that much improvement over get_node_as<T>("root/xyz") – while being more taxing for Nodes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll add caching in a 2nd commit once the base one works as expected.

@Yarwin
Copy link
Contributor

Yarwin commented Oct 25, 2025

	var by_name: MyAutoload = fetched
	assert_eq(by_name.verify_works(), 787, "Autoload typed by name")

I think this failing is fine (for GDScript MyAutoload shortcut refers either to some gdscript-internal class or native class); what matters is that the opposite works fine – i.e:

	var by_shortcut: MyRustClass = MyAutoload
	assert_eq(by_name.verify_works(), 787, "Autoload typed by name")

Copy link
Contributor

@Yarwin Yarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is tempting to over-engineer it with Type::of 😄, but better/more ergonomic shortcuts can be implemented directly by users (for example something along the lines of – fn stats() -> Gd<MyClass> {get_autload_by_name("Statistics")} + pub use stats;) while current approach is flexible enough.

@Bromeon
Copy link
Member Author

Bromeon commented Oct 25, 2025

Added cleanup logic + test for non-main-thread access.


it is tempting to over-engineer it with Type::of 😄,

Yeah, if this becomes a very requested feature, we could even do something like:

#[derive(GodotClass)]
#[class(init, base=Node, autoload="Statistics")]
struct GlobalStats { ... }

// Later, through Autoload trait:
GlobalStats::autoload().do_something();

But this should be discussed in the context of #1060.

@Bromeon Bromeon enabled auto-merge October 25, 2025 10:00
@Bromeon Bromeon added this pull request to the merge queue Oct 25, 2025
Merged via the queue into master with commit f025bd7 Oct 25, 2025
18 checks passed
@Bromeon Bromeon deleted the feature/autoload branch October 25, 2025 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: engine Godot classes (nodes, resources, ...) feature Adds functionality to the library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add get_singleton() function in the prelude for easy access to autoloads

4 participants