Skip to content

GDExtension objects made without memnew miss callbacks and crash #1057

@paulmiller

Description

@paulmiller

Godot version

4.0

System information

Arch Linux

Issue description

Say I have a GDExtension class Foo:

class Foo : public godot::Node {
  GDCLASS(Foo, godot::Node);

protected:
  static void _bind_methods();

public:
  void _enter_tree() override;
  void hello();
};

void Foo::_bind_methods() {
  ClassDB::bind_method(D_METHOD("hello"), &Foo::hello);
}

void Foo::_enter_tree() {
  UtilityFunctions::print("Foo::_enter_tree");
}

void Foo::hello() {
  UtilityFunctions::print("Foo::hello");
}

If I instantiate Foo in C++ using new, the resulting object seems to work at first; I can add it to the scene tree, and add a Sprite2D under it which appears normally:

var f : Foo =# Foo created in C++ with new
add_child(f)

var s = Sprite2D.new()
s.position = Vector2(100,100)
s.texture =f.add_child(s)

But it misbehaves in surprising ways: Foo::_enter_tree() doesn't get called for add_child(f), and if I try to call f.hello(), the game crashes.

Foo objects instantiated with memnew don't have this problem.


If I'm reading the code correctly, every extension object (Foo in this example) gets assigned a corresponding Godot engine object (Node in this example). Each contains an opaque pointer to the other:

  • Foo inherits from Wrapped. The Wrapped constructor sets Wrapped::_owner to point to the Node.
  • Node inherits from Object. ClassDB::set_object_extension_instance sets Object::_extension_instance to point to the Foo.

It seems like it shouldn't matter how I instantiate Foo - new Foo, memnew(Foo), and std::make_unique<Foo>() should all call the Wrapped constructor.

Although, the existence of the _extension_instance pointer implies some limitations on how I can use Foo. For example, a std::vector<Foo> presumably wouldn't work, since vector reallocation would
invalidate the _extension_instance of each corresponding Node.

It's not clear to me exactly how I'm allowed to use GDExtension objects, and so I can't tell whether this is a Godot bug or a documentation bug. Maybe new Foo isn't supposed to work in the first place?

Specifically, which of the following is allowed?

  • instantiating a Foo with new (as above)
  • freeing a Foo with delete (throws exception if the Foo was created with memnew!)
  • putting Foo objects in a reallocating container like std::vector<Foo>
  • handling Foo with a managed pointer like std::unique_ptr<Foo>
  • including Foo as a field of another GDExtension class, like:
class Bar : public godot::Node {
  GDCLASS(Bar, godot::Node);
  Foo f;
};
  • including Foo as a field of a regular class, like:
class Baz {
  Foo f;
};

If any of these uses are forbidden, they should be called out in the documentation.

Steps to reproduce

The attached project has the above code, and some other test functions.

  • unzip make-node-test.zip
  • check out godot-cpp into make-node-test/extension/godot-cpp/
  • cd make-node-test/extension/out/
  • cmake .. and build, e.g. CC=gcc CXX=g++ cmake -G Ninja .. && ninja
  • cp libfoo.so ../../project/
  • <Godot executable> --path ../../project/

Minimal reproduction project

make-node-test.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis has been identified as a bugdocumentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions