gdext
gdext copied to clipboard
Class without `init` cannot be returned from Rust to GDScript
#[derive(GodotClass)]
#[class]
pub struct World {
}
#[derive(GodotClass)]
#[class(init)]
pub struct WorldGen {
}
#[godot_api]
impl WorldGen {
#[func]
fn generate(&mut self) -> Gd<World> {
Gd::new(World {})
}
}
When calling WorldGen.new().generate()
from GDScript, Godot prints an error:
ERROR: Class 'World' has no native extension.
at: set_object_extension_instance (core/object/class_db.cpp:356)
If I put #[class(init)]
on World instead of bare #[class]
, it starts working. Same if I manually implement init
on GodotExt
.
I did expect World
not to be instantiable from GDScript or addable in the editor, but when instantiated from Rust it should still be possible tot pass it back to GDScript.
Notice that the editor calls init
sometimes, even if you're not creating any instances yourself.
#[godot_api]
impl GodotExt for World {
func init(_base: Base<RefCounted>) {
unimplemented!("should never be called");
}
}
It seems to have something to do with generating documentation:
...
#14 godot_core::registry::callbacks::create<my_crate::World> (_class_userdata=<optimized out>)
at .../godot-core/src/registry.rs:282
#15 0x0000555559d9f6da in ClassDB::class_get_default_property_value (p_class=..., p_property=...,
r_valid=0x7fffffffd4e0) at core/object/class_db.cpp:1464
#16 0x0000555557190e66 in get_documentation_default_value (p_class_name=..., p_property_name=...,
r_default_value_valid=@0x7fffffffd4e0: false) at editor/doc_tools.cpp:339
#17 0x00005555571dffe4 in DocTools::generate (this=0x55555f96ef80, p_basic_types=true)
at editor/doc_tools.cpp:453
#18 0x0000555557331bdd in EditorHelp::generate_doc () at editor/editor_help.cpp:2190
#19 EditorNode::EditorNode (this=<optimized out>, this=<optimized out>) at editor/editor_node.cpp:6625
#20 0x0000555556470a01 in Main::start () at main/main.cpp:2795
#21 0x00005555563de303 in main (argc=<optimized out>, argv=0x7fffffffe508)
at platform/linuxbsd/godot_linuxbsd.cpp:71
Checking the godot source-code, we need to set the create_instance_func
in GDExtensionClassCreationInfo
to be a null-pointer for godot to understand that a class cannot be instantiated:
bool ClassDB::can_instantiate(const StringName &p_class) {
OBJTYPE_RLOCK;
ClassInfo *ti = classes.getptr(p_class);
ERR_FAIL_COND_V_MSG(!ti, false, "Cannot get class '" + String(p_class) + "'.");
#ifdef TOOLS_ENABLED
if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) {
return false;
}
#endif
return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance));
}
Alternatively we can set a class to be virtual.
When generating documentation godot will try to instantiate a new object of that class. This is so that godot can call get_property_list
on that object to generate documentation. If we make the class either not instantiable or virtual, godot will not try to instantiate a new object of that class and will instead attempt to instantiate a direct inheriter of that class. failing this i believe godot just fails to generate docs for that class's properties.
https://github.com/godotengine/godot/pull/58972 see also here for a guide on what abstract vs virtual means and such. but the basic point is that we do need to properly set our class as not being instantiable.
I just tested the original snippet. Nowadays, the API expects explicit init
or no_init
, and the error message is quite detailed if forgotten:
error[E0277]: Class `World` requires either an `init` constructor, or explicit opt-out
--> itest/rust/src/register_tests/var_test.rs:27:12
|
27 | pub struct World {
| ^^^^^ needs `init`
|
= help: the trait `GodotDefault` is not implemented for `World`
= note: To provide a default constructor, use `#[class(init)]` or implement an `init` method
= note: To opt out, use `#[class(no_init)]`
= note: see also: https://godot-rust.github.io/book/register/constructors.html
So, after adding no_init
, we have this updated code:
#[derive(GodotClass)]
#[class(no_init)]
pub struct World {}
#[derive(GodotClass)]
#[class(init)]
pub struct WorldGen {}
#[godot_api]
impl WorldGen {
#[func]
fn generate(&mut self) -> Gd<World> {
Gd::from_object(World {})
}
}
Now, calling from GDScript:
func test_world_gen():
var w = WorldGen.new().generate()
print(w)
This works as expected, prints <RefCounted#-9223371997294098691>
.
The issue should thus be solved :beer: