godotengine / godot-cpp

C++ bindings for the Godot script API
MIT License
1.74k stars 573 forks source link

Add support for multiple inheritance #1514

Closed xana43 closed 4 months ago

xana43 commented 4 months ago

Godot version

Godot v4.2.3.rc (b9f01dcf8)

godot-cpp version

godot-4.2-stable (9da6ecd)

System information

Fedora Linux 40 (KDE Plasma) - Wayland - Vulkan (Forward+) - dedicated AMD Radeon RX 7900 XT (RADV NAVI31) () - AMD Ryzen 9 7950X 16-Core Processor (32 Threads)

Issue description

I'm trying to create a sort of a framework to make other projects in the future easier. Recently i've come accross an issue of not being able to do "interfaces" which due to the fact of not exactly being a specific thing in C++ would come down to multiple inheritance. I currently have a class

class RayInteractable
{
public:
     virtual void raySelect() = 0;
};

and it's child class

class Button3D : public StaticBody3D, public RayInteractable{
  ///code here
};

what i'm trying to do is this

class PlayerEntity final: public CharacterBody2d{
   void _unhandled_input(const Ref<InputEvent&> event) override{
          //raycast parameters based on the camera
         const Dictionary raycastResult{get_world_3d()->get_direct_space_state()->intersect_ray(getLookingRay())
         auto* rayInteractableObject = cast_to<RayInteractable>(result["collider"]);
         if(rayInteractableObject != nullptr)
         {
              rayInteractableObject->raySelect();
         }
   }
};

it's so that I can have multiple objects be selectable by a raycast, in this case it's a button3D implementations that I made, however I have another class that's effectively a pick uppable object using the same system. it's not the absolute end of the world because I could just check for each respective object individually (though the amount of code that would be in the physics process could get absolutely massive) but it would be nice if there was a way to do this as it'd make giving the same attribute to multiple different types of classes much easier.

the main main issue i'm having is, all of this works, the class that the "interface" is implemented on gets registered and works as expected, but trying to cast it to a usable pointer always yields null, after looking at the implementation of Object::cast_to it appears to be looking at the ClassDB, which is something I can't use for an "Interface"

Steps to reproduce

Try to implement an "interface" on a class that is registered

Minimal reproduction project

N/A

dsnopek commented 4 months ago

Thanks!

What you're doing with interface-like abstract classes should work within your GDExtension, however, I think you'll need to use dynamic_cast<T>() rather than Object::cast_to<T>(). Also, ClassDB itself doesn't support multiple inheritance, so none of this will be recognized on the Godot side.

xana43 commented 4 months ago

I'll do a little more testing, but when I tried to use dynamic_cast to convert a variant to the pointer type I wanted the compiler errors out saying I can't convert Variant to RayInteractable

dsnopek commented 4 months ago

Ah, you may need to first convert it to an Object *, so something like dynamic_cast<RayInteractable *>(static_cast<Object *>(variant))

xana43 commented 4 months ago
if(const RayInteractable* test = dynamic_cast<RayInteractable*>(static_cast<Object*>(raycastResult)); test == nullptr)
        {
            DEBUG_ERROR("could not cast to RayInteractble");
        }
        else
        {
            DEBUG_ERROR(test);
        }

I tried doing that, either static casting to an Object* or using

dynamic_cast<RayInteractable*>(Object::cast_to<Object>(raycastResult));

all yield the same result, the pointer returns null indicating that it can't be cast

EDIT: hit the wrong button when submitting this comment

NoctemCat commented 4 months ago

If its a variant I think you can use defined conversion from Variant to Object*. For me something like this worked when I got my object back from GDScript, but dictionary could be different

void GDExample::test_from_variant(const Variant& bar_variant)
{
    Object* base = bar_variant;
    if (TestInterface* test = dynamic_cast<TestInterface*>(base); test != nullptr)
        uf::print("C++ ", test->test_virtual());
}
xana43 commented 4 months ago

ok so, I figured it out, I just forgot to publically "implement" the interface, all the casting works now.