Compare commits

..

6 Commits

Author SHA1 Message Date
Serhii Snitsaruk d98be526b6
Merge pull request #151 from limbonaut/gdext-gen-doc-header
Generate built-in documentation header in GDExtension variant
2024-06-30 15:56:49 +02:00
Serhii Snitsaruk ae3b4ebb94
Generate built-in documentation header in GDExtension 2024-06-30 15:40:18 +02:00
Serhii Snitsaruk 2f62b53b6a
Merge pull request #150 from limbonaut/fix-gdext-crash
Fix: GDExtension load fails due to `Expression` used without `Ref<>`
2024-06-30 14:47:39 +02:00
Serhii Snitsaruk 8dfbd70718
Fix: GDExtension load fails due to Expression used w/o Ref 2024-06-30 14:29:40 +02:00
Serhii Snitsaruk 4d4f0d7886
Merge pull request #149 from limbonaut/gdext-virt-calls
Register and call virtual methods on scripts in GDExtension
2024-06-30 13:07:41 +02:00
Serhii Snitsaruk cd0bc8e796
Register and call virtual methods on scripts in GDExtension 2024-06-30 12:34:06 +02:00
9 changed files with 36 additions and 103 deletions

View File

@ -73,7 +73,7 @@ void BTState::_exit() {
} }
void BTState::_update(double p_delta) { void BTState::_update(double p_delta) {
VCALL_ARGS(_update, p_delta); GDVIRTUAL_CALL(_update, p_delta);
if (!is_active()) { if (!is_active()) {
// Bail out if a transition happened in the meantime. // Bail out if a transition happened in the meantime.
return; return;

View File

@ -129,13 +129,13 @@ String BTTask::get_task_name() {
ERR_FAIL_COND_V_MSG(has_generate_method && !task_script->is_tool(), _generate_name(), vformat("BTTask: @tool annotation is required if _generate_name is defined: %s", task_script->get_path())); ERR_FAIL_COND_V_MSG(has_generate_method && !task_script->is_tool(), _generate_name(), vformat("BTTask: @tool annotation is required if _generate_name is defined: %s", task_script->get_path()));
if (task_script->is_tool() && has_generate_method) { if (task_script->is_tool() && has_generate_method) {
String call_result; String call_result;
VCALL_V(_generate_name, call_result); GDVIRTUAL_CALL(_generate_name, call_result);
if (call_result.is_empty() || call_result == "<null>") { if (call_result.is_empty() || call_result == "<null>") {
// Force reset script instance. // Force reset script instance.
set_script(Variant()); set_script(Variant());
set_script(task_script); set_script(task_script);
// Retry. // Retry.
VCALL_V(_generate_name, call_result); GDVIRTUAL_CALL(_generate_name, call_result);
} }
ERR_FAIL_COND_V_MSG(call_result.is_empty() || call_result == "<null>", _generate_name(), vformat("BTTask: _generate_name() failed to return a proper name string (%s)", task_script->get_path())); ERR_FAIL_COND_V_MSG(call_result.is_empty() || call_result == "<null>", _generate_name(), vformat("BTTask: _generate_name() failed to return a proper name string (%s)", task_script->get_path()));
return call_result; return call_result;
@ -171,7 +171,9 @@ void BTTask::initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node
get_child(i)->initialize(p_agent, p_blackboard, p_scene_root); get_child(i)->initialize(p_agent, p_blackboard, p_scene_root);
} }
VCALL_OR_NATIVE(_setup); if (!GDVIRTUAL_CALL(_setup)) {
_setup();
}
} }
Ref<BTTask> BTTask::clone() const { Ref<BTTask> BTTask::clone() const {
@ -245,16 +247,21 @@ BT::Status BTTask::execute(double p_delta) {
data.children.get(i)->abort(); data.children.get(i)->abort();
} }
} }
if (!GDVIRTUAL_CALL(_enter)) {
VCALL_OR_NATIVE(_enter); _enter();
}
} else { } else {
data.elapsed += p_delta; data.elapsed += p_delta;
} }
VCALL_OR_NATIVE_ARGS_V(_tick, Status, data.status, p_delta); if (!GDVIRTUAL_CALL(_tick, p_delta, data.status)) {
data.status = _tick(p_delta);
}
if (data.status != RUNNING) { if (data.status != RUNNING) {
VCALL_OR_NATIVE(_exit); if (!GDVIRTUAL_CALL(_exit)) {
_exit();
}
data.elapsed = 0.0; data.elapsed = 0.0;
} }
return data.status; return data.status;
@ -265,7 +272,9 @@ void BTTask::abort() {
get_child(i)->abort(); get_child(i)->abort();
} }
if (data.status == RUNNING) { if (data.status == RUNNING) {
VCALL_OR_NATIVE(_exit); if (!GDVIRTUAL_CALL(_exit)) {
_exit();
}
} }
data.status = FRESH; data.status = FRESH;
data.elapsed = 0.0; data.elapsed = 0.0;
@ -356,7 +365,7 @@ PackedStringArray BTTask::get_configuration_warnings() {
PackedStringArray warnings; PackedStringArray warnings;
Ref<Script> task_script = get_script(); Ref<Script> task_script = get_script();
if (task_script.is_valid() && task_script->is_tool()) { if (task_script.is_valid() && task_script->is_tool()) {
VCALL_V(_get_configuration_warnings, warnings); // Get script warnings. GDVIRTUAL_CALL(_get_configuration_warnings, warnings); // Get script warnings.
} }
ret.append_array(warnings); ret.append_array(warnings);
ret.append_array(_get_configuration_warnings()); ret.append_array(_get_configuration_warnings());
@ -439,16 +448,12 @@ void BTTask::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "status", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_status"); ADD_PROPERTY(PropertyInfo(Variant::INT, "status", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_status");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "elapsed_time", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_elapsed_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "elapsed_time", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_elapsed_time");
#ifdef LIMBOAI_MODULE
GDVIRTUAL_BIND(_setup); GDVIRTUAL_BIND(_setup);
GDVIRTUAL_BIND(_enter); GDVIRTUAL_BIND(_enter);
GDVIRTUAL_BIND(_exit); GDVIRTUAL_BIND(_exit);
GDVIRTUAL_BIND(_tick, "delta"); GDVIRTUAL_BIND(_tick, "delta");
GDVIRTUAL_BIND(_generate_name); GDVIRTUAL_BIND(_generate_name);
GDVIRTUAL_BIND(_get_configuration_warnings); GDVIRTUAL_BIND(_get_configuration_warnings);
#elif LIMBOAI_GDEXTENSION
// TODO: Registering virtual functions is not available in godot-cpp...
#endif
} }
BTTask::BTTask() { BTTask::BTTask() {

View File

@ -37,6 +37,7 @@
#ifdef LIMBOAI_GDEXTENSION #ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/engine.hpp> #include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/resource.hpp> #include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/core/gdvirtual.gen.inc>
#include <godot_cpp/core/object.hpp> #include <godot_cpp/core/object.hpp>
#include <godot_cpp/templates/vector.hpp> #include <godot_cpp/templates/vector.hpp>
using namespace godot; using namespace godot;
@ -103,14 +104,12 @@ protected:
virtual void _exit() {} virtual void _exit() {}
virtual Status _tick(double p_delta) { return FAILURE; } virtual Status _tick(double p_delta) { return FAILURE; }
#ifdef LIMBOAI_MODULE
GDVIRTUAL0RC(String, _generate_name); GDVIRTUAL0RC(String, _generate_name);
GDVIRTUAL0(_setup); GDVIRTUAL0(_setup);
GDVIRTUAL0(_enter); GDVIRTUAL0(_enter);
GDVIRTUAL0(_exit); GDVIRTUAL0(_exit);
GDVIRTUAL1R(Status, _tick, double); GDVIRTUAL1R(Status, _tick, double);
GDVIRTUAL0RC(PackedStringArray, _get_configuration_warnings); GDVIRTUAL0RC(PackedStringArray, _get_configuration_warnings);
#endif // LIMBOAI_MODULE
public: public:
// TODO: GDExtension doesn't have this method hmm... // TODO: GDExtension doesn't have this method hmm...

View File

@ -79,7 +79,7 @@ PackedStringArray BTEvaluateExpression::get_configuration_warnings() {
void BTEvaluateExpression::_setup() { void BTEvaluateExpression::_setup() {
parse(); parse();
ERR_FAIL_COND_MSG(is_parsed != Error::OK, "BTEvaluateExpression: Failed to parse expression: " + expression.get_error_text()); ERR_FAIL_COND_MSG(is_parsed != Error::OK, "BTEvaluateExpression: Failed to parse expression: " + expression->get_error_text());
} }
Error BTEvaluateExpression::parse() { Error BTEvaluateExpression::parse() {
@ -93,7 +93,7 @@ Error BTEvaluateExpression::parse() {
processed_input_names_ptr[i + int(input_include_delta)] = input_names[i]; processed_input_names_ptr[i + int(input_include_delta)] = input_names[i];
} }
is_parsed = expression.parse(expression_string, processed_input_names); is_parsed = expression->parse(expression_string, processed_input_names);
return is_parsed; return is_parsed;
} }
@ -109,7 +109,7 @@ BT::Status BTEvaluateExpression::_tick(double p_delta) {
ERR_FAIL_COND_V_MSG(node_param.is_null(), FAILURE, "BTEvaluateExpression: Node parameter is not set."); ERR_FAIL_COND_V_MSG(node_param.is_null(), FAILURE, "BTEvaluateExpression: Node parameter is not set.");
Object *obj = node_param->get_value(get_scene_root(), get_blackboard()); Object *obj = node_param->get_value(get_scene_root(), get_blackboard());
ERR_FAIL_COND_V_MSG(obj == nullptr, FAILURE, "BTEvaluateExpression: Failed to get object: " + node_param->to_string()); ERR_FAIL_COND_V_MSG(obj == nullptr, FAILURE, "BTEvaluateExpression: Failed to get object: " + node_param->to_string());
ERR_FAIL_COND_V_MSG(is_parsed != Error::OK, FAILURE, "BTEvaluateExpression: Failed to parse expression: " + expression.get_error_text()); ERR_FAIL_COND_V_MSG(is_parsed != Error::OK, FAILURE, "BTEvaluateExpression: Failed to parse expression: " + expression->get_error_text());
if (input_include_delta) { if (input_include_delta) {
processed_input_values[0] = p_delta; processed_input_values[0] = p_delta;
@ -119,8 +119,8 @@ BT::Status BTEvaluateExpression::_tick(double p_delta) {
processed_input_values[i + int(input_include_delta)] = bb_variant->get_value(get_scene_root(), get_blackboard()); processed_input_values[i + int(input_include_delta)] = bb_variant->get_value(get_scene_root(), get_blackboard());
} }
Variant result = expression.execute(processed_input_values, obj, false); Variant result = expression->execute(processed_input_values, obj, false);
ERR_FAIL_COND_V_MSG(expression.has_execute_failed(), FAILURE, "BTEvaluateExpression: Failed to execute: " + expression.get_error_text()); ERR_FAIL_COND_V_MSG(expression->has_execute_failed(), FAILURE, "BTEvaluateExpression: Failed to execute: " + expression->get_error_text());
if (result_var != StringName()) { if (result_var != StringName()) {
get_blackboard()->set_var(result_var, result); get_blackboard()->set_var(result_var, result);
@ -156,4 +156,5 @@ void BTEvaluateExpression::_bind_methods() {
} }
BTEvaluateExpression::BTEvaluateExpression() { BTEvaluateExpression::BTEvaluateExpression() {
expression.instantiate();
} }

View File

@ -31,7 +31,7 @@ class BTEvaluateExpression : public BTAction {
TASK_CATEGORY(Utility); TASK_CATEGORY(Utility);
private: private:
Expression expression; Ref<Expression> expression;
Error is_parsed = FAILED; Error is_parsed = FAILED;
Ref<BBNode> node_param; Ref<BBNode> node_param;
String expression_string; String expression_string;

View File

@ -40,6 +40,10 @@ sources += Glob("limboai/editor/*.cpp")
sources += Glob("limboai/hsm/*.cpp") sources += Glob("limboai/hsm/*.cpp")
sources += Glob("limboai/util/*.cpp") sources += Glob("limboai/util/*.cpp")
# Generate documentation header.
if env["target"] in ["editor", "template_debug"]:
doc_data = env.GodotCPPDocData("limboai/gen/doc_data.gen.cpp", source=Glob("limboai/doc_classes/*.xml"))
sources.append(doc_data)
if env["platform"] == "macos": if env["platform"] == "macos":
library = env.SharedLibrary( library = env.SharedLibrary(

View File

@ -62,7 +62,7 @@ LimboState *LimboState::named(const String &p_name) {
void LimboState::_enter() { void LimboState::_enter() {
active = true; active = true;
VCALL(_enter); GDVIRTUAL_CALL(_enter);
emit_signal(LimboStringNames::get_singleton()->entered); emit_signal(LimboStringNames::get_singleton()->entered);
} }
@ -70,18 +70,18 @@ void LimboState::_exit() {
if (!active) { if (!active) {
return; return;
} }
VCALL(_exit); GDVIRTUAL_CALL(_exit);
emit_signal(LimboStringNames::get_singleton()->exited); emit_signal(LimboStringNames::get_singleton()->exited);
active = false; active = false;
} }
void LimboState::_update(double p_delta) { void LimboState::_update(double p_delta) {
VCALL_ARGS(_update, p_delta); GDVIRTUAL_CALL(_update, p_delta);
emit_signal(LimboStringNames::get_singleton()->updated, p_delta); emit_signal(LimboStringNames::get_singleton()->updated, p_delta);
} }
void LimboState::_setup() { void LimboState::_setup() {
VCALL(_setup); GDVIRTUAL_CALL(_setup);
emit_signal(LimboStringNames::get_singleton()->setup); emit_signal(LimboStringNames::get_singleton()->setup);
} }
@ -213,14 +213,10 @@ void LimboState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_blackboard_plan", "plan"), &LimboState::set_blackboard_plan); ClassDB::bind_method(D_METHOD("set_blackboard_plan", "plan"), &LimboState::set_blackboard_plan);
ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &LimboState::get_blackboard_plan); ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &LimboState::get_blackboard_plan);
#ifdef LIMBOAI_MODULE
GDVIRTUAL_BIND(_setup); GDVIRTUAL_BIND(_setup);
GDVIRTUAL_BIND(_enter); GDVIRTUAL_BIND(_enter);
GDVIRTUAL_BIND(_exit); GDVIRTUAL_BIND(_exit);
GDVIRTUAL_BIND(_update, "delta"); GDVIRTUAL_BIND(_update, "delta");
#elif LIMBOAI_GDEXTENSION
// TODO: Registering virtual functions is not available in godot-cpp...
#endif
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "EVENT_FINISHED", PROPERTY_HINT_NONE, "", 0), "", "event_finished"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "EVENT_FINISHED", PROPERTY_HINT_NONE, "", 0), "", "event_finished");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_agent", "get_agent"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_agent", "get_agent");

View File

@ -23,6 +23,7 @@
#endif // LIMBOAI_MODULE #endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION #ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/core/gdvirtual.gen.inc>
#include <godot_cpp/templates/hash_map.hpp> #include <godot_cpp/templates/hash_map.hpp>
#endif // LIMBOAI_GDEXTENSION #endif // LIMBOAI_GDEXTENSION
@ -59,12 +60,10 @@ protected:
virtual void _exit(); virtual void _exit();
virtual void _update(double p_delta); virtual void _update(double p_delta);
#ifdef LIMBOAI_MODULE
GDVIRTUAL0(_setup); GDVIRTUAL0(_setup);
GDVIRTUAL0(_enter); GDVIRTUAL0(_enter);
GDVIRTUAL0(_exit); GDVIRTUAL0(_exit);
GDVIRTUAL1(_update, double); GDVIRTUAL1(_update, double);
#endif // LIMBOAI_MODULE
public: public:
void set_blackboard_plan(const Ref<BlackboardPlan> &p_plan); void set_blackboard_plan(const Ref<BlackboardPlan> &p_plan);

View File

@ -62,33 +62,6 @@ _FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop)
#define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) r_ret = Variant::evaluate(m_op, m_lvalue, m_rvalue) #define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) r_ret = Variant::evaluate(m_op, m_lvalue, m_rvalue)
// * Virtual calls
#define VCALL(m_name, ...) (GDVIRTUAL_CALL(m_name, __VA_ARGS__))
#define VCALL_ARGS(m_name, ...) (GDVIRTUAL_CALL(m_name, __VA_ARGS__))
#define VCALL_V(m_name, r_ret) (GDVIRTUAL_CALL(m_name, r_ret))
#define VCALL_ARGS_V(m_name, r_ret, ...) (GDVIRTUAL_CALL(m_name, __VA_ARGS__, r_ret))
#define VCALL_OR_NATIVE(m_name) \
if (!GDVIRTUAL_CALL(m_name)) { \
m_name(); \
}
#define VCALL_OR_NATIVE_ARGS(m_name, ...) \
if (!GDVIRTUAL_CALL(m_name, __VA_ARGS__)) { \
m_name(__VA_ARGS__); \
}
#define VCALL_OR_NATIVE_V(m_name, m_ret_type, r_ret) \
if (!GDVIRTUAL_CALL(m_name, r_ret)) { \
r_ret = VariantCaster<m_ret_type>::cast(m_name()); \
}
#define VCALL_OR_NATIVE_ARGS_V(m_name, m_ret_type, r_ret, ...) \
if (!GDVIRTUAL_CALL(m_name, __VA_ARGS__, r_ret)) { \
r_ret = VariantCaster<m_ret_type>::cast(m_name(__VA_ARGS__)); \
}
// * Enum // * Enum
#define LW_KEY(key) (Key::key) #define LW_KEY(key) (Key::key)
@ -150,50 +123,6 @@ _FORCE_INLINE_ bool OBJECT_HAS_PROPERTY(Object *p_obj, const StringName &p_prop)
Variant::evaluate(m_op, m_lvalue, m_rvalue, r_ret, r_valid); \ Variant::evaluate(m_op, m_lvalue, m_rvalue, r_ret, r_valid); \
} }
// * Virtual calls:
// * This is a workaround for missing ClassDB::add_virtual_method().
// ! When using these macros, DON'T BIND the native virtual methods!
// -----------------------------
// VCALL*: only calls a script version if present.
// VCALL_OR_NATIVE*: calls a script version if present; otherwise, calls the native version.
#define VCALL(m_name) \
if (has_method(LW_NAME(m_name))) { \
call(LW_NAME(m_name)); \
}
#define VCALL_ARGS(m_name, ...) \
if (has_method(LW_NAME(m_name))) { \
call(LW_NAME(m_name), __VA_ARGS__); \
}
#define VCALL_V(m_name, r_ret) \
if (has_method(LW_NAME(m_name))) { \
r_ret = call(LW_NAME(m_name)); \
}
#define VCALL_ARGS_V(m_name, r_ret, ...) \
if (has_method(LW_NAME(m_name))) { \
r_ret = call(LW_NAME(m_name, __VA_ARGS__)); \
}
#define VCALL_OR_NATIVE(m_name) \
if (has_method(LW_NAME(m_name))) { \
call(LW_NAME(m_name)); \
} else { \
m_name(); \
}
#define VCALL_OR_NATIVE_ARGS(m_name, ...) \
if (has_method(LW_NAME(m_name))) { \
call(LW_NAME(m_name), __VA_ARGS__); \
} else { \
m_name(__VA_ARGS__); \
}
#define VCALL_OR_NATIVE_V(m_name, m_ret_type, r_ret) r_ret = (has_method(LW_NAME(m_name)) ? VariantCaster<m_ret_type>::cast(call(LW_NAME(m_name))) : VariantCaster<m_ret_type>::cast(m_name()))
#define VCALL_OR_NATIVE_ARGS_V(m_name, m_ret_type, r_ret, ...) r_ret = (has_method(LW_NAME(m_name)) ? VariantCaster<m_ret_type>::cast(call(LW_NAME(m_name), __VA_ARGS__)) : VariantCaster<m_ret_type>::cast(m_name(__VA_ARGS__)))
// * Enum // * Enum
#define LW_KEY(key) (Key::KEY_##key) #define LW_KEY(key) (Key::KEY_##key)