Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDExtension: inconsistent relation between GDNativeTypePtr and Object #61967

Closed
Bromeon opened this issue Jun 12, 2022 · 2 comments · Fixed by #77410
Closed

GDExtension: inconsistent relation between GDNativeTypePtr and Object #61967

Bromeon opened this issue Jun 12, 2022 · 2 comments · Fixed by #77410

Comments

@Bromeon
Copy link
Contributor

Bromeon commented Jun 12, 2022

Godot version

4.0.dev (8df8fff)

System information

Windows 10

Issue description

The variant conversion methods are defined as follows in gdnative_interface.h:

typedef void (*GDNativeVariantFromTypeConstructorFunc)(GDNativeVariantPtr, GDNativeTypePtr);
typedef void (*GDNativeTypeFromVariantConstructorFunc)(GDNativeTypePtr, GDNativeVariantPtr);

and they map to this C++ code for Object <-> Variant conversions:

template <>
struct VariantTypeConstructor<Object *> {
	_FORCE_INLINE_ static void variant_from_type(void *p_variant, void *p_value) {
		...
		// here, we see that p_value == void* == Object**
		Object *object = *(reinterpret_cast<Object **>(p_value));
		...
	}

	_FORCE_INLINE_ static void type_from_variant(void *p_value, void *p_variant) {
		// again: void* == Object**
		Object **value = reinterpret_cast<Object **>(p_value);
	}
};

Here we have the equivalence GDNativeTypePtr == void* == Object**.
In other words, each "type ptr" points to the address of an object, not to an object.


Now let's look at ptrcalls and how the arguments are passed:

// argptrs is void**
for (int i = 0; i < argc; i++) {
	GET_INSTRUCTION_ARG(v, i);
	// argptrs[i] is void*
	argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
}
...
GET_INSTRUCTION_ARG(ret, argc + 1);
VariantInternal::initialize(ret, Variant::NIL);
method->ptrcall(base_obj, argptrs, nullptr);

with:

struct ObjData {
	ObjectID id;
	Object *obj = nullptr; // <-- here we have Object*
};

const Variant::ObjData &Variant::_get_obj() const;

const void *get_opaque_pointer(const Variant *v) {
	...
	return v->_get_obj().obj; // obj == Object*
	// so the Object* is converted to void*
}

So, here we have the equivalence GDNativeTypePtr == void* == Object* -- unlike above Object**.


This inconsistency makes it quite hard to reason about "type pointers" and "object pointers", as the former mean different things depending on context.

Is there a particular reason for this choice, or do you think it would make sense to unify how GDNativeTypePtr and Object relate?

Steps to reproduce

Either of the following will cause a crash -- both are GDNativeTypePtrs:

  • Pass an Object* (instead of Object**) from the extension binding to variant_from_type
  • In a ptrcall in the extension, treat the argument as Object** (instead of Object*)

Minimal reproduction project

No response

@akien-mga akien-mga added this to the 4.0 milestone Jun 12, 2022
@akien-mga akien-mga moved this to To Assess in 4.x Priority Issues Jun 12, 2022
@YuriSizov YuriSizov moved this from To Assess to Todo in 4.x Priority Issues Sep 12, 2022
@akien-mga
Copy link
Member

Is this still relevant as of latest 4.0 builds?

@akien-mga akien-mga modified the milestones: 4.0, 4.1 Feb 27, 2023
@Bromeon
Copy link
Contributor Author

Bromeon commented Feb 27, 2023

Yes, unfortunately the workaround is still needed in godot-rust:

let converter = sys::builtin_fn!(object_to_variant);
let type_ptr = self.sys();

converter(
    variant_ptr,
    ptr::addr_of!(type_ptr) as sys::GDExtensionTypePtr,
);

Translated to C++:

converter(
    variant_ptr,
    reinterpret_cast<sys::GDExtensionTypePtr>(&type_ptr),
);

I also wondered how godot-cpp does this, but it doesn't seem to use object_to_variant. Instead, it does this:

typedef void GodotObject;

class Wrapped {
	...
	GodotObject *_owner = nullptr; // aka void*
};

Variant::Variant(const Object *v) {
	if (v) {
		// Note here:
		// v->_owner is void*
		// &v->_owner is void**  -->  passed to conversion function
		from_type_constructor[OBJECT](_native_ptr(), const_cast<GodotObject **>(&v->_owner));
	} else {
		GodotObject *nullobject = nullptr;
		from_type_constructor[OBJECT](_native_ptr(), &nullobject);
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

2 participants