How to register properties with custom classes in nativescript

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Chekr

I am trying to define and register a subclass within Godot. I would like to be able to pass complex structures into the engine itself using NativeScript. The principle is a “User” might have lots of data and be used in many places in the engine and I do not want to pass each variable individually.

I thought that maybe a Godot-derived object could be passed through another, but I cannot seem to register the object directly. I am receiving the error:

The issue stems from registration with register_property<>

Does anyone know how to solve this problem?

Error:

Godot.hpp: In instantiation of 'void godot::register_property(const char*, P T::*, P, godot_method_rpc_mode, godot_property_usage_flags, godot_property_hint, godot::String) [with T = godot::GdUserManager; P = godot::GdUser]':

Godot.hpp:314:10: error: conversion from 'godot::GdUser' to non-scalar type 'godot::Variant' requested
     Variant def_val = default_value;
             ^~~~~~~

Source:

gd-user-manager.h

#ifndef _GD_USER_MANAGER_H_
#define _GD_USER_MANAGER_H_

#include <Godot.hpp>
#include <Object.hpp>

#include "gd-user.h"

namespace godot {
class GdUserManager : public Object {
    GODOT_CLASS(GdUserManager, Object)

public:
    GdUser m_user;

public:
    static void _register_methods() {
        GdUser blah;
        // Doesn't work
        register_property<GdUserManager, GdUser>("user", &GdUserManager::m_user, blah);
        // Getter-setter variant also doesn't work
        //register_property<GdUserManager, GdUser>("user", &GdUserManager::set_user, &GdUserManager::get_user);
    }

    GdUserManager();
    ~GdUserManager();

    void _init() { }

    GdUser get_user() {
        return m_user;
    }
    void set_user(GdUser user) {
        m_user = user;
    }
};
}

#endif

gd-user.h

#ifndef _GD_USER_H_
#define _GD_USER_H_

#include <Godot.hpp>
#include <Object.hpp>

namespace godot {
class GdUser : public Object {
    GODOT_CLASS(GdUser, Object)

private:
    int m_test = 42;

public:
    static void _register_methods() {
        register_property<GdUser, int>("test", &GdUser::set_test, &GdUser::get_test, 41);
    }

    GdUser();
    ~GdUser();

    void _init() {}

    int get_test() {
        return m_test;
    }
    void set_test(int test) {
        m_test = test;
    }
};
}
#endif

Edit - including the *.gdns file
user-manager.gdns

[gd_resource type="NativeScript" load_steps=2 format=2]

[ext_resource path="res://lib/user-manager/bin/user-manager.gdnlib" type="GDNativeLibrary" id=1]

[resource]

resource_name = "user-manager"
class_name = "GdUserManager"
library = ExtResource( 1 )
_sections_unfolded = [ "Object" ]

Did you get to solve this problem?

I have eventually come to the realisation that GDNative simply doesn’t support instancing custom classes on the C++ side… what works for me is to create the instances from GDScript code, and then pass the object reference to be properly initialized.

This is pretty kludgy, imo, but kind of works for me since I am essentially implementing a model-view-controller pattern to integrate my C++ backend with Godot without having to create custom engine modules.

I would appreciate some clarity on the memory model for GDScript, looking at the big GDNative projects out there (like GitHub - BastiaanOlij/gdprocmesh: A procedural mesh generator for Godot build as a GDNative plugin) I realised that never was a custom Godot object instanced in the C++ code via Type::_new() or the standard new() operator.

MiquelRamirez | 2021-02-18 15:17

:bust_in_silhouette: Reply From: sparkart

Try casting your object to a Variant

I tried casting to a Variant type, but that comes with problems. The second field of the template for register_property defines the property type, and that same template type also applies to the default field (property 3 of the function call). Casting the default also requites the type of the template be changed, losing the intended reference entirely. Likewise, the custom type isn’t castable to a variant to begin with.

Meanwhile, the first line of the register_property function (Godot.hpp:313) tries treating the default directly as a Variant. However, the standard types (Object, Reference, etc.) can’t be handled this way. Looking at “Variant.hpp”, it seem that it itself handles resolution of types passed in and they are hard coded (unless I’m misinterpreting). There are also fields as part of the register_property function to define the properties, but it will never get that far without the variant being resolved.

