Native Introspection

The introspection mechanism allows making Script bindings in transparent mode. Unfortunately, not all programming languages provide it out of the box. Thunder Engine uses a Meta Object approach to define a class structure to be able to retrieve this information on runtime. To make properties visible in the Editor and in the Scripts the engine developers need to define a special construction inside a class body.

In the example below developer defines all required fields:

#include <nativebehaviour.h>

class MyComponent : public NativeBehaviour {
    A_REGISTER(MyComponent, NativeBehaviour, Components)

    A_PROPERTIES(
        A_PROPERTY(int, speed, MyComponent::speed, MyComponent::setSpeed)
    )
    A_METHODS(
        A_METHOD(void, MyComponent::jump)
    )

public:
    int speed() const {
        return m_speed;
    }

    void setSpeed(int speed) {
        m_speed = speed;
    }

    void jump(int force) {
        // Do jump code here
    }

private:
    int m_speed = 0;

};

Meta Object

Thunder Engine Meta-object system provides runtime type information like methods, properties, signals, and slots for inter-object communication.

The meta-object system is based on two things:

  • The Object class provides a base class for objects that can take advantage of the meta-object system.

  • The A_REGISTER macro inside the private section of the class declaration is used to enable meta-object features, such as properties, signals, and slots.

Native Properties

In the provided example developer specified an introspection definition of property speed.

A_PROPERTIES (
    A_PROPERTY(int, speed, MyComponent::speed, MyComponent::setSpeed)
)

Please find the description of parameters for A_PROPERTY bellow:

  • int - The return type of the property

  • speed - The name of the property that will be visible in the editor and in Scripts.

  • MyComponent::speed - The getter function of the property.

  • MyComponent::setSpeed - The setter function of the property.

Note

In case of no any public properties provided in native component. It will be a good practice to set A_NOPROPERTIES() in the class body. This will help to escape issues in introspection in case inhiritance.

Native Methods

In scripts, the developer can use not only properties but also call some functions for the component. In the example above, the developer has specified a jump method for introspection.

A_METHODS(
    A_METHOD(void, MyComponent::jump)
)

Please find the description of parameters for A_PROPERTY bellow:

  • void - The return type of the method

  • MyComponent::jump - The function which will be registered.

Note

In case of no public properties provided in the native component. It will be a good practice to set A_NOMETHODS() in the class body. This will help to escape issues in introspection in the case of inheritance.

Native Signals and Slots

In gameplay programming, if one object was changed, we often want another object will be notified. Signals and slots are used for communication between objects. The signals and slots are made possible by the Thunder introspection system.

#include <nativebehaviour.h>

class MyComponent : public NativeBehaviour {
    A_REGISTER(MyComponent, NativeBehaviour, Components)

    A_METHODS(
        A_SLOT(MyComponent::slot),
        A_SIGNAL(MyComponent::signal)
    )

public:
    void slot(int value) {
        // Make reaction on signal here
    }

    void signal(int value);

};

To connect objects we will need to call the connect function.

For example:

MyComponent component1;
MyComponent component2;

connect(&component1, _SIGNAL(signal(int)), &component2, _SLOT(slot(int)));

In this code we connect the signal from the object component1 with a slot in object component2 This means when the signal of object component1 will be emitted. The slot of object component2 will be called.

The connection has to follow a few rules:

  • The signature of a signal must match the signature of the receiving slot.

  • The signal can be connected to a signal to relay the event.

To emmit a signal we need to call:

component1.emitSignal(_SIGNAL(signal(int)), 5); // 5 is a value of signal argument