Chekr | 2019-06-09 12:28

Sorry, I forgot to clarify about the fact that Custom type is derived from Object, which creates a common interface for you to work with:

Variant has the following constructor:

Variant::Variant(const Object *p_object) {

	type = OBJECT;

	memnew_placement(_data._mem, ObjData);
	_get_obj().obj = const_cast<Object *>(p_object);
}

Encapsulate on the C++ end and use Object* as a common interface.

Since GDUser is derived from Object, use that instead for the template:

register_property<GdUserManager, Object*>

Let me know how it works out, I haven't tested this out and just going off of reading the source code. [wrap=footnote]sparkart | 2019-06-11 02:49[/wrap]

I changed the definition to be a pointer and refined the property registration that way. The clue being that “Object” is cast as a pointer, not a direct object:

class GdUserManager : Object{
// [...]
GdUser* m_user;

public:
    static void _register_methods() {
    GdUser* user = new GdUser();
    register_property<GdUserManager, GdUser*>("user", &GdUserManager::m_user, user);

After doing that everything builds fine. However, the second I open the project in the Godot Editor, the entire editor crashes with an error after doing a directory scan and finding the lib.

Dump:

[1] /lib/x86_64-linux-gnu/libc.so.6(+0x3ef20) [0x7f0e57d79f20] (??:0)
[2] Object::get_script_instance_binding(int) (/home/chekr/git/third_party/godot/core/object.cpp:1932 (discriminator 4))
[3] NativeScriptLanguage::get_instance_binding_data(int, Object*) (/home/chekr/git/third_party/godot/modules/gdnative/nativescript/nativescript.cpp:1352)
[4] /home/chekr/git/third_party/godot/bin/godot.x11.tools.64(godot_nativescript_get_instance_binding_data+0x28) [0x1afbe99] (/home/chekr/git/third_party/godot/modules/gdnative/nativescript/godot_nativescript.cpp:366)
[5] godot::Object* godot::get_wrapper<godot::Object>(void*) (??:0)
[6] void godot::register_property<godot::GdUserManager, godot::Object*>(char const*, godot::Object* godot::GdUserManager::*, godot::Object*, godot_method_rpc_mode, godot_property_usage_flags, godot_property_hint, godot::String) (??:0)
[7] godot::GdUserManager::_register_methods() (??:0)
[8] void godot::register_class<godot::GdUserManager>() (??:0)
[9] /home/chekr/git/chekr/segfault-gd/lib/user-manager/bin/x11/libuser-manager.so(godot_nativescript_init+0x13) [0x7f0dffa27c48] (??:0)
[10] NativeScriptLanguage::init_library(Ref<GDNativeLibrary> const&) (/home/chekr/git/third_party/godot/modules/gdnative/nativescript/nativescript.cpp:1507)
[11] NativeScriptLanguage::frame() (/home/chekr/git/third_party/godot/modules/gdnative/nativescript/nativescript.cpp:1582 (discriminator 2))
[12] Main::iteration() (/home/chekr/git/third_party/godot/main/main.cpp:1940)
[13] OS_X11::run() (/home/chekr/git/third_party/godot/platform/x11/os_x11.cpp:3034)
[14] /home/chekr/git/third_party/godot/bin/godot.x11.tools.64(main+0xdc) [0x1386fa3] (/home/chekr/git/third_party/godot/platform/x11/godot_x11.cpp:56)
[15] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f0e57d5cb97] (??:0)
[16] /home/chekr/git/third_party/godot/bin/godot.x11.tools.64(_start+0x2a) [0x1386e0a] (??:?)

Any ideas why this might be failing to cast?

Chekr | 2019-06-12 00:08

Further update:

New object instantiation cannot use the “new” keyword with Godot objects. I uses up using:

GdUser *user = new GdUser();

… instead based on a reccomentaiton from the git repo. The Godot editor no longer crashes at me, but I now get a runtime crash when calling the module with:

var test = load("res://lib/user-manager/bin/user-manager.gdns").new();

And I get:

Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[1] /lib/x86_64-linux-gnu/libc.so.6(+0x3ef20) [0x7ff488790f20] (??:0)
[2] godot::Variant::Variant(godot::Object const*) (??:0)
[3] godot::_PropertyDefaultGetFunc<godot::GdUserManager, godot::GdUser*>::_wrapped_getter(void*, void*, void*) (??:0)
[4] NativeScriptInstance::get(StringName const&, Variant&) const (/home/chekr/git/third_party/godot/modules/gdnative/nativescript/nativescript.cpp:607)
[5] Object::get(StringName const&, bool*) const (/home/chekr/git/third_party/godot/core/object.cpp:486)
[6] encode_variant(Variant const&, unsigned char*, int&, bool) (/home/chekr/git/third_party/godot/core/io/marshalls.cpp:1128 (discriminator 3))
[7] ScriptDebuggerRemote::_put_variable(String const&, Variant const&) (/home/chekr/git/third_party/godot/core/script_debugger_remote.cpp:111)
[8] ScriptDebuggerRemote::debug(ScriptLanguage*, bool) (/home/chekr/git/third_party/godot/core/script_debugger_remote.cpp:226)
[9] GDScriptLanguage::debug_break(String const&, bool) (/home/chekr/git/third_party/godot/modules/gdscript/gdscript_editor.cpp:237)
[10] GDScriptFunction::call(GDScriptInstance*, Variant const**, int, Variant::CallError&, GDScriptFunction::CallState*) (/home/chekr/git/third_party/godot/modules/gdscript/gdscript_function.cpp:1519 (discriminator 3))
[11] GDScriptInstance::_ml_call_reversed(GDScript*, StringName const&, Variant const**, int) (/home/chekr/git/third_party/godot/modules/gdscript/gdscript.cpp:1206)
[12] GDScriptInstance::call_multilevel_reversed(StringName const&, Variant const**, int) (/home/chekr/git/third_party/godot/modules/gdscript/gdscript.cpp:1215)
[13] Node::_notification(int) (/home/chekr/git/third_party/godot/scene/main/node.cpp:139)
[14] Node::_notificationv(int, bool) (/home/chekr/git/third_party/godot/./scene/main/node.h:46 (discriminator 14))
[15] CanvasItem::_notificationv(int, bool) (/home/chekr/git/third_party/godot/./scene/2d/canvas_item.h:166 (discriminator 3))
[16] Control::_notificationv(int, bool) (/home/chekr/git/third_party/godot/./scene/gui/control.h:51 (discriminator 3))
[17] Object::notification(int, bool) (/home/chekr/git/third_party/godot/core/object.cpp:954)
[18] Node::_propagate_ready() (/home/chekr/git/third_party/godot/scene/main/node.cpp:184)
[19] Node::_propagate_ready() (/home/chekr/git/third_party/godot/scene/main/node.cpp:173 (discriminator 2))
[20] Node::_set_tree(SceneTree*) (/home/chekr/git/third_party/godot/scene/main/node.cpp:2548)
[21] SceneTree::init() (/home/chekr/git/third_party/godot/scene/main/scene_tree.cpp:457)
[22] OS_X11::run() (/home/chekr/git/third_party/godot/platform/x11/os_x11.cpp:3000)
[23] /home/chekr/git/third_party/godot/bin/godot.x11.tools.64(main+0xdc) [0x117c333] (/home/chekr/git/third_party/godot/platform/x11/godot_x11.cpp:56)
[24] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7ff488773b97] (??:0)
[25] /home/chekr/git/third_party/godot/bin/godot.x11.tools.64(_start+0x2a) [0x117c19a] (??:?)

Chekr | 2019-06-12 12:35

Hey Chekr,
I ran into something like this and the solution to my problem though I wasn’t getting the exact same error, was that when registering my classes in the gdlibrary.cpp I had my TestObject being after the object that was trying to use TestObject as a property. This caused Godot to crash everytime on boot, makes sense it’s dependent on which gets called first but it was my TestObject::_new that was causing the crash. Hope this helps. Cheers.

AYeti | 2020-02-11 05:17