initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
12
core/object/SCsub
Normal file
12
core/object/SCsub
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
import make_virtuals
|
||||
|
||||
env.CommandNoCache(["gdvirtual.gen.inc"], "make_virtuals.py", env.Run(make_virtuals.run))
|
||||
|
||||
env_object = env.Clone()
|
||||
|
||||
env_object.add_source_files(env.core_sources, "*.cpp")
|
83
core/object/callable_method_pointer.cpp
Normal file
83
core/object/callable_method_pointer.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/**************************************************************************/
|
||||
/* callable_method_pointer.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "callable_method_pointer.h"
|
||||
|
||||
bool CallableCustomMethodPointerBase::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
|
||||
const CallableCustomMethodPointerBase *a = static_cast<const CallableCustomMethodPointerBase *>(p_a);
|
||||
const CallableCustomMethodPointerBase *b = static_cast<const CallableCustomMethodPointerBase *>(p_b);
|
||||
|
||||
if (a->comp_size != b->comp_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid sorting by memory address proximity, which leads to unpredictable performance over time
|
||||
// due to the reuse of old addresses for newer objects. Use byte-wise comparison to leverage the
|
||||
// backwards encoding of little-endian systems as a way to decouple spatiality and time.
|
||||
return memcmp(a->comp_ptr, b->comp_ptr, a->comp_size * 4) == 0;
|
||||
}
|
||||
|
||||
bool CallableCustomMethodPointerBase::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
|
||||
const CallableCustomMethodPointerBase *a = static_cast<const CallableCustomMethodPointerBase *>(p_a);
|
||||
const CallableCustomMethodPointerBase *b = static_cast<const CallableCustomMethodPointerBase *>(p_b);
|
||||
|
||||
if (a->comp_size != b->comp_size) {
|
||||
return a->comp_size < b->comp_size;
|
||||
}
|
||||
|
||||
// See note in compare_equal().
|
||||
return memcmp(a->comp_ptr, b->comp_ptr, a->comp_size * 4) < 0;
|
||||
}
|
||||
|
||||
CallableCustom::CompareEqualFunc CallableCustomMethodPointerBase::get_compare_equal_func() const {
|
||||
return compare_equal;
|
||||
}
|
||||
|
||||
CallableCustom::CompareLessFunc CallableCustomMethodPointerBase::get_compare_less_func() const {
|
||||
return compare_less;
|
||||
}
|
||||
|
||||
uint32_t CallableCustomMethodPointerBase::hash() const {
|
||||
return h;
|
||||
}
|
||||
|
||||
void CallableCustomMethodPointerBase::_setup(uint32_t *p_base_ptr, uint32_t p_ptr_size) {
|
||||
comp_ptr = p_base_ptr;
|
||||
comp_size = p_ptr_size / 4;
|
||||
|
||||
// Precompute hash.
|
||||
for (uint32_t i = 0; i < comp_size; i++) {
|
||||
if (i == 0) {
|
||||
h = hash_murmur3_one_32(comp_ptr[i]);
|
||||
} else {
|
||||
h = hash_murmur3_one_32(comp_ptr[i], h);
|
||||
}
|
||||
}
|
||||
}
|
290
core/object/callable_method_pointer.h
Normal file
290
core/object/callable_method_pointer.h
Normal file
@@ -0,0 +1,290 @@
|
||||
/**************************************************************************/
|
||||
/* callable_method_pointer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/object.h"
|
||||
#include "core/variant/binder_common.h"
|
||||
#include "core/variant/callable.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
class CallableCustomMethodPointerBase : public CallableCustom {
|
||||
uint32_t *comp_ptr = nullptr;
|
||||
uint32_t comp_size;
|
||||
uint32_t h;
|
||||
#ifdef DEBUG_ENABLED
|
||||
const char *text = "";
|
||||
#endif // DEBUG_ENABLED
|
||||
static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
|
||||
static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
|
||||
|
||||
protected:
|
||||
void _setup(uint32_t *p_base_ptr, uint32_t p_ptr_size);
|
||||
|
||||
public:
|
||||
virtual StringName get_method() const {
|
||||
#ifdef DEBUG_ENABLED
|
||||
return StringName(text);
|
||||
#else
|
||||
return StringName();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void set_text(const char *p_text) {
|
||||
text = p_text;
|
||||
}
|
||||
virtual String get_as_text() const {
|
||||
return text;
|
||||
}
|
||||
#else
|
||||
virtual String get_as_text() const {
|
||||
return String();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
virtual CompareEqualFunc get_compare_equal_func() const;
|
||||
virtual CompareLessFunc get_compare_less_func() const;
|
||||
|
||||
virtual uint32_t hash() const;
|
||||
};
|
||||
|
||||
template <typename T, typename R, typename... P>
|
||||
class CallableCustomMethodPointer : public CallableCustomMethodPointerBase {
|
||||
struct Data {
|
||||
T *instance;
|
||||
uint64_t object_id;
|
||||
R (T::*method)(P...);
|
||||
} data;
|
||||
|
||||
public:
|
||||
virtual ObjectID get_object() const {
|
||||
if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) {
|
||||
return ObjectID();
|
||||
}
|
||||
return data.instance->get_instance_id();
|
||||
}
|
||||
|
||||
virtual int get_argument_count(bool &r_is_valid) const {
|
||||
r_is_valid = true;
|
||||
return sizeof...(P);
|
||||
}
|
||||
|
||||
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
|
||||
ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
|
||||
if constexpr (std::is_same<R, void>::value) {
|
||||
call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error);
|
||||
} else {
|
||||
call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error);
|
||||
}
|
||||
}
|
||||
|
||||
CallableCustomMethodPointer(T *p_instance, R (T::*p_method)(P...)) {
|
||||
memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes.
|
||||
data.instance = p_instance;
|
||||
data.object_id = p_instance->get_instance_id();
|
||||
data.method = p_method;
|
||||
_setup((uint32_t *)&data, sizeof(Data));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... P>
|
||||
Callable create_custom_callable_function_pointer(T *p_instance,
|
||||
#ifdef DEBUG_ENABLED
|
||||
const char *p_func_text,
|
||||
#endif // DEBUG_ENABLED
|
||||
void (T::*p_method)(P...)) {
|
||||
typedef CallableCustomMethodPointer<T, void, P...> CCMP; // Messes with memnew otherwise.
|
||||
CCMP *ccmp = memnew(CCMP(p_instance, p_method));
|
||||
#ifdef DEBUG_ENABLED
|
||||
ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand.
|
||||
#endif // DEBUG_ENABLED
|
||||
return Callable(ccmp);
|
||||
}
|
||||
|
||||
template <typename T, typename R, typename... P>
|
||||
Callable create_custom_callable_function_pointer(T *p_instance,
|
||||
#ifdef DEBUG_ENABLED
|
||||
const char *p_func_text,
|
||||
#endif // DEBUG_ENABLED
|
||||
R (T::*p_method)(P...)) {
|
||||
typedef CallableCustomMethodPointer<T, R, P...> CCMP; // Messes with memnew otherwise.
|
||||
CCMP *ccmp = memnew(CCMP(p_instance, p_method));
|
||||
#ifdef DEBUG_ENABLED
|
||||
ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand.
|
||||
#endif // DEBUG_ENABLED
|
||||
return Callable(ccmp);
|
||||
}
|
||||
|
||||
// CONST VERSION
|
||||
|
||||
template <typename T, typename R, typename... P>
|
||||
class CallableCustomMethodPointerC : public CallableCustomMethodPointerBase {
|
||||
struct Data {
|
||||
T *instance;
|
||||
uint64_t object_id;
|
||||
R (T::*method)(P...) const;
|
||||
} data;
|
||||
|
||||
public:
|
||||
virtual ObjectID get_object() const override {
|
||||
if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) {
|
||||
return ObjectID();
|
||||
}
|
||||
return data.instance->get_instance_id();
|
||||
}
|
||||
|
||||
virtual int get_argument_count(bool &r_is_valid) const override {
|
||||
r_is_valid = true;
|
||||
return sizeof...(P);
|
||||
}
|
||||
|
||||
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
|
||||
ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
|
||||
if constexpr (std::is_same<R, void>::value) {
|
||||
call_with_variant_argsc(data.instance, data.method, p_arguments, p_argcount, r_call_error);
|
||||
} else {
|
||||
call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error);
|
||||
}
|
||||
}
|
||||
|
||||
CallableCustomMethodPointerC(T *p_instance, R (T::*p_method)(P...) const) {
|
||||
memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes.
|
||||
data.instance = p_instance;
|
||||
data.object_id = p_instance->get_instance_id();
|
||||
data.method = p_method;
|
||||
_setup((uint32_t *)&data, sizeof(Data));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... P>
|
||||
Callable create_custom_callable_function_pointer(T *p_instance,
|
||||
#ifdef DEBUG_ENABLED
|
||||
const char *p_func_text,
|
||||
#endif // DEBUG_ENABLED
|
||||
void (T::*p_method)(P...) const) {
|
||||
typedef CallableCustomMethodPointerC<T, void, P...> CCMP; // Messes with memnew otherwise.
|
||||
CCMP *ccmp = memnew(CCMP(p_instance, p_method));
|
||||
#ifdef DEBUG_ENABLED
|
||||
ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand.
|
||||
#endif // DEBUG_ENABLED
|
||||
return Callable(ccmp);
|
||||
}
|
||||
|
||||
template <typename T, typename R, typename... P>
|
||||
Callable create_custom_callable_function_pointer(T *p_instance,
|
||||
#ifdef DEBUG_ENABLED
|
||||
const char *p_func_text,
|
||||
#endif
|
||||
R (T::*p_method)(P...) const) {
|
||||
typedef CallableCustomMethodPointerC<T, R, P...> CCMP; // Messes with memnew otherwise.
|
||||
CCMP *ccmp = memnew(CCMP(p_instance, p_method));
|
||||
#ifdef DEBUG_ENABLED
|
||||
ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand.
|
||||
#endif // DEBUG_ENABLED
|
||||
return Callable(ccmp);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
#define callable_mp(I, M) create_custom_callable_function_pointer(I, #M, M)
|
||||
#else
|
||||
#define callable_mp(I, M) create_custom_callable_function_pointer(I, M)
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
// STATIC VERSIONS
|
||||
|
||||
template <typename R, typename... P>
|
||||
class CallableCustomStaticMethodPointer : public CallableCustomMethodPointerBase {
|
||||
struct Data {
|
||||
R (*method)(P...);
|
||||
} data;
|
||||
|
||||
public:
|
||||
virtual bool is_valid() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ObjectID get_object() const override {
|
||||
return ObjectID();
|
||||
}
|
||||
|
||||
virtual int get_argument_count(bool &r_is_valid) const override {
|
||||
r_is_valid = true;
|
||||
return sizeof...(P);
|
||||
}
|
||||
|
||||
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
|
||||
if constexpr (std::is_same<R, void>::value) {
|
||||
call_with_variant_args_static(data.method, p_arguments, p_argcount, r_call_error);
|
||||
} else {
|
||||
call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error);
|
||||
}
|
||||
}
|
||||
|
||||
CallableCustomStaticMethodPointer(R (*p_method)(P...)) {
|
||||
memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes.
|
||||
data.method = p_method;
|
||||
_setup((uint32_t *)&data, sizeof(Data));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... P>
|
||||
Callable create_custom_callable_static_function_pointer(
|
||||
#ifdef DEBUG_ENABLED
|
||||
const char *p_func_text,
|
||||
#endif // DEBUG_ENABLED
|
||||
void (*p_method)(P...)) {
|
||||
typedef CallableCustomStaticMethodPointer<void, P...> CCMP; // Messes with memnew otherwise.
|
||||
CCMP *ccmp = memnew(CCMP(p_method));
|
||||
#ifdef DEBUG_ENABLED
|
||||
ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand.
|
||||
#endif // DEBUG_ENABLED
|
||||
return Callable(ccmp);
|
||||
}
|
||||
|
||||
template <typename R, typename... P>
|
||||
Callable create_custom_callable_static_function_pointer(
|
||||
#ifdef DEBUG_ENABLED
|
||||
const char *p_func_text,
|
||||
#endif // DEBUG_ENABLED
|
||||
R (*p_method)(P...)) {
|
||||
typedef CallableCustomStaticMethodPointer<R, P...> CCMP; // Messes with memnew otherwise.
|
||||
CCMP *ccmp = memnew(CCMP(p_method));
|
||||
#ifdef DEBUG_ENABLED
|
||||
ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand.
|
||||
#endif // DEBUG_ENABLED
|
||||
return Callable(ccmp);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
#define callable_mp_static(M) create_custom_callable_static_function_pointer(#M, M)
|
||||
#else
|
||||
#define callable_mp_static(M) create_custom_callable_static_function_pointer(M)
|
||||
#endif
|
2437
core/object/class_db.cpp
Normal file
2437
core/object/class_db.cpp
Normal file
File diff suppressed because it is too large
Load Diff
576
core/object/class_db.h
Normal file
576
core/object/class_db.h
Normal file
@@ -0,0 +1,576 @@
|
||||
/**************************************************************************/
|
||||
/* class_db.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/method_bind.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
// Makes callable_mp readily available in all classes connecting signals.
|
||||
// Needs to come after method_bind and object have been included.
|
||||
#include "core/object/callable_method_pointer.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#define DEFVAL(m_defval) (m_defval)
|
||||
#define DEFVAL_ARRAY DEFVAL(ClassDB::default_array_arg)
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
||||
struct MethodDefinition {
|
||||
StringName name;
|
||||
Vector<StringName> args;
|
||||
MethodDefinition() {}
|
||||
MethodDefinition(const char *p_name) :
|
||||
name(p_name) {}
|
||||
MethodDefinition(const StringName &p_name) :
|
||||
name(p_name) {}
|
||||
};
|
||||
|
||||
MethodDefinition D_METHODP(const char *p_name, const char *const **p_args, uint32_t p_argcount);
|
||||
|
||||
template <typename... VarArgs>
|
||||
MethodDefinition D_METHOD(const char *p_name, const VarArgs... p_args) {
|
||||
const char *args[sizeof...(p_args) + 1] = { p_args..., nullptr }; // +1 makes sure zero sized arrays are also supported.
|
||||
const char *const *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
|
||||
return D_METHODP(p_name, sizeof...(p_args) == 0 ? nullptr : (const char *const **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// When DEBUG_ENABLED is set this will let the engine know
|
||||
// the argument names for easier debugging.
|
||||
#define D_METHOD(m_c, ...) m_c
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
class ClassDB {
|
||||
friend class Object;
|
||||
|
||||
public:
|
||||
enum APIType {
|
||||
API_CORE,
|
||||
API_EDITOR,
|
||||
API_EXTENSION,
|
||||
API_EDITOR_EXTENSION,
|
||||
API_NONE
|
||||
};
|
||||
|
||||
public:
|
||||
struct PropertySetGet {
|
||||
int index;
|
||||
StringName setter;
|
||||
StringName getter;
|
||||
MethodBind *_setptr = nullptr;
|
||||
MethodBind *_getptr = nullptr;
|
||||
Variant::Type type;
|
||||
};
|
||||
|
||||
struct ClassInfo {
|
||||
APIType api = API_NONE;
|
||||
ClassInfo *inherits_ptr = nullptr;
|
||||
void *class_ptr = nullptr;
|
||||
|
||||
ObjectGDExtension *gdextension = nullptr;
|
||||
|
||||
HashMap<StringName, MethodBind *> method_map;
|
||||
HashMap<StringName, LocalVector<MethodBind *>> method_map_compatibility;
|
||||
HashMap<StringName, int64_t> constant_map;
|
||||
struct EnumInfo {
|
||||
List<StringName> constants;
|
||||
bool is_bitfield = false;
|
||||
};
|
||||
|
||||
HashMap<StringName, EnumInfo> enum_map;
|
||||
HashMap<StringName, MethodInfo> signal_map;
|
||||
List<PropertyInfo> property_list;
|
||||
HashMap<StringName, PropertyInfo> property_map;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
List<StringName> constant_order;
|
||||
List<StringName> method_order;
|
||||
HashSet<StringName> methods_in_properties;
|
||||
List<MethodInfo> virtual_methods;
|
||||
HashMap<StringName, MethodInfo> virtual_methods_map;
|
||||
HashMap<StringName, Vector<Error>> method_error_values;
|
||||
HashMap<StringName, List<StringName>> linked_properties;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
List<StringName> dependency_list;
|
||||
#endif
|
||||
|
||||
HashMap<StringName, PropertySetGet> property_setget;
|
||||
HashMap<StringName, Vector<uint32_t>> virtual_methods_compat;
|
||||
|
||||
StringName inherits;
|
||||
StringName name;
|
||||
bool disabled = false;
|
||||
bool exposed = false;
|
||||
bool reloadable = false;
|
||||
bool is_virtual = false;
|
||||
bool is_runtime = false;
|
||||
// The bool argument indicates the need to postinitialize.
|
||||
Object *(*creation_func)(bool) = nullptr;
|
||||
|
||||
ClassInfo() {}
|
||||
~ClassInfo() {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static Object *creator(bool p_notify_postinitialize) {
|
||||
Object *ret = new ("") T;
|
||||
ret->_initialize();
|
||||
if (p_notify_postinitialize) {
|
||||
ret->_postinitialize();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// We need a recursive r/w lock because there are various code paths
|
||||
// that may in turn invoke other entry points with require locking.
|
||||
class Locker {
|
||||
public:
|
||||
enum State {
|
||||
STATE_UNLOCKED,
|
||||
STATE_READ,
|
||||
STATE_WRITE,
|
||||
};
|
||||
|
||||
private:
|
||||
inline static RWLock lock;
|
||||
inline thread_local static State thread_state = STATE_UNLOCKED;
|
||||
|
||||
public:
|
||||
class Lock {
|
||||
State state = STATE_UNLOCKED;
|
||||
|
||||
public:
|
||||
explicit Lock(State p_state);
|
||||
~Lock();
|
||||
};
|
||||
};
|
||||
|
||||
static HashMap<StringName, ClassInfo> classes;
|
||||
static HashMap<StringName, StringName> resource_base_extensions;
|
||||
static HashMap<StringName, StringName> compat_classes;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
static HashMap<StringName, ObjectGDExtension> placeholder_extensions;
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount);
|
||||
#else
|
||||
static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const char *method_name, const Variant **p_defs, int p_defcount);
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
static APIType current_api;
|
||||
static HashMap<APIType, uint32_t> api_hashes_cache;
|
||||
|
||||
static void _add_class(const StringName &p_class, const StringName &p_inherits);
|
||||
|
||||
static HashMap<StringName, HashMap<StringName, Variant>> default_values;
|
||||
static HashSet<StringName> default_values_cached;
|
||||
|
||||
// Native structs, used by binder
|
||||
struct NativeStruct {
|
||||
String ccode; // C code to create the native struct, fields separated by ; Arrays accepted (even containing other structs), also function pointers. All types must be Godot types.
|
||||
uint64_t struct_size; // local size of struct, for comparison
|
||||
};
|
||||
static HashMap<StringName, NativeStruct> native_structs;
|
||||
|
||||
static Array default_array_arg;
|
||||
static bool is_default_array_arg(const Array &p_array);
|
||||
|
||||
private:
|
||||
// Non-locking variants of get_parent_class and is_parent_class.
|
||||
static StringName _get_parent_class(const StringName &p_class);
|
||||
static bool _is_parent_class(const StringName &p_class, const StringName &p_inherits);
|
||||
static void _bind_compatibility(ClassInfo *type, MethodBind *p_method);
|
||||
static MethodBind *_bind_vararg_method(MethodBind *p_bind, const StringName &p_name, const Vector<Variant> &p_default_args, bool p_compatibility);
|
||||
static void _bind_method_custom(const StringName &p_class, MethodBind *p_method, bool p_compatibility);
|
||||
|
||||
static Object *_instantiate_internal(const StringName &p_class, bool p_require_real_class = false, bool p_notify_postinitialize = true, bool p_exposed_only = true);
|
||||
|
||||
static bool _can_instantiate(ClassInfo *p_class_info, bool p_exposed_only = true);
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
static void register_class(bool p_virtual = false) {
|
||||
Locker::Lock lock(Locker::STATE_WRITE);
|
||||
static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
|
||||
T::initialize_class();
|
||||
ClassInfo *t = classes.getptr(T::get_class_static());
|
||||
ERR_FAIL_NULL(t);
|
||||
t->creation_func = &creator<T>;
|
||||
t->exposed = true;
|
||||
t->is_virtual = p_virtual;
|
||||
t->class_ptr = T::get_class_ptr_static();
|
||||
t->api = current_api;
|
||||
T::register_custom_data_to_otdb();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void register_abstract_class() {
|
||||
Locker::Lock lock(Locker::STATE_WRITE);
|
||||
static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
|
||||
T::initialize_class();
|
||||
ClassInfo *t = classes.getptr(T::get_class_static());
|
||||
ERR_FAIL_NULL(t);
|
||||
t->exposed = true;
|
||||
t->class_ptr = T::get_class_ptr_static();
|
||||
t->api = current_api;
|
||||
//nothing
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void register_internal_class() {
|
||||
Locker::Lock lock(Locker::STATE_WRITE);
|
||||
static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
|
||||
T::initialize_class();
|
||||
ClassInfo *t = classes.getptr(T::get_class_static());
|
||||
ERR_FAIL_NULL(t);
|
||||
t->creation_func = &creator<T>;
|
||||
t->exposed = false;
|
||||
t->is_virtual = false;
|
||||
t->class_ptr = T::get_class_ptr_static();
|
||||
t->api = current_api;
|
||||
T::register_custom_data_to_otdb();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void register_runtime_class() {
|
||||
Locker::Lock lock(Locker::STATE_WRITE);
|
||||
static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
|
||||
T::initialize_class();
|
||||
ClassInfo *t = classes.getptr(T::get_class_static());
|
||||
ERR_FAIL_NULL(t);
|
||||
ERR_FAIL_COND_MSG(t->inherits_ptr && !t->inherits_ptr->creation_func, vformat("Cannot register runtime class '%s' that descends from an abstract parent class.", T::get_class_static()));
|
||||
t->creation_func = &creator<T>;
|
||||
t->exposed = true;
|
||||
t->is_virtual = false;
|
||||
t->is_runtime = true;
|
||||
t->class_ptr = T::get_class_ptr_static();
|
||||
t->api = current_api;
|
||||
T::register_custom_data_to_otdb();
|
||||
}
|
||||
|
||||
static void register_extension_class(ObjectGDExtension *p_extension);
|
||||
static void unregister_extension_class(const StringName &p_class, bool p_free_method_binds = true);
|
||||
|
||||
template <typename T>
|
||||
static Object *_create_ptr_func(bool p_notify_postinitialize) {
|
||||
return T::create(p_notify_postinitialize);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void register_custom_instance_class() {
|
||||
Locker::Lock lock(Locker::STATE_WRITE);
|
||||
static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
|
||||
T::initialize_class();
|
||||
ClassInfo *t = classes.getptr(T::get_class_static());
|
||||
ERR_FAIL_NULL(t);
|
||||
t->creation_func = &_create_ptr_func<T>;
|
||||
t->exposed = true;
|
||||
t->class_ptr = T::get_class_ptr_static();
|
||||
t->api = current_api;
|
||||
T::register_custom_data_to_otdb();
|
||||
}
|
||||
|
||||
static void get_class_list(List<StringName> *p_classes);
|
||||
#ifdef TOOLS_ENABLED
|
||||
static void get_extensions_class_list(List<StringName> *p_classes);
|
||||
static void get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes);
|
||||
static ObjectGDExtension *get_placeholder_extension(const StringName &p_class);
|
||||
#endif
|
||||
static void get_inheriters_from_class(const StringName &p_class, LocalVector<StringName> &p_classes);
|
||||
static void get_direct_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes);
|
||||
static StringName get_parent_class_nocheck(const StringName &p_class);
|
||||
static bool get_inheritance_chain_nocheck(const StringName &p_class, Vector<StringName> &r_result);
|
||||
static StringName get_parent_class(const StringName &p_class);
|
||||
static StringName get_compatibility_remapped_class(const StringName &p_class);
|
||||
static bool class_exists(const StringName &p_class);
|
||||
static bool is_parent_class(const StringName &p_class, const StringName &p_inherits);
|
||||
static bool can_instantiate(const StringName &p_class);
|
||||
static bool is_abstract(const StringName &p_class);
|
||||
static bool is_virtual(const StringName &p_class);
|
||||
static Object *instantiate(const StringName &p_class);
|
||||
static Object *instantiate_no_placeholders(const StringName &p_class);
|
||||
static Object *instantiate_without_postinitialization(const StringName &p_class);
|
||||
static void set_object_extension_instance(Object *p_object, const StringName &p_class, GDExtensionClassInstancePtr p_instance);
|
||||
|
||||
static APIType get_api_type(const StringName &p_class);
|
||||
|
||||
static uint32_t get_api_hash(APIType p_api);
|
||||
|
||||
template <typename>
|
||||
struct member_function_traits;
|
||||
|
||||
template <typename R, typename T, typename... Args>
|
||||
struct member_function_traits<R (T::*)(Args...)> {
|
||||
using return_type = R;
|
||||
};
|
||||
|
||||
template <typename R, typename T, typename... Args>
|
||||
struct member_function_traits<R (T::*)(Args...) const> {
|
||||
using return_type = R;
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct member_function_traits<R (*)(Args...)> {
|
||||
using return_type = R;
|
||||
};
|
||||
|
||||
template <typename N, typename M, typename... VarArgs>
|
||||
static MethodBind *bind_method(N p_method_name, M p_method, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
MethodBind *bind = create_method_bind(p_method);
|
||||
if constexpr (std::is_same_v<typename member_function_traits<M>::return_type, Object *>) {
|
||||
bind->set_return_type_is_raw_object_ptr(true);
|
||||
}
|
||||
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
template <typename N, typename M, typename... VarArgs>
|
||||
static MethodBind *bind_static_method(const StringName &p_class, N p_method_name, M p_method, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
MethodBind *bind = create_static_method_bind(p_method);
|
||||
bind->set_instance_class(p_class);
|
||||
if constexpr (std::is_same_v<typename member_function_traits<M>::return_type, Object *>) {
|
||||
bind->set_return_type_is_raw_object_ptr(true);
|
||||
}
|
||||
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
template <typename N, typename M, typename... VarArgs>
|
||||
static MethodBind *bind_compatibility_method(N p_method_name, M p_method, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
MethodBind *bind = create_method_bind(p_method);
|
||||
if constexpr (std::is_same_v<typename member_function_traits<M>::return_type, Object *>) {
|
||||
bind->set_return_type_is_raw_object_ptr(true);
|
||||
}
|
||||
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
template <typename N, typename M, typename... VarArgs>
|
||||
static MethodBind *bind_compatibility_static_method(const StringName &p_class, N p_method_name, M p_method, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
MethodBind *bind = create_static_method_bind(p_method);
|
||||
bind->set_instance_class(p_class);
|
||||
if constexpr (std::is_same_v<typename member_function_traits<M>::return_type, Object *>) {
|
||||
bind->set_return_type_is_raw_object_ptr(true);
|
||||
}
|
||||
return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
static MethodBind *bind_vararg_method(uint32_t p_flags, const StringName &p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const Vector<Variant> &p_default_args = Vector<Variant>(), bool p_return_nil_is_variant = true) {
|
||||
Locker::Lock lock(Locker::STATE_WRITE);
|
||||
|
||||
MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant);
|
||||
ERR_FAIL_NULL_V(bind, nullptr);
|
||||
|
||||
if constexpr (std::is_same_v<typename member_function_traits<M>::return_type, Object *>) {
|
||||
bind->set_return_type_is_raw_object_ptr(true);
|
||||
}
|
||||
return _bind_vararg_method(bind, p_name, p_default_args, false);
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
static MethodBind *bind_compatibility_vararg_method(uint32_t p_flags, const StringName &p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const Vector<Variant> &p_default_args = Vector<Variant>(), bool p_return_nil_is_variant = true) {
|
||||
Locker::Lock lock(Locker::STATE_WRITE);
|
||||
|
||||
MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant);
|
||||
ERR_FAIL_NULL_V(bind, nullptr);
|
||||
|
||||
if constexpr (std::is_same_v<typename member_function_traits<M>::return_type, Object *>) {
|
||||
bind->set_return_type_is_raw_object_ptr(true);
|
||||
}
|
||||
return _bind_vararg_method(bind, p_name, p_default_args, true);
|
||||
}
|
||||
|
||||
static void bind_method_custom(const StringName &p_class, MethodBind *p_method);
|
||||
static void bind_compatibility_method_custom(const StringName &p_class, MethodBind *p_method);
|
||||
|
||||
static void add_signal(const StringName &p_class, const MethodInfo &p_signal);
|
||||
static bool has_signal(const StringName &p_class, const StringName &p_signal, bool p_no_inheritance = false);
|
||||
static bool get_signal(const StringName &p_class, const StringName &p_signal, MethodInfo *r_signal);
|
||||
static void get_signal_list(const StringName &p_class, List<MethodInfo> *p_signals, bool p_no_inheritance = false);
|
||||
|
||||
static void add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix = "", int p_indent_depth = 0);
|
||||
static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix = "", int p_indent_depth = 0);
|
||||
static void add_property_array_count(const StringName &p_class, const String &p_label, const StringName &p_count_property, const StringName &p_count_setter, const StringName &p_count_getter, const String &p_array_element_prefix, uint32_t p_count_usage = PROPERTY_USAGE_DEFAULT);
|
||||
static void add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix);
|
||||
static void add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index = -1);
|
||||
static void set_property_default_value(const StringName &p_class, const StringName &p_name, const Variant &p_default);
|
||||
static void add_linked_property(const StringName &p_class, const String &p_property, const String &p_linked_property);
|
||||
static void get_property_list(const StringName &p_class, List<PropertyInfo> *p_list, bool p_no_inheritance = false, const Object *p_validator = nullptr);
|
||||
static bool get_property_info(const StringName &p_class, const StringName &p_property, PropertyInfo *r_info, bool p_no_inheritance = false, const Object *p_validator = nullptr);
|
||||
static void get_linked_properties_info(const StringName &p_class, const StringName &p_property, List<StringName> *r_properties, bool p_no_inheritance = false);
|
||||
static bool set_property(Object *p_object, const StringName &p_property, const Variant &p_value, bool *r_valid = nullptr);
|
||||
static bool get_property(Object *p_object, const StringName &p_property, Variant &r_value);
|
||||
static bool has_property(const StringName &p_class, const StringName &p_property, bool p_no_inheritance = false);
|
||||
static int get_property_index(const StringName &p_class, const StringName &p_property, bool *r_is_valid = nullptr);
|
||||
static Variant::Type get_property_type(const StringName &p_class, const StringName &p_property, bool *r_is_valid = nullptr);
|
||||
static StringName get_property_setter(const StringName &p_class, const StringName &p_property);
|
||||
static StringName get_property_getter(const StringName &p_class, const StringName &p_property);
|
||||
|
||||
static bool has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false);
|
||||
static void set_method_flags(const StringName &p_class, const StringName &p_method, int p_flags);
|
||||
|
||||
static void get_method_list(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
|
||||
static void get_method_list_with_compatibility(const StringName &p_class, List<Pair<MethodInfo, uint32_t>> *p_methods_with_hash, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
|
||||
static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
|
||||
static int get_method_argument_count(const StringName &p_class, const StringName &p_method, bool *r_is_valid = nullptr, bool p_no_inheritance = false);
|
||||
static MethodBind *get_method(const StringName &p_class, const StringName &p_name);
|
||||
static MethodBind *get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists = nullptr, bool *r_is_deprecated = nullptr);
|
||||
static Vector<uint32_t> get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name);
|
||||
|
||||
static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector<String> &p_arg_names = Vector<String>(), bool p_object_core = false);
|
||||
static void add_virtual_compatibility_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector<String> &p_arg_names = Vector<String>(), bool p_object_core = false);
|
||||
static void get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false);
|
||||
static void add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info);
|
||||
static Vector<uint32_t> get_virtual_method_compatibility_hashes(const StringName &p_class, const StringName &p_name);
|
||||
|
||||
static void bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int64_t p_constant, bool p_is_bitfield = false);
|
||||
static void get_integer_constant_list(const StringName &p_class, List<String> *p_constants, bool p_no_inheritance = false);
|
||||
static int64_t get_integer_constant(const StringName &p_class, const StringName &p_name, bool *p_success = nullptr);
|
||||
static bool has_integer_constant(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
|
||||
|
||||
static StringName get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
|
||||
static StringName get_integer_constant_bitfield(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
|
||||
static void get_enum_list(const StringName &p_class, List<StringName> *p_enums, bool p_no_inheritance = false);
|
||||
static void get_enum_constants(const StringName &p_class, const StringName &p_enum, List<StringName> *p_constants, bool p_no_inheritance = false);
|
||||
static bool has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
|
||||
static bool is_enum_bitfield(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false);
|
||||
|
||||
static void set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values);
|
||||
static Vector<Error> get_method_error_return_values(const StringName &p_class, const StringName &p_method);
|
||||
static Variant class_get_default_property_value(const StringName &p_class, const StringName &p_property, bool *r_valid = nullptr);
|
||||
|
||||
static void set_class_enabled(const StringName &p_class, bool p_enable);
|
||||
static bool is_class_enabled(const StringName &p_class);
|
||||
|
||||
static bool is_class_exposed(const StringName &p_class);
|
||||
static bool is_class_reloadable(const StringName &p_class);
|
||||
static bool is_class_runtime(const StringName &p_class);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
static void add_class_dependency(const StringName &p_class, const StringName &p_dependency);
|
||||
static void get_class_dependencies(const StringName &p_class, List<StringName> *r_rependencies);
|
||||
#endif
|
||||
|
||||
static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class);
|
||||
static void get_resource_base_extensions(List<String> *p_extensions);
|
||||
static void get_extensions_for_type(const StringName &p_class, List<String> *p_extensions);
|
||||
static bool is_resource_extension(const StringName &p_extension);
|
||||
|
||||
static void add_compatibility_class(const StringName &p_class, const StringName &p_fallback);
|
||||
static StringName get_compatibility_class(const StringName &p_class);
|
||||
|
||||
static void set_current_api(APIType p_api);
|
||||
static APIType get_current_api();
|
||||
static void cleanup_defaults();
|
||||
static void cleanup();
|
||||
|
||||
static void register_native_struct(const StringName &p_name, const String &p_code, uint64_t p_current_size);
|
||||
static void get_native_struct_list(List<StringName> *r_names);
|
||||
static String get_native_struct_code(const StringName &p_name);
|
||||
static uint64_t get_native_struct_size(const StringName &p_name); // Used for asserting
|
||||
|
||||
static Object *_instantiate_allow_unexposed(const StringName &p_class); // Used to create unexposed classes from GDExtension, typically for unexposed EditorPlugin.
|
||||
};
|
||||
|
||||
#define BIND_ENUM_CONSTANT(m_constant) \
|
||||
::ClassDB::bind_integer_constant(get_class_static(), __constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant);
|
||||
|
||||
#define BIND_BITFIELD_FLAG(m_constant) \
|
||||
::ClassDB::bind_integer_constant(get_class_static(), __constant_get_bitfield_name(m_constant, #m_constant), #m_constant, m_constant, true);
|
||||
|
||||
#define BIND_CONSTANT(m_constant) \
|
||||
::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
||||
#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...) \
|
||||
::ClassDB::set_method_error_return_values(get_class_static(), m_method, Vector<Error>{ __VA_ARGS__ });
|
||||
|
||||
#else
|
||||
|
||||
#define BIND_METHOD_ERR_RETURN_DOC(m_method, ...)
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#define GDREGISTER_CLASS(m_class) \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_class<m_class>(); \
|
||||
}
|
||||
#define GDREGISTER_VIRTUAL_CLASS(m_class) \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_class<m_class>(true); \
|
||||
}
|
||||
#define GDREGISTER_ABSTRACT_CLASS(m_class) \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_abstract_class<m_class>(); \
|
||||
}
|
||||
#define GDREGISTER_INTERNAL_CLASS(m_class) \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_internal_class<m_class>(); \
|
||||
}
|
||||
|
||||
#define GDREGISTER_RUNTIME_CLASS(m_class) \
|
||||
if (m_class::_class_is_enabled) { \
|
||||
::ClassDB::register_runtime_class<m_class>(); \
|
||||
}
|
||||
|
||||
#define GDREGISTER_NATIVE_STRUCT(m_class, m_code) ClassDB::register_native_struct(#m_class, m_code, sizeof(m_class))
|
267
core/object/make_virtuals.py
Normal file
267
core/object/make_virtuals.py
Normal file
@@ -0,0 +1,267 @@
|
||||
script_call = """ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\
|
||||
if (_script_instance) {\\
|
||||
Callable::CallError ce;\\
|
||||
$CALLSIARGS\\
|
||||
$CALLSIBEGIN_script_instance->callp(_gdvirtual_##$VARNAME##_sn, $CALLSIARGPASS, ce);\\
|
||||
if (ce.error == Callable::CallError::CALL_OK) {\\
|
||||
$CALLSIRET\\
|
||||
return true;\\
|
||||
}\\
|
||||
}"""
|
||||
|
||||
script_has_method = """ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\
|
||||
if (_script_instance && _script_instance->has_method(_gdvirtual_##$VARNAME##_sn)) {\\
|
||||
return true;\\
|
||||
}"""
|
||||
|
||||
proto = """#define GDVIRTUAL$VER($ALIAS $RET m_name $ARG)\\
|
||||
mutable void *_gdvirtual_##$VARNAME = nullptr;\\
|
||||
_FORCE_INLINE_ bool _gdvirtual_##$VARNAME##_call($CALLARGS) $CONST {\\
|
||||
static const StringName _gdvirtual_##$VARNAME##_sn = StringName(#m_name, true);\\
|
||||
$SCRIPTCALL\\
|
||||
if (_get_extension()) {\\
|
||||
if (unlikely(!_gdvirtual_##$VARNAME)) {\\
|
||||
MethodInfo mi = _gdvirtual_##$VARNAME##_get_method_info();\\
|
||||
uint32_t hash = mi.get_compatibility_hash();\\
|
||||
_gdvirtual_##$VARNAME = nullptr;\\
|
||||
if (_get_extension()->get_virtual_call_data2 && _get_extension()->call_virtual_with_data) {\\
|
||||
_gdvirtual_##$VARNAME = _get_extension()->get_virtual_call_data2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\
|
||||
} else if (_get_extension()->get_virtual2) {\\
|
||||
_gdvirtual_##$VARNAME = (void *)_get_extension()->get_virtual2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\
|
||||
}\\
|
||||
_GDVIRTUAL_GET_DEPRECATED(_gdvirtual_##$VARNAME, _gdvirtual_##$VARNAME##_sn, $COMPAT)\\
|
||||
_GDVIRTUAL_TRACK(_gdvirtual_##$VARNAME);\\
|
||||
if (_gdvirtual_##$VARNAME == nullptr) {\\
|
||||
_gdvirtual_##$VARNAME = reinterpret_cast<void*>(_INVALID_GDVIRTUAL_FUNC_ADDR);\\
|
||||
}\\
|
||||
}\\
|
||||
if (_gdvirtual_##$VARNAME != reinterpret_cast<void*>(_INVALID_GDVIRTUAL_FUNC_ADDR)) {\\
|
||||
$CALLPTRARGS\\
|
||||
$CALLPTRRETDEF\\
|
||||
if (_get_extension()->call_virtual_with_data) {\\
|
||||
_get_extension()->call_virtual_with_data(_get_extension_instance(), &_gdvirtual_##$VARNAME##_sn, _gdvirtual_##$VARNAME, $CALLPTRARGPASS, $CALLPTRRETPASS);\\
|
||||
$CALLPTRRET\\
|
||||
} else {\\
|
||||
((GDExtensionClassCallVirtual)_gdvirtual_##$VARNAME)(_get_extension_instance(), $CALLPTRARGPASS, $CALLPTRRETPASS);\\
|
||||
$CALLPTRRET\\
|
||||
}\\
|
||||
return true;\\
|
||||
}\\
|
||||
}\\
|
||||
$REQCHECK\\
|
||||
$RVOID\\
|
||||
return false;\\
|
||||
}\\
|
||||
_FORCE_INLINE_ bool _gdvirtual_##$VARNAME##_overridden() const {\\
|
||||
static const StringName _gdvirtual_##$VARNAME##_sn = StringName(#m_name, true);\\
|
||||
$SCRIPTHASMETHOD\\
|
||||
if (_get_extension()) {\\
|
||||
if (unlikely(!_gdvirtual_##$VARNAME)) {\\
|
||||
MethodInfo mi = _gdvirtual_##$VARNAME##_get_method_info();\\
|
||||
uint32_t hash = mi.get_compatibility_hash();\\
|
||||
_gdvirtual_##$VARNAME = nullptr;\\
|
||||
if (_get_extension()->get_virtual_call_data2 && _get_extension()->call_virtual_with_data) {\\
|
||||
_gdvirtual_##$VARNAME = _get_extension()->get_virtual_call_data2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\
|
||||
} else if (_get_extension()->get_virtual2) {\\
|
||||
_gdvirtual_##$VARNAME = (void *)_get_extension()->get_virtual2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\
|
||||
}\\
|
||||
_GDVIRTUAL_GET_DEPRECATED(_gdvirtual_##$VARNAME, _gdvirtual_##$VARNAME##_sn, $COMPAT)\\
|
||||
_GDVIRTUAL_TRACK(_gdvirtual_##$VARNAME);\\
|
||||
if (_gdvirtual_##$VARNAME == nullptr) {\\
|
||||
_gdvirtual_##$VARNAME = reinterpret_cast<void*>(_INVALID_GDVIRTUAL_FUNC_ADDR);\\
|
||||
}\\
|
||||
}\\
|
||||
if (_gdvirtual_##$VARNAME != reinterpret_cast<void*>(_INVALID_GDVIRTUAL_FUNC_ADDR)) {\\
|
||||
return true;\\
|
||||
}\\
|
||||
}\\
|
||||
return false;\\
|
||||
}\\
|
||||
_FORCE_INLINE_ static MethodInfo _gdvirtual_##$VARNAME##_get_method_info() {\\
|
||||
MethodInfo method_info;\\
|
||||
method_info.name = #m_name;\\
|
||||
method_info.flags = $METHOD_FLAGS;\\
|
||||
$FILL_METHOD_INFO\\
|
||||
return method_info;\\
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def generate_version(argcount, const=False, returns=False, required=False, compat=False):
|
||||
s = proto
|
||||
if compat:
|
||||
s = s.replace("$SCRIPTCALL", "")
|
||||
s = s.replace("$SCRIPTHASMETHOD", "")
|
||||
else:
|
||||
s = s.replace("$SCRIPTCALL", script_call)
|
||||
s = s.replace("$SCRIPTHASMETHOD", script_has_method)
|
||||
|
||||
sproto = str(argcount)
|
||||
method_info = ""
|
||||
method_flags = "METHOD_FLAG_VIRTUAL"
|
||||
if returns:
|
||||
sproto += "R"
|
||||
s = s.replace("$RET", "m_ret,")
|
||||
s = s.replace("$RVOID", "(void)r_ret;") # If required, may lead to uninitialized errors
|
||||
s = s.replace("$CALLPTRRETDEF", "PtrToArg<m_ret>::EncodeT ret;")
|
||||
method_info += "method_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\\n"
|
||||
method_info += "\t\tmethod_info.return_val_metadata = GetTypeInfo<m_ret>::METADATA;"
|
||||
else:
|
||||
s = s.replace("$RET ", "")
|
||||
s = s.replace("\t\t$RVOID\\\n", "")
|
||||
s = s.replace("\t\t\t$CALLPTRRETDEF\\\n", "")
|
||||
|
||||
if const:
|
||||
sproto += "C"
|
||||
method_flags += " | METHOD_FLAG_CONST"
|
||||
s = s.replace("$CONST", "const")
|
||||
else:
|
||||
s = s.replace("$CONST ", "")
|
||||
|
||||
if required:
|
||||
sproto += "_REQUIRED"
|
||||
method_flags += " | METHOD_FLAG_VIRTUAL_REQUIRED"
|
||||
s = s.replace(
|
||||
"$REQCHECK",
|
||||
'ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");',
|
||||
)
|
||||
else:
|
||||
s = s.replace("\t\t$REQCHECK\\\n", "")
|
||||
|
||||
if compat:
|
||||
sproto += "_COMPAT"
|
||||
s = s.replace("$COMPAT", "true")
|
||||
s = s.replace("$ALIAS", "m_alias,")
|
||||
s = s.replace("$VARNAME", "m_alias")
|
||||
else:
|
||||
s = s.replace("$COMPAT", "false")
|
||||
s = s.replace("$ALIAS ", "")
|
||||
s = s.replace("$VARNAME", "m_name")
|
||||
|
||||
s = s.replace("$METHOD_FLAGS", method_flags)
|
||||
s = s.replace("$VER", sproto)
|
||||
argtext = ""
|
||||
callargtext = ""
|
||||
callsiargs = ""
|
||||
callsiargptrs = ""
|
||||
callptrargsptr = ""
|
||||
if argcount > 0:
|
||||
argtext += ", "
|
||||
callsiargs = f"Variant vargs[{argcount}] = {{ "
|
||||
callsiargptrs = f"\t\t\tconst Variant *vargptrs[{argcount}] = {{ "
|
||||
callptrargsptr = f"\t\t\tGDExtensionConstTypePtr argptrs[{argcount}] = {{ "
|
||||
callptrargs = ""
|
||||
for i in range(argcount):
|
||||
if i > 0:
|
||||
argtext += ", "
|
||||
callargtext += ", "
|
||||
callsiargs += ", "
|
||||
callsiargptrs += ", "
|
||||
callptrargs += "\t\t\t"
|
||||
callptrargsptr += ", "
|
||||
argtext += f"m_type{i + 1}"
|
||||
callargtext += f"m_type{i + 1} arg{i + 1}"
|
||||
callsiargs += f"arg{i + 1}"
|
||||
callsiargptrs += f"&vargs[{i}]"
|
||||
callptrargs += (
|
||||
f"PtrToArg<m_type{i + 1}>::EncodeT argval{i + 1} = (PtrToArg<m_type{i + 1}>::EncodeT)arg{i + 1};\\\n"
|
||||
)
|
||||
callptrargsptr += f"&argval{i + 1}"
|
||||
if method_info:
|
||||
method_info += "\\\n\t\t"
|
||||
method_info += f"method_info.arguments.push_back(GetTypeInfo<m_type{i + 1}>::get_class_info());\\\n"
|
||||
method_info += f"\t\tmethod_info.arguments_metadata.push_back(GetTypeInfo<m_type{i + 1}>::METADATA);"
|
||||
|
||||
if argcount:
|
||||
callsiargs += " };\\\n"
|
||||
callsiargptrs += " };"
|
||||
s = s.replace("$CALLSIARGS", callsiargs + callsiargptrs)
|
||||
s = s.replace("$CALLSIARGPASS", f"(const Variant **)vargptrs, {argcount}")
|
||||
callptrargsptr += " };"
|
||||
s = s.replace("$CALLPTRARGS", callptrargs + callptrargsptr)
|
||||
s = s.replace("$CALLPTRARGPASS", "reinterpret_cast<GDExtensionConstTypePtr *>(argptrs)")
|
||||
else:
|
||||
s = s.replace("\t\t\t$CALLSIARGS\\\n", "")
|
||||
s = s.replace("$CALLSIARGPASS", "nullptr, 0")
|
||||
s = s.replace("\t\t\t$CALLPTRARGS\\\n", "")
|
||||
s = s.replace("$CALLPTRARGPASS", "nullptr")
|
||||
|
||||
if returns:
|
||||
if argcount > 0:
|
||||
callargtext += ", "
|
||||
callargtext += "m_ret &r_ret"
|
||||
s = s.replace("$CALLSIBEGIN", "Variant ret = ")
|
||||
s = s.replace("$CALLSIRET", "r_ret = VariantCaster<m_ret>::cast(ret);")
|
||||
s = s.replace("$CALLPTRRETPASS", "&ret")
|
||||
s = s.replace("$CALLPTRRET", "r_ret = (m_ret)ret;")
|
||||
else:
|
||||
s = s.replace("$CALLSIBEGIN", "")
|
||||
s = s.replace("\t\t\t\t$CALLSIRET\\\n", "")
|
||||
s = s.replace("$CALLPTRRETPASS", "nullptr")
|
||||
s = s.replace("\t\t\t\t$CALLPTRRET\\\n", "")
|
||||
|
||||
s = s.replace(" $ARG", argtext)
|
||||
s = s.replace("$CALLARGS", callargtext)
|
||||
if method_info:
|
||||
s = s.replace("$FILL_METHOD_INFO", method_info)
|
||||
else:
|
||||
s = s.replace("\t\t$FILL_METHOD_INFO\\\n", method_info)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def run(target, source, env):
|
||||
max_versions = 12
|
||||
|
||||
txt = """/* THIS FILE IS GENERATED DO NOT EDIT */
|
||||
#pragma once
|
||||
|
||||
#include "core/object/script_instance.h"
|
||||
|
||||
inline constexpr uintptr_t _INVALID_GDVIRTUAL_FUNC_ADDR = static_cast<uintptr_t>(-1);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#define _GDVIRTUAL_TRACK(m_virtual)\\
|
||||
if (_get_extension()->reloadable) {\\
|
||||
VirtualMethodTracker *tracker = memnew(VirtualMethodTracker);\\
|
||||
tracker->method = (void **)&m_virtual;\\
|
||||
tracker->next = virtual_method_list;\\
|
||||
virtual_method_list = tracker;\\
|
||||
}
|
||||
#else
|
||||
#define _GDVIRTUAL_TRACK(m_virtual)
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
#define _GDVIRTUAL_GET_DEPRECATED(m_virtual, m_name_sn, m_compat)\\
|
||||
else if (m_compat || ClassDB::get_virtual_method_compatibility_hashes(get_class_static(), m_name_sn).size() == 0) {\\
|
||||
if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\
|
||||
m_virtual = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &m_name_sn);\\
|
||||
} else if (_get_extension()->get_virtual) {\\
|
||||
m_virtual = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &m_name_sn);\\
|
||||
}\\
|
||||
}
|
||||
#else
|
||||
#define _GDVIRTUAL_GET_DEPRECATED(m_name, m_name_sn, m_compat)
|
||||
#endif
|
||||
|
||||
"""
|
||||
|
||||
for i in range(max_versions + 1):
|
||||
txt += f"/* {i} Arguments */\n\n"
|
||||
txt += generate_version(i, False, False)
|
||||
txt += generate_version(i, False, True)
|
||||
txt += generate_version(i, True, False)
|
||||
txt += generate_version(i, True, True)
|
||||
txt += generate_version(i, False, False, True)
|
||||
txt += generate_version(i, False, True, True)
|
||||
txt += generate_version(i, True, False, True)
|
||||
txt += generate_version(i, True, True, True)
|
||||
txt += generate_version(i, False, False, False, True)
|
||||
txt += generate_version(i, False, True, False, True)
|
||||
txt += generate_version(i, True, False, False, True)
|
||||
txt += generate_version(i, True, True, False, True)
|
||||
|
||||
with open(str(target[0]), "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(txt)
|
516
core/object/message_queue.cpp
Normal file
516
core/object/message_queue.cpp
Normal file
@@ -0,0 +1,516 @@
|
||||
/**************************************************************************/
|
||||
/* message_queue.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "message_queue.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
// Includes safety checks to ensure that a queue set as a thread singleton override
|
||||
// is only ever called from the thread it was set for.
|
||||
#define LOCK_MUTEX \
|
||||
if (this != MessageQueue::thread_singleton) { \
|
||||
DEV_ASSERT(!is_current_thread_override); \
|
||||
mutex.lock(); \
|
||||
} else { \
|
||||
DEV_ASSERT(is_current_thread_override); \
|
||||
}
|
||||
#else
|
||||
#define LOCK_MUTEX \
|
||||
if (this != MessageQueue::thread_singleton) { \
|
||||
mutex.lock(); \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define UNLOCK_MUTEX \
|
||||
if (this != MessageQueue::thread_singleton) { \
|
||||
mutex.unlock(); \
|
||||
}
|
||||
|
||||
void CallQueue::_add_page() {
|
||||
if (pages_used == page_bytes.size()) {
|
||||
pages.push_back(allocator->alloc());
|
||||
page_bytes.push_back(0);
|
||||
}
|
||||
page_bytes[pages_used] = 0;
|
||||
pages_used++;
|
||||
}
|
||||
|
||||
Error CallQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
|
||||
return push_callablep(Callable(p_id, p_method), p_args, p_argcount, p_show_error);
|
||||
}
|
||||
|
||||
Error CallQueue::push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
|
||||
return push_callp(p_object->get_instance_id(), p_method, p_args, p_argcount, p_show_error);
|
||||
}
|
||||
|
||||
Error CallQueue::push_notification(Object *p_object, int p_notification) {
|
||||
return push_notification(p_object->get_instance_id(), p_notification);
|
||||
}
|
||||
|
||||
Error CallQueue::push_set(Object *p_object, const StringName &p_prop, const Variant &p_value) {
|
||||
return push_set(p_object->get_instance_id(), p_prop, p_value);
|
||||
}
|
||||
|
||||
Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) {
|
||||
uint32_t room_needed = sizeof(Message) + sizeof(Variant) * p_argcount;
|
||||
|
||||
ERR_FAIL_COND_V_MSG(room_needed > uint32_t(PAGE_SIZE_BYTES), ERR_INVALID_PARAMETER, "Message is too large to fit on a page (" + itos(PAGE_SIZE_BYTES) + " bytes), consider passing less arguments.");
|
||||
|
||||
LOCK_MUTEX;
|
||||
|
||||
_ensure_first_page();
|
||||
|
||||
if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
|
||||
if (pages_used == max_pages) {
|
||||
fprintf(stderr, "Failed method: %s. Message queue out of memory. %s\n", String(p_callable).utf8().get_data(), error_text.utf8().get_data());
|
||||
statistics();
|
||||
UNLOCK_MUTEX;
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
_add_page();
|
||||
}
|
||||
|
||||
Page *page = pages[pages_used - 1];
|
||||
|
||||
uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]];
|
||||
|
||||
Message *msg = memnew_placement(buffer_end, Message);
|
||||
msg->args = p_argcount;
|
||||
msg->callable = p_callable;
|
||||
msg->type = TYPE_CALL;
|
||||
if (p_show_error) {
|
||||
msg->type |= FLAG_SHOW_ERROR;
|
||||
}
|
||||
// Support callables of static methods.
|
||||
if (p_callable.get_object_id().is_null() && p_callable.is_valid()) {
|
||||
msg->type |= FLAG_NULL_IS_OK;
|
||||
}
|
||||
|
||||
buffer_end += sizeof(Message);
|
||||
|
||||
for (int i = 0; i < p_argcount; i++) {
|
||||
Variant *v = memnew_placement(buffer_end, Variant);
|
||||
buffer_end += sizeof(Variant);
|
||||
*v = *p_args[i];
|
||||
}
|
||||
|
||||
page_bytes[pages_used - 1] += room_needed;
|
||||
|
||||
UNLOCK_MUTEX;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) {
|
||||
LOCK_MUTEX;
|
||||
uint32_t room_needed = sizeof(Message) + sizeof(Variant);
|
||||
|
||||
_ensure_first_page();
|
||||
|
||||
if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
|
||||
if (pages_used == max_pages) {
|
||||
String type;
|
||||
if (ObjectDB::get_instance(p_id)) {
|
||||
type = ObjectDB::get_instance(p_id)->get_class();
|
||||
}
|
||||
fprintf(stderr, "Failed set: %s: %s target ID: %s. Message queue out of memory. %s\n", type.utf8().get_data(), String(p_prop).utf8().get_data(), itos(p_id).utf8().get_data(), error_text.utf8().get_data());
|
||||
statistics();
|
||||
|
||||
UNLOCK_MUTEX;
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
_add_page();
|
||||
}
|
||||
|
||||
Page *page = pages[pages_used - 1];
|
||||
uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]];
|
||||
|
||||
Message *msg = memnew_placement(buffer_end, Message);
|
||||
msg->args = 1;
|
||||
msg->callable = Callable(p_id, p_prop);
|
||||
msg->type = TYPE_SET;
|
||||
|
||||
buffer_end += sizeof(Message);
|
||||
|
||||
Variant *v = memnew_placement(buffer_end, Variant);
|
||||
*v = p_value;
|
||||
|
||||
page_bytes[pages_used - 1] += room_needed;
|
||||
UNLOCK_MUTEX;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error CallQueue::push_notification(ObjectID p_id, int p_notification) {
|
||||
ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER);
|
||||
LOCK_MUTEX;
|
||||
uint32_t room_needed = sizeof(Message);
|
||||
|
||||
_ensure_first_page();
|
||||
|
||||
if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
|
||||
if (pages_used == max_pages) {
|
||||
fprintf(stderr, "Failed notification: %d target ID: %s. Message queue out of memory. %s\n", p_notification, itos(p_id).utf8().get_data(), error_text.utf8().get_data());
|
||||
statistics();
|
||||
UNLOCK_MUTEX;
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
_add_page();
|
||||
}
|
||||
|
||||
Page *page = pages[pages_used - 1];
|
||||
uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]];
|
||||
|
||||
Message *msg = memnew_placement(buffer_end, Message);
|
||||
|
||||
msg->type = TYPE_NOTIFICATION;
|
||||
msg->callable = Callable(p_id, CoreStringName(notification)); //name is meaningless but callable needs it
|
||||
//msg->target;
|
||||
msg->notification = p_notification;
|
||||
|
||||
page_bytes[pages_used - 1] += room_needed;
|
||||
UNLOCK_MUTEX;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error) {
|
||||
const Variant **argptrs = nullptr;
|
||||
if (p_argcount) {
|
||||
argptrs = (const Variant **)alloca(sizeof(Variant *) * p_argcount);
|
||||
for (int i = 0; i < p_argcount; i++) {
|
||||
argptrs[i] = &p_args[i];
|
||||
}
|
||||
}
|
||||
|
||||
Callable::CallError ce;
|
||||
Variant ret;
|
||||
p_callable.callp(argptrs, p_argcount, ret, ce);
|
||||
if (p_show_error && ce.error != Callable::CallError::CALL_OK) {
|
||||
ERR_PRINT("Error calling deferred method: " + Variant::get_callable_error_text(p_callable, argptrs, p_argcount, ce) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
Error CallQueue::flush() {
|
||||
LOCK_MUTEX;
|
||||
|
||||
if (pages.is_empty()) {
|
||||
// Never allocated
|
||||
UNLOCK_MUTEX;
|
||||
return OK; // Do nothing.
|
||||
}
|
||||
|
||||
if (flushing) {
|
||||
UNLOCK_MUTEX;
|
||||
return ERR_BUSY;
|
||||
}
|
||||
|
||||
flushing = true;
|
||||
|
||||
uint32_t i = 0;
|
||||
uint32_t offset = 0;
|
||||
|
||||
while (i < pages_used && offset < page_bytes[i]) {
|
||||
Page *page = pages[i];
|
||||
|
||||
//lock on each iteration, so a call can re-add itself to the message queue
|
||||
|
||||
Message *message = (Message *)&page->data[offset];
|
||||
|
||||
uint32_t advance = sizeof(Message);
|
||||
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
|
||||
advance += sizeof(Variant) * message->args;
|
||||
}
|
||||
|
||||
//pre-advance so this function is reentrant
|
||||
offset += advance;
|
||||
|
||||
Object *target = message->callable.get_object();
|
||||
|
||||
UNLOCK_MUTEX;
|
||||
|
||||
switch (message->type & FLAG_MASK) {
|
||||
case TYPE_CALL: {
|
||||
if (target || (message->type & FLAG_NULL_IS_OK)) {
|
||||
Variant *args = (Variant *)(message + 1);
|
||||
_call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR);
|
||||
}
|
||||
} break;
|
||||
case TYPE_NOTIFICATION: {
|
||||
if (target) {
|
||||
target->notification(message->notification);
|
||||
}
|
||||
} break;
|
||||
case TYPE_SET: {
|
||||
if (target) {
|
||||
Variant *arg = (Variant *)(message + 1);
|
||||
target->set(message->callable.get_method(), *arg);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
|
||||
Variant *args = (Variant *)(message + 1);
|
||||
for (int k = 0; k < message->args; k++) {
|
||||
args[k].~Variant();
|
||||
}
|
||||
}
|
||||
|
||||
message->~Message();
|
||||
|
||||
LOCK_MUTEX;
|
||||
if (offset == page_bytes[i]) {
|
||||
i++;
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
page_bytes[0] = 0;
|
||||
pages_used = 1;
|
||||
|
||||
flushing = false;
|
||||
UNLOCK_MUTEX;
|
||||
return OK;
|
||||
}
|
||||
|
||||
void CallQueue::clear() {
|
||||
LOCK_MUTEX;
|
||||
|
||||
if (pages.is_empty()) {
|
||||
UNLOCK_MUTEX;
|
||||
return; // Nothing to clear.
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < pages_used; i++) {
|
||||
uint32_t offset = 0;
|
||||
while (offset < page_bytes[i]) {
|
||||
Page *page = pages[i];
|
||||
|
||||
//lock on each iteration, so a call can re-add itself to the message queue
|
||||
|
||||
Message *message = (Message *)&page->data[offset];
|
||||
|
||||
uint32_t advance = sizeof(Message);
|
||||
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
|
||||
advance += sizeof(Variant) * message->args;
|
||||
}
|
||||
|
||||
offset += advance;
|
||||
|
||||
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
|
||||
Variant *args = (Variant *)(message + 1);
|
||||
for (int k = 0; k < message->args; k++) {
|
||||
args[k].~Variant();
|
||||
}
|
||||
}
|
||||
|
||||
message->~Message();
|
||||
}
|
||||
}
|
||||
|
||||
pages_used = 1;
|
||||
page_bytes[0] = 0;
|
||||
|
||||
UNLOCK_MUTEX;
|
||||
}
|
||||
|
||||
void CallQueue::statistics() {
|
||||
LOCK_MUTEX;
|
||||
HashMap<StringName, int> set_count;
|
||||
HashMap<int, int> notify_count;
|
||||
HashMap<Callable, int> call_count;
|
||||
int null_count = 0;
|
||||
|
||||
for (uint32_t i = 0; i < pages_used; i++) {
|
||||
uint32_t offset = 0;
|
||||
while (offset < page_bytes[i]) {
|
||||
Page *page = pages[i];
|
||||
|
||||
//lock on each iteration, so a call can re-add itself to the message queue
|
||||
|
||||
Message *message = (Message *)&page->data[offset];
|
||||
|
||||
uint32_t advance = sizeof(Message);
|
||||
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
|
||||
advance += sizeof(Variant) * message->args;
|
||||
}
|
||||
|
||||
Object *target = message->callable.get_object();
|
||||
|
||||
bool null_target = true;
|
||||
switch (message->type & FLAG_MASK) {
|
||||
case TYPE_CALL: {
|
||||
if (target || (message->type & FLAG_NULL_IS_OK)) {
|
||||
if (!call_count.has(message->callable)) {
|
||||
call_count[message->callable] = 0;
|
||||
}
|
||||
|
||||
call_count[message->callable]++;
|
||||
null_target = false;
|
||||
}
|
||||
} break;
|
||||
case TYPE_NOTIFICATION: {
|
||||
if (target) {
|
||||
if (!notify_count.has(message->notification)) {
|
||||
notify_count[message->notification] = 0;
|
||||
}
|
||||
|
||||
notify_count[message->notification]++;
|
||||
null_target = false;
|
||||
}
|
||||
} break;
|
||||
case TYPE_SET: {
|
||||
if (target) {
|
||||
StringName t = message->callable.get_method();
|
||||
if (!set_count.has(t)) {
|
||||
set_count[t] = 0;
|
||||
}
|
||||
|
||||
set_count[t]++;
|
||||
null_target = false;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
if (null_target) {
|
||||
// Object was deleted.
|
||||
fprintf(stdout, "Object was deleted while awaiting a callback.\n");
|
||||
|
||||
null_count++;
|
||||
}
|
||||
|
||||
offset += advance;
|
||||
|
||||
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
|
||||
Variant *args = (Variant *)(message + 1);
|
||||
for (int k = 0; k < message->args; k++) {
|
||||
args[k].~Variant();
|
||||
}
|
||||
}
|
||||
|
||||
message->~Message();
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stdout, "TOTAL PAGES: %d (%d bytes).\n", pages_used, pages_used * PAGE_SIZE_BYTES);
|
||||
fprintf(stdout, "NULL count: %d.\n", null_count);
|
||||
|
||||
for (const KeyValue<StringName, int> &E : set_count) {
|
||||
fprintf(stdout, "SET %s: %d.\n", String(E.key).utf8().get_data(), E.value);
|
||||
}
|
||||
|
||||
for (const KeyValue<Callable, int> &E : call_count) {
|
||||
fprintf(stdout, "CALL %s: %d.\n", String(E.key).utf8().get_data(), E.value);
|
||||
}
|
||||
|
||||
for (const KeyValue<int, int> &E : notify_count) {
|
||||
fprintf(stdout, "NOTIFY %d: %d.\n", E.key, E.value);
|
||||
}
|
||||
|
||||
UNLOCK_MUTEX;
|
||||
}
|
||||
|
||||
bool CallQueue::is_flushing() const {
|
||||
return flushing;
|
||||
}
|
||||
|
||||
bool CallQueue::has_messages() const {
|
||||
if (pages_used == 0) {
|
||||
return false;
|
||||
}
|
||||
if (pages_used == 1 && page_bytes[0] == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int CallQueue::get_max_buffer_usage() const {
|
||||
return pages.size() * PAGE_SIZE_BYTES;
|
||||
}
|
||||
|
||||
CallQueue::CallQueue(Allocator *p_custom_allocator, uint32_t p_max_pages, const String &p_error_text) {
|
||||
if (p_custom_allocator) {
|
||||
allocator = p_custom_allocator;
|
||||
allocator_is_custom = true;
|
||||
} else {
|
||||
allocator = memnew(Allocator(16)); // 16 elements per allocator page, 64kb per allocator page. Anything small will do, though.
|
||||
allocator_is_custom = false;
|
||||
}
|
||||
max_pages = p_max_pages;
|
||||
error_text = p_error_text;
|
||||
}
|
||||
|
||||
CallQueue::~CallQueue() {
|
||||
clear();
|
||||
// Let go of pages.
|
||||
for (uint32_t i = 0; i < pages.size(); i++) {
|
||||
allocator->free(pages[i]);
|
||||
}
|
||||
if (!allocator_is_custom) {
|
||||
memdelete(allocator);
|
||||
}
|
||||
DEV_ASSERT(!is_current_thread_override);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
|
||||
CallQueue *MessageQueue::main_singleton = nullptr;
|
||||
thread_local CallQueue *MessageQueue::thread_singleton = nullptr;
|
||||
|
||||
void MessageQueue::set_thread_singleton_override(CallQueue *p_thread_singleton) {
|
||||
#ifdef DEV_ENABLED
|
||||
if (thread_singleton) {
|
||||
thread_singleton->is_current_thread_override = false;
|
||||
}
|
||||
#endif
|
||||
thread_singleton = p_thread_singleton;
|
||||
#ifdef DEV_ENABLED
|
||||
if (thread_singleton) {
|
||||
thread_singleton->is_current_thread_override = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MessageQueue::MessageQueue() :
|
||||
CallQueue(nullptr,
|
||||
int(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_mb", PROPERTY_HINT_RANGE, "1,512,1,or_greater"), 32)) * 1024 * 1024 / PAGE_SIZE_BYTES,
|
||||
"Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_mb' in project settings.") {
|
||||
ERR_FAIL_COND_MSG(main_singleton != nullptr, "A MessageQueue singleton already exists.");
|
||||
main_singleton = this;
|
||||
}
|
||||
|
||||
MessageQueue::~MessageQueue() {
|
||||
main_singleton = nullptr;
|
||||
}
|
172
core/object/message_queue.h
Normal file
172
core/object/message_queue.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/**************************************************************************/
|
||||
/* message_queue.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/object_id.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
#include "core/templates/paged_allocator.h"
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
class Object;
|
||||
|
||||
class CallQueue {
|
||||
friend class MessageQueue;
|
||||
|
||||
public:
|
||||
enum {
|
||||
PAGE_SIZE_BYTES = 4096
|
||||
};
|
||||
|
||||
struct Page {
|
||||
uint8_t data[PAGE_SIZE_BYTES];
|
||||
};
|
||||
|
||||
// Needs to be public to be able to define it outside the class.
|
||||
// Needs to lock because there can be multiple of these allocators in several threads.
|
||||
typedef PagedAllocator<Page, true> Allocator;
|
||||
|
||||
private:
|
||||
enum {
|
||||
TYPE_CALL,
|
||||
TYPE_NOTIFICATION,
|
||||
TYPE_SET,
|
||||
TYPE_END, // End marker.
|
||||
FLAG_NULL_IS_OK = 1 << 13,
|
||||
FLAG_SHOW_ERROR = 1 << 14,
|
||||
FLAG_MASK = FLAG_NULL_IS_OK - 1,
|
||||
};
|
||||
|
||||
Mutex mutex;
|
||||
|
||||
Allocator *allocator = nullptr;
|
||||
bool allocator_is_custom = false;
|
||||
|
||||
LocalVector<Page *> pages;
|
||||
LocalVector<uint32_t> page_bytes;
|
||||
uint32_t max_pages = 0;
|
||||
uint32_t pages_used = 0;
|
||||
bool flushing = false;
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
bool is_current_thread_override = false;
|
||||
#endif
|
||||
|
||||
struct Message {
|
||||
Callable callable;
|
||||
int16_t type;
|
||||
union {
|
||||
int16_t notification;
|
||||
int16_t args;
|
||||
};
|
||||
};
|
||||
|
||||
_FORCE_INLINE_ void _ensure_first_page() {
|
||||
if (unlikely(pages.is_empty())) {
|
||||
pages.push_back(allocator->alloc());
|
||||
page_bytes.push_back(0);
|
||||
pages_used = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void _add_page();
|
||||
|
||||
void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error);
|
||||
|
||||
String error_text;
|
||||
|
||||
public:
|
||||
Error push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
|
||||
template <typename... VarArgs>
|
||||
Error push_call(ObjectID p_id, const StringName &p_method, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
return push_callp(p_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
Error push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error = false);
|
||||
Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value);
|
||||
Error push_notification(ObjectID p_id, int p_notification);
|
||||
|
||||
template <typename... VarArgs>
|
||||
Error push_callable(const Callable &p_callable, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
return push_callablep(p_callable, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
Error push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
|
||||
template <typename... VarArgs>
|
||||
Error push_call(Object *p_object, const StringName &p_method, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
return push_callp(p_object, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
|
||||
}
|
||||
|
||||
Error push_notification(Object *p_object, int p_notification);
|
||||
Error push_set(Object *p_object, const StringName &p_prop, const Variant &p_value);
|
||||
|
||||
Error flush();
|
||||
void clear();
|
||||
void statistics();
|
||||
|
||||
bool has_messages() const;
|
||||
|
||||
bool is_flushing() const;
|
||||
int get_max_buffer_usage() const;
|
||||
|
||||
CallQueue(Allocator *p_custom_allocator = nullptr, uint32_t p_max_pages = 8192, const String &p_error_text = String());
|
||||
virtual ~CallQueue();
|
||||
};
|
||||
|
||||
class MessageQueue : public CallQueue {
|
||||
static CallQueue *main_singleton;
|
||||
static thread_local CallQueue *thread_singleton;
|
||||
friend class CallQueue;
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ static CallQueue *get_singleton() { return thread_singleton ? thread_singleton : main_singleton; }
|
||||
_FORCE_INLINE_ static CallQueue *get_main_singleton() { return main_singleton; }
|
||||
|
||||
static void set_thread_singleton_override(CallQueue *p_thread_singleton);
|
||||
|
||||
MessageQueue();
|
||||
~MessageQueue();
|
||||
};
|
123
core/object/method_bind.cpp
Normal file
123
core/object/method_bind.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/**************************************************************************/
|
||||
/* method_bind.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
// object.h needs to be the first include *before* method_bind.h
|
||||
// FIXME: Find out why and fix potential cyclical dependencies.
|
||||
#include "core/object/object.h"
|
||||
|
||||
#include "method_bind.h"
|
||||
|
||||
uint32_t MethodBind::get_hash() const {
|
||||
MethodInfo mi;
|
||||
mi.return_val = get_return_info();
|
||||
mi.flags = get_hint_flags();
|
||||
for (int i = 0; i < get_argument_count(); i++) {
|
||||
mi.arguments.push_back(get_argument_info(i));
|
||||
}
|
||||
mi.default_arguments = default_arguments;
|
||||
|
||||
return mi.get_compatibility_hash();
|
||||
}
|
||||
|
||||
PropertyInfo MethodBind::get_argument_info(int p_argument) const {
|
||||
ERR_FAIL_INDEX_V(p_argument, get_argument_count(), PropertyInfo());
|
||||
|
||||
PropertyInfo info = _gen_argument_type_info(p_argument);
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (info.name.is_empty()) {
|
||||
info.name = p_argument < arg_names.size() ? String(arg_names[p_argument]) : String("_unnamed_arg" + itos(p_argument));
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
return info;
|
||||
}
|
||||
|
||||
PropertyInfo MethodBind::get_return_info() const {
|
||||
return _gen_argument_type_info(-1);
|
||||
}
|
||||
|
||||
void MethodBind::_set_const(bool p_const) {
|
||||
_const = p_const;
|
||||
}
|
||||
|
||||
void MethodBind::_set_static(bool p_static) {
|
||||
_static = p_static;
|
||||
}
|
||||
|
||||
void MethodBind::_set_returns(bool p_returns) {
|
||||
_returns = p_returns;
|
||||
}
|
||||
|
||||
StringName MethodBind::get_name() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
void MethodBind::set_name(const StringName &p_name) {
|
||||
name = p_name;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void MethodBind::set_argument_names(const Vector<StringName> &p_names) {
|
||||
arg_names = p_names;
|
||||
}
|
||||
|
||||
Vector<StringName> MethodBind::get_argument_names() const {
|
||||
return arg_names;
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
void MethodBind::set_default_arguments(const Vector<Variant> &p_defargs) {
|
||||
default_arguments = p_defargs;
|
||||
default_argument_count = default_arguments.size();
|
||||
}
|
||||
|
||||
void MethodBind::_generate_argument_types(int p_count) {
|
||||
set_argument_count(p_count);
|
||||
|
||||
Variant::Type *argt = memnew_arr(Variant::Type, p_count + 1);
|
||||
argt[0] = _gen_argument_type(-1); // return type
|
||||
|
||||
for (int i = 0; i < p_count; i++) {
|
||||
argt[i + 1] = _gen_argument_type(i);
|
||||
}
|
||||
|
||||
argument_types = argt;
|
||||
}
|
||||
|
||||
MethodBind::MethodBind() {
|
||||
static int last_id = 0;
|
||||
method_id = last_id++;
|
||||
}
|
||||
|
||||
MethodBind::~MethodBind() {
|
||||
if (argument_types) {
|
||||
memdelete_arr(argument_types);
|
||||
}
|
||||
}
|
781
core/object/method_bind.h
Normal file
781
core/object/method_bind.h
Normal file
@@ -0,0 +1,781 @@
|
||||
/**************************************************************************/
|
||||
/* method_bind.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/variant/binder_common.h"
|
||||
|
||||
VARIANT_BITFIELD_CAST(MethodFlags)
|
||||
|
||||
// some helpers
|
||||
|
||||
class MethodBind {
|
||||
int method_id;
|
||||
uint32_t hint_flags = METHOD_FLAGS_DEFAULT;
|
||||
StringName name;
|
||||
StringName instance_class;
|
||||
Vector<Variant> default_arguments;
|
||||
int default_argument_count = 0;
|
||||
int argument_count = 0;
|
||||
|
||||
bool _static = false;
|
||||
bool _const = false;
|
||||
bool _returns = false;
|
||||
bool _returns_raw_obj_ptr = false;
|
||||
|
||||
protected:
|
||||
Variant::Type *argument_types = nullptr;
|
||||
#ifdef DEBUG_ENABLED
|
||||
Vector<StringName> arg_names;
|
||||
#endif // DEBUG_ENABLED
|
||||
void _set_const(bool p_const);
|
||||
void _set_static(bool p_static);
|
||||
void _set_returns(bool p_returns);
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const = 0;
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const = 0;
|
||||
void _generate_argument_types(int p_count);
|
||||
|
||||
void set_argument_count(int p_count) { argument_count = p_count; }
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ const Vector<Variant> &get_default_arguments() const { return default_arguments; }
|
||||
_FORCE_INLINE_ int get_default_argument_count() const { return default_argument_count; }
|
||||
|
||||
_FORCE_INLINE_ Variant has_default_argument(int p_arg) const {
|
||||
int idx = p_arg - (argument_count - default_arguments.size());
|
||||
|
||||
if (idx < 0 || idx >= default_arguments.size()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Variant get_default_argument(int p_arg) const {
|
||||
int idx = p_arg - (argument_count - default_arguments.size());
|
||||
|
||||
if (idx < 0 || idx >= default_arguments.size()) {
|
||||
return Variant();
|
||||
} else {
|
||||
return default_arguments[idx];
|
||||
}
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Variant::Type get_argument_type(int p_argument) const {
|
||||
ERR_FAIL_COND_V(p_argument < -1 || p_argument >= argument_count, Variant::NIL);
|
||||
return argument_types[p_argument + 1];
|
||||
}
|
||||
|
||||
PropertyInfo get_argument_info(int p_argument) const;
|
||||
PropertyInfo get_return_info() const;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void set_argument_names(const Vector<StringName> &p_names); // Set by ClassDB, can't be inferred otherwise.
|
||||
Vector<StringName> get_argument_names() const;
|
||||
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const = 0;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
void set_hint_flags(uint32_t p_hint) { hint_flags = p_hint; }
|
||||
uint32_t get_hint_flags() const { return hint_flags | (is_const() ? METHOD_FLAG_CONST : 0) | (is_vararg() ? METHOD_FLAG_VARARG : 0) | (is_static() ? METHOD_FLAG_STATIC : 0); }
|
||||
_FORCE_INLINE_ StringName get_instance_class() const { return instance_class; }
|
||||
_FORCE_INLINE_ void set_instance_class(const StringName &p_class) { instance_class = p_class; }
|
||||
|
||||
_FORCE_INLINE_ int get_argument_count() const { return argument_count; }
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual bool is_valid() const { return true; }
|
||||
#endif
|
||||
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const = 0;
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const = 0;
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const = 0;
|
||||
|
||||
StringName get_name() const;
|
||||
void set_name(const StringName &p_name);
|
||||
_FORCE_INLINE_ int get_method_id() const { return method_id; }
|
||||
_FORCE_INLINE_ bool is_const() const { return _const; }
|
||||
_FORCE_INLINE_ bool is_static() const { return _static; }
|
||||
_FORCE_INLINE_ bool has_return() const { return _returns; }
|
||||
virtual bool is_vararg() const { return false; }
|
||||
|
||||
_FORCE_INLINE_ bool is_return_type_raw_object_ptr() { return _returns_raw_obj_ptr; }
|
||||
_FORCE_INLINE_ void set_return_type_is_raw_object_ptr(bool p_returns_raw_obj) { _returns_raw_obj_ptr = p_returns_raw_obj; }
|
||||
|
||||
void set_default_arguments(const Vector<Variant> &p_defargs);
|
||||
|
||||
uint32_t get_hash() const;
|
||||
|
||||
MethodBind();
|
||||
virtual ~MethodBind();
|
||||
};
|
||||
|
||||
// MethodBindVarArg base CRTP
|
||||
template <typename Derived, typename T, typename R, bool should_returns>
|
||||
class MethodBindVarArgBase : public MethodBind {
|
||||
protected:
|
||||
R (T::*method)(const Variant **, int, Callable::CallError &);
|
||||
MethodInfo method_info;
|
||||
|
||||
public:
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
|
||||
if (p_arg < 0) {
|
||||
return _gen_return_type_info();
|
||||
} else if (p_arg < method_info.arguments.size()) {
|
||||
return method_info.arguments[p_arg];
|
||||
} else {
|
||||
return PropertyInfo(Variant::NIL, "arg_" + itos(p_arg), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT);
|
||||
}
|
||||
}
|
||||
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const override {
|
||||
return _gen_argument_type_info(p_arg).type;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int) const override {
|
||||
return GodotTypeInfo::METADATA_NONE;
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
|
||||
ERR_FAIL_MSG("Validated call can't be used with vararg methods. This is a bug.");
|
||||
}
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
|
||||
ERR_FAIL_MSG("ptrcall can't be used with vararg methods. This is a bug.");
|
||||
}
|
||||
|
||||
virtual bool is_const() const { return false; }
|
||||
|
||||
virtual bool is_vararg() const override { return true; }
|
||||
|
||||
MethodBindVarArgBase(
|
||||
R (T::*p_method)(const Variant **, int, Callable::CallError &),
|
||||
const MethodInfo &p_method_info,
|
||||
bool p_return_nil_is_variant) :
|
||||
method(p_method), method_info(p_method_info) {
|
||||
set_argument_count(method_info.arguments.size());
|
||||
Variant::Type *at = memnew_arr(Variant::Type, method_info.arguments.size() + 1);
|
||||
at[0] = _gen_return_type_info().type;
|
||||
if (method_info.arguments.size()) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
Vector<StringName> names;
|
||||
names.resize(method_info.arguments.size());
|
||||
#endif // DEBUG_ENABLED
|
||||
for (int64_t i = 0; i < method_info.arguments.size(); ++i) {
|
||||
at[i + 1] = method_info.arguments[i].type;
|
||||
#ifdef DEBUG_ENABLED
|
||||
names.write[i] = method_info.arguments[i].name;
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
set_argument_names(names);
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
argument_types = at;
|
||||
if (p_return_nil_is_variant) {
|
||||
method_info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
|
||||
}
|
||||
|
||||
_set_returns(should_returns);
|
||||
}
|
||||
|
||||
private:
|
||||
PropertyInfo _gen_return_type_info() const {
|
||||
return Derived::_gen_return_type_info_impl();
|
||||
}
|
||||
};
|
||||
|
||||
// variadic, no return
|
||||
template <typename T>
|
||||
class MethodBindVarArgT : public MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false> {
|
||||
friend class MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>;
|
||||
|
||||
public:
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == MethodBind::get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
(static_cast<T *>(p_object)->*MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>::method)(p_args, p_arg_count, r_error);
|
||||
return {};
|
||||
}
|
||||
|
||||
MethodBindVarArgT(
|
||||
void (T::*p_method)(const Variant **, int, Callable::CallError &),
|
||||
const MethodInfo &p_method_info,
|
||||
bool p_return_nil_is_variant) :
|
||||
MethodBindVarArgBase<MethodBindVarArgT<T>, T, void, false>(p_method, p_method_info, p_return_nil_is_variant) {
|
||||
}
|
||||
|
||||
private:
|
||||
static PropertyInfo _gen_return_type_info_impl() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
MethodBind *create_vararg_method_bind(void (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) {
|
||||
MethodBind *a = memnew((MethodBindVarArgT<T>)(p_method, p_info, p_return_nil_is_variant));
|
||||
a->set_instance_class(T::get_class_static());
|
||||
return a;
|
||||
}
|
||||
|
||||
// variadic, return
|
||||
template <typename T, typename R>
|
||||
class MethodBindVarArgTR : public MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true> {
|
||||
friend class MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>;
|
||||
|
||||
public:
|
||||
GODOT_GCC_WARNING_PUSH_AND_IGNORE("-Wmaybe-uninitialized") // Workaround GH-66343 raised only with UBSAN, seems to be a false positive.
|
||||
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == MethodBind::get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
return (static_cast<T *>(p_object)->*MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>::method)(p_args, p_arg_count, r_error);
|
||||
}
|
||||
|
||||
GODOT_GCC_WARNING_POP
|
||||
|
||||
MethodBindVarArgTR(
|
||||
R (T::*p_method)(const Variant **, int, Callable::CallError &),
|
||||
const MethodInfo &p_info,
|
||||
bool p_return_nil_is_variant) :
|
||||
MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>(p_method, p_info, p_return_nil_is_variant) {
|
||||
}
|
||||
|
||||
private:
|
||||
static PropertyInfo _gen_return_type_info_impl() {
|
||||
return GetTypeInfo<R>::get_class_info();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename R>
|
||||
MethodBind *create_vararg_method_bind(R (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) {
|
||||
MethodBind *a = memnew((MethodBindVarArgTR<T, R>)(p_method, p_info, p_return_nil_is_variant));
|
||||
a->set_instance_class(T::get_class_static());
|
||||
return a;
|
||||
}
|
||||
|
||||
/**** VARIADIC TEMPLATES ****/
|
||||
|
||||
#ifndef TYPED_METHOD_BIND
|
||||
class __UnexistingClass;
|
||||
#define MB_T __UnexistingClass
|
||||
#else
|
||||
#define MB_T T
|
||||
#endif
|
||||
|
||||
// no return, not const
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
template <typename T, typename... P>
|
||||
#else
|
||||
template <typename... P>
|
||||
#endif
|
||||
class MethodBindT : public MethodBind {
|
||||
void (MB_T::*method)(P...);
|
||||
|
||||
protected:
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
return call_get_argument_type<P...>(p_arg);
|
||||
} else {
|
||||
return Variant::NIL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
|
||||
PropertyInfo pi;
|
||||
call_get_argument_type_info<P...>(p_arg, pi);
|
||||
return pi;
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override {
|
||||
return call_get_argument_metadata<P...>(p_arg);
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_variant_args_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, r_error, get_default_arguments());
|
||||
#else
|
||||
call_with_variant_args_dv(reinterpret_cast<MB_T *>(p_object), method, p_args, p_arg_count, r_error, get_default_arguments());
|
||||
#endif
|
||||
return Variant();
|
||||
}
|
||||
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_validated_object_instance_args(static_cast<T *>(p_object), method, p_args);
|
||||
#else
|
||||
call_with_validated_object_instance_args(reinterpret_cast<MB_T *>(p_object), method, p_args);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_ptr_args<T, P...>(static_cast<T *>(p_object), method, p_args);
|
||||
#else
|
||||
call_with_ptr_args<MB_T, P...>(reinterpret_cast<MB_T *>(p_object), method, p_args);
|
||||
#endif
|
||||
}
|
||||
|
||||
MethodBindT(void (MB_T::*p_method)(P...)) {
|
||||
method = p_method;
|
||||
_generate_argument_types(sizeof...(P));
|
||||
set_argument_count(sizeof...(P));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... P>
|
||||
MethodBind *create_method_bind(void (T::*p_method)(P...)) {
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
MethodBind *a = memnew((MethodBindT<T, P...>)(p_method));
|
||||
#else
|
||||
MethodBind *a = memnew((MethodBindT<P...>)(reinterpret_cast<void (MB_T::*)(P...)>(p_method)));
|
||||
#endif
|
||||
a->set_instance_class(T::get_class_static());
|
||||
return a;
|
||||
}
|
||||
|
||||
// no return, const
|
||||
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
template <typename T, typename... P>
|
||||
#else
|
||||
template <typename... P>
|
||||
#endif
|
||||
class MethodBindTC : public MethodBind {
|
||||
void (MB_T::*method)(P...) const;
|
||||
|
||||
protected:
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
return call_get_argument_type<P...>(p_arg);
|
||||
} else {
|
||||
return Variant::NIL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
|
||||
PropertyInfo pi;
|
||||
call_get_argument_type_info<P...>(p_arg, pi);
|
||||
return pi;
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override {
|
||||
return call_get_argument_metadata<P...>(p_arg);
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), Variant(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_variant_argsc_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, r_error, get_default_arguments());
|
||||
#else
|
||||
call_with_variant_argsc_dv(reinterpret_cast<MB_T *>(p_object), method, p_args, p_arg_count, r_error, get_default_arguments());
|
||||
#endif
|
||||
return Variant();
|
||||
}
|
||||
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_validated_object_instance_argsc(static_cast<T *>(p_object), method, p_args);
|
||||
#else
|
||||
call_with_validated_object_instance_argsc(reinterpret_cast<MB_T *>(p_object), method, p_args);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_ptr_argsc<T, P...>(static_cast<T *>(p_object), method, p_args);
|
||||
#else
|
||||
call_with_ptr_argsc<MB_T, P...>(reinterpret_cast<MB_T *>(p_object), method, p_args);
|
||||
#endif
|
||||
}
|
||||
|
||||
MethodBindTC(void (MB_T::*p_method)(P...) const) {
|
||||
method = p_method;
|
||||
_set_const(true);
|
||||
_generate_argument_types(sizeof...(P));
|
||||
set_argument_count(sizeof...(P));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... P>
|
||||
MethodBind *create_method_bind(void (T::*p_method)(P...) const) {
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
MethodBind *a = memnew((MethodBindTC<T, P...>)(p_method));
|
||||
#else
|
||||
MethodBind *a = memnew((MethodBindTC<P...>)(reinterpret_cast<void (MB_T::*)(P...) const>(p_method)));
|
||||
#endif
|
||||
a->set_instance_class(T::get_class_static());
|
||||
return a;
|
||||
}
|
||||
|
||||
// return, not const
|
||||
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
template <typename T, typename R, typename... P>
|
||||
#else
|
||||
template <typename R, typename... P>
|
||||
#endif
|
||||
class MethodBindTR : public MethodBind {
|
||||
R (MB_T::*method)(P...);
|
||||
|
||||
protected:
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
return call_get_argument_type<P...>(p_arg);
|
||||
} else {
|
||||
return GetTypeInfo<R>::VARIANT_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
PropertyInfo pi;
|
||||
call_get_argument_type_info<P...>(p_arg, pi);
|
||||
return pi;
|
||||
} else {
|
||||
return GetTypeInfo<R>::get_class_info();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override {
|
||||
if (p_arg >= 0) {
|
||||
return call_get_argument_metadata<P...>(p_arg);
|
||||
} else {
|
||||
return GetTypeInfo<R>::METADATA;
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
Variant ret;
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), ret, vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_variant_args_ret_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, ret, r_error, get_default_arguments());
|
||||
#else
|
||||
call_with_variant_args_ret_dv(reinterpret_cast<MB_T *>(p_object), method, p_args, p_arg_count, ret, r_error, get_default_arguments());
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_validated_object_instance_args_ret(static_cast<T *>(p_object), method, p_args, r_ret);
|
||||
#else
|
||||
call_with_validated_object_instance_args_ret(reinterpret_cast<MB_T *>(p_object), method, p_args, r_ret);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_ptr_args_ret<T, R, P...>(static_cast<T *>(p_object), method, p_args, r_ret);
|
||||
#else
|
||||
call_with_ptr_args_ret<MB_T, R, P...>(reinterpret_cast<MB_T *>(p_object), method, p_args, r_ret);
|
||||
#endif
|
||||
}
|
||||
|
||||
MethodBindTR(R (MB_T::*p_method)(P...)) {
|
||||
method = p_method;
|
||||
_set_returns(true);
|
||||
_generate_argument_types(sizeof...(P));
|
||||
set_argument_count(sizeof...(P));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename R, typename... P>
|
||||
MethodBind *create_method_bind(R (T::*p_method)(P...)) {
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
MethodBind *a = memnew((MethodBindTR<T, R, P...>)(p_method));
|
||||
#else
|
||||
MethodBind *a = memnew((MethodBindTR<R, P...>)(reinterpret_cast<R (MB_T::*)(P...)>(p_method)));
|
||||
#endif
|
||||
|
||||
a->set_instance_class(T::get_class_static());
|
||||
return a;
|
||||
}
|
||||
|
||||
// return, const
|
||||
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
template <typename T, typename R, typename... P>
|
||||
#else
|
||||
template <typename R, typename... P>
|
||||
#endif
|
||||
class MethodBindTRC : public MethodBind {
|
||||
R (MB_T::*method)(P...) const;
|
||||
|
||||
protected:
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
return call_get_argument_type<P...>(p_arg);
|
||||
} else {
|
||||
return GetTypeInfo<R>::VARIANT_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
PropertyInfo pi;
|
||||
call_get_argument_type_info<P...>(p_arg, pi);
|
||||
return pi;
|
||||
} else {
|
||||
return GetTypeInfo<R>::get_class_info();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override {
|
||||
if (p_arg >= 0) {
|
||||
return call_get_argument_metadata<P...>(p_arg);
|
||||
} else {
|
||||
return GetTypeInfo<R>::METADATA;
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
Variant ret;
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_V_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), ret, vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_variant_args_retc_dv(static_cast<T *>(p_object), method, p_args, p_arg_count, ret, r_error, get_default_arguments());
|
||||
#else
|
||||
call_with_variant_args_retc_dv(reinterpret_cast<MB_T *>(p_object), method, p_args, p_arg_count, ret, r_error, get_default_arguments());
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_validated_object_instance_args_retc(static_cast<T *>(p_object), method, p_args, r_ret);
|
||||
#else
|
||||
call_with_validated_object_instance_args_retc(reinterpret_cast<MB_T *>(p_object), method, p_args, r_ret);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder() && p_object->get_class_name() == get_instance_class(), vformat("Cannot call method bind '%s' on placeholder instance.", MethodBind::get_name()));
|
||||
#endif
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
call_with_ptr_args_retc<T, R, P...>(static_cast<T *>(p_object), method, p_args, r_ret);
|
||||
#else
|
||||
call_with_ptr_args_retc<MB_T, R, P...>(reinterpret_cast<MB_T *>(p_object), method, p_args, r_ret);
|
||||
#endif
|
||||
}
|
||||
|
||||
MethodBindTRC(R (MB_T::*p_method)(P...) const) {
|
||||
method = p_method;
|
||||
_set_returns(true);
|
||||
_set_const(true);
|
||||
_generate_argument_types(sizeof...(P));
|
||||
set_argument_count(sizeof...(P));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename R, typename... P>
|
||||
MethodBind *create_method_bind(R (T::*p_method)(P...) const) {
|
||||
#ifdef TYPED_METHOD_BIND
|
||||
MethodBind *a = memnew((MethodBindTRC<T, R, P...>)(p_method));
|
||||
#else
|
||||
MethodBind *a = memnew((MethodBindTRC<R, P...>)(reinterpret_cast<R (MB_T::*)(P...) const>(p_method)));
|
||||
#endif
|
||||
a->set_instance_class(T::get_class_static());
|
||||
return a;
|
||||
}
|
||||
|
||||
/* STATIC BINDS */
|
||||
|
||||
// no return
|
||||
|
||||
template <typename... P>
|
||||
class MethodBindTS : public MethodBind {
|
||||
void (*function)(P...);
|
||||
|
||||
protected:
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
return call_get_argument_type<P...>(p_arg);
|
||||
} else {
|
||||
return Variant::NIL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
|
||||
PropertyInfo pi;
|
||||
call_get_argument_type_info<P...>(p_arg, pi);
|
||||
return pi;
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override {
|
||||
return call_get_argument_metadata<P...>(p_arg);
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
(void)p_object; // unused
|
||||
call_with_variant_args_static_dv(function, p_args, p_arg_count, r_error, get_default_arguments());
|
||||
return Variant();
|
||||
}
|
||||
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
|
||||
call_with_validated_variant_args_static_method(function, p_args);
|
||||
}
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
|
||||
(void)p_object;
|
||||
(void)r_ret;
|
||||
call_with_ptr_args_static_method(function, p_args);
|
||||
}
|
||||
|
||||
MethodBindTS(void (*p_function)(P...)) {
|
||||
function = p_function;
|
||||
_generate_argument_types(sizeof...(P));
|
||||
set_argument_count(sizeof...(P));
|
||||
_set_static(true);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... P>
|
||||
MethodBind *create_static_method_bind(void (*p_method)(P...)) {
|
||||
MethodBind *a = memnew((MethodBindTS<P...>)(p_method));
|
||||
return a;
|
||||
}
|
||||
|
||||
// return
|
||||
|
||||
template <typename R, typename... P>
|
||||
class MethodBindTRS : public MethodBind {
|
||||
R (*function)(P...);
|
||||
|
||||
protected:
|
||||
virtual Variant::Type _gen_argument_type(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
return call_get_argument_type<P...>(p_arg);
|
||||
} else {
|
||||
return GetTypeInfo<R>::VARIANT_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
virtual PropertyInfo _gen_argument_type_info(int p_arg) const override {
|
||||
if (p_arg >= 0 && p_arg < (int)sizeof...(P)) {
|
||||
PropertyInfo pi;
|
||||
call_get_argument_type_info<P...>(p_arg, pi);
|
||||
return pi;
|
||||
} else {
|
||||
return GetTypeInfo<R>::get_class_info();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override {
|
||||
if (p_arg >= 0) {
|
||||
return call_get_argument_metadata<P...>(p_arg);
|
||||
} else {
|
||||
return GetTypeInfo<R>::METADATA;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
||||
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
|
||||
Variant ret;
|
||||
call_with_variant_args_static_ret_dv(function, p_args, p_arg_count, ret, r_error, get_default_arguments());
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
|
||||
call_with_validated_variant_args_static_method_ret(function, p_args, r_ret);
|
||||
}
|
||||
|
||||
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
|
||||
(void)p_object;
|
||||
call_with_ptr_args_static_method_ret(function, p_args, r_ret);
|
||||
}
|
||||
|
||||
MethodBindTRS(R (*p_function)(P...)) {
|
||||
function = p_function;
|
||||
_generate_argument_types(sizeof...(P));
|
||||
set_argument_count(sizeof...(P));
|
||||
_set_static(true);
|
||||
_set_returns(true);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename R, typename... P>
|
||||
MethodBind *create_static_method_bind(R (*p_method)(P...)) {
|
||||
MethodBind *a = memnew((MethodBindTRS<R, P...>)(p_method));
|
||||
return a;
|
||||
}
|
2551
core/object/object.cpp
Normal file
2551
core/object/object.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1083
core/object/object.h
Normal file
1083
core/object/object.h
Normal file
File diff suppressed because it is too large
Load Diff
63
core/object/object_id.h
Normal file
63
core/object/object_id.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/**************************************************************************/
|
||||
/* object_id.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/typedefs.h"
|
||||
|
||||
// Class to store an object ID (int64)
|
||||
// needs to be compatile with int64 because this is what Variant uses
|
||||
// Also, need to be explicitly only castable to 64 bits integer types
|
||||
// to avoid bugs due to loss of precision
|
||||
|
||||
class ObjectID {
|
||||
uint64_t id = 0;
|
||||
|
||||
public:
|
||||
_ALWAYS_INLINE_ bool is_ref_counted() const { return (id & (uint64_t(1) << 63)) != 0; }
|
||||
_ALWAYS_INLINE_ bool is_valid() const { return id != 0; }
|
||||
_ALWAYS_INLINE_ bool is_null() const { return id == 0; }
|
||||
_ALWAYS_INLINE_ operator uint64_t() const { return id; }
|
||||
_ALWAYS_INLINE_ operator int64_t() const { return (int64_t)id; }
|
||||
|
||||
_ALWAYS_INLINE_ bool operator==(const ObjectID &p_id) const { return id == p_id.id; }
|
||||
_ALWAYS_INLINE_ bool operator!=(const ObjectID &p_id) const { return id != p_id.id; }
|
||||
_ALWAYS_INLINE_ bool operator<(const ObjectID &p_id) const { return id < p_id.id; }
|
||||
|
||||
_ALWAYS_INLINE_ void operator=(int64_t p_int64) { id = p_int64; }
|
||||
_ALWAYS_INLINE_ void operator=(uint64_t p_uint64) { id = p_uint64; }
|
||||
|
||||
_ALWAYS_INLINE_ ObjectID() {}
|
||||
_ALWAYS_INLINE_ explicit ObjectID(const uint64_t p_id) { id = p_id; }
|
||||
_ALWAYS_INLINE_ explicit ObjectID(const int64_t p_id) { id = p_id; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct is_zero_constructible<ObjectID> : std::true_type {};
|
129
core/object/ref_counted.cpp
Normal file
129
core/object/ref_counted.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/**************************************************************************/
|
||||
/* ref_counted.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "ref_counted.h"
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
bool RefCounted::init_ref() {
|
||||
if (reference()) {
|
||||
if (!is_referenced() && refcount_init.unref()) {
|
||||
unreference(); // first referencing is already 1, so compensate for the ref above
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RefCounted::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("init_ref"), &RefCounted::init_ref);
|
||||
ClassDB::bind_method(D_METHOD("reference"), &RefCounted::reference);
|
||||
ClassDB::bind_method(D_METHOD("unreference"), &RefCounted::unreference);
|
||||
ClassDB::bind_method(D_METHOD("get_reference_count"), &RefCounted::get_reference_count);
|
||||
}
|
||||
|
||||
int RefCounted::get_reference_count() const {
|
||||
return refcount.get();
|
||||
}
|
||||
|
||||
bool RefCounted::reference() {
|
||||
uint32_t rc_val = refcount.refval();
|
||||
bool success = rc_val != 0;
|
||||
|
||||
if (success && rc_val <= 2 /* higher is not relevant */) {
|
||||
if (get_script_instance()) {
|
||||
get_script_instance()->refcount_incremented();
|
||||
}
|
||||
if (_get_extension() && _get_extension()->reference) {
|
||||
_get_extension()->reference(_get_extension_instance());
|
||||
}
|
||||
|
||||
_instance_binding_reference(true);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool RefCounted::unreference() {
|
||||
uint32_t rc_val = refcount.unrefval();
|
||||
bool die = rc_val == 0;
|
||||
|
||||
if (rc_val <= 1 /* higher is not relevant */) {
|
||||
if (get_script_instance()) {
|
||||
bool script_ret = get_script_instance()->refcount_decremented();
|
||||
die = die && script_ret;
|
||||
}
|
||||
if (_get_extension() && _get_extension()->unreference) {
|
||||
_get_extension()->unreference(_get_extension_instance());
|
||||
}
|
||||
|
||||
bool binding_ret = _instance_binding_reference(false);
|
||||
die = die && binding_ret;
|
||||
}
|
||||
|
||||
return die;
|
||||
}
|
||||
|
||||
RefCounted::RefCounted() :
|
||||
Object(true) {
|
||||
refcount.init();
|
||||
refcount_init.init();
|
||||
}
|
||||
|
||||
Variant WeakRef::get_ref() const {
|
||||
if (ref.is_null()) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
Object *obj = ObjectDB::get_instance(ref);
|
||||
if (!obj) {
|
||||
return Variant();
|
||||
}
|
||||
RefCounted *r = cast_to<RefCounted>(obj);
|
||||
if (r) {
|
||||
return Ref<RefCounted>(r);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void WeakRef::set_obj(Object *p_object) {
|
||||
ref = p_object ? p_object->get_instance_id() : ObjectID();
|
||||
}
|
||||
|
||||
void WeakRef::set_ref(const Ref<RefCounted> &p_ref) {
|
||||
ref = p_ref.is_valid() ? p_ref->get_instance_id() : ObjectID();
|
||||
}
|
||||
|
||||
void WeakRef::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_ref"), &WeakRef::get_ref);
|
||||
}
|
281
core/object/ref_counted.h
Normal file
281
core/object/ref_counted.h
Normal file
@@ -0,0 +1,281 @@
|
||||
/**************************************************************************/
|
||||
/* ref_counted.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
|
||||
class RefCounted : public Object {
|
||||
GDCLASS(RefCounted, Object);
|
||||
SafeRefCount refcount;
|
||||
SafeRefCount refcount_init;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ bool is_referenced() const { return refcount_init.get() != 1; }
|
||||
bool init_ref();
|
||||
bool reference(); // returns false if refcount is at zero and didn't get increased
|
||||
bool unreference();
|
||||
int get_reference_count() const;
|
||||
|
||||
RefCounted();
|
||||
~RefCounted() {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Ref {
|
||||
T *reference = nullptr;
|
||||
|
||||
_FORCE_INLINE_ void ref(const Ref &p_from) {
|
||||
ref_pointer<false>(p_from.reference);
|
||||
}
|
||||
|
||||
template <bool Init>
|
||||
_FORCE_INLINE_ void ref_pointer(T *p_refcounted) {
|
||||
if (p_refcounted == reference) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This will go out of scope and get unref'd.
|
||||
Ref cleanup_ref;
|
||||
cleanup_ref.reference = reference;
|
||||
reference = p_refcounted;
|
||||
if (reference) {
|
||||
if constexpr (Init) {
|
||||
if (!reference->init_ref()) {
|
||||
reference = nullptr;
|
||||
}
|
||||
} else {
|
||||
if (!reference->reference()) {
|
||||
reference = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//virtual RefCounted * get_reference() const { return reference; }
|
||||
public:
|
||||
static _FORCE_INLINE_ String get_class_static() {
|
||||
return T::get_class_static();
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool operator==(const T *p_ptr) const {
|
||||
return reference == p_ptr;
|
||||
}
|
||||
_FORCE_INLINE_ bool operator!=(const T *p_ptr) const {
|
||||
return reference != p_ptr;
|
||||
}
|
||||
#ifdef STRICT_CHECKS
|
||||
// Delete these to prevent raw comparisons with `nullptr`.
|
||||
bool operator==(std::nullptr_t) const = delete;
|
||||
bool operator!=(std::nullptr_t) const = delete;
|
||||
#endif // STRICT_CHECKS
|
||||
|
||||
_FORCE_INLINE_ bool operator<(const Ref<T> &p_r) const {
|
||||
return reference < p_r.reference;
|
||||
}
|
||||
_FORCE_INLINE_ bool operator==(const Ref<T> &p_r) const {
|
||||
return reference == p_r.reference;
|
||||
}
|
||||
_FORCE_INLINE_ bool operator!=(const Ref<T> &p_r) const {
|
||||
return reference != p_r.reference;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ T *operator*() const {
|
||||
return reference;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ T *operator->() const {
|
||||
return reference;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ T *ptr() const {
|
||||
return reference;
|
||||
}
|
||||
|
||||
operator Variant() const {
|
||||
return Variant(reference);
|
||||
}
|
||||
|
||||
void operator=(const Ref &p_from) {
|
||||
ref(p_from);
|
||||
}
|
||||
|
||||
void operator=(Ref &&p_from) {
|
||||
if (reference == p_from.reference) {
|
||||
return;
|
||||
}
|
||||
unref();
|
||||
reference = p_from.reference;
|
||||
p_from.reference = nullptr;
|
||||
}
|
||||
|
||||
template <typename T_Other>
|
||||
void operator=(const Ref<T_Other> &p_from) {
|
||||
ref_pointer<false>(Object::cast_to<T>(p_from.ptr()));
|
||||
}
|
||||
|
||||
void operator=(T *p_from) {
|
||||
ref_pointer<true>(p_from);
|
||||
}
|
||||
|
||||
void operator=(const Variant &p_variant) {
|
||||
Object *object = p_variant.get_validated_object();
|
||||
|
||||
if (object == reference) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref_pointer<false>(Object::cast_to<T>(object));
|
||||
}
|
||||
|
||||
template <typename T_Other>
|
||||
void reference_ptr(T_Other *p_ptr) {
|
||||
if (reference == p_ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref_pointer<true>(Object::cast_to<T>(p_ptr));
|
||||
}
|
||||
|
||||
Ref(const Ref &p_from) {
|
||||
this->operator=(p_from);
|
||||
}
|
||||
|
||||
Ref(Ref &&p_from) {
|
||||
reference = p_from.reference;
|
||||
p_from.reference = nullptr;
|
||||
}
|
||||
|
||||
template <typename T_Other>
|
||||
Ref(const Ref<T_Other> &p_from) {
|
||||
this->operator=(p_from);
|
||||
}
|
||||
|
||||
Ref(T *p_from) {
|
||||
this->operator=(p_from);
|
||||
}
|
||||
|
||||
Ref(const Variant &p_from) {
|
||||
this->operator=(p_from);
|
||||
}
|
||||
|
||||
inline bool is_valid() const { return reference != nullptr; }
|
||||
inline bool is_null() const { return reference == nullptr; }
|
||||
|
||||
void unref() {
|
||||
// TODO: this should be moved to mutexes, since this engine does not really
|
||||
// do a lot of referencing on references and stuff
|
||||
// mutexes will avoid more crashes?
|
||||
|
||||
if (reference) {
|
||||
// NOTE: `reinterpret_cast` is "safe" here because we know `T` has simple linear
|
||||
// inheritance to `RefCounted`. This guarantees that `T * == `RefCounted *`, which
|
||||
// allows us to declare `Ref<T>` with forward declared `T` types.
|
||||
if (reinterpret_cast<RefCounted *>(reference)->unreference()) {
|
||||
memdelete(reinterpret_cast<RefCounted *>(reference));
|
||||
}
|
||||
reference = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... VarArgs>
|
||||
void instantiate(VarArgs... p_params) {
|
||||
ref(memnew(T(p_params...)));
|
||||
}
|
||||
|
||||
Ref() = default;
|
||||
|
||||
~Ref() {
|
||||
unref();
|
||||
}
|
||||
};
|
||||
|
||||
class WeakRef : public RefCounted {
|
||||
GDCLASS(WeakRef, RefCounted);
|
||||
|
||||
ObjectID ref;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Variant get_ref() const;
|
||||
void set_obj(Object *p_object);
|
||||
void set_ref(const Ref<RefCounted> &p_ref);
|
||||
|
||||
WeakRef() {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct PtrToArg<Ref<T>> {
|
||||
_FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) {
|
||||
if (p_ptr == nullptr) {
|
||||
return Ref<T>();
|
||||
}
|
||||
// p_ptr points to a RefCounted object
|
||||
return Ref<T>(*reinterpret_cast<T *const *>(p_ptr));
|
||||
}
|
||||
|
||||
typedef Ref<T> EncodeT;
|
||||
|
||||
_FORCE_INLINE_ static void encode(Ref<T> p_val, const void *p_ptr) {
|
||||
// p_ptr points to an EncodeT object which is a Ref<T> object.
|
||||
*(const_cast<Ref<RefCounted> *>(reinterpret_cast<const Ref<RefCounted> *>(p_ptr))) = p_val;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetTypeInfo<Ref<T>> {
|
||||
static const Variant::Type VARIANT_TYPE = Variant::OBJECT;
|
||||
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
|
||||
|
||||
static inline PropertyInfo get_class_info() {
|
||||
return PropertyInfo(Variant::OBJECT, String(), PROPERTY_HINT_RESOURCE_TYPE, T::get_class_static());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct VariantInternalAccessor<Ref<T>> {
|
||||
static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); }
|
||||
static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::object_assign(v, p_ref); }
|
||||
};
|
||||
|
||||
// Zero-constructing Ref initializes reference to nullptr (and thus empty).
|
||||
template <typename T>
|
||||
struct is_zero_constructible<Ref<T>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
Ref<T> ObjectDB::get_ref(ObjectID p_instance_id) {
|
||||
return Ref<T>(get_instance(p_instance_id));
|
||||
}
|
197
core/object/script_backtrace.cpp
Normal file
197
core/object/script_backtrace.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/**************************************************************************/
|
||||
/* script_backtrace.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "script_backtrace.h"
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
void ScriptBacktrace::_store_variables(const List<String> &p_names, const List<Variant> &p_values, LocalVector<StackVariable> &r_variables) {
|
||||
r_variables.reserve(p_names.size());
|
||||
|
||||
const List<String>::Element *name = p_names.front();
|
||||
const List<Variant>::Element *value = p_values.front();
|
||||
|
||||
for (int i = 0; i < p_names.size(); i++) {
|
||||
StackVariable variable;
|
||||
variable.name = name->get();
|
||||
variable.value = value->get();
|
||||
r_variables.push_back(std::move(variable));
|
||||
|
||||
name = name->next();
|
||||
value = value->next();
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptBacktrace::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_language_name"), &ScriptBacktrace::get_language_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_empty"), &ScriptBacktrace::is_empty);
|
||||
ClassDB::bind_method(D_METHOD("get_frame_count"), &ScriptBacktrace::get_frame_count);
|
||||
ClassDB::bind_method(D_METHOD("get_frame_function", "index"), &ScriptBacktrace::get_frame_function);
|
||||
ClassDB::bind_method(D_METHOD("get_frame_file", "index"), &ScriptBacktrace::get_frame_file);
|
||||
ClassDB::bind_method(D_METHOD("get_frame_line", "index"), &ScriptBacktrace::get_frame_line);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_global_variable_count"), &ScriptBacktrace::get_global_variable_count);
|
||||
ClassDB::bind_method(D_METHOD("get_global_variable_name", "variable_index"), &ScriptBacktrace::get_global_variable_name);
|
||||
ClassDB::bind_method(D_METHOD("get_global_variable_value", "variable_index"), &ScriptBacktrace::get_global_variable_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_local_variable_count", "frame_index"), &ScriptBacktrace::get_local_variable_count);
|
||||
ClassDB::bind_method(D_METHOD("get_local_variable_name", "frame_index", "variable_index"), &ScriptBacktrace::get_local_variable_name);
|
||||
ClassDB::bind_method(D_METHOD("get_local_variable_value", "frame_index", "variable_index"), &ScriptBacktrace::get_local_variable_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_member_variable_count", "frame_index"), &ScriptBacktrace::get_member_variable_count);
|
||||
ClassDB::bind_method(D_METHOD("get_member_variable_name", "frame_index", "variable_index"), &ScriptBacktrace::get_member_variable_name);
|
||||
ClassDB::bind_method(D_METHOD("get_member_variable_value", "frame_index", "variable_index"), &ScriptBacktrace::get_member_variable_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("format", "indent_all", "indent_frames"), &ScriptBacktrace::format, DEFVAL(0), DEFVAL(4));
|
||||
}
|
||||
|
||||
ScriptBacktrace::ScriptBacktrace(ScriptLanguage *p_language, bool p_include_variables) {
|
||||
language_name = p_language->get_name();
|
||||
|
||||
Vector<ScriptLanguage::StackInfo> stack_infos = p_language->debug_get_current_stack_info();
|
||||
stack_frames.reserve(stack_infos.size());
|
||||
|
||||
if (p_include_variables) {
|
||||
List<String> globals_names;
|
||||
List<Variant> globals_values;
|
||||
p_language->debug_get_globals(&globals_names, &globals_values);
|
||||
_store_variables(globals_names, globals_values, global_variables);
|
||||
}
|
||||
|
||||
for (int i = 0; i < stack_infos.size(); i++) {
|
||||
const ScriptLanguage::StackInfo &stack_info = stack_infos[i];
|
||||
|
||||
StackFrame stack_frame;
|
||||
stack_frame.function = stack_info.func;
|
||||
stack_frame.file = stack_info.file;
|
||||
stack_frame.line = stack_info.line;
|
||||
|
||||
if (p_include_variables) {
|
||||
List<String> locals_names;
|
||||
List<Variant> locals_values;
|
||||
p_language->debug_get_stack_level_locals(i, &locals_names, &locals_values);
|
||||
_store_variables(locals_names, locals_values, stack_frame.local_variables);
|
||||
|
||||
List<String> members_names;
|
||||
List<Variant> members_values;
|
||||
p_language->debug_get_stack_level_members(i, &members_names, &members_values);
|
||||
_store_variables(members_names, members_values, stack_frame.member_variables);
|
||||
}
|
||||
|
||||
stack_frames.push_back(std::move(stack_frame));
|
||||
}
|
||||
}
|
||||
|
||||
String ScriptBacktrace::get_frame_function(int p_index) const {
|
||||
ERR_FAIL_INDEX_V(p_index, (int)stack_frames.size(), String());
|
||||
return stack_frames[p_index].function;
|
||||
}
|
||||
|
||||
String ScriptBacktrace::get_frame_file(int p_index) const {
|
||||
ERR_FAIL_INDEX_V(p_index, (int)stack_frames.size(), String());
|
||||
return stack_frames[p_index].file;
|
||||
}
|
||||
|
||||
int ScriptBacktrace::get_frame_line(int p_index) const {
|
||||
ERR_FAIL_INDEX_V(p_index, (int)stack_frames.size(), -1);
|
||||
return stack_frames[p_index].line;
|
||||
}
|
||||
|
||||
String ScriptBacktrace::get_global_variable_name(int p_variable_index) const {
|
||||
ERR_FAIL_INDEX_V(p_variable_index, (int)global_variables.size(), String());
|
||||
return global_variables[p_variable_index].name;
|
||||
}
|
||||
|
||||
Variant ScriptBacktrace::get_global_variable_value(int p_variable_index) const {
|
||||
ERR_FAIL_INDEX_V(p_variable_index, (int)global_variables.size(), String());
|
||||
return global_variables[p_variable_index].value;
|
||||
}
|
||||
|
||||
int ScriptBacktrace::get_local_variable_count(int p_frame_index) const {
|
||||
ERR_FAIL_INDEX_V(p_frame_index, (int)stack_frames.size(), 0);
|
||||
return (int)stack_frames[p_frame_index].local_variables.size();
|
||||
}
|
||||
|
||||
String ScriptBacktrace::get_local_variable_name(int p_frame_index, int p_variable_index) const {
|
||||
ERR_FAIL_INDEX_V(p_frame_index, (int)stack_frames.size(), String());
|
||||
const LocalVector<StackVariable> &local_variables = stack_frames[p_frame_index].local_variables;
|
||||
ERR_FAIL_INDEX_V(p_variable_index, (int)local_variables.size(), String());
|
||||
return local_variables[p_variable_index].name;
|
||||
}
|
||||
|
||||
Variant ScriptBacktrace::get_local_variable_value(int p_frame_index, int p_variable_index) const {
|
||||
ERR_FAIL_INDEX_V(p_frame_index, (int)stack_frames.size(), String());
|
||||
const LocalVector<StackVariable> &variables = stack_frames[p_frame_index].local_variables;
|
||||
ERR_FAIL_INDEX_V(p_variable_index, (int)variables.size(), String());
|
||||
return variables[p_variable_index].value;
|
||||
}
|
||||
|
||||
int ScriptBacktrace::get_member_variable_count(int p_frame_index) const {
|
||||
ERR_FAIL_INDEX_V(p_frame_index, (int)stack_frames.size(), 0);
|
||||
return (int)stack_frames[p_frame_index].member_variables.size();
|
||||
}
|
||||
|
||||
String ScriptBacktrace::get_member_variable_name(int p_frame_index, int p_variable_index) const {
|
||||
ERR_FAIL_INDEX_V(p_frame_index, (int)stack_frames.size(), String());
|
||||
const LocalVector<StackVariable> &variables = stack_frames[p_frame_index].member_variables;
|
||||
ERR_FAIL_INDEX_V(p_variable_index, (int)variables.size(), String());
|
||||
return variables[p_variable_index].name;
|
||||
}
|
||||
|
||||
Variant ScriptBacktrace::get_member_variable_value(int p_frame_index, int p_variable_index) const {
|
||||
ERR_FAIL_INDEX_V(p_frame_index, (int)stack_frames.size(), String());
|
||||
const LocalVector<StackVariable> &variables = stack_frames[p_frame_index].member_variables;
|
||||
ERR_FAIL_INDEX_V(p_variable_index, (int)variables.size(), String());
|
||||
return variables[p_variable_index].value;
|
||||
}
|
||||
|
||||
String ScriptBacktrace::format(int p_indent_all, int p_indent_frames) const {
|
||||
if (is_empty()) {
|
||||
return String();
|
||||
}
|
||||
|
||||
static const String space = String::chr(U' ');
|
||||
String indent_all = space.repeat(p_indent_all);
|
||||
String indent_frames = space.repeat(p_indent_frames);
|
||||
String indent_total = indent_all + indent_frames;
|
||||
|
||||
String result = indent_all + language_name + " backtrace (most recent call first):";
|
||||
for (int i = 0; i < (int)stack_frames.size(); i++) {
|
||||
const StackFrame &stack_frame = stack_frames[i];
|
||||
result += "\n" + indent_total + "[" + itos(i) + "] " + stack_frame.function;
|
||||
|
||||
if (!stack_frame.file.is_empty()) {
|
||||
result += " (" + stack_frame.file + ":" + itos(stack_frame.line) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
88
core/object/script_backtrace.h
Normal file
88
core/object/script_backtrace.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/**************************************************************************/
|
||||
/* script_backtrace.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class ScriptLanguage;
|
||||
|
||||
class ScriptBacktrace : public RefCounted {
|
||||
GDCLASS(ScriptBacktrace, RefCounted);
|
||||
|
||||
struct StackVariable {
|
||||
String name;
|
||||
Variant value;
|
||||
};
|
||||
|
||||
struct StackFrame {
|
||||
LocalVector<StackVariable> local_variables;
|
||||
LocalVector<StackVariable> member_variables;
|
||||
String function;
|
||||
String file;
|
||||
int line = 0;
|
||||
};
|
||||
|
||||
LocalVector<StackFrame> stack_frames;
|
||||
LocalVector<StackVariable> global_variables;
|
||||
String language_name;
|
||||
|
||||
static void _store_variables(const List<String> &p_names, const List<Variant> &p_values, LocalVector<StackVariable> &r_variables);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
ScriptBacktrace() = default;
|
||||
ScriptBacktrace(ScriptLanguage *p_language, bool p_include_variables = false);
|
||||
|
||||
String get_language_name() const { return language_name; }
|
||||
|
||||
bool is_empty() const { return stack_frames.is_empty(); }
|
||||
int get_frame_count() const { return stack_frames.size(); }
|
||||
String get_frame_function(int p_index) const;
|
||||
String get_frame_file(int p_index) const;
|
||||
int get_frame_line(int p_index) const;
|
||||
|
||||
int get_global_variable_count() const { return global_variables.size(); }
|
||||
String get_global_variable_name(int p_variable_index) const;
|
||||
Variant get_global_variable_value(int p_variable_index) const;
|
||||
|
||||
int get_local_variable_count(int p_frame_index) const;
|
||||
String get_local_variable_name(int p_frame_index, int p_variable_index) const;
|
||||
Variant get_local_variable_value(int p_frame_index, int p_variable_index) const;
|
||||
|
||||
int get_member_variable_count(int p_frame_index) const;
|
||||
String get_member_variable_name(int p_frame_index, int p_variable_index) const;
|
||||
Variant get_member_variable_value(int p_frame_index, int p_variable_index) const;
|
||||
|
||||
String format(int p_indent_all = 0, int p_indent_frames = 4) const;
|
||||
virtual String to_string() override { return format(); }
|
||||
};
|
93
core/object/script_instance.cpp
Normal file
93
core/object/script_instance.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/**************************************************************************/
|
||||
/* script_instance.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "script_instance.h"
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
int ScriptInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
|
||||
// Default implementation simply traverses hierarchy.
|
||||
Ref<Script> script = get_script();
|
||||
while (script.is_valid()) {
|
||||
bool valid = false;
|
||||
int ret = script->get_script_method_argument_count(p_method, &valid);
|
||||
if (valid) {
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
script = script->get_base_script();
|
||||
}
|
||||
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Variant ScriptInstance::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
|
||||
return callp(p_method, p_args, p_argcount, r_error);
|
||||
}
|
||||
|
||||
void ScriptInstance::get_property_state(List<Pair<StringName, Variant>> &state) {
|
||||
List<PropertyInfo> pinfo;
|
||||
get_property_list(&pinfo);
|
||||
for (const PropertyInfo &E : pinfo) {
|
||||
if (E.usage & PROPERTY_USAGE_STORAGE) {
|
||||
Pair<StringName, Variant> p;
|
||||
p.first = E.name;
|
||||
if (get(p.first, p.second)) {
|
||||
state.push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptInstance::property_set_fallback(const StringName &, const Variant &, bool *r_valid) {
|
||||
if (r_valid) {
|
||||
*r_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
Variant ScriptInstance::property_get_fallback(const StringName &, bool *r_valid) {
|
||||
if (r_valid) {
|
||||
*r_valid = false;
|
||||
}
|
||||
return Variant();
|
||||
}
|
||||
|
||||
const Variant ScriptInstance::get_rpc_config() const {
|
||||
return get_script()->get_rpc_config();
|
||||
}
|
||||
|
||||
ScriptInstance::~ScriptInstance() {
|
||||
}
|
97
core/object/script_instance.h
Normal file
97
core/object/script_instance.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/**************************************************************************/
|
||||
/* script_instance.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class Script;
|
||||
class ScriptLanguage;
|
||||
|
||||
class ScriptInstance {
|
||||
public:
|
||||
virtual bool set(const StringName &p_name, const Variant &p_value) = 0;
|
||||
virtual bool get(const StringName &p_name, Variant &r_ret) const = 0;
|
||||
virtual void get_property_list(List<PropertyInfo> *p_properties) const = 0;
|
||||
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const = 0;
|
||||
virtual void validate_property(PropertyInfo &p_property) const = 0;
|
||||
|
||||
virtual bool property_can_revert(const StringName &p_name) const = 0;
|
||||
virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const = 0;
|
||||
|
||||
virtual Object *get_owner() { return nullptr; }
|
||||
virtual void get_property_state(List<Pair<StringName, Variant>> &state);
|
||||
|
||||
virtual void get_method_list(List<MethodInfo> *p_list) const = 0;
|
||||
virtual bool has_method(const StringName &p_method) const = 0;
|
||||
|
||||
virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const;
|
||||
|
||||
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0;
|
||||
|
||||
template <typename... VarArgs>
|
||||
Variant call(const StringName &p_method, VarArgs... p_args) {
|
||||
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
|
||||
const Variant *argptrs[sizeof...(p_args) + 1];
|
||||
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
|
||||
argptrs[i] = &args[i];
|
||||
}
|
||||
Callable::CallError cerr;
|
||||
return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr);
|
||||
}
|
||||
|
||||
virtual Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); // implement if language supports const functions
|
||||
virtual void notification(int p_notification, bool p_reversed = false) = 0;
|
||||
virtual String to_string(bool *r_valid) {
|
||||
if (r_valid) {
|
||||
*r_valid = false;
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
//this is used by script languages that keep a reference counter of their own
|
||||
//you can make Ref<> not die when it reaches zero, so deleting the reference
|
||||
//depends entirely from the script
|
||||
|
||||
virtual void refcount_incremented() {}
|
||||
virtual bool refcount_decremented() { return true; } //return true if it can die
|
||||
|
||||
virtual Ref<Script> get_script() const = 0;
|
||||
|
||||
virtual bool is_placeholder() const { return false; }
|
||||
|
||||
virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid);
|
||||
virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid);
|
||||
|
||||
virtual const Variant get_rpc_config() const;
|
||||
|
||||
virtual ScriptLanguage *get_language() = 0;
|
||||
virtual ~ScriptInstance();
|
||||
};
|
909
core/object/script_language.cpp
Normal file
909
core/object/script_language.cpp
Normal file
@@ -0,0 +1,909 @@
|
||||
/**************************************************************************/
|
||||
/* script_language.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "script_language.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/core_bind.h"
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
#include "core/debugger/script_debugger.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
|
||||
ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];
|
||||
int ScriptServer::_language_count = 0;
|
||||
bool ScriptServer::languages_ready = false;
|
||||
Mutex ScriptServer::languages_mutex;
|
||||
thread_local bool ScriptServer::thread_entered = false;
|
||||
|
||||
bool ScriptServer::scripting_enabled = true;
|
||||
bool ScriptServer::reload_scripts_on_save = false;
|
||||
ScriptEditRequestFunction ScriptServer::edit_request_func = nullptr;
|
||||
|
||||
// These need to be the last static variables in this file, since we're exploiting the reverse-order destruction of static variables.
|
||||
static bool is_program_exiting = false;
|
||||
struct ProgramExitGuard {
|
||||
~ProgramExitGuard() {
|
||||
is_program_exiting = true;
|
||||
}
|
||||
};
|
||||
static ProgramExitGuard program_exit_guard;
|
||||
|
||||
void Script::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
if (EngineDebugger::is_active()) {
|
||||
callable_mp(this, &Script::_set_debugger_break_language).call_deferred();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
Variant Script::_get_property_default_value(const StringName &p_property) {
|
||||
Variant ret;
|
||||
get_property_default_value(p_property, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
TypedArray<Dictionary> Script::_get_script_property_list() {
|
||||
TypedArray<Dictionary> ret;
|
||||
List<PropertyInfo> list;
|
||||
get_script_property_list(&list);
|
||||
for (const PropertyInfo &E : list) {
|
||||
ret.append(E.operator Dictionary());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TypedArray<Dictionary> Script::_get_script_method_list() {
|
||||
TypedArray<Dictionary> ret;
|
||||
List<MethodInfo> list;
|
||||
get_script_method_list(&list);
|
||||
for (const MethodInfo &E : list) {
|
||||
ret.append(E.operator Dictionary());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TypedArray<Dictionary> Script::_get_script_signal_list() {
|
||||
TypedArray<Dictionary> ret;
|
||||
List<MethodInfo> list;
|
||||
get_script_signal_list(&list);
|
||||
for (const MethodInfo &E : list) {
|
||||
ret.append(E.operator Dictionary());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Dictionary Script::_get_script_constant_map() {
|
||||
Dictionary ret;
|
||||
HashMap<StringName, Variant> map;
|
||||
get_constants(&map);
|
||||
for (const KeyValue<StringName, Variant> &E : map) {
|
||||
ret[E.key] = E.value;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Script::_set_debugger_break_language() {
|
||||
if (EngineDebugger::is_active()) {
|
||||
EngineDebugger::get_script_debugger()->set_break_language(get_language());
|
||||
}
|
||||
}
|
||||
|
||||
int Script::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const {
|
||||
MethodInfo mi = get_method_info(p_method);
|
||||
|
||||
if (mi == MethodInfo()) {
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = true;
|
||||
}
|
||||
return mi.arguments.size();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
PropertyInfo Script::get_class_category() const {
|
||||
String path = get_path();
|
||||
String scr_name;
|
||||
|
||||
if (is_built_in()) {
|
||||
if (get_name().is_empty()) {
|
||||
scr_name = TTR("Built-in script");
|
||||
} else {
|
||||
scr_name = vformat("%s (%s)", get_name(), TTR("Built-in"));
|
||||
}
|
||||
} else {
|
||||
if (get_name().is_empty()) {
|
||||
scr_name = path.get_file();
|
||||
} else {
|
||||
scr_name = get_name();
|
||||
}
|
||||
}
|
||||
|
||||
return PropertyInfo(Variant::NIL, scr_name, PROPERTY_HINT_NONE, path, PROPERTY_USAGE_CATEGORY);
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
void Script::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("can_instantiate"), &Script::can_instantiate);
|
||||
//ClassDB::bind_method(D_METHOD("instance_create","base_object"),&Script::instance_create);
|
||||
ClassDB::bind_method(D_METHOD("instance_has", "base_object"), &Script::instance_has);
|
||||
ClassDB::bind_method(D_METHOD("has_source_code"), &Script::has_source_code);
|
||||
ClassDB::bind_method(D_METHOD("get_source_code"), &Script::get_source_code);
|
||||
ClassDB::bind_method(D_METHOD("set_source_code", "source"), &Script::set_source_code);
|
||||
ClassDB::bind_method(D_METHOD("reload", "keep_state"), &Script::reload, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_base_script"), &Script::get_base_script);
|
||||
ClassDB::bind_method(D_METHOD("get_instance_base_type"), &Script::get_instance_base_type);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_global_name"), &Script::get_global_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_script_signal", "signal_name"), &Script::has_script_signal);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_script_property_list"), &Script::_get_script_property_list);
|
||||
ClassDB::bind_method(D_METHOD("get_script_method_list"), &Script::_get_script_method_list);
|
||||
ClassDB::bind_method(D_METHOD("get_script_signal_list"), &Script::_get_script_signal_list);
|
||||
ClassDB::bind_method(D_METHOD("get_script_constant_map"), &Script::_get_script_constant_map);
|
||||
ClassDB::bind_method(D_METHOD("get_property_default_value", "property"), &Script::_get_property_default_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_tool"), &Script::is_tool);
|
||||
ClassDB::bind_method(D_METHOD("is_abstract"), &Script::is_abstract);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_rpc_config"), &Script::_get_rpc_config_bind);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_source_code", "get_source_code");
|
||||
}
|
||||
|
||||
void Script::reload_from_file() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Replicates how the ScriptEditor reloads script resources, which generally handles it.
|
||||
// However, when scripts are to be reloaded but aren't open in the internal editor, we go through here instead.
|
||||
const Ref<Script> rel = ResourceLoader::load(ResourceLoader::path_remap(get_path()), get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
|
||||
if (rel.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_source_code(rel->get_source_code());
|
||||
set_last_modified_time(rel->get_last_modified_time());
|
||||
|
||||
// Only reload the script when there are no compilation errors to prevent printing the error messages twice.
|
||||
if (rel->is_valid()) {
|
||||
if (Engine::get_singleton()->is_editor_hint() && is_tool()) {
|
||||
get_language()->reload_tool_script(this, true);
|
||||
} else {
|
||||
// It's important to set p_keep_state to true in order to manage reloading scripts
|
||||
// that are currently instantiated.
|
||||
reload(true);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
Resource::reload_from_file();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScriptServer::set_scripting_enabled(bool p_enabled) {
|
||||
scripting_enabled = p_enabled;
|
||||
}
|
||||
|
||||
bool ScriptServer::is_scripting_enabled() {
|
||||
return scripting_enabled;
|
||||
}
|
||||
|
||||
ScriptLanguage *ScriptServer::get_language(int p_idx) {
|
||||
MutexLock lock(languages_mutex);
|
||||
ERR_FAIL_INDEX_V(p_idx, _language_count, nullptr);
|
||||
return _languages[p_idx];
|
||||
}
|
||||
|
||||
ScriptLanguage *ScriptServer::get_language_for_extension(const String &p_extension) {
|
||||
MutexLock lock(languages_mutex);
|
||||
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
if (_languages[i] && _languages[i]->get_extension() == p_extension) {
|
||||
return _languages[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Error ScriptServer::register_language(ScriptLanguage *p_language) {
|
||||
MutexLock lock(languages_mutex);
|
||||
ERR_FAIL_NULL_V(p_language, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V_MSG(_language_count >= MAX_LANGUAGES, ERR_UNAVAILABLE, "Script languages limit has been reach, cannot register more.");
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
const ScriptLanguage *other_language = _languages[i];
|
||||
ERR_FAIL_COND_V_MSG(other_language->get_extension() == p_language->get_extension(), ERR_ALREADY_EXISTS, vformat("A script language with extension '%s' is already registered.", p_language->get_extension()));
|
||||
ERR_FAIL_COND_V_MSG(other_language->get_name() == p_language->get_name(), ERR_ALREADY_EXISTS, vformat("A script language with name '%s' is already registered.", p_language->get_name()));
|
||||
ERR_FAIL_COND_V_MSG(other_language->get_type() == p_language->get_type(), ERR_ALREADY_EXISTS, vformat("A script language with type '%s' is already registered.", p_language->get_type()));
|
||||
}
|
||||
_languages[_language_count++] = p_language;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ScriptServer::unregister_language(const ScriptLanguage *p_language) {
|
||||
MutexLock lock(languages_mutex);
|
||||
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
if (_languages[i] == p_language) {
|
||||
_language_count--;
|
||||
if (i < _language_count) {
|
||||
SWAP(_languages[i], _languages[_language_count]);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
return ERR_DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
void ScriptServer::init_languages() {
|
||||
{ // Load global classes.
|
||||
global_classes_clear();
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) {
|
||||
Array script_classes = GLOBAL_GET("_global_script_classes");
|
||||
|
||||
for (const Variant &script_class : script_classes) {
|
||||
Dictionary c = script_class;
|
||||
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
|
||||
continue;
|
||||
}
|
||||
add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
|
||||
}
|
||||
ProjectSettings::get_singleton()->clear("_global_script_classes");
|
||||
}
|
||||
#endif
|
||||
|
||||
Array script_classes = ProjectSettings::get_singleton()->get_global_class_list();
|
||||
for (const Variant &script_class : script_classes) {
|
||||
Dictionary c = script_class;
|
||||
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
|
||||
continue;
|
||||
}
|
||||
add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<ScriptLanguage *> langs_to_init;
|
||||
{
|
||||
MutexLock lock(languages_mutex);
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
if (_languages[i]) {
|
||||
langs_to_init.insert(_languages[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ScriptLanguage *E : langs_to_init) {
|
||||
E->init();
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(languages_mutex);
|
||||
languages_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptServer::finish_languages() {
|
||||
HashSet<ScriptLanguage *> langs_to_finish;
|
||||
|
||||
{
|
||||
MutexLock lock(languages_mutex);
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
if (_languages[i]) {
|
||||
langs_to_finish.insert(_languages[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ScriptLanguage *E : langs_to_finish) {
|
||||
if (CoreBind::OS::get_singleton()) {
|
||||
CoreBind::OS::get_singleton()->remove_script_loggers(E); // Unregister loggers using this script language.
|
||||
}
|
||||
E->finish();
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(languages_mutex);
|
||||
languages_ready = false;
|
||||
}
|
||||
|
||||
global_classes_clear();
|
||||
}
|
||||
|
||||
bool ScriptServer::are_languages_initialized() {
|
||||
MutexLock lock(languages_mutex);
|
||||
return languages_ready;
|
||||
}
|
||||
|
||||
bool ScriptServer::thread_is_entered() {
|
||||
return thread_entered;
|
||||
}
|
||||
|
||||
void ScriptServer::set_reload_scripts_on_save(bool p_enable) {
|
||||
reload_scripts_on_save = p_enable;
|
||||
}
|
||||
|
||||
bool ScriptServer::is_reload_scripts_on_save_enabled() {
|
||||
return reload_scripts_on_save;
|
||||
}
|
||||
|
||||
void ScriptServer::thread_enter() {
|
||||
if (thread_entered) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock lock(languages_mutex);
|
||||
if (!languages_ready) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
_languages[i]->thread_enter();
|
||||
}
|
||||
|
||||
thread_entered = true;
|
||||
}
|
||||
|
||||
void ScriptServer::thread_exit() {
|
||||
if (!thread_entered) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock lock(languages_mutex);
|
||||
if (!languages_ready) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
_languages[i]->thread_exit();
|
||||
}
|
||||
|
||||
thread_entered = false;
|
||||
}
|
||||
|
||||
HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes;
|
||||
HashMap<StringName, Vector<StringName>> ScriptServer::inheriters_cache;
|
||||
bool ScriptServer::inheriters_cache_dirty = true;
|
||||
|
||||
void ScriptServer::global_classes_clear() {
|
||||
global_classes.clear();
|
||||
inheriters_cache.clear();
|
||||
}
|
||||
|
||||
void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path, bool p_is_abstract, bool p_is_tool) {
|
||||
ERR_FAIL_COND_MSG(p_class == p_base || (global_classes.has(p_base) && get_global_class_native_base(p_base) == p_class), "Cyclic inheritance in script class.");
|
||||
GlobalScriptClass *existing = global_classes.getptr(p_class);
|
||||
if (existing) {
|
||||
// Update an existing class (only set dirty if something changed).
|
||||
if (existing->base != p_base || existing->path != p_path || existing->language != p_language) {
|
||||
existing->base = p_base;
|
||||
existing->path = p_path;
|
||||
existing->language = p_language;
|
||||
existing->is_abstract = p_is_abstract;
|
||||
existing->is_tool = p_is_tool;
|
||||
inheriters_cache_dirty = true;
|
||||
}
|
||||
} else {
|
||||
// Add new class.
|
||||
GlobalScriptClass g;
|
||||
g.language = p_language;
|
||||
g.path = p_path;
|
||||
g.base = p_base;
|
||||
g.is_abstract = p_is_abstract;
|
||||
g.is_tool = p_is_tool;
|
||||
global_classes[p_class] = g;
|
||||
inheriters_cache_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptServer::remove_global_class(const StringName &p_class) {
|
||||
global_classes.erase(p_class);
|
||||
inheriters_cache_dirty = true;
|
||||
}
|
||||
|
||||
void ScriptServer::get_inheriters_list(const StringName &p_base_type, List<StringName> *r_classes) {
|
||||
if (inheriters_cache_dirty) {
|
||||
inheriters_cache.clear();
|
||||
for (const KeyValue<StringName, GlobalScriptClass> &K : global_classes) {
|
||||
if (!inheriters_cache.has(K.value.base)) {
|
||||
inheriters_cache[K.value.base] = Vector<StringName>();
|
||||
}
|
||||
inheriters_cache[K.value.base].push_back(K.key);
|
||||
}
|
||||
for (KeyValue<StringName, Vector<StringName>> &K : inheriters_cache) {
|
||||
K.value.sort_custom<StringName::AlphCompare>();
|
||||
}
|
||||
inheriters_cache_dirty = false;
|
||||
}
|
||||
|
||||
if (!inheriters_cache.has(p_base_type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<StringName> &v = inheriters_cache[p_base_type];
|
||||
for (int i = 0; i < v.size(); i++) {
|
||||
r_classes->push_back(v[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptServer::remove_global_class_by_path(const String &p_path) {
|
||||
for (const KeyValue<StringName, GlobalScriptClass> &kv : global_classes) {
|
||||
if (kv.value.path == p_path) {
|
||||
global_classes.erase(kv.key);
|
||||
inheriters_cache_dirty = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ScriptServer::is_global_class(const StringName &p_class) {
|
||||
return global_classes.has(p_class);
|
||||
}
|
||||
|
||||
StringName ScriptServer::get_global_class_language(const StringName &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
|
||||
return global_classes[p_class].language;
|
||||
}
|
||||
|
||||
String ScriptServer::get_global_class_path(const String &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
|
||||
return global_classes[p_class].path;
|
||||
}
|
||||
|
||||
StringName ScriptServer::get_global_class_base(const String &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
|
||||
return global_classes[p_class].base;
|
||||
}
|
||||
|
||||
StringName ScriptServer::get_global_class_native_base(const String &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
|
||||
String base = global_classes[p_class].base;
|
||||
while (global_classes.has(base)) {
|
||||
base = global_classes[base].base;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
bool ScriptServer::is_global_class_abstract(const String &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), false);
|
||||
return global_classes[p_class].is_abstract;
|
||||
}
|
||||
|
||||
bool ScriptServer::is_global_class_tool(const String &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), false);
|
||||
return global_classes[p_class].is_tool;
|
||||
}
|
||||
|
||||
void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) {
|
||||
List<StringName> classes;
|
||||
for (const KeyValue<StringName, GlobalScriptClass> &E : global_classes) {
|
||||
classes.push_back(E.key);
|
||||
}
|
||||
classes.sort_custom<StringName::AlphCompare>();
|
||||
for (const StringName &E : classes) {
|
||||
r_global_classes->push_back(E);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptServer::save_global_classes() {
|
||||
Dictionary class_icons;
|
||||
|
||||
Array script_classes = ProjectSettings::get_singleton()->get_global_class_list();
|
||||
for (const Variant &script_class : script_classes) {
|
||||
Dictionary d = script_class;
|
||||
if (!d.has("name") || !d.has("icon")) {
|
||||
continue;
|
||||
}
|
||||
class_icons[d["name"]] = d["icon"];
|
||||
}
|
||||
|
||||
List<StringName> gc;
|
||||
get_global_class_list(&gc);
|
||||
Array gcarr;
|
||||
for (const StringName &E : gc) {
|
||||
const GlobalScriptClass &global_class = global_classes[E];
|
||||
Dictionary d;
|
||||
d["class"] = E;
|
||||
d["language"] = global_class.language;
|
||||
d["path"] = global_class.path;
|
||||
d["base"] = global_class.base;
|
||||
d["icon"] = class_icons.get(E, "");
|
||||
d["is_abstract"] = global_class.is_abstract;
|
||||
d["is_tool"] = global_class.is_tool;
|
||||
gcarr.push_back(d);
|
||||
}
|
||||
ProjectSettings::get_singleton()->store_global_class_list(gcarr);
|
||||
}
|
||||
|
||||
Vector<Ref<ScriptBacktrace>> ScriptServer::capture_script_backtraces(bool p_include_variables) {
|
||||
if (is_program_exiting) {
|
||||
return Vector<Ref<ScriptBacktrace>>();
|
||||
}
|
||||
|
||||
MutexLock lock(languages_mutex);
|
||||
if (!languages_ready) {
|
||||
return Vector<Ref<ScriptBacktrace>>();
|
||||
}
|
||||
|
||||
Vector<Ref<ScriptBacktrace>> result;
|
||||
result.resize(_language_count);
|
||||
for (int i = 0; i < _language_count; i++) {
|
||||
result.write[i].instantiate(_languages[i], p_include_variables);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////
|
||||
|
||||
void ScriptLanguage::get_core_type_words(List<String> *p_core_type_words) const {
|
||||
p_core_type_words->push_back("String");
|
||||
p_core_type_words->push_back("Vector2");
|
||||
p_core_type_words->push_back("Vector2i");
|
||||
p_core_type_words->push_back("Rect2");
|
||||
p_core_type_words->push_back("Rect2i");
|
||||
p_core_type_words->push_back("Vector3");
|
||||
p_core_type_words->push_back("Vector3i");
|
||||
p_core_type_words->push_back("Transform2D");
|
||||
p_core_type_words->push_back("Vector4");
|
||||
p_core_type_words->push_back("Vector4i");
|
||||
p_core_type_words->push_back("Plane");
|
||||
p_core_type_words->push_back("Quaternion");
|
||||
p_core_type_words->push_back("AABB");
|
||||
p_core_type_words->push_back("Basis");
|
||||
p_core_type_words->push_back("Transform3D");
|
||||
p_core_type_words->push_back("Projection");
|
||||
p_core_type_words->push_back("Color");
|
||||
p_core_type_words->push_back("StringName");
|
||||
p_core_type_words->push_back("NodePath");
|
||||
p_core_type_words->push_back("RID");
|
||||
p_core_type_words->push_back("Callable");
|
||||
p_core_type_words->push_back("Signal");
|
||||
p_core_type_words->push_back("Dictionary");
|
||||
p_core_type_words->push_back("Array");
|
||||
p_core_type_words->push_back("PackedByteArray");
|
||||
p_core_type_words->push_back("PackedInt32Array");
|
||||
p_core_type_words->push_back("PackedInt64Array");
|
||||
p_core_type_words->push_back("PackedFloat32Array");
|
||||
p_core_type_words->push_back("PackedFloat64Array");
|
||||
p_core_type_words->push_back("PackedStringArray");
|
||||
p_core_type_words->push_back("PackedVector2Array");
|
||||
p_core_type_words->push_back("PackedVector3Array");
|
||||
p_core_type_words->push_back("PackedColorArray");
|
||||
p_core_type_words->push_back("PackedVector4Array");
|
||||
}
|
||||
|
||||
void ScriptLanguage::frame() {
|
||||
}
|
||||
|
||||
TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_characteristics(const String &p_base) {
|
||||
// Return characteristics of the match found by order of importance.
|
||||
// Matches will be ranked by a lexicographical order on the vector returned by this function.
|
||||
// The lower values indicate better matches and that they should go before in the order of appearance.
|
||||
if (last_matches == matches) {
|
||||
return charac;
|
||||
}
|
||||
charac.clear();
|
||||
// Ensure base is not empty and at the same time that matches is not empty too.
|
||||
if (p_base.length() == 0) {
|
||||
last_matches = matches;
|
||||
charac.push_back(location);
|
||||
return charac;
|
||||
}
|
||||
charac.push_back(matches.size());
|
||||
charac.push_back((matches[0].first == 0) ? 0 : 1);
|
||||
const char32_t *target_char = &p_base[0];
|
||||
int bad_case = 0;
|
||||
for (const Pair<int, int> &match_segment : matches) {
|
||||
const char32_t *string_to_complete_char = &display[match_segment.first];
|
||||
for (int j = 0; j < match_segment.second; j++, string_to_complete_char++, target_char++) {
|
||||
if (*string_to_complete_char != *target_char) {
|
||||
bad_case++;
|
||||
}
|
||||
}
|
||||
}
|
||||
charac.push_back(bad_case);
|
||||
charac.push_back(location);
|
||||
charac.push_back(matches[0].first);
|
||||
last_matches = matches;
|
||||
return charac;
|
||||
}
|
||||
|
||||
void ScriptLanguage::CodeCompletionOption::clear_characteristics() {
|
||||
charac = TypedArray<int>();
|
||||
}
|
||||
|
||||
TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_cached_characteristics() const {
|
||||
// Only returns the cached value and warns if it was not updated since the last change of matches.
|
||||
if (last_matches != matches) {
|
||||
WARN_PRINT("Characteristics are not up to date.");
|
||||
}
|
||||
|
||||
return charac;
|
||||
}
|
||||
|
||||
void ScriptLanguage::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(SCRIPT_NAME_CASING_AUTO);
|
||||
BIND_ENUM_CONSTANT(SCRIPT_NAME_CASING_PASCAL_CASE);
|
||||
BIND_ENUM_CONSTANT(SCRIPT_NAME_CASING_SNAKE_CASE);
|
||||
BIND_ENUM_CONSTANT(SCRIPT_NAME_CASING_KEBAB_CASE);
|
||||
BIND_ENUM_CONSTANT(SCRIPT_NAME_CASING_CAMEL_CASE);
|
||||
}
|
||||
|
||||
bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) {
|
||||
if (script->is_placeholder_fallback_enabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.has(p_name)) {
|
||||
Variant defval;
|
||||
if (script->get_property_default_value(p_name, defval)) {
|
||||
// The evaluate function ensures that a NIL variant is equal to e.g. an empty Resource.
|
||||
// Simply doing defval == p_value does not do this.
|
||||
if (Variant::evaluate(Variant::OP_EQUAL, defval, p_value)) {
|
||||
values.erase(p_name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
values[p_name] = p_value;
|
||||
return true;
|
||||
} else {
|
||||
Variant defval;
|
||||
if (script->get_property_default_value(p_name, defval)) {
|
||||
if (Variant::evaluate(Variant::OP_NOT_EQUAL, defval, p_value)) {
|
||||
values[p_name] = p_value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlaceHolderScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
|
||||
if (values.has(p_name)) {
|
||||
r_ret = values[p_name];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (constants.has(p_name)) {
|
||||
r_ret = constants[p_name];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!script->is_placeholder_fallback_enabled()) {
|
||||
Variant defval;
|
||||
if (script->get_property_default_value(p_name, defval)) {
|
||||
r_ret = defval;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlaceHolderScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const {
|
||||
if (script->is_placeholder_fallback_enabled()) {
|
||||
for (const PropertyInfo &E : properties) {
|
||||
p_properties->push_back(E);
|
||||
}
|
||||
} else {
|
||||
for (const PropertyInfo &E : properties) {
|
||||
PropertyInfo pinfo = E;
|
||||
p_properties->push_back(E);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Variant::Type PlaceHolderScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const {
|
||||
if (values.has(p_name)) {
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = true;
|
||||
}
|
||||
return values[p_name].get_type();
|
||||
}
|
||||
|
||||
if (constants.has(p_name)) {
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = true;
|
||||
}
|
||||
return constants[p_name].get_type();
|
||||
}
|
||||
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = false;
|
||||
}
|
||||
|
||||
return Variant::NIL;
|
||||
}
|
||||
|
||||
void PlaceHolderScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
|
||||
if (script->is_placeholder_fallback_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (script.is_valid()) {
|
||||
script->get_script_method_list(p_list);
|
||||
}
|
||||
}
|
||||
|
||||
bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const {
|
||||
if (script->is_placeholder_fallback_enabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (script.is_valid()) {
|
||||
Ref<Script> scr = script;
|
||||
while (scr.is_valid()) {
|
||||
if (scr->has_method(p_method)) {
|
||||
return true;
|
||||
}
|
||||
scr = scr->get_base_script();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Variant PlaceHolderScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
|
||||
#if TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return String("Attempt to call a method on a placeholder instance. Check if the script is in tool mode.");
|
||||
} else {
|
||||
return String("Attempt to call a method on a placeholder instance. Probably a bug, please report.");
|
||||
}
|
||||
#else
|
||||
return Variant();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void PlaceHolderScriptInstance::update(const List<PropertyInfo> &p_properties, const HashMap<StringName, Variant> &p_values) {
|
||||
HashSet<StringName> new_values;
|
||||
for (const PropertyInfo &E : p_properties) {
|
||||
if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StringName n = E.name;
|
||||
new_values.insert(n);
|
||||
|
||||
if (!values.has(n) || (E.type != Variant::NIL && values[n].get_type() != E.type)) {
|
||||
if (p_values.has(n)) {
|
||||
values[n] = p_values[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
properties = p_properties;
|
||||
List<StringName> to_remove;
|
||||
|
||||
for (KeyValue<StringName, Variant> &E : values) {
|
||||
if (!new_values.has(E.key)) {
|
||||
to_remove.push_back(E.key);
|
||||
}
|
||||
|
||||
Variant defval;
|
||||
if (script->get_property_default_value(E.key, defval)) {
|
||||
//remove because it's the same as the default value
|
||||
if (defval == E.value) {
|
||||
to_remove.push_back(E.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (to_remove.size()) {
|
||||
values.erase(to_remove.front()->get());
|
||||
to_remove.pop_front();
|
||||
}
|
||||
|
||||
if (owner && owner->get_script_instance() == this) {
|
||||
owner->notify_property_list_changed();
|
||||
}
|
||||
//change notify
|
||||
|
||||
constants.clear();
|
||||
script->get_constants(&constants);
|
||||
}
|
||||
|
||||
void PlaceHolderScriptInstance::property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid) {
|
||||
if (script->is_placeholder_fallback_enabled()) {
|
||||
HashMap<StringName, Variant>::Iterator E = values.find(p_name);
|
||||
|
||||
if (E) {
|
||||
E->value = p_value;
|
||||
} else {
|
||||
values.insert(p_name, p_value);
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (const PropertyInfo &F : properties) {
|
||||
if (F.name == p_name) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
PropertyHint hint = PROPERTY_HINT_NONE;
|
||||
const Object *obj = p_value.get_validated_object();
|
||||
if (obj && obj->is_class("Node")) {
|
||||
hint = PROPERTY_HINT_NODE_TYPE;
|
||||
}
|
||||
properties.push_back(PropertyInfo(p_value.get_type(), p_name, hint, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE));
|
||||
}
|
||||
}
|
||||
|
||||
if (r_valid) {
|
||||
*r_valid = false; // Cannot change the value in either case
|
||||
}
|
||||
}
|
||||
|
||||
Variant PlaceHolderScriptInstance::property_get_fallback(const StringName &p_name, bool *r_valid) {
|
||||
if (script->is_placeholder_fallback_enabled()) {
|
||||
HashMap<StringName, Variant>::ConstIterator E = values.find(p_name);
|
||||
|
||||
if (E) {
|
||||
if (r_valid) {
|
||||
*r_valid = true;
|
||||
}
|
||||
return E->value;
|
||||
}
|
||||
|
||||
E = constants.find(p_name);
|
||||
if (E) {
|
||||
if (r_valid) {
|
||||
*r_valid = true;
|
||||
}
|
||||
return E->value;
|
||||
}
|
||||
}
|
||||
|
||||
if (r_valid) {
|
||||
*r_valid = false;
|
||||
}
|
||||
|
||||
return Variant();
|
||||
}
|
||||
|
||||
PlaceHolderScriptInstance::PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner) :
|
||||
owner(p_owner),
|
||||
language(p_language),
|
||||
script(p_script) {
|
||||
}
|
||||
|
||||
PlaceHolderScriptInstance::~PlaceHolderScriptInstance() {
|
||||
if (script.is_valid()) {
|
||||
script->_placeholder_erased(this);
|
||||
}
|
||||
}
|
502
core/object/script_language.h
Normal file
502
core/object/script_language.h
Normal file
@@ -0,0 +1,502 @@
|
||||
/**************************************************************************/
|
||||
/* script_language.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/doc_data.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "core/object/script_backtrace.h"
|
||||
#include "core/object/script_instance.h"
|
||||
#include "core/templates/pair.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
class ScriptLanguage;
|
||||
template <typename T>
|
||||
class TypedArray;
|
||||
|
||||
typedef void (*ScriptEditRequestFunction)(const String &p_path);
|
||||
|
||||
class ScriptServer {
|
||||
enum {
|
||||
MAX_LANGUAGES = 16
|
||||
};
|
||||
|
||||
static ScriptLanguage *_languages[MAX_LANGUAGES];
|
||||
static int _language_count;
|
||||
static bool languages_ready;
|
||||
static Mutex languages_mutex;
|
||||
static thread_local bool thread_entered;
|
||||
|
||||
static bool scripting_enabled;
|
||||
static bool reload_scripts_on_save;
|
||||
|
||||
struct GlobalScriptClass {
|
||||
StringName language;
|
||||
String path;
|
||||
StringName base;
|
||||
bool is_abstract = false;
|
||||
bool is_tool = false;
|
||||
};
|
||||
|
||||
static HashMap<StringName, GlobalScriptClass> global_classes;
|
||||
static HashMap<StringName, Vector<StringName>> inheriters_cache;
|
||||
static bool inheriters_cache_dirty;
|
||||
|
||||
public:
|
||||
static ScriptEditRequestFunction edit_request_func;
|
||||
|
||||
static void set_scripting_enabled(bool p_enabled);
|
||||
static bool is_scripting_enabled();
|
||||
_FORCE_INLINE_ static int get_language_count() { return _language_count; }
|
||||
static ScriptLanguage *get_language(int p_idx);
|
||||
static ScriptLanguage *get_language_for_extension(const String &p_extension);
|
||||
static Error register_language(ScriptLanguage *p_language);
|
||||
static Error unregister_language(const ScriptLanguage *p_language);
|
||||
|
||||
static void set_reload_scripts_on_save(bool p_enable);
|
||||
static bool is_reload_scripts_on_save_enabled();
|
||||
|
||||
static void thread_enter();
|
||||
static void thread_exit();
|
||||
|
||||
static void global_classes_clear();
|
||||
static void add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path, bool p_is_abstract, bool p_is_tool);
|
||||
static void remove_global_class(const StringName &p_class);
|
||||
static void remove_global_class_by_path(const String &p_path);
|
||||
static bool is_global_class(const StringName &p_class);
|
||||
static StringName get_global_class_language(const StringName &p_class);
|
||||
static String get_global_class_path(const String &p_class);
|
||||
static StringName get_global_class_base(const String &p_class);
|
||||
static StringName get_global_class_native_base(const String &p_class);
|
||||
static bool is_global_class_abstract(const String &p_class);
|
||||
static bool is_global_class_tool(const String &p_class);
|
||||
static void get_global_class_list(List<StringName> *r_global_classes);
|
||||
static void get_inheriters_list(const StringName &p_base_type, List<StringName> *r_classes);
|
||||
static void save_global_classes();
|
||||
|
||||
static Vector<Ref<ScriptBacktrace>> capture_script_backtraces(bool p_include_variables = false);
|
||||
|
||||
static void init_languages();
|
||||
static void finish_languages();
|
||||
static bool are_languages_initialized();
|
||||
static bool thread_is_entered();
|
||||
};
|
||||
|
||||
class PlaceHolderScriptInstance;
|
||||
|
||||
class Script : public Resource {
|
||||
GDCLASS(Script, Resource);
|
||||
OBJ_SAVE_TYPE(Script);
|
||||
|
||||
protected:
|
||||
// Scripts are reloaded via the Script Editor when edited in Godot,
|
||||
// the LSP server when edited in a connected external editor, or
|
||||
// through EditorFileSystem::_update_script_documentation when updated directly on disk.
|
||||
virtual bool editor_can_reload_from_file() override { return false; }
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
friend class PlaceHolderScriptInstance;
|
||||
virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {}
|
||||
|
||||
Variant _get_property_default_value(const StringName &p_property);
|
||||
TypedArray<Dictionary> _get_script_property_list();
|
||||
TypedArray<Dictionary> _get_script_method_list();
|
||||
TypedArray<Dictionary> _get_script_signal_list();
|
||||
Dictionary _get_script_constant_map();
|
||||
|
||||
void _set_debugger_break_language();
|
||||
|
||||
Variant _get_rpc_config_bind() const {
|
||||
return get_rpc_config().duplicate(true);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual void reload_from_file() override;
|
||||
|
||||
virtual bool can_instantiate() const = 0;
|
||||
|
||||
virtual Ref<Script> get_base_script() const = 0; //for script inheritance
|
||||
virtual StringName get_global_name() const = 0;
|
||||
virtual bool inherits_script(const Ref<Script> &p_script) const = 0;
|
||||
|
||||
virtual StringName get_instance_base_type() const = 0; // this may not work in all scripts, will return empty if so
|
||||
virtual ScriptInstance *instance_create(Object *p_this) = 0;
|
||||
virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) { return nullptr; }
|
||||
virtual bool instance_has(const Object *p_this) const = 0;
|
||||
|
||||
virtual bool has_source_code() const = 0;
|
||||
virtual String get_source_code() const = 0;
|
||||
virtual void set_source_code(const String &p_code) = 0;
|
||||
virtual Error reload(bool p_keep_state = false) = 0;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual StringName get_doc_class_name() const = 0;
|
||||
virtual Vector<DocData::ClassDoc> get_documentation() const = 0;
|
||||
virtual String get_class_icon_path() const = 0;
|
||||
virtual PropertyInfo get_class_category() const;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
// TODO: In the next compat breakage rename to `*_script_*` to disambiguate from `Object::has_method()`.
|
||||
virtual bool has_method(const StringName &p_method) const = 0;
|
||||
virtual bool has_static_method(const StringName &p_method) const { return false; }
|
||||
|
||||
virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const;
|
||||
|
||||
virtual MethodInfo get_method_info(const StringName &p_method) const = 0;
|
||||
|
||||
virtual bool is_tool() const = 0;
|
||||
virtual bool is_valid() const = 0;
|
||||
virtual bool is_abstract() const = 0;
|
||||
|
||||
virtual ScriptLanguage *get_language() const = 0;
|
||||
|
||||
virtual bool has_script_signal(const StringName &p_signal) const = 0;
|
||||
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const = 0;
|
||||
|
||||
virtual bool get_property_default_value(const StringName &p_property, Variant &r_value) const = 0;
|
||||
|
||||
virtual void update_exports() {} //editor tool
|
||||
virtual void get_script_method_list(List<MethodInfo> *p_list) const = 0;
|
||||
virtual void get_script_property_list(List<PropertyInfo> *p_list) const = 0;
|
||||
|
||||
virtual int get_member_line(const StringName &p_member) const { return -1; }
|
||||
|
||||
virtual void get_constants(HashMap<StringName, Variant> *p_constants) {}
|
||||
virtual void get_members(HashSet<StringName> *p_members) {}
|
||||
|
||||
virtual bool is_placeholder_fallback_enabled() const { return false; }
|
||||
|
||||
virtual const Variant get_rpc_config() const = 0;
|
||||
|
||||
Script() {}
|
||||
};
|
||||
|
||||
class ScriptLanguage : public Object {
|
||||
GDCLASS(ScriptLanguage, Object)
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual String get_name() const = 0;
|
||||
|
||||
/* LANGUAGE FUNCTIONS */
|
||||
virtual void init() = 0;
|
||||
virtual String get_type() const = 0;
|
||||
virtual String get_extension() const = 0;
|
||||
virtual void finish() = 0;
|
||||
|
||||
/* EDITOR FUNCTIONS */
|
||||
struct Warning {
|
||||
int start_line = -1, end_line = -1;
|
||||
int code;
|
||||
String string_code;
|
||||
String message;
|
||||
};
|
||||
|
||||
struct ScriptError {
|
||||
String path;
|
||||
int line = -1;
|
||||
int column = -1;
|
||||
String message;
|
||||
};
|
||||
|
||||
enum TemplateLocation {
|
||||
TEMPLATE_BUILT_IN,
|
||||
TEMPLATE_EDITOR,
|
||||
TEMPLATE_PROJECT
|
||||
};
|
||||
|
||||
enum ScriptNameCasing {
|
||||
SCRIPT_NAME_CASING_AUTO,
|
||||
SCRIPT_NAME_CASING_PASCAL_CASE,
|
||||
SCRIPT_NAME_CASING_SNAKE_CASE,
|
||||
SCRIPT_NAME_CASING_KEBAB_CASE,
|
||||
SCRIPT_NAME_CASING_CAMEL_CASE,
|
||||
};
|
||||
|
||||
struct ScriptTemplate {
|
||||
String inherit = "Object";
|
||||
String name;
|
||||
String description;
|
||||
String content;
|
||||
int id = 0;
|
||||
TemplateLocation origin = TemplateLocation::TEMPLATE_BUILT_IN;
|
||||
|
||||
String get_hash() const {
|
||||
return itos(origin) + inherit + name;
|
||||
}
|
||||
};
|
||||
|
||||
void get_core_type_words(List<String> *p_core_type_words) const;
|
||||
virtual Vector<String> get_reserved_words() const = 0;
|
||||
virtual bool is_control_flow_keyword(const String &p_string) const = 0;
|
||||
virtual Vector<String> get_comment_delimiters() const = 0;
|
||||
virtual Vector<String> get_doc_comment_delimiters() const = 0;
|
||||
virtual Vector<String> get_string_delimiters() const = 0;
|
||||
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { return Ref<Script>(); }
|
||||
virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) { return Vector<ScriptTemplate>(); }
|
||||
virtual bool is_using_templates() { return false; }
|
||||
virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptError> *r_errors = nullptr, List<Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const = 0;
|
||||
virtual String validate_path(const String &p_path) const { return ""; }
|
||||
virtual Script *create_script() const = 0;
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
virtual bool has_named_classes() const = 0;
|
||||
#endif
|
||||
virtual bool supports_builtin_mode() const = 0;
|
||||
virtual bool supports_documentation() const { return false; }
|
||||
virtual bool can_inherit_from_file() const { return false; }
|
||||
virtual int find_function(const String &p_function, const String &p_code) const = 0;
|
||||
virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const = 0;
|
||||
virtual bool can_make_function() const { return true; }
|
||||
virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { return ERR_UNAVAILABLE; }
|
||||
virtual bool overrides_external_editor() { return false; }
|
||||
virtual ScriptNameCasing preferred_file_name_casing() const { return SCRIPT_NAME_CASING_SNAKE_CASE; }
|
||||
|
||||
// Keep enums in sync with:
|
||||
// scene/gui/code_edit.h - CodeEdit::CodeCompletionKind
|
||||
enum CodeCompletionKind {
|
||||
CODE_COMPLETION_KIND_CLASS,
|
||||
CODE_COMPLETION_KIND_FUNCTION,
|
||||
CODE_COMPLETION_KIND_SIGNAL,
|
||||
CODE_COMPLETION_KIND_VARIABLE,
|
||||
CODE_COMPLETION_KIND_MEMBER,
|
||||
CODE_COMPLETION_KIND_ENUM,
|
||||
CODE_COMPLETION_KIND_CONSTANT,
|
||||
CODE_COMPLETION_KIND_NODE_PATH,
|
||||
CODE_COMPLETION_KIND_FILE_PATH,
|
||||
CODE_COMPLETION_KIND_PLAIN_TEXT,
|
||||
CODE_COMPLETION_KIND_MAX
|
||||
};
|
||||
|
||||
// scene/gui/code_edit.h - CodeEdit::CodeCompletionLocation
|
||||
enum CodeCompletionLocation {
|
||||
LOCATION_LOCAL = 0,
|
||||
LOCATION_PARENT_MASK = 1 << 8,
|
||||
LOCATION_OTHER_USER_CODE = 1 << 9,
|
||||
LOCATION_OTHER = 1 << 10,
|
||||
};
|
||||
|
||||
struct CodeCompletionOption {
|
||||
CodeCompletionKind kind = CODE_COMPLETION_KIND_PLAIN_TEXT;
|
||||
String display;
|
||||
String insert_text;
|
||||
Color font_color;
|
||||
Ref<Resource> icon;
|
||||
Variant default_value;
|
||||
Vector<Pair<int, int>> matches;
|
||||
Vector<Pair<int, int>> last_matches = { { -1, -1 } }; // This value correspond to an impossible match
|
||||
int location = LOCATION_OTHER;
|
||||
String theme_color_name;
|
||||
|
||||
CodeCompletionOption() {}
|
||||
|
||||
CodeCompletionOption(const String &p_text, CodeCompletionKind p_kind, int p_location = LOCATION_OTHER, const String &p_theme_color_name = "") {
|
||||
display = p_text;
|
||||
insert_text = p_text;
|
||||
kind = p_kind;
|
||||
location = p_location;
|
||||
theme_color_name = p_theme_color_name;
|
||||
}
|
||||
|
||||
TypedArray<int> get_option_characteristics(const String &p_base);
|
||||
void clear_characteristics();
|
||||
TypedArray<int> get_option_cached_characteristics() const;
|
||||
|
||||
private:
|
||||
TypedArray<int> charac;
|
||||
};
|
||||
|
||||
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }
|
||||
|
||||
enum LookupResultType {
|
||||
LOOKUP_RESULT_SCRIPT_LOCATION, // Use if none of the options below apply.
|
||||
LOOKUP_RESULT_CLASS,
|
||||
LOOKUP_RESULT_CLASS_CONSTANT,
|
||||
LOOKUP_RESULT_CLASS_PROPERTY,
|
||||
LOOKUP_RESULT_CLASS_METHOD,
|
||||
LOOKUP_RESULT_CLASS_SIGNAL,
|
||||
LOOKUP_RESULT_CLASS_ENUM,
|
||||
LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE, // Deprecated.
|
||||
LOOKUP_RESULT_CLASS_ANNOTATION,
|
||||
LOOKUP_RESULT_LOCAL_CONSTANT,
|
||||
LOOKUP_RESULT_LOCAL_VARIABLE,
|
||||
LOOKUP_RESULT_MAX,
|
||||
};
|
||||
|
||||
struct LookupResult {
|
||||
LookupResultType type;
|
||||
|
||||
// For `CLASS_*`.
|
||||
String class_name;
|
||||
String class_member;
|
||||
|
||||
// For `LOCAL_*`.
|
||||
String description;
|
||||
bool is_deprecated = false;
|
||||
String deprecated_message;
|
||||
bool is_experimental = false;
|
||||
String experimental_message;
|
||||
|
||||
// For `LOCAL_*`.
|
||||
String doc_type;
|
||||
String enumeration;
|
||||
bool is_bitfield = false;
|
||||
|
||||
// For `LOCAL_*`.
|
||||
String value;
|
||||
|
||||
// `SCRIPT_LOCATION` and `LOCAL_*` must have, `CLASS_*` can have.
|
||||
Ref<Script> script;
|
||||
String script_path;
|
||||
int location = -1;
|
||||
};
|
||||
|
||||
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { return ERR_UNAVAILABLE; }
|
||||
|
||||
virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const = 0;
|
||||
virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) = 0;
|
||||
virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) {}
|
||||
virtual void remove_named_global_constant(const StringName &p_name) {}
|
||||
|
||||
/* MULTITHREAD FUNCTIONS */
|
||||
|
||||
//some VMs need to be notified of thread creation/exiting to allocate a stack
|
||||
virtual void thread_enter() {}
|
||||
virtual void thread_exit() {}
|
||||
|
||||
/* DEBUGGER FUNCTIONS */
|
||||
struct StackInfo {
|
||||
String file;
|
||||
String func;
|
||||
int line;
|
||||
};
|
||||
|
||||
virtual String debug_get_error() const = 0;
|
||||
virtual int debug_get_stack_level_count() const = 0;
|
||||
virtual int debug_get_stack_level_line(int p_level) const = 0;
|
||||
virtual String debug_get_stack_level_function(int p_level) const = 0;
|
||||
virtual String debug_get_stack_level_source(int p_level) const = 0;
|
||||
virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) = 0;
|
||||
virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) = 0;
|
||||
virtual ScriptInstance *debug_get_stack_level_instance(int p_level) { return nullptr; }
|
||||
virtual void debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) = 0;
|
||||
virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) = 0;
|
||||
|
||||
virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); }
|
||||
|
||||
virtual void reload_all_scripts() = 0;
|
||||
virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) = 0;
|
||||
virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) = 0;
|
||||
/* LOADER FUNCTIONS */
|
||||
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const = 0;
|
||||
virtual void get_public_functions(List<MethodInfo> *p_functions) const = 0;
|
||||
virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const = 0;
|
||||
virtual void get_public_annotations(List<MethodInfo> *p_annotations) const = 0;
|
||||
|
||||
struct ProfilingInfo {
|
||||
StringName signature;
|
||||
uint64_t call_count;
|
||||
uint64_t total_time;
|
||||
uint64_t self_time;
|
||||
uint64_t internal_time;
|
||||
};
|
||||
|
||||
virtual void profiling_start() = 0;
|
||||
virtual void profiling_stop() = 0;
|
||||
virtual void profiling_set_save_native_calls(bool p_enable) = 0;
|
||||
|
||||
virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) = 0;
|
||||
virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) = 0;
|
||||
|
||||
virtual void frame();
|
||||
|
||||
virtual bool handles_global_class_type(const String &p_type) const { return false; }
|
||||
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const { return String(); }
|
||||
|
||||
virtual ~ScriptLanguage() {}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(ScriptLanguage::ScriptNameCasing);
|
||||
|
||||
extern uint8_t script_encryption_key[32];
|
||||
|
||||
class PlaceHolderScriptInstance : public ScriptInstance {
|
||||
Object *owner = nullptr;
|
||||
List<PropertyInfo> properties;
|
||||
HashMap<StringName, Variant> values;
|
||||
HashMap<StringName, Variant> constants;
|
||||
ScriptLanguage *language = nullptr;
|
||||
Ref<Script> script;
|
||||
|
||||
public:
|
||||
virtual bool set(const StringName &p_name, const Variant &p_value) override;
|
||||
virtual bool get(const StringName &p_name, Variant &r_ret) const override;
|
||||
virtual void get_property_list(List<PropertyInfo> *p_properties) const override;
|
||||
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const override;
|
||||
virtual void validate_property(PropertyInfo &p_property) const override {}
|
||||
|
||||
virtual bool property_can_revert(const StringName &p_name) const override { return false; }
|
||||
virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { return false; }
|
||||
|
||||
virtual void get_method_list(List<MethodInfo> *p_list) const override;
|
||||
virtual bool has_method(const StringName &p_method) const override;
|
||||
|
||||
virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
|
||||
virtual void notification(int p_notification, bool p_reversed = false) override {}
|
||||
|
||||
virtual Ref<Script> get_script() const override { return script; }
|
||||
|
||||
virtual ScriptLanguage *get_language() override { return language; }
|
||||
|
||||
Object *get_owner() override { return owner; }
|
||||
|
||||
void update(const List<PropertyInfo> &p_properties, const HashMap<StringName, Variant> &p_values); //likely changed in editor
|
||||
|
||||
virtual bool is_placeholder() const override { return true; }
|
||||
|
||||
virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr) override;
|
||||
virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid = nullptr) override;
|
||||
|
||||
virtual const Variant get_rpc_config() const override { return Variant(); }
|
||||
|
||||
PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner);
|
||||
~PlaceHolderScriptInstance();
|
||||
};
|
195
core/object/script_language_extension.cpp
Normal file
195
core/object/script_language_extension.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
/**************************************************************************/
|
||||
/* script_language_extension.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "script_language_extension.h"
|
||||
|
||||
void ScriptExtension::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_editor_can_reload_from_file);
|
||||
GDVIRTUAL_BIND(_placeholder_erased, "placeholder");
|
||||
|
||||
GDVIRTUAL_BIND(_can_instantiate);
|
||||
GDVIRTUAL_BIND(_get_base_script);
|
||||
GDVIRTUAL_BIND(_get_global_name);
|
||||
GDVIRTUAL_BIND(_inherits_script, "script");
|
||||
|
||||
GDVIRTUAL_BIND(_get_instance_base_type);
|
||||
GDVIRTUAL_BIND(_instance_create, "for_object");
|
||||
GDVIRTUAL_BIND(_placeholder_instance_create, "for_object");
|
||||
|
||||
GDVIRTUAL_BIND(_instance_has, "object");
|
||||
|
||||
GDVIRTUAL_BIND(_has_source_code);
|
||||
GDVIRTUAL_BIND(_get_source_code);
|
||||
|
||||
GDVIRTUAL_BIND(_set_source_code, "code");
|
||||
GDVIRTUAL_BIND(_reload, "keep_state");
|
||||
|
||||
GDVIRTUAL_BIND(_get_doc_class_name);
|
||||
GDVIRTUAL_BIND(_get_documentation);
|
||||
GDVIRTUAL_BIND(_get_class_icon_path);
|
||||
|
||||
GDVIRTUAL_BIND(_has_method, "method");
|
||||
GDVIRTUAL_BIND(_has_static_method, "method");
|
||||
|
||||
GDVIRTUAL_BIND(_get_script_method_argument_count, "method");
|
||||
|
||||
GDVIRTUAL_BIND(_get_method_info, "method");
|
||||
|
||||
GDVIRTUAL_BIND(_is_tool);
|
||||
GDVIRTUAL_BIND(_is_valid);
|
||||
GDVIRTUAL_BIND(_is_abstract);
|
||||
GDVIRTUAL_BIND(_get_language);
|
||||
|
||||
GDVIRTUAL_BIND(_has_script_signal, "signal");
|
||||
GDVIRTUAL_BIND(_get_script_signal_list);
|
||||
|
||||
GDVIRTUAL_BIND(_has_property_default_value, "property");
|
||||
GDVIRTUAL_BIND(_get_property_default_value, "property");
|
||||
|
||||
GDVIRTUAL_BIND(_update_exports);
|
||||
GDVIRTUAL_BIND(_get_script_method_list);
|
||||
GDVIRTUAL_BIND(_get_script_property_list);
|
||||
|
||||
GDVIRTUAL_BIND(_get_member_line, "member");
|
||||
|
||||
GDVIRTUAL_BIND(_get_constants);
|
||||
GDVIRTUAL_BIND(_get_members);
|
||||
GDVIRTUAL_BIND(_is_placeholder_fallback_enabled);
|
||||
|
||||
GDVIRTUAL_BIND(_get_rpc_config);
|
||||
}
|
||||
|
||||
void ScriptLanguageExtension::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_get_name);
|
||||
GDVIRTUAL_BIND(_init);
|
||||
GDVIRTUAL_BIND(_get_type);
|
||||
GDVIRTUAL_BIND(_get_extension);
|
||||
GDVIRTUAL_BIND(_finish);
|
||||
|
||||
GDVIRTUAL_BIND(_get_reserved_words);
|
||||
GDVIRTUAL_BIND(_is_control_flow_keyword, "keyword");
|
||||
GDVIRTUAL_BIND(_get_comment_delimiters);
|
||||
GDVIRTUAL_BIND(_get_doc_comment_delimiters);
|
||||
GDVIRTUAL_BIND(_get_string_delimiters);
|
||||
GDVIRTUAL_BIND(_make_template, "template", "class_name", "base_class_name");
|
||||
GDVIRTUAL_BIND(_get_built_in_templates, "object");
|
||||
GDVIRTUAL_BIND(_is_using_templates);
|
||||
GDVIRTUAL_BIND(_validate, "script", "path", "validate_functions", "validate_errors", "validate_warnings", "validate_safe_lines");
|
||||
|
||||
GDVIRTUAL_BIND(_validate_path, "path");
|
||||
GDVIRTUAL_BIND(_create_script);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
GDVIRTUAL_BIND(_has_named_classes);
|
||||
#endif
|
||||
GDVIRTUAL_BIND(_supports_builtin_mode);
|
||||
GDVIRTUAL_BIND(_supports_documentation);
|
||||
GDVIRTUAL_BIND(_can_inherit_from_file);
|
||||
GDVIRTUAL_BIND(_find_function, "function", "code");
|
||||
GDVIRTUAL_BIND(_make_function, "class_name", "function_name", "function_args");
|
||||
GDVIRTUAL_BIND(_can_make_function);
|
||||
GDVIRTUAL_BIND(_open_in_external_editor, "script", "line", "column");
|
||||
GDVIRTUAL_BIND(_overrides_external_editor);
|
||||
GDVIRTUAL_BIND(_preferred_file_name_casing);
|
||||
|
||||
GDVIRTUAL_BIND(_complete_code, "code", "path", "owner");
|
||||
GDVIRTUAL_BIND(_lookup_code, "code", "symbol", "path", "owner");
|
||||
GDVIRTUAL_BIND(_auto_indent_code, "code", "from_line", "to_line");
|
||||
|
||||
GDVIRTUAL_BIND(_add_global_constant, "name", "value");
|
||||
GDVIRTUAL_BIND(_add_named_global_constant, "name", "value");
|
||||
GDVIRTUAL_BIND(_remove_named_global_constant, "name");
|
||||
|
||||
GDVIRTUAL_BIND(_thread_enter);
|
||||
GDVIRTUAL_BIND(_thread_exit);
|
||||
GDVIRTUAL_BIND(_debug_get_error);
|
||||
GDVIRTUAL_BIND(_debug_get_stack_level_count);
|
||||
|
||||
GDVIRTUAL_BIND(_debug_get_stack_level_line, "level");
|
||||
GDVIRTUAL_BIND(_debug_get_stack_level_function, "level");
|
||||
GDVIRTUAL_BIND(_debug_get_stack_level_source, "level");
|
||||
GDVIRTUAL_BIND(_debug_get_stack_level_locals, "level", "max_subitems", "max_depth");
|
||||
GDVIRTUAL_BIND(_debug_get_stack_level_members, "level", "max_subitems", "max_depth");
|
||||
GDVIRTUAL_BIND(_debug_get_stack_level_instance, "level");
|
||||
GDVIRTUAL_BIND(_debug_get_globals, "max_subitems", "max_depth");
|
||||
GDVIRTUAL_BIND(_debug_parse_stack_level_expression, "level", "expression", "max_subitems", "max_depth");
|
||||
|
||||
GDVIRTUAL_BIND(_debug_get_current_stack_info);
|
||||
|
||||
GDVIRTUAL_BIND(_reload_all_scripts);
|
||||
GDVIRTUAL_BIND(_reload_scripts, "scripts", "soft_reload");
|
||||
GDVIRTUAL_BIND(_reload_tool_script, "script", "soft_reload");
|
||||
|
||||
GDVIRTUAL_BIND(_get_recognized_extensions);
|
||||
GDVIRTUAL_BIND(_get_public_functions);
|
||||
GDVIRTUAL_BIND(_get_public_constants);
|
||||
GDVIRTUAL_BIND(_get_public_annotations);
|
||||
|
||||
GDVIRTUAL_BIND(_profiling_start);
|
||||
GDVIRTUAL_BIND(_profiling_stop);
|
||||
GDVIRTUAL_BIND(_profiling_set_save_native_calls, "enable");
|
||||
|
||||
GDVIRTUAL_BIND(_profiling_get_accumulated_data, "info_array", "info_max");
|
||||
GDVIRTUAL_BIND(_profiling_get_frame_data, "info_array", "info_max");
|
||||
|
||||
GDVIRTUAL_BIND(_frame);
|
||||
|
||||
GDVIRTUAL_BIND(_handles_global_class_type, "type");
|
||||
GDVIRTUAL_BIND(_get_global_class_name, "path");
|
||||
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_SCRIPT_LOCATION);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_CONSTANT);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_PROPERTY);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_METHOD);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_SIGNAL);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ENUM);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE); // Deprecated.
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_CLASS_ANNOTATION);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_LOCAL_CONSTANT);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_LOCAL_VARIABLE);
|
||||
BIND_ENUM_CONSTANT(LOOKUP_RESULT_MAX);
|
||||
|
||||
BIND_ENUM_CONSTANT(LOCATION_LOCAL);
|
||||
BIND_ENUM_CONSTANT(LOCATION_PARENT_MASK);
|
||||
BIND_ENUM_CONSTANT(LOCATION_OTHER_USER_CODE);
|
||||
BIND_ENUM_CONSTANT(LOCATION_OTHER);
|
||||
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_CLASS);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_FUNCTION);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_SIGNAL);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_VARIABLE);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_MEMBER);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_ENUM);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_CONSTANT);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_NODE_PATH);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_FILE_PATH);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_PLAIN_TEXT);
|
||||
BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_MAX);
|
||||
}
|
953
core/object/script_language_extension.h
Normal file
953
core/object/script_language_extension.h
Normal file
@@ -0,0 +1,953 @@
|
||||
/**************************************************************************/
|
||||
/* script_language_extension.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/extension/ext_wrappers.gen.inc"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/variant/native_ptr.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
class ScriptExtension : public Script {
|
||||
GDCLASS(ScriptExtension, Script)
|
||||
|
||||
protected:
|
||||
EXBIND0R(bool, editor_can_reload_from_file)
|
||||
|
||||
GDVIRTUAL1(_placeholder_erased, GDExtensionPtr<void>)
|
||||
virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override {
|
||||
GDVIRTUAL_CALL(_placeholder_erased, p_placeholder);
|
||||
}
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
EXBIND0RC(bool, can_instantiate)
|
||||
EXBIND0RC(Ref<Script>, get_base_script)
|
||||
EXBIND0RC(StringName, get_global_name)
|
||||
EXBIND1RC(bool, inherits_script, const Ref<Script> &)
|
||||
EXBIND0RC(StringName, get_instance_base_type)
|
||||
|
||||
GDVIRTUAL1RC_REQUIRED(GDExtensionPtr<void>, _instance_create, Object *)
|
||||
virtual ScriptInstance *instance_create(Object *p_this) override {
|
||||
GDExtensionPtr<void> ret = nullptr;
|
||||
GDVIRTUAL_CALL(_instance_create, p_this, ret);
|
||||
return reinterpret_cast<ScriptInstance *>(ret.operator void *());
|
||||
}
|
||||
GDVIRTUAL1RC_REQUIRED(GDExtensionPtr<void>, _placeholder_instance_create, Object *)
|
||||
PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override {
|
||||
GDExtensionPtr<void> ret = nullptr;
|
||||
GDVIRTUAL_CALL(_placeholder_instance_create, p_this, ret);
|
||||
return reinterpret_cast<PlaceHolderScriptInstance *>(ret.operator void *());
|
||||
}
|
||||
|
||||
EXBIND1RC(bool, instance_has, const Object *)
|
||||
EXBIND0RC(bool, has_source_code)
|
||||
EXBIND0RC(String, get_source_code)
|
||||
EXBIND1(set_source_code, const String &)
|
||||
EXBIND1R(Error, reload, bool)
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(StringName, _get_doc_class_name)
|
||||
GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_documentation)
|
||||
GDVIRTUAL0RC(String, _get_class_icon_path)
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual StringName get_doc_class_name() const override {
|
||||
StringName ret;
|
||||
GDVIRTUAL_CALL(_get_doc_class_name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual Vector<DocData::ClassDoc> get_documentation() const override {
|
||||
TypedArray<Dictionary> doc;
|
||||
GDVIRTUAL_CALL(_get_documentation, doc);
|
||||
|
||||
Vector<DocData::ClassDoc> class_doc;
|
||||
for (int i = 0; i < doc.size(); i++) {
|
||||
class_doc.append(DocData::ClassDoc::from_dict(doc[i]));
|
||||
}
|
||||
|
||||
return class_doc;
|
||||
}
|
||||
|
||||
virtual String get_class_icon_path() const override {
|
||||
String ret;
|
||||
GDVIRTUAL_CALL(_get_class_icon_path, ret);
|
||||
return ret;
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
EXBIND1RC(bool, has_method, const StringName &)
|
||||
EXBIND1RC(bool, has_static_method, const StringName &)
|
||||
|
||||
GDVIRTUAL1RC(Variant, _get_script_method_argument_count, const StringName &)
|
||||
virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
|
||||
Variant ret;
|
||||
if (GDVIRTUAL_CALL(_get_script_method_argument_count, p_method, ret) && ret.get_type() == Variant::INT) {
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = true;
|
||||
}
|
||||
return ret.operator int();
|
||||
}
|
||||
// Fallback to default.
|
||||
return Script::get_script_method_argument_count(p_method, r_is_valid);
|
||||
}
|
||||
|
||||
GDVIRTUAL1RC_REQUIRED(Dictionary, _get_method_info, const StringName &)
|
||||
virtual MethodInfo get_method_info(const StringName &p_method) const override {
|
||||
Dictionary mi;
|
||||
GDVIRTUAL_CALL(_get_method_info, p_method, mi);
|
||||
return MethodInfo::from_dict(mi);
|
||||
}
|
||||
|
||||
EXBIND0RC(bool, is_tool)
|
||||
EXBIND0RC(bool, is_valid)
|
||||
|
||||
virtual bool is_abstract() const override {
|
||||
bool abst;
|
||||
return GDVIRTUAL_CALL(_is_abstract, abst) && abst;
|
||||
}
|
||||
GDVIRTUAL0RC(bool, _is_abstract)
|
||||
|
||||
EXBIND0RC(ScriptLanguage *, get_language)
|
||||
EXBIND1RC(bool, has_script_signal, const StringName &)
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_script_signal_list)
|
||||
|
||||
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override {
|
||||
TypedArray<Dictionary> sl;
|
||||
GDVIRTUAL_CALL(_get_script_signal_list, sl);
|
||||
for (int i = 0; i < sl.size(); i++) {
|
||||
r_signals->push_back(MethodInfo::from_dict(sl[i]));
|
||||
}
|
||||
}
|
||||
|
||||
GDVIRTUAL1RC_REQUIRED(bool, _has_property_default_value, const StringName &)
|
||||
GDVIRTUAL1RC_REQUIRED(Variant, _get_property_default_value, const StringName &)
|
||||
|
||||
virtual bool get_property_default_value(const StringName &p_property, Variant &r_value) const override {
|
||||
bool has_dv = false;
|
||||
if (!GDVIRTUAL_CALL(_has_property_default_value, p_property, has_dv) || !has_dv) {
|
||||
return false;
|
||||
}
|
||||
Variant ret;
|
||||
GDVIRTUAL_CALL(_get_property_default_value, p_property, ret);
|
||||
r_value = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
EXBIND0(update_exports)
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_script_method_list)
|
||||
|
||||
virtual void get_script_method_list(List<MethodInfo> *r_methods) const override {
|
||||
TypedArray<Dictionary> sl;
|
||||
GDVIRTUAL_CALL(_get_script_method_list, sl);
|
||||
for (int i = 0; i < sl.size(); i++) {
|
||||
r_methods->push_back(MethodInfo::from_dict(sl[i]));
|
||||
}
|
||||
}
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_script_property_list)
|
||||
|
||||
virtual void get_script_property_list(List<PropertyInfo> *r_propertys) const override {
|
||||
TypedArray<Dictionary> sl;
|
||||
GDVIRTUAL_CALL(_get_script_property_list, sl);
|
||||
for (int i = 0; i < sl.size(); i++) {
|
||||
r_propertys->push_back(PropertyInfo::from_dict(sl[i]));
|
||||
}
|
||||
}
|
||||
|
||||
EXBIND1RC(int, get_member_line, const StringName &)
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(Dictionary, _get_constants)
|
||||
|
||||
virtual void get_constants(HashMap<StringName, Variant> *p_constants) override {
|
||||
Dictionary constants;
|
||||
GDVIRTUAL_CALL(_get_constants, constants);
|
||||
for (const KeyValue<Variant, Variant> &kv : constants) {
|
||||
p_constants->insert(kv.key, kv.value);
|
||||
}
|
||||
}
|
||||
GDVIRTUAL0RC_REQUIRED(TypedArray<StringName>, _get_members)
|
||||
virtual void get_members(HashSet<StringName> *p_members) override {
|
||||
TypedArray<StringName> members;
|
||||
GDVIRTUAL_CALL(_get_members, members);
|
||||
for (int i = 0; i < members.size(); i++) {
|
||||
p_members->insert(members[i]);
|
||||
}
|
||||
}
|
||||
|
||||
EXBIND0RC(bool, is_placeholder_fallback_enabled)
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(Variant, _get_rpc_config)
|
||||
|
||||
virtual const Variant get_rpc_config() const override {
|
||||
Variant ret;
|
||||
GDVIRTUAL_CALL(_get_rpc_config, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ScriptExtension() {}
|
||||
};
|
||||
|
||||
typedef ScriptLanguage::ProfilingInfo ScriptLanguageExtensionProfilingInfo;
|
||||
|
||||
GDVIRTUAL_NATIVE_PTR(ScriptLanguageExtensionProfilingInfo)
|
||||
|
||||
class ScriptLanguageExtension : public ScriptLanguage {
|
||||
GDCLASS(ScriptLanguageExtension, ScriptLanguage)
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
EXBIND0RC(String, get_name)
|
||||
|
||||
EXBIND0(init)
|
||||
EXBIND0RC(String, get_type)
|
||||
EXBIND0RC(String, get_extension)
|
||||
EXBIND0(finish)
|
||||
|
||||
/* EDITOR FUNCTIONS */
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(Vector<String>, _get_reserved_words)
|
||||
|
||||
virtual Vector<String> get_reserved_words() const override {
|
||||
Vector<String> ret;
|
||||
GDVIRTUAL_CALL(_get_reserved_words, ret);
|
||||
return ret;
|
||||
}
|
||||
EXBIND1RC(bool, is_control_flow_keyword, const String &)
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(Vector<String>, _get_comment_delimiters)
|
||||
|
||||
virtual Vector<String> get_comment_delimiters() const override {
|
||||
Vector<String> ret;
|
||||
GDVIRTUAL_CALL(_get_comment_delimiters, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GDVIRTUAL0RC(Vector<String>, _get_doc_comment_delimiters)
|
||||
|
||||
virtual Vector<String> get_doc_comment_delimiters() const override {
|
||||
Vector<String> ret;
|
||||
GDVIRTUAL_CALL(_get_doc_comment_delimiters, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(Vector<String>, _get_string_delimiters)
|
||||
|
||||
virtual Vector<String> get_string_delimiters() const override {
|
||||
Vector<String> ret;
|
||||
GDVIRTUAL_CALL(_get_string_delimiters, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXBIND3RC(Ref<Script>, make_template, const String &, const String &, const String &)
|
||||
|
||||
GDVIRTUAL1RC_REQUIRED(TypedArray<Dictionary>, _get_built_in_templates, StringName)
|
||||
|
||||
virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override {
|
||||
TypedArray<Dictionary> ret;
|
||||
GDVIRTUAL_CALL(_get_built_in_templates, p_object, ret);
|
||||
Vector<ScriptTemplate> stret;
|
||||
for (int i = 0; i < ret.size(); i++) {
|
||||
Dictionary d = ret[i];
|
||||
ScriptTemplate st;
|
||||
ERR_CONTINUE(!d.has("inherit"));
|
||||
st.inherit = d["inherit"];
|
||||
ERR_CONTINUE(!d.has("name"));
|
||||
st.name = d["name"];
|
||||
ERR_CONTINUE(!d.has("description"));
|
||||
st.description = d["description"];
|
||||
ERR_CONTINUE(!d.has("content"));
|
||||
st.content = d["content"];
|
||||
ERR_CONTINUE(!d.has("id"));
|
||||
st.id = d["id"];
|
||||
ERR_CONTINUE(!d.has("origin"));
|
||||
st.origin = TemplateLocation(int(d["origin"]));
|
||||
stret.push_back(st);
|
||||
}
|
||||
return stret;
|
||||
}
|
||||
|
||||
EXBIND0R(bool, is_using_templates)
|
||||
|
||||
GDVIRTUAL6RC_REQUIRED(Dictionary, _validate, const String &, const String &, bool, bool, bool, bool)
|
||||
virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptError> *r_errors = nullptr, List<Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_validate, p_script, p_path, r_functions != nullptr, r_errors != nullptr, r_warnings != nullptr, r_safe_lines != nullptr, ret);
|
||||
if (!ret.has("valid")) {
|
||||
return false;
|
||||
}
|
||||
if (r_functions != nullptr && ret.has("functions")) {
|
||||
Vector<String> functions = ret["functions"];
|
||||
for (int i = 0; i < functions.size(); i++) {
|
||||
r_functions->push_back(functions[i]);
|
||||
}
|
||||
}
|
||||
if (r_errors != nullptr && ret.has("errors")) {
|
||||
Array errors = ret["errors"];
|
||||
for (const Variant &error : errors) {
|
||||
Dictionary err = error;
|
||||
ERR_CONTINUE(!err.has("line"));
|
||||
ERR_CONTINUE(!err.has("column"));
|
||||
ERR_CONTINUE(!err.has("message"));
|
||||
|
||||
ScriptError serr;
|
||||
if (err.has("path")) {
|
||||
serr.path = err["path"];
|
||||
}
|
||||
serr.line = err["line"];
|
||||
serr.column = err["column"];
|
||||
serr.message = err["message"];
|
||||
|
||||
r_errors->push_back(serr);
|
||||
}
|
||||
}
|
||||
if (r_warnings != nullptr && ret.has("warnings")) {
|
||||
ERR_FAIL_COND_V(!ret.has("warnings"), false);
|
||||
Array warnings = ret["warnings"];
|
||||
for (const Variant &warning : warnings) {
|
||||
Dictionary warn = warning;
|
||||
ERR_CONTINUE(!warn.has("start_line"));
|
||||
ERR_CONTINUE(!warn.has("end_line"));
|
||||
ERR_CONTINUE(!warn.has("code"));
|
||||
ERR_CONTINUE(!warn.has("string_code"));
|
||||
ERR_CONTINUE(!warn.has("message"));
|
||||
|
||||
Warning swarn;
|
||||
swarn.start_line = warn["start_line"];
|
||||
swarn.end_line = warn["end_line"];
|
||||
swarn.code = warn["code"];
|
||||
swarn.string_code = warn["string_code"];
|
||||
swarn.message = warn["message"];
|
||||
|
||||
r_warnings->push_back(swarn);
|
||||
}
|
||||
}
|
||||
if (r_safe_lines != nullptr && ret.has("safe_lines")) {
|
||||
PackedInt32Array safe_lines = ret["safe_lines"];
|
||||
for (int i = 0; i < safe_lines.size(); i++) {
|
||||
r_safe_lines->insert(safe_lines[i]);
|
||||
}
|
||||
}
|
||||
return ret["valid"];
|
||||
}
|
||||
|
||||
EXBIND1RC(String, validate_path, const String &)
|
||||
GDVIRTUAL0RC_REQUIRED(Object *, _create_script)
|
||||
Script *create_script() const override {
|
||||
Object *ret = nullptr;
|
||||
GDVIRTUAL_CALL(_create_script, ret);
|
||||
return Object::cast_to<Script>(ret);
|
||||
}
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
EXBIND0RC(bool, has_named_classes)
|
||||
#endif
|
||||
EXBIND0RC(bool, supports_builtin_mode)
|
||||
EXBIND0RC(bool, supports_documentation)
|
||||
EXBIND0RC(bool, can_inherit_from_file)
|
||||
|
||||
EXBIND2RC(int, find_function, const String &, const String &)
|
||||
EXBIND3RC(String, make_function, const String &, const String &, const PackedStringArray &)
|
||||
EXBIND0RC(bool, can_make_function)
|
||||
EXBIND3R(Error, open_in_external_editor, const Ref<Script> &, int, int)
|
||||
EXBIND0R(bool, overrides_external_editor)
|
||||
|
||||
GDVIRTUAL0RC(ScriptNameCasing, _preferred_file_name_casing);
|
||||
|
||||
virtual ScriptNameCasing preferred_file_name_casing() const override {
|
||||
ScriptNameCasing ret;
|
||||
if (GDVIRTUAL_CALL(_preferred_file_name_casing, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return ScriptNameCasing::SCRIPT_NAME_CASING_SNAKE_CASE;
|
||||
}
|
||||
|
||||
GDVIRTUAL3RC_REQUIRED(Dictionary, _complete_code, const String &, const String &, Object *)
|
||||
|
||||
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_complete_code, p_code, p_path, p_owner, ret);
|
||||
if (!ret.has("result")) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (r_options != nullptr && ret.has("options")) {
|
||||
Array options = ret["options"];
|
||||
for (const Variant &var : options) {
|
||||
Dictionary op = var;
|
||||
CodeCompletionOption option;
|
||||
ERR_CONTINUE(!op.has("kind"));
|
||||
option.kind = CodeCompletionKind(int(op["kind"]));
|
||||
ERR_CONTINUE(!op.has("display"));
|
||||
option.display = op["display"];
|
||||
ERR_CONTINUE(!op.has("insert_text"));
|
||||
option.insert_text = op["insert_text"];
|
||||
ERR_CONTINUE(!op.has("font_color"));
|
||||
option.font_color = op["font_color"];
|
||||
ERR_CONTINUE(!op.has("icon"));
|
||||
option.icon = op["icon"];
|
||||
ERR_CONTINUE(!op.has("default_value"));
|
||||
option.default_value = op["default_value"];
|
||||
ERR_CONTINUE(!op.has("location"));
|
||||
option.location = op["location"];
|
||||
if (op.has("matches")) {
|
||||
PackedInt32Array matches = op["matches"];
|
||||
ERR_CONTINUE(matches.size() & 1);
|
||||
for (int j = 0; j < matches.size(); j += 2) {
|
||||
option.matches.push_back(Pair<int, int>(matches[j], matches[j + 1]));
|
||||
}
|
||||
}
|
||||
r_options->push_back(option);
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(!ret.has("force"), ERR_UNAVAILABLE);
|
||||
r_force = ret["force"];
|
||||
ERR_FAIL_COND_V(!ret.has("call_hint"), ERR_UNAVAILABLE);
|
||||
r_call_hint = ret["call_hint"];
|
||||
ERR_FAIL_COND_V(!ret.has("result"), ERR_UNAVAILABLE);
|
||||
Error result = Error(int(ret["result"]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GDVIRTUAL4RC_REQUIRED(Dictionary, _lookup_code, const String &, const String &, const String &, Object *)
|
||||
|
||||
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_lookup_code, p_code, p_symbol, p_path, p_owner, ret);
|
||||
|
||||
ERR_FAIL_COND_V(!ret.has("result"), ERR_UNAVAILABLE);
|
||||
const Error result = Error(int(ret["result"]));
|
||||
|
||||
ERR_FAIL_COND_V(!ret.has("type"), ERR_UNAVAILABLE);
|
||||
r_result.type = LookupResultType(int(ret["type"]));
|
||||
|
||||
r_result.class_name = ret.get("class_name", "");
|
||||
r_result.class_member = ret.get("class_member", "");
|
||||
|
||||
r_result.description = ret.get("description", "");
|
||||
r_result.is_deprecated = ret.get("is_deprecated", false);
|
||||
r_result.deprecated_message = ret.get("deprecated_message", "");
|
||||
r_result.is_experimental = ret.get("is_experimental", false);
|
||||
r_result.experimental_message = ret.get("experimental_message", "");
|
||||
|
||||
r_result.doc_type = ret.get("doc_type", "");
|
||||
r_result.enumeration = ret.get("enumeration", "");
|
||||
r_result.is_bitfield = ret.get("is_bitfield", false);
|
||||
|
||||
r_result.value = ret.get("value", "");
|
||||
|
||||
r_result.script = ret.get("script", Ref<Script>());
|
||||
r_result.script_path = ret.get("script_path", "");
|
||||
r_result.location = ret.get("location", -1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GDVIRTUAL3RC_REQUIRED(String, _auto_indent_code, const String &, int, int)
|
||||
virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {
|
||||
String ret;
|
||||
GDVIRTUAL_CALL(_auto_indent_code, p_code, p_from_line, p_to_line, ret);
|
||||
p_code = ret;
|
||||
}
|
||||
EXBIND2(add_global_constant, const StringName &, const Variant &)
|
||||
EXBIND2(add_named_global_constant, const StringName &, const Variant &)
|
||||
EXBIND1(remove_named_global_constant, const StringName &)
|
||||
|
||||
/* MULTITHREAD FUNCTIONS */
|
||||
|
||||
//some VMs need to be notified of thread creation/exiting to allocate a stack
|
||||
EXBIND0(thread_enter)
|
||||
EXBIND0(thread_exit)
|
||||
|
||||
EXBIND0RC(String, debug_get_error)
|
||||
EXBIND0RC(int, debug_get_stack_level_count)
|
||||
EXBIND1RC(int, debug_get_stack_level_line, int)
|
||||
EXBIND1RC(String, debug_get_stack_level_function, int)
|
||||
EXBIND1RC(String, debug_get_stack_level_source, int)
|
||||
|
||||
GDVIRTUAL3R_REQUIRED(Dictionary, _debug_get_stack_level_locals, int, int, int)
|
||||
virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_debug_get_stack_level_locals, p_level, p_max_subitems, p_max_depth, ret);
|
||||
if (ret.is_empty()) {
|
||||
return;
|
||||
}
|
||||
if (p_locals != nullptr && ret.has("locals")) {
|
||||
PackedStringArray strings = ret["locals"];
|
||||
for (int i = 0; i < strings.size(); i++) {
|
||||
p_locals->push_back(strings[i]);
|
||||
}
|
||||
}
|
||||
if (p_values != nullptr && ret.has("values")) {
|
||||
Array values = ret["values"];
|
||||
for (const Variant &value : values) {
|
||||
p_values->push_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
GDVIRTUAL3R_REQUIRED(Dictionary, _debug_get_stack_level_members, int, int, int)
|
||||
virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_debug_get_stack_level_members, p_level, p_max_subitems, p_max_depth, ret);
|
||||
if (ret.is_empty()) {
|
||||
return;
|
||||
}
|
||||
if (p_members != nullptr && ret.has("members")) {
|
||||
PackedStringArray strings = ret["members"];
|
||||
for (int i = 0; i < strings.size(); i++) {
|
||||
p_members->push_back(strings[i]);
|
||||
}
|
||||
}
|
||||
if (p_values != nullptr && ret.has("values")) {
|
||||
Array values = ret["values"];
|
||||
for (const Variant &value : values) {
|
||||
p_values->push_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
GDVIRTUAL1R_REQUIRED(GDExtensionPtr<void>, _debug_get_stack_level_instance, int)
|
||||
|
||||
virtual ScriptInstance *debug_get_stack_level_instance(int p_level) override {
|
||||
GDExtensionPtr<void> ret = nullptr;
|
||||
GDVIRTUAL_CALL(_debug_get_stack_level_instance, p_level, ret);
|
||||
return reinterpret_cast<ScriptInstance *>(ret.operator void *());
|
||||
}
|
||||
GDVIRTUAL2R_REQUIRED(Dictionary, _debug_get_globals, int, int)
|
||||
virtual void debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_debug_get_globals, p_max_subitems, p_max_depth, ret);
|
||||
if (ret.is_empty()) {
|
||||
return;
|
||||
}
|
||||
if (p_globals != nullptr && ret.has("globals")) {
|
||||
PackedStringArray strings = ret["globals"];
|
||||
for (int i = 0; i < strings.size(); i++) {
|
||||
p_globals->push_back(strings[i]);
|
||||
}
|
||||
}
|
||||
if (p_values != nullptr && ret.has("values")) {
|
||||
Array values = ret["values"];
|
||||
for (const Variant &value : values) {
|
||||
p_values->push_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EXBIND4R(String, debug_parse_stack_level_expression, int, const String &, int, int)
|
||||
|
||||
GDVIRTUAL0R_REQUIRED(TypedArray<Dictionary>, _debug_get_current_stack_info)
|
||||
virtual Vector<StackInfo> debug_get_current_stack_info() override {
|
||||
TypedArray<Dictionary> ret;
|
||||
GDVIRTUAL_CALL(_debug_get_current_stack_info, ret);
|
||||
Vector<StackInfo> sret;
|
||||
for (const Variant &var : ret) {
|
||||
StackInfo si;
|
||||
Dictionary d = var;
|
||||
ERR_CONTINUE(!d.has("file"));
|
||||
ERR_CONTINUE(!d.has("func"));
|
||||
ERR_CONTINUE(!d.has("line"));
|
||||
si.file = d["file"];
|
||||
si.func = d["func"];
|
||||
si.line = d["line"];
|
||||
sret.push_back(si);
|
||||
}
|
||||
return sret;
|
||||
}
|
||||
|
||||
EXBIND0(reload_all_scripts)
|
||||
EXBIND2(reload_scripts, const Array &, bool)
|
||||
EXBIND2(reload_tool_script, const Ref<Script> &, bool)
|
||||
/* LOADER FUNCTIONS */
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(PackedStringArray, _get_recognized_extensions)
|
||||
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override {
|
||||
PackedStringArray ret;
|
||||
GDVIRTUAL_CALL(_get_recognized_extensions, ret);
|
||||
for (int i = 0; i < ret.size(); i++) {
|
||||
p_extensions->push_back(ret[i]);
|
||||
}
|
||||
}
|
||||
|
||||
GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_public_functions)
|
||||
virtual void get_public_functions(List<MethodInfo> *p_functions) const override {
|
||||
TypedArray<Dictionary> ret;
|
||||
GDVIRTUAL_CALL(_get_public_functions, ret);
|
||||
for (const Variant &var : ret) {
|
||||
MethodInfo mi = MethodInfo::from_dict(var);
|
||||
p_functions->push_back(mi);
|
||||
}
|
||||
}
|
||||
GDVIRTUAL0RC_REQUIRED(Dictionary, _get_public_constants)
|
||||
virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_get_public_constants, ret);
|
||||
for (int i = 0; i < ret.size(); i++) {
|
||||
Dictionary d = ret[i];
|
||||
ERR_CONTINUE(!d.has("name"));
|
||||
ERR_CONTINUE(!d.has("value"));
|
||||
p_constants->push_back(Pair<String, Variant>(d["name"], d["value"]));
|
||||
}
|
||||
}
|
||||
GDVIRTUAL0RC_REQUIRED(TypedArray<Dictionary>, _get_public_annotations)
|
||||
virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override {
|
||||
TypedArray<Dictionary> ret;
|
||||
GDVIRTUAL_CALL(_get_public_annotations, ret);
|
||||
for (const Variant &var : ret) {
|
||||
MethodInfo mi = MethodInfo::from_dict(var);
|
||||
p_annotations->push_back(mi);
|
||||
}
|
||||
}
|
||||
|
||||
EXBIND0(profiling_start)
|
||||
EXBIND0(profiling_stop)
|
||||
EXBIND1(profiling_set_save_native_calls, bool)
|
||||
|
||||
GDVIRTUAL2R_REQUIRED(int, _profiling_get_accumulated_data, GDExtensionPtr<ScriptLanguageExtensionProfilingInfo>, int)
|
||||
|
||||
virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override {
|
||||
int ret = 0;
|
||||
GDVIRTUAL_CALL(_profiling_get_accumulated_data, p_info_arr, p_info_max, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GDVIRTUAL2R_REQUIRED(int, _profiling_get_frame_data, GDExtensionPtr<ScriptLanguageExtensionProfilingInfo>, int)
|
||||
|
||||
virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override {
|
||||
int ret = 0;
|
||||
GDVIRTUAL_CALL(_profiling_get_frame_data, p_info_arr, p_info_max, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXBIND0(frame)
|
||||
|
||||
EXBIND1RC(bool, handles_global_class_type, const String &)
|
||||
|
||||
GDVIRTUAL1RC_REQUIRED(Dictionary, _get_global_class_name, const String &)
|
||||
|
||||
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const override {
|
||||
Dictionary ret;
|
||||
GDVIRTUAL_CALL(_get_global_class_name, p_path, ret);
|
||||
if (!ret.has("name")) {
|
||||
return String();
|
||||
}
|
||||
if (r_base_type != nullptr && ret.has("base_type")) {
|
||||
*r_base_type = ret["base_type"];
|
||||
}
|
||||
if (r_icon_path != nullptr && ret.has("icon_path")) {
|
||||
*r_icon_path = ret["icon_path"];
|
||||
}
|
||||
if (r_is_abstract != nullptr && ret.has("is_abstract")) {
|
||||
*r_is_abstract = ret["is_abstract"];
|
||||
}
|
||||
if (r_is_tool != nullptr && ret.has("is_tool")) {
|
||||
*r_is_tool = ret["is_tool"];
|
||||
}
|
||||
return ret["name"];
|
||||
}
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(ScriptLanguageExtension::LookupResultType)
|
||||
VARIANT_ENUM_CAST(ScriptLanguageExtension::CodeCompletionKind)
|
||||
VARIANT_ENUM_CAST(ScriptLanguageExtension::CodeCompletionLocation)
|
||||
|
||||
class ScriptInstanceExtension : public ScriptInstance {
|
||||
public:
|
||||
const GDExtensionScriptInstanceInfo3 *native_info;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool free_native_info = false;
|
||||
struct DeprecatedNativeInfo {
|
||||
GDExtensionScriptInstanceNotification notification_func = nullptr;
|
||||
GDExtensionScriptInstanceFreePropertyList free_property_list_func = nullptr;
|
||||
GDExtensionScriptInstanceFreeMethodList free_method_list_func = nullptr;
|
||||
};
|
||||
DeprecatedNativeInfo *deprecated_native_info = nullptr;
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
GDExtensionScriptInstanceDataPtr instance = nullptr;
|
||||
|
||||
GODOT_GCC_WARNING_PUSH_AND_IGNORE("-Wignored-qualifiers") // There should not be warnings on explicit casts.
|
||||
|
||||
virtual bool set(const StringName &p_name, const Variant &p_value) override {
|
||||
if (native_info->set_func) {
|
||||
return native_info->set_func(instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionConstVariantPtr)&p_value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual bool get(const StringName &p_name, Variant &r_ret) const override {
|
||||
if (native_info->get_func) {
|
||||
return native_info->get_func(instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionVariantPtr)&r_ret);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual void get_property_list(List<PropertyInfo> *p_list) const override {
|
||||
if (native_info->get_property_list_func) {
|
||||
uint32_t pcount;
|
||||
const GDExtensionPropertyInfo *pinfo = native_info->get_property_list_func(instance, &pcount);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (pcount > 0) {
|
||||
if (native_info->get_class_category_func) {
|
||||
GDExtensionPropertyInfo gdext_class_category;
|
||||
if (native_info->get_class_category_func(instance, &gdext_class_category)) {
|
||||
p_list->push_back(PropertyInfo(gdext_class_category));
|
||||
}
|
||||
} else {
|
||||
Ref<Script> script = get_script();
|
||||
if (script.is_valid()) {
|
||||
p_list->push_back(script->get_class_category());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
for (uint32_t i = 0; i < pcount; i++) {
|
||||
p_list->push_back(PropertyInfo(pinfo[i]));
|
||||
}
|
||||
if (native_info->free_property_list_func) {
|
||||
native_info->free_property_list_func(instance, pinfo, pcount);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (deprecated_native_info && deprecated_native_info->free_property_list_func) {
|
||||
deprecated_native_info->free_property_list_func(instance, pinfo);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const override {
|
||||
if (native_info->get_property_type_func) {
|
||||
GDExtensionBool is_valid = 0;
|
||||
GDExtensionVariantType type = native_info->get_property_type_func(instance, (GDExtensionConstStringNamePtr)&p_name, &is_valid);
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = is_valid != 0;
|
||||
}
|
||||
return Variant::Type(type);
|
||||
}
|
||||
return Variant::NIL;
|
||||
}
|
||||
virtual void validate_property(PropertyInfo &p_property) const override {
|
||||
if (native_info->validate_property_func) {
|
||||
// GDExtension uses a StringName rather than a String for property name.
|
||||
StringName prop_name = p_property.name;
|
||||
GDExtensionPropertyInfo gdext_prop = {
|
||||
(GDExtensionVariantType)p_property.type,
|
||||
&prop_name,
|
||||
&p_property.class_name,
|
||||
(uint32_t)p_property.hint,
|
||||
&p_property.hint_string,
|
||||
p_property.usage,
|
||||
};
|
||||
if (native_info->validate_property_func(instance, &gdext_prop)) {
|
||||
p_property.type = (Variant::Type)gdext_prop.type;
|
||||
p_property.name = *reinterpret_cast<StringName *>(gdext_prop.name);
|
||||
p_property.class_name = *reinterpret_cast<StringName *>(gdext_prop.class_name);
|
||||
p_property.hint = (PropertyHint)gdext_prop.hint;
|
||||
p_property.hint_string = *reinterpret_cast<String *>(gdext_prop.hint_string);
|
||||
p_property.usage = gdext_prop.usage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool property_can_revert(const StringName &p_name) const override {
|
||||
if (native_info->property_can_revert_func) {
|
||||
return native_info->property_can_revert_func(instance, (GDExtensionConstStringNamePtr)&p_name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override {
|
||||
if (native_info->property_get_revert_func) {
|
||||
return native_info->property_get_revert_func(instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionVariantPtr)&r_ret);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual Object *get_owner() override {
|
||||
if (native_info->get_owner_func) {
|
||||
return (Object *)native_info->get_owner_func(instance);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static void _add_property_with_state(GDExtensionConstStringNamePtr p_name, GDExtensionConstVariantPtr p_value, void *p_userdata) {
|
||||
List<Pair<StringName, Variant>> *state = (List<Pair<StringName, Variant>> *)p_userdata;
|
||||
state->push_back(Pair<StringName, Variant>(*(const StringName *)p_name, *(const Variant *)p_value));
|
||||
}
|
||||
virtual void get_property_state(List<Pair<StringName, Variant>> &state) override {
|
||||
if (native_info->get_property_state_func) {
|
||||
native_info->get_property_state_func(instance, _add_property_with_state, &state);
|
||||
return;
|
||||
}
|
||||
ScriptInstance::get_property_state(state);
|
||||
}
|
||||
|
||||
virtual void get_method_list(List<MethodInfo> *p_list) const override {
|
||||
if (native_info->get_method_list_func) {
|
||||
uint32_t mcount;
|
||||
const GDExtensionMethodInfo *minfo = native_info->get_method_list_func(instance, &mcount);
|
||||
for (uint32_t i = 0; i < mcount; i++) {
|
||||
p_list->push_back(MethodInfo(minfo[i]));
|
||||
}
|
||||
if (native_info->free_method_list_func) {
|
||||
native_info->free_method_list_func(instance, minfo, mcount);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (deprecated_native_info && deprecated_native_info->free_method_list_func) {
|
||||
deprecated_native_info->free_method_list_func(instance, minfo);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual bool has_method(const StringName &p_method) const override {
|
||||
if (native_info->has_method_func) {
|
||||
return native_info->has_method_func(instance, (GDExtensionStringNamePtr)&p_method);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override {
|
||||
if (native_info->get_method_argument_count_func) {
|
||||
GDExtensionBool is_valid = 0;
|
||||
GDExtensionInt ret = native_info->get_method_argument_count_func(instance, (GDExtensionStringNamePtr)&p_method, &is_valid);
|
||||
if (r_is_valid) {
|
||||
*r_is_valid = is_valid != 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// Fallback to default.
|
||||
return ScriptInstance::get_method_argument_count(p_method, r_is_valid);
|
||||
}
|
||||
|
||||
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
|
||||
Variant ret;
|
||||
if (native_info->call_func) {
|
||||
GDExtensionCallError ce;
|
||||
native_info->call_func(instance, (GDExtensionConstStringNamePtr)&p_method, (GDExtensionConstVariantPtr *)p_args, p_argcount, (GDExtensionVariantPtr)&ret, &ce);
|
||||
r_error.error = Callable::CallError::Error(ce.error);
|
||||
r_error.argument = ce.argument;
|
||||
r_error.expected = ce.expected;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual void notification(int p_notification, bool p_reversed = false) override {
|
||||
if (native_info->notification_func) {
|
||||
native_info->notification_func(instance, p_notification, p_reversed);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (deprecated_native_info && deprecated_native_info->notification_func) {
|
||||
deprecated_native_info->notification_func(instance, p_notification);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
}
|
||||
}
|
||||
|
||||
virtual String to_string(bool *r_valid) override {
|
||||
if (native_info->to_string_func) {
|
||||
GDExtensionBool valid;
|
||||
String ret;
|
||||
native_info->to_string_func(instance, &valid, reinterpret_cast<GDExtensionStringPtr>(&ret));
|
||||
if (r_valid) {
|
||||
*r_valid = valid != 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
virtual void refcount_incremented() override {
|
||||
if (native_info->refcount_incremented_func) {
|
||||
native_info->refcount_incremented_func(instance);
|
||||
}
|
||||
}
|
||||
virtual bool refcount_decremented() override {
|
||||
if (native_info->refcount_decremented_func) {
|
||||
return native_info->refcount_decremented_func(instance);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual Ref<Script> get_script() const override {
|
||||
if (native_info->get_script_func) {
|
||||
GDExtensionObjectPtr script = native_info->get_script_func(instance);
|
||||
return Ref<Script>(reinterpret_cast<Script *>(script));
|
||||
}
|
||||
return Ref<Script>();
|
||||
}
|
||||
|
||||
virtual bool is_placeholder() const override {
|
||||
if (native_info->is_placeholder_func) {
|
||||
return native_info->is_placeholder_func(instance);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid) override {
|
||||
if (native_info->set_fallback_func) {
|
||||
bool ret = native_info->set_fallback_func(instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionConstVariantPtr)&p_value);
|
||||
if (r_valid) {
|
||||
*r_valid = ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid) override {
|
||||
Variant ret;
|
||||
if (native_info->get_fallback_func) {
|
||||
bool valid = native_info->get_fallback_func(instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret);
|
||||
if (r_valid) {
|
||||
*r_valid = valid;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual ScriptLanguage *get_language() override {
|
||||
if (native_info->get_language_func) {
|
||||
GDExtensionScriptLanguagePtr lang = native_info->get_language_func(instance);
|
||||
return reinterpret_cast<ScriptLanguage *>(lang);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
virtual ~ScriptInstanceExtension() {
|
||||
if (native_info->free_func) {
|
||||
native_info->free_func(instance);
|
||||
}
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
if (free_native_info) {
|
||||
memfree(const_cast<GDExtensionScriptInstanceInfo3 *>(native_info));
|
||||
}
|
||||
if (deprecated_native_info) {
|
||||
memfree(deprecated_native_info);
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
}
|
||||
|
||||
GODOT_GCC_WARNING_POP
|
||||
};
|
562
core/object/undo_redo.cpp
Normal file
562
core/object/undo_redo.cpp
Normal file
@@ -0,0 +1,562 @@
|
||||
/**************************************************************************/
|
||||
/* undo_redo.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "undo_redo.h"
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
void UndoRedo::Operation::delete_reference() {
|
||||
if (type != Operation::TYPE_REFERENCE) {
|
||||
return;
|
||||
}
|
||||
if (ref.is_valid()) {
|
||||
ref.unref();
|
||||
} else {
|
||||
Object *obj = ObjectDB::get_instance(object);
|
||||
if (obj) {
|
||||
memdelete(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UndoRedo::discard_redo() {
|
||||
if (current_action == actions.size() - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = current_action + 1; i < actions.size(); i++) {
|
||||
for (Operation &E : actions.write[i].do_ops) {
|
||||
E.delete_reference();
|
||||
}
|
||||
//ERASE do data
|
||||
}
|
||||
|
||||
actions.resize(current_action + 1);
|
||||
}
|
||||
|
||||
bool UndoRedo::_redo(bool p_execute) {
|
||||
ERR_FAIL_COND_V(action_level > 0, false);
|
||||
|
||||
if ((current_action + 1) >= actions.size()) {
|
||||
return false; //nothing to redo
|
||||
}
|
||||
|
||||
current_action++;
|
||||
|
||||
List<Operation>::Element *start_doops_element = actions.write[current_action].do_ops.front();
|
||||
while (merge_total > 0 && start_doops_element) {
|
||||
start_doops_element = start_doops_element->next();
|
||||
merge_total--;
|
||||
}
|
||||
|
||||
_process_operation_list(start_doops_element, p_execute);
|
||||
version++;
|
||||
emit_signal(SNAME("version_changed"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UndoRedo::create_action(const String &p_name, MergeMode p_mode, bool p_backward_undo_ops) {
|
||||
uint64_t ticks = OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
if (action_level == 0) {
|
||||
discard_redo();
|
||||
|
||||
// Check if the merge operation is valid
|
||||
if (p_mode != MERGE_DISABLE && actions.size() && actions[actions.size() - 1].name == p_name && actions[actions.size() - 1].backward_undo_ops == p_backward_undo_ops && actions[actions.size() - 1].last_tick + 800 > ticks) {
|
||||
current_action = actions.size() - 2;
|
||||
|
||||
if (p_mode == MERGE_ENDS) {
|
||||
// Clear all do ops from last action if they are not forced kept
|
||||
LocalVector<List<Operation>::Element *> to_remove;
|
||||
for (List<Operation>::Element *E = actions.write[current_action + 1].do_ops.front(); E; E = E->next()) {
|
||||
if (!E->get().force_keep_in_merge_ends) {
|
||||
to_remove.push_back(E);
|
||||
}
|
||||
}
|
||||
|
||||
for (List<Operation>::Element *E : to_remove) {
|
||||
// Delete all object references
|
||||
E->get().delete_reference();
|
||||
E->erase();
|
||||
}
|
||||
}
|
||||
|
||||
if (p_mode == MERGE_ALL) {
|
||||
merge_total = actions.write[current_action + 1].do_ops.size();
|
||||
} else {
|
||||
merge_total = 0;
|
||||
}
|
||||
|
||||
actions.write[actions.size() - 1].last_tick = ticks;
|
||||
|
||||
// Revert reverse from previous commit.
|
||||
if (actions[actions.size() - 1].backward_undo_ops) {
|
||||
actions.write[actions.size() - 1].undo_ops.reverse();
|
||||
}
|
||||
|
||||
merge_mode = p_mode;
|
||||
merging = true;
|
||||
} else {
|
||||
Action new_action;
|
||||
new_action.name = p_name;
|
||||
new_action.last_tick = ticks;
|
||||
new_action.backward_undo_ops = p_backward_undo_ops;
|
||||
actions.push_back(new_action);
|
||||
|
||||
merge_mode = MERGE_DISABLE;
|
||||
merge_total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
action_level++;
|
||||
|
||||
force_keep_in_merge_ends = false;
|
||||
}
|
||||
|
||||
void UndoRedo::add_do_method(const Callable &p_callable) {
|
||||
ERR_FAIL_COND(!p_callable.is_valid());
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
|
||||
ObjectID object_id = p_callable.get_object_id();
|
||||
Object *object = ObjectDB::get_instance(object_id);
|
||||
ERR_FAIL_COND(object_id.is_valid() && object == nullptr);
|
||||
|
||||
Operation do_op;
|
||||
do_op.callable = p_callable;
|
||||
do_op.object = object_id;
|
||||
if (Object::cast_to<RefCounted>(object)) {
|
||||
do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
|
||||
}
|
||||
do_op.type = Operation::TYPE_METHOD;
|
||||
do_op.name = p_callable.get_method();
|
||||
if (do_op.name == StringName()) {
|
||||
// There's no `get_method()` for custom callables, so use `operator String()` instead.
|
||||
do_op.name = static_cast<String>(p_callable);
|
||||
}
|
||||
|
||||
actions.write[current_action + 1].do_ops.push_back(do_op);
|
||||
}
|
||||
|
||||
void UndoRedo::add_undo_method(const Callable &p_callable) {
|
||||
ERR_FAIL_COND(!p_callable.is_valid());
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
|
||||
// No undo if the merge mode is MERGE_ENDS
|
||||
if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectID object_id = p_callable.get_object_id();
|
||||
Object *object = ObjectDB::get_instance(object_id);
|
||||
ERR_FAIL_COND(object_id.is_valid() && object == nullptr);
|
||||
|
||||
Operation undo_op;
|
||||
undo_op.callable = p_callable;
|
||||
undo_op.object = object_id;
|
||||
if (Object::cast_to<RefCounted>(object)) {
|
||||
undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
|
||||
}
|
||||
undo_op.type = Operation::TYPE_METHOD;
|
||||
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
|
||||
undo_op.name = p_callable.get_method();
|
||||
if (undo_op.name == StringName()) {
|
||||
// There's no `get_method()` for custom callables, so use `operator String()` instead.
|
||||
undo_op.name = static_cast<String>(p_callable);
|
||||
}
|
||||
|
||||
actions.write[current_action + 1].undo_ops.push_back(undo_op);
|
||||
}
|
||||
|
||||
void UndoRedo::add_do_property(Object *p_object, const StringName &p_property, const Variant &p_value) {
|
||||
ERR_FAIL_NULL(p_object);
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
Operation do_op;
|
||||
do_op.object = p_object->get_instance_id();
|
||||
if (Object::cast_to<RefCounted>(p_object)) {
|
||||
do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
|
||||
}
|
||||
|
||||
do_op.type = Operation::TYPE_PROPERTY;
|
||||
do_op.name = p_property;
|
||||
do_op.value = p_value;
|
||||
actions.write[current_action + 1].do_ops.push_back(do_op);
|
||||
}
|
||||
|
||||
void UndoRedo::add_undo_property(Object *p_object, const StringName &p_property, const Variant &p_value) {
|
||||
ERR_FAIL_NULL(p_object);
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
|
||||
// No undo if the merge mode is MERGE_ENDS
|
||||
if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
Operation undo_op;
|
||||
undo_op.object = p_object->get_instance_id();
|
||||
if (Object::cast_to<RefCounted>(p_object)) {
|
||||
undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
|
||||
}
|
||||
|
||||
undo_op.type = Operation::TYPE_PROPERTY;
|
||||
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
|
||||
undo_op.name = p_property;
|
||||
undo_op.value = p_value;
|
||||
actions.write[current_action + 1].undo_ops.push_back(undo_op);
|
||||
}
|
||||
|
||||
void UndoRedo::add_do_reference(Object *p_object) {
|
||||
ERR_FAIL_NULL(p_object);
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
Operation do_op;
|
||||
do_op.object = p_object->get_instance_id();
|
||||
if (Object::cast_to<RefCounted>(p_object)) {
|
||||
do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
|
||||
}
|
||||
|
||||
do_op.type = Operation::TYPE_REFERENCE;
|
||||
actions.write[current_action + 1].do_ops.push_back(do_op);
|
||||
}
|
||||
|
||||
void UndoRedo::add_undo_reference(Object *p_object) {
|
||||
ERR_FAIL_NULL(p_object);
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
|
||||
// No undo if the merge mode is MERGE_ENDS
|
||||
if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
Operation undo_op;
|
||||
undo_op.object = p_object->get_instance_id();
|
||||
if (Object::cast_to<RefCounted>(p_object)) {
|
||||
undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(p_object));
|
||||
}
|
||||
|
||||
undo_op.type = Operation::TYPE_REFERENCE;
|
||||
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
|
||||
actions.write[current_action + 1].undo_ops.push_back(undo_op);
|
||||
}
|
||||
|
||||
void UndoRedo::start_force_keep_in_merge_ends() {
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
|
||||
force_keep_in_merge_ends = true;
|
||||
}
|
||||
|
||||
void UndoRedo::end_force_keep_in_merge_ends() {
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
ERR_FAIL_COND((current_action + 1) >= actions.size());
|
||||
|
||||
force_keep_in_merge_ends = false;
|
||||
}
|
||||
|
||||
void UndoRedo::_pop_history_tail() {
|
||||
discard_redo();
|
||||
|
||||
if (!actions.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Operation &E : actions.write[0].undo_ops) {
|
||||
E.delete_reference();
|
||||
}
|
||||
|
||||
actions.remove_at(0);
|
||||
if (current_action >= 0) {
|
||||
current_action--;
|
||||
}
|
||||
}
|
||||
|
||||
bool UndoRedo::is_committing_action() const {
|
||||
return committing > 0;
|
||||
}
|
||||
|
||||
void UndoRedo::commit_action(bool p_execute) {
|
||||
ERR_FAIL_COND(action_level <= 0);
|
||||
action_level--;
|
||||
if (action_level > 0) {
|
||||
return; //still nested
|
||||
}
|
||||
|
||||
bool add_message = !merging;
|
||||
|
||||
if (merging) {
|
||||
version--;
|
||||
merging = false;
|
||||
}
|
||||
|
||||
if (actions[actions.size() - 1].backward_undo_ops) {
|
||||
actions.write[actions.size() - 1].undo_ops.reverse();
|
||||
}
|
||||
|
||||
committing++;
|
||||
_redo(p_execute); // perform action
|
||||
committing--;
|
||||
|
||||
if (max_steps > 0) {
|
||||
// Clear early steps.
|
||||
|
||||
while (actions.size() > max_steps) {
|
||||
_pop_history_tail();
|
||||
}
|
||||
}
|
||||
|
||||
if (add_message && callback && actions.size() > 0) {
|
||||
callback(callback_ud, actions[actions.size() - 1].name);
|
||||
}
|
||||
}
|
||||
|
||||
void UndoRedo::_process_operation_list(List<Operation>::Element *E, bool p_execute) {
|
||||
const int PREALLOCATE_ARGS_COUNT = 16;
|
||||
|
||||
LocalVector<const Variant *> args;
|
||||
args.reserve(PREALLOCATE_ARGS_COUNT);
|
||||
|
||||
for (; E; E = E->next()) {
|
||||
Operation &op = E->get();
|
||||
|
||||
Object *obj = ObjectDB::get_instance(op.object);
|
||||
if (!obj) { //may have been deleted and this is fine
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (op.type) {
|
||||
case Operation::TYPE_METHOD: {
|
||||
if (p_execute) {
|
||||
Callable::CallError ce;
|
||||
Variant ret;
|
||||
op.callable.callp(nullptr, 0, ret, ce);
|
||||
if (ce.error != Callable::CallError::CALL_OK) {
|
||||
ERR_PRINT(vformat("Error calling UndoRedo method operation '%s': %s.", String(op.name), Variant::get_call_error_text(obj, op.name, nullptr, 0, ce)));
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
Resource *res = Object::cast_to<Resource>(obj);
|
||||
if (res) {
|
||||
res->set_edited(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (method_callback) {
|
||||
Vector<Variant> binds;
|
||||
if (op.callable.is_custom()) {
|
||||
CallableCustomBind *ccb = dynamic_cast<CallableCustomBind *>(op.callable.get_custom());
|
||||
if (ccb) {
|
||||
binds = ccb->get_binds();
|
||||
}
|
||||
}
|
||||
|
||||
if (binds.is_empty()) {
|
||||
method_callback(method_callback_ud, obj, op.name, nullptr, 0);
|
||||
} else {
|
||||
args.clear();
|
||||
|
||||
for (int i = 0; i < binds.size(); i++) {
|
||||
args.push_back(&binds[i]);
|
||||
}
|
||||
|
||||
method_callback(method_callback_ud, obj, op.name, args.ptr(), binds.size());
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case Operation::TYPE_PROPERTY: {
|
||||
if (p_execute) {
|
||||
obj->set(op.name, op.value);
|
||||
#ifdef TOOLS_ENABLED
|
||||
Resource *res = Object::cast_to<Resource>(obj);
|
||||
if (res) {
|
||||
res->set_edited(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (property_callback) {
|
||||
property_callback(prop_callback_ud, obj, op.name, op.value);
|
||||
}
|
||||
} break;
|
||||
case Operation::TYPE_REFERENCE: {
|
||||
//do nothing
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UndoRedo::redo() {
|
||||
return _redo(true);
|
||||
}
|
||||
|
||||
bool UndoRedo::undo() {
|
||||
ERR_FAIL_COND_V(action_level > 0, false);
|
||||
if (current_action < 0) {
|
||||
return false; //nothing to redo
|
||||
}
|
||||
_process_operation_list(actions.write[current_action].undo_ops.front(), true);
|
||||
current_action--;
|
||||
version--;
|
||||
emit_signal(SNAME("version_changed"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int UndoRedo::get_history_count() {
|
||||
ERR_FAIL_COND_V(action_level > 0, -1);
|
||||
|
||||
return actions.size();
|
||||
}
|
||||
|
||||
int UndoRedo::get_current_action() {
|
||||
ERR_FAIL_COND_V(action_level > 0, -1);
|
||||
|
||||
return current_action;
|
||||
}
|
||||
|
||||
String UndoRedo::get_action_name(int p_id) {
|
||||
ERR_FAIL_INDEX_V(p_id, actions.size(), "");
|
||||
|
||||
return actions[p_id].name;
|
||||
}
|
||||
|
||||
void UndoRedo::clear_history(bool p_increase_version) {
|
||||
ERR_FAIL_COND(action_level > 0);
|
||||
discard_redo();
|
||||
|
||||
while (actions.size()) {
|
||||
_pop_history_tail();
|
||||
}
|
||||
|
||||
if (p_increase_version) {
|
||||
version++;
|
||||
emit_signal(SNAME("version_changed"));
|
||||
}
|
||||
}
|
||||
|
||||
String UndoRedo::get_current_action_name() const {
|
||||
ERR_FAIL_COND_V(action_level > 0, "");
|
||||
if (current_action < 0) {
|
||||
return "";
|
||||
}
|
||||
return actions[current_action].name;
|
||||
}
|
||||
|
||||
int UndoRedo::get_action_level() const {
|
||||
return action_level;
|
||||
}
|
||||
|
||||
bool UndoRedo::has_undo() const {
|
||||
return current_action >= 0;
|
||||
}
|
||||
|
||||
bool UndoRedo::has_redo() const {
|
||||
return (current_action + 1) < actions.size();
|
||||
}
|
||||
|
||||
bool UndoRedo::is_merging() const {
|
||||
return merging;
|
||||
}
|
||||
|
||||
uint64_t UndoRedo::get_version() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
void UndoRedo::set_max_steps(int p_max_steps) {
|
||||
max_steps = p_max_steps;
|
||||
}
|
||||
|
||||
int UndoRedo::get_max_steps() const {
|
||||
return max_steps;
|
||||
}
|
||||
|
||||
void UndoRedo::set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud) {
|
||||
callback = p_callback;
|
||||
callback_ud = p_ud;
|
||||
}
|
||||
|
||||
void UndoRedo::set_method_notify_callback(MethodNotifyCallback p_method_callback, void *p_ud) {
|
||||
method_callback = p_method_callback;
|
||||
method_callback_ud = p_ud;
|
||||
}
|
||||
|
||||
void UndoRedo::set_property_notify_callback(PropertyNotifyCallback p_property_callback, void *p_ud) {
|
||||
property_callback = p_property_callback;
|
||||
prop_callback_ud = p_ud;
|
||||
}
|
||||
|
||||
UndoRedo::~UndoRedo() {
|
||||
clear_history();
|
||||
}
|
||||
|
||||
void UndoRedo::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "backward_undo_ops"), &UndoRedo::create_action, DEFVAL(MERGE_DISABLE), DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("commit_action", "execute"), &UndoRedo::commit_action, DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("is_committing_action"), &UndoRedo::is_committing_action);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("add_do_method", "callable"), &UndoRedo::add_do_method);
|
||||
ClassDB::bind_method(D_METHOD("add_undo_method", "callable"), &UndoRedo::add_undo_method);
|
||||
ClassDB::bind_method(D_METHOD("add_do_property", "object", "property", "value"), &UndoRedo::add_do_property);
|
||||
ClassDB::bind_method(D_METHOD("add_undo_property", "object", "property", "value"), &UndoRedo::add_undo_property);
|
||||
ClassDB::bind_method(D_METHOD("add_do_reference", "object"), &UndoRedo::add_do_reference);
|
||||
ClassDB::bind_method(D_METHOD("add_undo_reference", "object"), &UndoRedo::add_undo_reference);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("start_force_keep_in_merge_ends"), &UndoRedo::start_force_keep_in_merge_ends);
|
||||
ClassDB::bind_method(D_METHOD("end_force_keep_in_merge_ends"), &UndoRedo::end_force_keep_in_merge_ends);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_history_count"), &UndoRedo::get_history_count);
|
||||
ClassDB::bind_method(D_METHOD("get_current_action"), &UndoRedo::get_current_action);
|
||||
ClassDB::bind_method(D_METHOD("get_action_name", "id"), &UndoRedo::get_action_name);
|
||||
ClassDB::bind_method(D_METHOD("clear_history", "increase_version"), &UndoRedo::clear_history, DEFVAL(true));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_current_action_name"), &UndoRedo::get_current_action_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("has_undo"), &UndoRedo::has_undo);
|
||||
ClassDB::bind_method(D_METHOD("has_redo"), &UndoRedo::has_redo);
|
||||
ClassDB::bind_method(D_METHOD("get_version"), &UndoRedo::get_version);
|
||||
ClassDB::bind_method(D_METHOD("set_max_steps", "max_steps"), &UndoRedo::set_max_steps);
|
||||
ClassDB::bind_method(D_METHOD("get_max_steps"), &UndoRedo::get_max_steps);
|
||||
ClassDB::bind_method(D_METHOD("redo"), &UndoRedo::redo);
|
||||
ClassDB::bind_method(D_METHOD("undo"), &UndoRedo::undo);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_steps", PROPERTY_HINT_RANGE, "0,50,1,or_greater"), "set_max_steps", "get_max_steps");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("version_changed"));
|
||||
|
||||
BIND_ENUM_CONSTANT(MERGE_DISABLE);
|
||||
BIND_ENUM_CONSTANT(MERGE_ENDS);
|
||||
BIND_ENUM_CONSTANT(MERGE_ALL);
|
||||
}
|
152
core/object/undo_redo.h
Normal file
152
core/object/undo_redo.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/**************************************************************************/
|
||||
/* undo_redo.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class UndoRedo : public Object {
|
||||
GDCLASS(UndoRedo, Object);
|
||||
OBJ_SAVE_TYPE(UndoRedo);
|
||||
|
||||
public:
|
||||
enum MergeMode {
|
||||
MERGE_DISABLE,
|
||||
MERGE_ENDS,
|
||||
MERGE_ALL
|
||||
};
|
||||
|
||||
typedef void (*CommitNotifyCallback)(void *p_ud, const String &p_name);
|
||||
|
||||
typedef void (*MethodNotifyCallback)(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount);
|
||||
typedef void (*PropertyNotifyCallback)(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value);
|
||||
|
||||
private:
|
||||
struct Operation {
|
||||
enum Type {
|
||||
TYPE_METHOD,
|
||||
TYPE_PROPERTY,
|
||||
TYPE_REFERENCE
|
||||
} type;
|
||||
|
||||
bool force_keep_in_merge_ends = false;
|
||||
Ref<RefCounted> ref;
|
||||
ObjectID object;
|
||||
StringName name;
|
||||
Callable callable;
|
||||
Variant value;
|
||||
|
||||
void delete_reference();
|
||||
};
|
||||
|
||||
struct Action {
|
||||
String name;
|
||||
List<Operation> do_ops;
|
||||
List<Operation> undo_ops;
|
||||
uint64_t last_tick = 0;
|
||||
bool backward_undo_ops = false;
|
||||
};
|
||||
|
||||
Vector<Action> actions;
|
||||
int current_action = -1;
|
||||
bool force_keep_in_merge_ends = false;
|
||||
int action_level = 0;
|
||||
int max_steps = 0;
|
||||
MergeMode merge_mode = MERGE_DISABLE;
|
||||
bool merging = false;
|
||||
uint64_t version = 1;
|
||||
int merge_total = 0;
|
||||
|
||||
void _pop_history_tail();
|
||||
void _process_operation_list(List<Operation>::Element *E, bool p_execute);
|
||||
void _discard_redo();
|
||||
bool _redo(bool p_execute);
|
||||
|
||||
CommitNotifyCallback callback = nullptr;
|
||||
void *callback_ud = nullptr;
|
||||
void *method_callback_ud = nullptr;
|
||||
void *prop_callback_ud = nullptr;
|
||||
|
||||
MethodNotifyCallback method_callback = nullptr;
|
||||
PropertyNotifyCallback property_callback = nullptr;
|
||||
|
||||
int committing = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void create_action(const String &p_name = "", MergeMode p_mode = MERGE_DISABLE, bool p_backward_undo_ops = false);
|
||||
|
||||
void add_do_method(const Callable &p_callable);
|
||||
void add_undo_method(const Callable &p_callable);
|
||||
void add_do_property(Object *p_object, const StringName &p_property, const Variant &p_value);
|
||||
void add_undo_property(Object *p_object, const StringName &p_property, const Variant &p_value);
|
||||
void add_do_reference(Object *p_object);
|
||||
void add_undo_reference(Object *p_object);
|
||||
|
||||
void start_force_keep_in_merge_ends();
|
||||
void end_force_keep_in_merge_ends();
|
||||
|
||||
bool is_committing_action() const;
|
||||
void commit_action(bool p_execute = true);
|
||||
|
||||
bool redo();
|
||||
bool undo();
|
||||
String get_current_action_name() const;
|
||||
int get_action_level() const;
|
||||
|
||||
int get_history_count();
|
||||
int get_current_action();
|
||||
String get_action_name(int p_id);
|
||||
void clear_history(bool p_increase_version = true);
|
||||
void discard_redo();
|
||||
|
||||
bool has_undo() const;
|
||||
bool has_redo() const;
|
||||
|
||||
bool is_merging() const;
|
||||
|
||||
uint64_t get_version() const;
|
||||
|
||||
void set_max_steps(int p_max_steps);
|
||||
int get_max_steps() const;
|
||||
|
||||
void set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud);
|
||||
|
||||
void set_method_notify_callback(MethodNotifyCallback p_method_callback, void *p_ud);
|
||||
void set_property_notify_callback(PropertyNotifyCallback p_property_callback, void *p_ud);
|
||||
|
||||
UndoRedo() {}
|
||||
~UndoRedo();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(UndoRedo::MergeMode);
|
954
core/object/worker_thread_pool.cpp
Normal file
954
core/object/worker_thread_pool.cpp
Normal file
@@ -0,0 +1,954 @@
|
||||
/**************************************************************************/
|
||||
/* worker_thread_pool.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "worker_thread_pool.h"
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/os/safe_binary_mutex.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
|
||||
WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1;
|
||||
|
||||
HashMap<StringName, WorkerThreadPool *> WorkerThreadPool::named_pools;
|
||||
|
||||
void WorkerThreadPool::Task::free_template_userdata() {
|
||||
ERR_FAIL_NULL(template_userdata);
|
||||
ERR_FAIL_NULL(native_func_userdata);
|
||||
BaseTemplateUserdata *btu = (BaseTemplateUserdata *)native_func_userdata;
|
||||
memdelete(btu);
|
||||
}
|
||||
|
||||
WorkerThreadPool *WorkerThreadPool::singleton = nullptr;
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
thread_local WorkerThreadPool::UnlockableLocks WorkerThreadPool::unlockable_locks[MAX_UNLOCKABLE_LOCKS];
|
||||
#endif
|
||||
|
||||
void WorkerThreadPool::_process_task(Task *p_task) {
|
||||
#ifdef THREADS_ENABLED
|
||||
int pool_thread_index = thread_ids[Thread::get_caller_id()];
|
||||
ThreadData &curr_thread = threads[pool_thread_index];
|
||||
Task *prev_task = nullptr; // In case this is recursively called.
|
||||
|
||||
bool safe_for_nodes_backup = is_current_thread_safe_for_nodes();
|
||||
CallQueue *call_queue_backup = MessageQueue::get_singleton() != MessageQueue::get_main_singleton() ? MessageQueue::get_singleton() : nullptr;
|
||||
|
||||
{
|
||||
// Tasks must start with these at default values. They are free to set-and-forget otherwise.
|
||||
set_current_thread_safe_for_nodes(false);
|
||||
MessageQueue::set_thread_singleton_override(nullptr);
|
||||
|
||||
// Since the WorkerThreadPool is started before the script server,
|
||||
// its pre-created threads can't have ScriptServer::thread_enter() called on them early.
|
||||
// Therefore, we do it late at the first opportunity, so in case the task
|
||||
// about to be run uses scripting, guarantees are held.
|
||||
ScriptServer::thread_enter();
|
||||
|
||||
task_mutex.lock();
|
||||
p_task->pool_thread_index = pool_thread_index;
|
||||
prev_task = curr_thread.current_task;
|
||||
curr_thread.current_task = p_task;
|
||||
curr_thread.has_pump_task = p_task->is_pump_task;
|
||||
if (p_task->pending_notify_yield_over) {
|
||||
curr_thread.yield_is_over = true;
|
||||
}
|
||||
task_mutex.unlock();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
bool low_priority = p_task->low_priority;
|
||||
#endif
|
||||
|
||||
if (p_task->group) {
|
||||
// Handling a group
|
||||
bool do_post = false;
|
||||
|
||||
while (true) {
|
||||
uint32_t work_index = p_task->group->index.postincrement();
|
||||
|
||||
if (work_index >= p_task->group->max) {
|
||||
break;
|
||||
}
|
||||
if (p_task->native_group_func) {
|
||||
p_task->native_group_func(p_task->native_func_userdata, work_index);
|
||||
} else if (p_task->template_userdata) {
|
||||
p_task->template_userdata->callback_indexed(work_index);
|
||||
} else {
|
||||
p_task->callable.call(work_index);
|
||||
}
|
||||
|
||||
// This is the only way to ensure posting is done when all tasks are really complete.
|
||||
uint32_t completed_amount = p_task->group->completed_index.increment();
|
||||
|
||||
if (completed_amount == p_task->group->max) {
|
||||
do_post = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_post && p_task->template_userdata) {
|
||||
memdelete(p_task->template_userdata); // This is no longer needed at this point, so get rid of it.
|
||||
}
|
||||
|
||||
if (do_post) {
|
||||
p_task->group->done_semaphore.post();
|
||||
p_task->group->completed.set_to(true);
|
||||
}
|
||||
uint32_t max_users = p_task->group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment.
|
||||
uint32_t finished_users = p_task->group->finished.increment();
|
||||
|
||||
if (finished_users == max_users) {
|
||||
// Get rid of the group, because nobody else is using it.
|
||||
MutexLock task_lock(task_mutex);
|
||||
group_allocator.free(p_task->group);
|
||||
}
|
||||
|
||||
// For groups, tasks get rid of themselves.
|
||||
|
||||
task_mutex.lock();
|
||||
task_allocator.free(p_task);
|
||||
} else {
|
||||
if (p_task->native_func) {
|
||||
p_task->native_func(p_task->native_func_userdata);
|
||||
} else if (p_task->template_userdata) {
|
||||
p_task->template_userdata->callback();
|
||||
memdelete(p_task->template_userdata);
|
||||
} else {
|
||||
p_task->callable.call();
|
||||
}
|
||||
|
||||
task_mutex.lock();
|
||||
p_task->completed = true;
|
||||
p_task->pool_thread_index = -1;
|
||||
if (p_task->waiting_user) {
|
||||
p_task->done_semaphore.post(p_task->waiting_user);
|
||||
}
|
||||
// Let awaiters know.
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
if (threads[i].awaited_task == p_task) {
|
||||
threads[i].cond_var.notify_one();
|
||||
threads[i].signaled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
{
|
||||
curr_thread.current_task = prev_task;
|
||||
if (low_priority) {
|
||||
low_priority_threads_used--;
|
||||
|
||||
if (_try_promote_low_priority_task()) {
|
||||
if (prev_task) { // Otherwise, this thread will catch it.
|
||||
_notify_threads(&curr_thread, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task_mutex.unlock();
|
||||
}
|
||||
|
||||
set_current_thread_safe_for_nodes(safe_for_nodes_backup);
|
||||
MessageQueue::set_thread_singleton_override(call_queue_backup);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_thread_function(void *p_user) {
|
||||
ThreadData *thread_data = (ThreadData *)p_user;
|
||||
Thread::set_name(vformat("WorkerThread %d", thread_data->index));
|
||||
|
||||
while (true) {
|
||||
Task *task_to_process = nullptr;
|
||||
{
|
||||
// Create the lock outside the inner loop so it isn't needlessly unlocked and relocked
|
||||
// when no task was found to process, and the loop is re-entered.
|
||||
MutexLock lock(thread_data->pool->task_mutex);
|
||||
|
||||
while (true) {
|
||||
bool exit = thread_data->pool->_handle_runlevel(thread_data, lock);
|
||||
if (unlikely(exit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
thread_data->signaled = false;
|
||||
|
||||
if (!thread_data->pool->task_queue.first()) {
|
||||
// There wasn't a task available yet.
|
||||
// Let's wait for the next notification, then recheck.
|
||||
thread_data->cond_var.wait(lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Got a task to process! Remove it from the queue, then break into the task handling section.
|
||||
task_to_process = thread_data->pool->task_queue.first()->self();
|
||||
thread_data->pool->task_queue.remove(thread_data->pool->task_queue.first());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DEV_ASSERT(task_to_process);
|
||||
thread_data->pool->_process_task(task_to_process);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_post_tasks(Task **p_tasks, uint32_t p_count, bool p_high_priority, MutexLock<BinaryMutex> &p_lock, bool p_pump_task) {
|
||||
// Fall back to processing on the calling thread if there are no worker threads.
|
||||
// Separated into its own variable to make it easier to extend this logic
|
||||
// in custom builds.
|
||||
|
||||
// Avoid calling pump tasks or low priority tasks from the calling thread.
|
||||
bool process_on_calling_thread = threads.is_empty() && !p_pump_task;
|
||||
if (process_on_calling_thread) {
|
||||
p_lock.temp_unlock();
|
||||
for (uint32_t i = 0; i < p_count; i++) {
|
||||
_process_task(p_tasks[i]);
|
||||
}
|
||||
p_lock.temp_relock();
|
||||
return;
|
||||
}
|
||||
|
||||
while (runlevel == RUNLEVEL_EXIT_LANGUAGES) {
|
||||
control_cond_var.wait(p_lock);
|
||||
}
|
||||
|
||||
uint32_t to_process = 0;
|
||||
uint32_t to_promote = 0;
|
||||
|
||||
ThreadData *caller_pool_thread = thread_ids.has(Thread::get_caller_id()) ? &threads[thread_ids[Thread::get_caller_id()]] : nullptr;
|
||||
|
||||
for (uint32_t i = 0; i < p_count; i++) {
|
||||
p_tasks[i]->low_priority = !p_high_priority;
|
||||
if (p_high_priority || low_priority_threads_used < max_low_priority_threads) {
|
||||
task_queue.add_last(&p_tasks[i]->task_elem);
|
||||
if (!p_high_priority) {
|
||||
low_priority_threads_used++;
|
||||
}
|
||||
to_process++;
|
||||
} else {
|
||||
// Too many threads using low priority, must go to queue.
|
||||
low_priority_task_queue.add_last(&p_tasks[i]->task_elem);
|
||||
to_promote++;
|
||||
}
|
||||
}
|
||||
|
||||
_notify_threads(caller_pool_thread, to_process, to_promote);
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count) {
|
||||
uint32_t to_process = p_process_count;
|
||||
uint32_t to_promote = p_promote_count;
|
||||
|
||||
// This is where which threads are awaken is decided according to the workload.
|
||||
// Threads that will anyway have a chance to check the situation and process/promote tasks
|
||||
// are excluded from being notified. Others will be tried anyway to try to distribute load.
|
||||
// The current thread, if is a pool thread, is also excluded depending on the promoting/processing
|
||||
// needs because it will anyway loop again. However, it will contribute to decreasing the count,
|
||||
// which helps reducing sync traffic.
|
||||
|
||||
uint32_t thread_count = threads.size();
|
||||
|
||||
// First round:
|
||||
// 1. For processing: notify threads that are not running tasks, to keep the stacks as shallow as possible.
|
||||
// 2. For promoting: since it's exclusive with processing, we fin threads able to promote low-prio tasks now.
|
||||
for (uint32_t i = 0;
|
||||
i < thread_count && (to_process || to_promote);
|
||||
i++, notify_index = (notify_index + 1) % thread_count) {
|
||||
ThreadData &th = threads[notify_index];
|
||||
|
||||
if (th.signaled) {
|
||||
continue;
|
||||
}
|
||||
if (th.current_task) {
|
||||
// Good thread for promoting low-prio?
|
||||
if (to_promote && th.awaited_task && th.current_task->low_priority) {
|
||||
if (likely(&th != p_current_thread_data)) {
|
||||
th.cond_var.notify_one();
|
||||
}
|
||||
th.signaled = true;
|
||||
to_promote--;
|
||||
}
|
||||
} else {
|
||||
if (to_process) {
|
||||
if (likely(&th != p_current_thread_data)) {
|
||||
th.cond_var.notify_one();
|
||||
}
|
||||
th.signaled = true;
|
||||
to_process--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second round:
|
||||
// For processing: if the first round wasn't enough, let's try now with threads processing tasks but currently awaiting.
|
||||
for (uint32_t i = 0;
|
||||
i < thread_count && to_process;
|
||||
i++, notify_index = (notify_index + 1) % thread_count) {
|
||||
ThreadData &th = threads[notify_index];
|
||||
|
||||
if (th.signaled) {
|
||||
continue;
|
||||
}
|
||||
if (th.awaited_task) {
|
||||
if (likely(&th != p_current_thread_data)) {
|
||||
th.cond_var.notify_one();
|
||||
}
|
||||
th.signaled = true;
|
||||
to_process--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WorkerThreadPool::_try_promote_low_priority_task() {
|
||||
if (low_priority_task_queue.first()) {
|
||||
Task *low_prio_task = low_priority_task_queue.first()->self();
|
||||
low_priority_task_queue.remove(low_priority_task_queue.first());
|
||||
task_queue.add_last(&low_prio_task->task_elem);
|
||||
low_priority_threads_used++;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
WorkerThreadPool::TaskID WorkerThreadPool::add_native_task(void (*p_func)(void *), void *p_userdata, bool p_high_priority, const String &p_description) {
|
||||
return _add_task(Callable(), p_func, p_userdata, nullptr, p_high_priority, p_description);
|
||||
}
|
||||
|
||||
WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description, bool p_pump_task) {
|
||||
MutexLock<BinaryMutex> lock(task_mutex);
|
||||
|
||||
// Get a free task
|
||||
Task *task = task_allocator.alloc();
|
||||
TaskID id = last_task++;
|
||||
task->self = id;
|
||||
task->callable = p_callable;
|
||||
task->native_func = p_func;
|
||||
task->native_func_userdata = p_userdata;
|
||||
task->description = p_description;
|
||||
task->template_userdata = p_template_userdata;
|
||||
task->is_pump_task = p_pump_task;
|
||||
tasks.insert(id, task);
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
if (p_pump_task) {
|
||||
pump_task_count++;
|
||||
int thread_count = get_thread_count();
|
||||
if (pump_task_count >= thread_count) {
|
||||
print_verbose(vformat("A greater number of dedicated threads were requested (%d) than threads available (%d). Please increase the number of available worker task threads. Recovering this session by spawning more worker task threads.", pump_task_count + 1, thread_count)); // +1 because we want to keep a Thread without any pump tasks free.
|
||||
|
||||
Thread::Settings settings;
|
||||
#ifdef __APPLE__
|
||||
// The default stack size for new threads on Apple platforms is 512KiB.
|
||||
// This is insufficient when using a library like SPIRV-Cross,
|
||||
// which can generate deep stacks and result in a stack overflow.
|
||||
#ifdef DEV_ENABLED
|
||||
// Debug builds need an even larger stack size.
|
||||
settings.stack_size = 2 * 1024 * 1024; // 2 MiB
|
||||
#else
|
||||
settings.stack_size = 1 * 1024 * 1024; // 1 MiB
|
||||
#endif
|
||||
#endif
|
||||
// Re-sizing implies relocation, which is not supported for this array.
|
||||
CRASH_COND_MSG(thread_count + 1 > (int)threads.get_capacity(), "Reserve trick for worker thread pool failed. Crashing.");
|
||||
threads.resize_initialized(thread_count + 1);
|
||||
threads[thread_count].index = thread_count;
|
||||
threads[thread_count].pool = this;
|
||||
threads[thread_count].thread.start(&WorkerThreadPool::_thread_function, &threads[thread_count], settings);
|
||||
thread_ids.insert(threads[thread_count].thread.get_id(), thread_count);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
_post_tasks(&task, 1, p_high_priority, lock, p_pump_task);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bool p_high_priority, const String &p_description, bool p_pump_task) {
|
||||
return _add_task(p_action, nullptr, nullptr, nullptr, p_high_priority, p_description, p_pump_task);
|
||||
}
|
||||
|
||||
WorkerThreadPool::TaskID WorkerThreadPool::add_task_bind(const Callable &p_action, bool p_high_priority, const String &p_description) {
|
||||
return _add_task(p_action, nullptr, nullptr, nullptr, p_high_priority, p_description, false);
|
||||
}
|
||||
|
||||
bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const {
|
||||
MutexLock task_lock(task_mutex);
|
||||
const Task *const *taskp = tasks.getptr(p_task_id);
|
||||
if (!taskp) {
|
||||
ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task
|
||||
}
|
||||
|
||||
return (*taskp)->completed;
|
||||
}
|
||||
|
||||
Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
|
||||
task_mutex.lock();
|
||||
Task **taskp = tasks.getptr(p_task_id);
|
||||
if (!taskp) {
|
||||
task_mutex.unlock();
|
||||
ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid Task ID"); // Invalid task
|
||||
}
|
||||
Task *task = *taskp;
|
||||
|
||||
if (task->completed) {
|
||||
if (task->waiting_pool == 0 && task->waiting_user == 0) {
|
||||
tasks.erase(p_task_id);
|
||||
task_allocator.free(task);
|
||||
}
|
||||
task_mutex.unlock();
|
||||
return OK;
|
||||
}
|
||||
|
||||
ThreadData *caller_pool_thread = thread_ids.has(Thread::get_caller_id()) ? &threads[thread_ids[Thread::get_caller_id()]] : nullptr;
|
||||
if (caller_pool_thread && p_task_id <= caller_pool_thread->current_task->self) {
|
||||
// Deadlock prevention:
|
||||
// When a pool thread wants to wait for an older task, the following situations can happen:
|
||||
// 1. Awaited task is deep in the stack of the awaiter.
|
||||
// 2. A group of awaiter threads end up depending on some tasks buried in the stack
|
||||
// of their worker threads in such a way that progress can't be made.
|
||||
// Both would entail a deadlock. Some may be handled here in the WorkerThreadPool
|
||||
// with some extra logic and bookkeeping. However, there would still be unavoidable
|
||||
// cases of deadlock because of the way waiting threads process outstanding tasks.
|
||||
// Taking into account there's no feasible solution for every possible case
|
||||
// with the current design, we just simply reject attempts to await on older tasks,
|
||||
// with a specific error code that signals the situation so the caller can handle it.
|
||||
task_mutex.unlock();
|
||||
return ERR_BUSY;
|
||||
}
|
||||
|
||||
if (caller_pool_thread) {
|
||||
task->waiting_pool++;
|
||||
} else {
|
||||
task->waiting_user++;
|
||||
}
|
||||
|
||||
if (caller_pool_thread) {
|
||||
task_mutex.unlock();
|
||||
_wait_collaboratively(caller_pool_thread, task);
|
||||
task_mutex.lock();
|
||||
task->waiting_pool--;
|
||||
if (task->waiting_pool == 0 && task->waiting_user == 0) {
|
||||
tasks.erase(p_task_id);
|
||||
task_allocator.free(task);
|
||||
}
|
||||
} else {
|
||||
task_mutex.unlock();
|
||||
task->done_semaphore.wait();
|
||||
task_mutex.lock();
|
||||
task->waiting_user--;
|
||||
if (task->waiting_pool == 0 && task->waiting_user == 0) {
|
||||
tasks.erase(p_task_id);
|
||||
task_allocator.free(task);
|
||||
}
|
||||
}
|
||||
|
||||
task_mutex.unlock();
|
||||
return OK;
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_lock_unlockable_mutexes() {
|
||||
#ifdef THREADS_ENABLED
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) {
|
||||
if (unlockable_locks[i].ulock) {
|
||||
unlockable_locks[i].ulock->lock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_unlock_unlockable_mutexes() {
|
||||
#ifdef THREADS_ENABLED
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) {
|
||||
if (unlockable_locks[i].ulock) {
|
||||
unlockable_locks[i].ulock->unlock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task) {
|
||||
// Keep processing tasks until the condition to stop waiting is met.
|
||||
|
||||
while (true) {
|
||||
Task *task_to_process = nullptr;
|
||||
bool relock_unlockables = false;
|
||||
{
|
||||
MutexLock lock(task_mutex);
|
||||
|
||||
bool was_signaled = p_caller_pool_thread->signaled;
|
||||
p_caller_pool_thread->signaled = false;
|
||||
|
||||
bool exit = _handle_runlevel(p_caller_pool_thread, lock);
|
||||
if (unlikely(exit)) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool wait_is_over = false;
|
||||
if (unlikely(p_task == ThreadData::YIELDING)) {
|
||||
if (p_caller_pool_thread->yield_is_over) {
|
||||
p_caller_pool_thread->yield_is_over = false;
|
||||
wait_is_over = true;
|
||||
}
|
||||
} else {
|
||||
if (p_task->completed) {
|
||||
wait_is_over = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (wait_is_over) {
|
||||
if (was_signaled) {
|
||||
// This thread was awaken for some additional reason, but it's about to exit.
|
||||
// Let's find out what may be pending and forward the requests.
|
||||
uint32_t to_process = task_queue.first() ? 1 : 0;
|
||||
uint32_t to_promote = p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first() ? 1 : 0;
|
||||
if (to_process || to_promote) {
|
||||
// This thread must be left alone since it won't loop again.
|
||||
p_caller_pool_thread->signaled = true;
|
||||
_notify_threads(p_caller_pool_thread, to_process, to_promote);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) {
|
||||
if (_try_promote_low_priority_task()) {
|
||||
_notify_threads(p_caller_pool_thread, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (p_caller_pool_thread->pool->task_queue.first()) {
|
||||
task_to_process = task_queue.first()->self();
|
||||
if ((p_task == ThreadData::YIELDING || p_caller_pool_thread->has_pump_task == true) && task_to_process->is_pump_task) {
|
||||
task_to_process = nullptr;
|
||||
_notify_threads(p_caller_pool_thread, 1, 0);
|
||||
} else {
|
||||
task_queue.remove(task_queue.first());
|
||||
}
|
||||
}
|
||||
|
||||
if (!task_to_process) {
|
||||
p_caller_pool_thread->awaited_task = p_task;
|
||||
|
||||
if (this == singleton) {
|
||||
_unlock_unlockable_mutexes();
|
||||
}
|
||||
relock_unlockables = true;
|
||||
|
||||
p_caller_pool_thread->cond_var.wait(lock);
|
||||
|
||||
p_caller_pool_thread->awaited_task = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (relock_unlockables && this == singleton) {
|
||||
_lock_unlockable_mutexes();
|
||||
}
|
||||
|
||||
if (task_to_process) {
|
||||
_process_task(task_to_process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_switch_runlevel(Runlevel p_runlevel) {
|
||||
DEV_ASSERT(p_runlevel > runlevel);
|
||||
runlevel = p_runlevel;
|
||||
memset(&runlevel_data, 0, sizeof(runlevel_data));
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].cond_var.notify_one();
|
||||
threads[i].signaled = true;
|
||||
}
|
||||
control_cond_var.notify_all();
|
||||
}
|
||||
|
||||
// Returns whether threads have to exit. This may perform the check about handling needed.
|
||||
bool WorkerThreadPool::_handle_runlevel(ThreadData *p_thread_data, MutexLock<BinaryMutex> &p_lock) {
|
||||
bool exit = false;
|
||||
switch (runlevel) {
|
||||
case RUNLEVEL_NORMAL: {
|
||||
} break;
|
||||
case RUNLEVEL_PRE_EXIT_LANGUAGES: {
|
||||
if (!p_thread_data->pre_exited_languages) {
|
||||
if (!task_queue.first() && !low_priority_task_queue.first()) {
|
||||
p_thread_data->pre_exited_languages = true;
|
||||
runlevel_data.pre_exit_languages.num_idle_threads++;
|
||||
control_cond_var.notify_all();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case RUNLEVEL_EXIT_LANGUAGES: {
|
||||
if (!p_thread_data->exited_languages) {
|
||||
p_lock.temp_unlock();
|
||||
ScriptServer::thread_exit();
|
||||
p_lock.temp_relock();
|
||||
p_thread_data->exited_languages = true;
|
||||
runlevel_data.exit_languages.num_exited_threads++;
|
||||
control_cond_var.notify_all();
|
||||
}
|
||||
} break;
|
||||
case RUNLEVEL_EXIT: {
|
||||
exit = true;
|
||||
} break;
|
||||
}
|
||||
return exit;
|
||||
}
|
||||
|
||||
void WorkerThreadPool::yield() {
|
||||
int th_index = get_thread_index();
|
||||
ERR_FAIL_COND_MSG(th_index == -1, "This function can only be called from a worker thread.");
|
||||
_wait_collaboratively(&threads[th_index], ThreadData::YIELDING);
|
||||
|
||||
task_mutex.lock();
|
||||
if (runlevel < RUNLEVEL_EXIT_LANGUAGES) {
|
||||
// If this long-lived task started before the scripting server was initialized,
|
||||
// now is a good time to have scripting languages ready for the current thread.
|
||||
// Otherwise, such a piece of setup won't happen unless another task has been
|
||||
// run during the collaborative wait.
|
||||
task_mutex.unlock();
|
||||
ScriptServer::thread_enter();
|
||||
} else {
|
||||
task_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
|
||||
MutexLock task_lock(task_mutex);
|
||||
Task **taskp = tasks.getptr(p_task_id);
|
||||
if (!taskp) {
|
||||
ERR_FAIL_MSG("Invalid Task ID.");
|
||||
}
|
||||
Task *task = *taskp;
|
||||
if (task->pool_thread_index == -1) { // Completed or not started yet.
|
||||
if (!task->completed) {
|
||||
// This avoids a race condition where a task is created and yield-over called before it's processed.
|
||||
task->pending_notify_yield_over = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadData &td = threads[task->pool_thread_index];
|
||||
td.yield_is_over = true;
|
||||
td.signaled = true;
|
||||
td.cond_var.notify_one();
|
||||
}
|
||||
|
||||
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
|
||||
ERR_FAIL_COND_V(p_elements < 0, INVALID_TASK_ID);
|
||||
if (p_tasks < 0) {
|
||||
p_tasks = MAX(1u, threads.size());
|
||||
}
|
||||
|
||||
MutexLock<BinaryMutex> lock(task_mutex);
|
||||
|
||||
Group *group = group_allocator.alloc();
|
||||
GroupID id = last_task++;
|
||||
group->max = p_elements;
|
||||
group->self = id;
|
||||
|
||||
Task **tasks_posted = nullptr;
|
||||
if (p_elements == 0) {
|
||||
// Should really not call it with zero Elements, but at least it should work.
|
||||
group->completed.set_to(true);
|
||||
group->done_semaphore.post();
|
||||
group->tasks_used = 0;
|
||||
p_tasks = 0;
|
||||
if (p_template_userdata) {
|
||||
memdelete(p_template_userdata);
|
||||
}
|
||||
|
||||
} else {
|
||||
group->tasks_used = p_tasks;
|
||||
tasks_posted = (Task **)alloca(sizeof(Task *) * p_tasks);
|
||||
for (int i = 0; i < p_tasks; i++) {
|
||||
Task *task = task_allocator.alloc();
|
||||
task->native_group_func = p_func;
|
||||
task->native_func_userdata = p_userdata;
|
||||
task->description = p_description;
|
||||
task->group = group;
|
||||
task->callable = p_callable;
|
||||
task->template_userdata = p_template_userdata;
|
||||
tasks_posted[i] = task;
|
||||
// No task ID is used.
|
||||
}
|
||||
}
|
||||
|
||||
groups[id] = group;
|
||||
|
||||
_post_tasks(tasks_posted, p_tasks, p_high_priority, lock, false);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
WorkerThreadPool::GroupID WorkerThreadPool::add_native_group_task(void (*p_func)(void *, uint32_t), void *p_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
|
||||
return _add_group_task(Callable(), p_func, p_userdata, nullptr, p_elements, p_tasks, p_high_priority, p_description);
|
||||
}
|
||||
|
||||
WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_action, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
|
||||
return _add_group_task(p_action, nullptr, nullptr, nullptr, p_elements, p_tasks, p_high_priority, p_description);
|
||||
}
|
||||
|
||||
uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const {
|
||||
MutexLock task_lock(task_mutex);
|
||||
const Group *const *groupp = groups.getptr(p_group);
|
||||
if (!groupp) {
|
||||
ERR_FAIL_V_MSG(0, "Invalid Group ID");
|
||||
}
|
||||
return (*groupp)->completed_index.get();
|
||||
}
|
||||
bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const {
|
||||
MutexLock task_lock(task_mutex);
|
||||
const Group *const *groupp = groups.getptr(p_group);
|
||||
if (!groupp) {
|
||||
ERR_FAIL_V_MSG(false, "Invalid Group ID");
|
||||
}
|
||||
return (*groupp)->completed.is_set();
|
||||
}
|
||||
|
||||
void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
|
||||
#ifdef THREADS_ENABLED
|
||||
task_mutex.lock();
|
||||
Group **groupp = groups.getptr(p_group);
|
||||
task_mutex.unlock();
|
||||
if (!groupp) {
|
||||
ERR_FAIL_MSG("Invalid Group ID.");
|
||||
}
|
||||
|
||||
{
|
||||
Group *group = *groupp;
|
||||
|
||||
if (this == singleton) {
|
||||
_unlock_unlockable_mutexes();
|
||||
}
|
||||
group->done_semaphore.wait();
|
||||
if (this == singleton) {
|
||||
_lock_unlockable_mutexes();
|
||||
}
|
||||
|
||||
uint32_t max_users = group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment.
|
||||
uint32_t finished_users = group->finished.increment(); // fetch happens before inc, so increment later.
|
||||
|
||||
if (finished_users == max_users) {
|
||||
// All tasks using this group are gone (finished before the group), so clear the group too.
|
||||
MutexLock task_lock(task_mutex);
|
||||
group_allocator.free(group);
|
||||
}
|
||||
}
|
||||
|
||||
MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
|
||||
groups.erase(p_group);
|
||||
#endif
|
||||
}
|
||||
|
||||
int WorkerThreadPool::get_thread_index() const {
|
||||
Thread::ID tid = Thread::get_caller_id();
|
||||
return thread_ids.has(tid) ? thread_ids[tid] : -1;
|
||||
}
|
||||
|
||||
WorkerThreadPool::TaskID WorkerThreadPool::get_caller_task_id() const {
|
||||
int th_index = get_thread_index();
|
||||
if (th_index != -1 && threads[th_index].current_task) {
|
||||
return threads[th_index].current_task->self;
|
||||
} else {
|
||||
return INVALID_TASK_ID;
|
||||
}
|
||||
}
|
||||
|
||||
WorkerThreadPool::GroupID WorkerThreadPool::get_caller_group_id() const {
|
||||
int th_index = get_thread_index();
|
||||
if (th_index != -1 && threads[th_index].current_task && threads[th_index].current_task->group) {
|
||||
return threads[th_index].current_task->group->self;
|
||||
} else {
|
||||
return INVALID_TASK_ID;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &p_ulock) {
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) {
|
||||
DEV_ASSERT((bool)unlockable_locks[i].ulock == (bool)unlockable_locks[i].rc);
|
||||
if (unlockable_locks[i].ulock == &p_ulock) {
|
||||
// Already registered in the current thread.
|
||||
unlockable_locks[i].rc++;
|
||||
return i;
|
||||
} else if (!unlockable_locks[i].ulock) {
|
||||
unlockable_locks[i].ulock = &p_ulock;
|
||||
unlockable_locks[i].rc = 1;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable lock slots available. Engine bug.");
|
||||
}
|
||||
|
||||
void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {
|
||||
DEV_ASSERT(unlockable_locks[p_zone_id].ulock && unlockable_locks[p_zone_id].rc);
|
||||
unlockable_locks[p_zone_id].rc--;
|
||||
if (unlockable_locks[p_zone_id].rc == 0) {
|
||||
unlockable_locks[p_zone_id].ulock = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) {
|
||||
ERR_FAIL_COND(threads.size() > 0);
|
||||
|
||||
runlevel = RUNLEVEL_NORMAL;
|
||||
|
||||
if (p_thread_count < 0) {
|
||||
p_thread_count = OS::get_singleton()->get_default_thread_pool_size();
|
||||
}
|
||||
|
||||
max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1);
|
||||
|
||||
print_verbose(vformat("WorkerThreadPool: %d threads, %d max low-priority.", p_thread_count, max_low_priority_threads));
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
// Reserve 5 threads in case we need separate threads for 1) 2D physics 2) 3D physics 3) rendering 4) GPU texture compression, 5) all other tasks.
|
||||
// We cannot safely increase the Vector size at runtime, so reserve enough up front, but only launch those needed.
|
||||
threads.reserve(5);
|
||||
#endif
|
||||
threads.resize(p_thread_count);
|
||||
|
||||
Thread::Settings settings;
|
||||
#ifdef __APPLE__
|
||||
// The default stack size for new threads on Apple platforms is 512KiB.
|
||||
// This is insufficient when using a library like SPIRV-Cross,
|
||||
// which can generate deep stacks and result in a stack overflow.
|
||||
#ifdef DEV_ENABLED
|
||||
// Debug builds need an even larger stack size.
|
||||
settings.stack_size = 2 * 1024 * 1024; // 2 MiB
|
||||
#else
|
||||
settings.stack_size = 1 * 1024 * 1024; // 1 MiB
|
||||
#endif
|
||||
#endif
|
||||
|
||||
for (uint32_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].index = i;
|
||||
threads[i].pool = this;
|
||||
threads[i].thread.start(&WorkerThreadPool::_thread_function, &threads[i], settings);
|
||||
thread_ids.insert(threads[i].thread.get_id(), i);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerThreadPool::exit_languages_threads() {
|
||||
if (threads.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLock lock(task_mutex);
|
||||
|
||||
// Wait until all threads are idle.
|
||||
_switch_runlevel(RUNLEVEL_PRE_EXIT_LANGUAGES);
|
||||
while (runlevel_data.pre_exit_languages.num_idle_threads != threads.size()) {
|
||||
control_cond_var.wait(lock);
|
||||
}
|
||||
|
||||
// Wait until all threads have detached from scripting languages.
|
||||
_switch_runlevel(RUNLEVEL_EXIT_LANGUAGES);
|
||||
while (runlevel_data.exit_languages.num_exited_threads != threads.size()) {
|
||||
control_cond_var.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkerThreadPool::finish() {
|
||||
if (threads.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(task_mutex);
|
||||
SelfList<Task> *E = low_priority_task_queue.first();
|
||||
while (E) {
|
||||
print_error("Task waiting was never re-claimed: " + E->self()->description);
|
||||
E = E->next();
|
||||
}
|
||||
|
||||
_switch_runlevel(RUNLEVEL_EXIT);
|
||||
}
|
||||
|
||||
for (ThreadData &data : threads) {
|
||||
data.thread.wait_to_finish();
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(task_mutex);
|
||||
for (KeyValue<TaskID, Task *> &E : tasks) {
|
||||
task_allocator.free(E.value);
|
||||
}
|
||||
}
|
||||
|
||||
threads.clear();
|
||||
}
|
||||
|
||||
void WorkerThreadPool::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("add_task", "action", "high_priority", "description"), &WorkerThreadPool::add_task_bind, DEFVAL(false), DEFVAL(String()));
|
||||
ClassDB::bind_method(D_METHOD("is_task_completed", "task_id"), &WorkerThreadPool::is_task_completed);
|
||||
ClassDB::bind_method(D_METHOD("wait_for_task_completion", "task_id"), &WorkerThreadPool::wait_for_task_completion);
|
||||
ClassDB::bind_method(D_METHOD("get_caller_task_id"), &WorkerThreadPool::get_caller_task_id);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("add_group_task", "action", "elements", "tasks_needed", "high_priority", "description"), &WorkerThreadPool::add_group_task, DEFVAL(-1), DEFVAL(false), DEFVAL(String()));
|
||||
ClassDB::bind_method(D_METHOD("is_group_task_completed", "group_id"), &WorkerThreadPool::is_group_task_completed);
|
||||
ClassDB::bind_method(D_METHOD("get_group_processed_element_count", "group_id"), &WorkerThreadPool::get_group_processed_element_count);
|
||||
ClassDB::bind_method(D_METHOD("wait_for_group_task_completion", "group_id"), &WorkerThreadPool::wait_for_group_task_completion);
|
||||
ClassDB::bind_method(D_METHOD("get_caller_group_id"), &WorkerThreadPool::get_caller_group_id);
|
||||
}
|
||||
|
||||
WorkerThreadPool *WorkerThreadPool::get_named_pool(const StringName &p_name) {
|
||||
WorkerThreadPool **pool_ptr = named_pools.getptr(p_name);
|
||||
if (pool_ptr) {
|
||||
return *pool_ptr;
|
||||
} else {
|
||||
WorkerThreadPool *pool = memnew(WorkerThreadPool(false));
|
||||
pool->init();
|
||||
named_pools[p_name] = pool;
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
|
||||
WorkerThreadPool::WorkerThreadPool(bool p_singleton) {
|
||||
if (p_singleton) {
|
||||
singleton = this;
|
||||
}
|
||||
}
|
||||
|
||||
WorkerThreadPool::~WorkerThreadPool() {
|
||||
finish();
|
||||
|
||||
if (this == singleton) {
|
||||
singleton = nullptr;
|
||||
for (KeyValue<StringName, WorkerThreadPool *> &E : named_pools) {
|
||||
E.value->finish();
|
||||
memdelete(E.value);
|
||||
}
|
||||
named_pools.clear();
|
||||
}
|
||||
}
|
302
core/object/worker_thread_pool.h
Normal file
302
core/object/worker_thread_pool.h
Normal file
@@ -0,0 +1,302 @@
|
||||
/**************************************************************************/
|
||||
/* worker_thread_pool.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/os/condition_variable.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/os/semaphore.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
#include "core/templates/paged_allocator.h"
|
||||
#include "core/templates/rid.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
|
||||
class WorkerThreadPool : public Object {
|
||||
GDCLASS(WorkerThreadPool, Object)
|
||||
public:
|
||||
enum {
|
||||
INVALID_TASK_ID = -1
|
||||
};
|
||||
|
||||
typedef int64_t TaskID;
|
||||
typedef int64_t GroupID;
|
||||
|
||||
private:
|
||||
struct Task;
|
||||
|
||||
struct BaseTemplateUserdata {
|
||||
virtual void callback() {}
|
||||
virtual void callback_indexed(uint32_t p_index) {}
|
||||
virtual ~BaseTemplateUserdata() {}
|
||||
};
|
||||
|
||||
struct Group {
|
||||
GroupID self = -1;
|
||||
SafeNumeric<uint32_t> index;
|
||||
SafeNumeric<uint32_t> completed_index;
|
||||
uint32_t max = 0;
|
||||
Semaphore done_semaphore;
|
||||
SafeFlag completed;
|
||||
SafeNumeric<uint32_t> finished;
|
||||
uint32_t tasks_used = 0;
|
||||
};
|
||||
|
||||
struct Task {
|
||||
TaskID self = -1;
|
||||
Callable callable;
|
||||
void (*native_func)(void *) = nullptr;
|
||||
void (*native_group_func)(void *, uint32_t) = nullptr;
|
||||
void *native_func_userdata = nullptr;
|
||||
String description;
|
||||
Semaphore done_semaphore; // For user threads awaiting.
|
||||
bool completed : 1;
|
||||
bool pending_notify_yield_over : 1;
|
||||
bool is_pump_task : 1;
|
||||
Group *group = nullptr;
|
||||
SelfList<Task> task_elem;
|
||||
uint32_t waiting_pool = 0;
|
||||
uint32_t waiting_user = 0;
|
||||
bool low_priority = false;
|
||||
BaseTemplateUserdata *template_userdata = nullptr;
|
||||
int pool_thread_index = -1;
|
||||
|
||||
void free_template_userdata();
|
||||
Task() :
|
||||
completed(false),
|
||||
pending_notify_yield_over(false),
|
||||
is_pump_task(false),
|
||||
task_elem(this) {}
|
||||
};
|
||||
|
||||
static const uint32_t TASKS_PAGE_SIZE = 1024;
|
||||
static const uint32_t GROUPS_PAGE_SIZE = 256;
|
||||
|
||||
PagedAllocator<Task, false, TASKS_PAGE_SIZE> task_allocator;
|
||||
PagedAllocator<Group, false, GROUPS_PAGE_SIZE> group_allocator;
|
||||
|
||||
SelfList<Task>::List low_priority_task_queue;
|
||||
SelfList<Task>::List task_queue;
|
||||
|
||||
BinaryMutex task_mutex;
|
||||
|
||||
struct ThreadData {
|
||||
static Task *const YIELDING; // Too bad constexpr doesn't work here.
|
||||
|
||||
uint32_t index = 0;
|
||||
Thread thread;
|
||||
bool signaled : 1;
|
||||
bool yield_is_over : 1;
|
||||
bool pre_exited_languages : 1;
|
||||
bool exited_languages : 1;
|
||||
bool has_pump_task : 1; // Threads can only have one pump task.
|
||||
Task *current_task = nullptr;
|
||||
Task *awaited_task = nullptr; // Null if not awaiting the condition variable, or special value (YIELDING).
|
||||
ConditionVariable cond_var;
|
||||
WorkerThreadPool *pool = nullptr;
|
||||
|
||||
ThreadData() :
|
||||
signaled(false),
|
||||
yield_is_over(false),
|
||||
pre_exited_languages(false),
|
||||
exited_languages(false),
|
||||
has_pump_task(false) {}
|
||||
};
|
||||
|
||||
TightLocalVector<ThreadData> threads;
|
||||
enum Runlevel {
|
||||
RUNLEVEL_NORMAL,
|
||||
RUNLEVEL_PRE_EXIT_LANGUAGES, // Block adding new tasks
|
||||
RUNLEVEL_EXIT_LANGUAGES, // All threads detach from scripting threads.
|
||||
RUNLEVEL_EXIT,
|
||||
} runlevel = RUNLEVEL_NORMAL;
|
||||
union { // Cleared on every runlevel change.
|
||||
struct {
|
||||
uint32_t num_idle_threads;
|
||||
} pre_exit_languages;
|
||||
struct {
|
||||
uint32_t num_exited_threads;
|
||||
} exit_languages;
|
||||
} runlevel_data;
|
||||
ConditionVariable control_cond_var;
|
||||
|
||||
HashMap<Thread::ID, int> thread_ids;
|
||||
HashMap<
|
||||
TaskID,
|
||||
Task *,
|
||||
HashMapHasherDefault,
|
||||
HashMapComparatorDefault<TaskID>,
|
||||
PagedAllocator<HashMapElement<TaskID, Task *>, false, TASKS_PAGE_SIZE>>
|
||||
tasks;
|
||||
HashMap<
|
||||
GroupID,
|
||||
Group *,
|
||||
HashMapHasherDefault,
|
||||
HashMapComparatorDefault<GroupID>,
|
||||
PagedAllocator<HashMapElement<GroupID, Group *>, false, GROUPS_PAGE_SIZE>>
|
||||
groups;
|
||||
|
||||
uint32_t max_low_priority_threads = 0;
|
||||
uint32_t low_priority_threads_used = 0;
|
||||
uint32_t notify_index = 0; // For rotating across threads, no help distributing load.
|
||||
|
||||
uint64_t last_task = 1;
|
||||
int pump_task_count = 0;
|
||||
|
||||
static HashMap<StringName, WorkerThreadPool *> named_pools;
|
||||
|
||||
static void _thread_function(void *p_user);
|
||||
|
||||
void _process_task(Task *task);
|
||||
|
||||
void _post_tasks(Task **p_tasks, uint32_t p_count, bool p_high_priority, MutexLock<BinaryMutex> &p_lock, bool p_pump_task);
|
||||
void _notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count);
|
||||
|
||||
bool _try_promote_low_priority_task();
|
||||
|
||||
static WorkerThreadPool *singleton;
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
static const uint32_t MAX_UNLOCKABLE_LOCKS = 2;
|
||||
struct UnlockableLocks {
|
||||
THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> *ulock = nullptr;
|
||||
uint32_t rc = 0;
|
||||
};
|
||||
static thread_local UnlockableLocks unlockable_locks[MAX_UNLOCKABLE_LOCKS];
|
||||
#endif
|
||||
|
||||
TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description, bool p_pump_task = false);
|
||||
GroupID _add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description);
|
||||
|
||||
template <typename C, typename M, typename U>
|
||||
struct TaskUserData : public BaseTemplateUserdata {
|
||||
C *instance;
|
||||
M method;
|
||||
U userdata;
|
||||
virtual void callback() override {
|
||||
(instance->*method)(userdata);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename C, typename M, typename U>
|
||||
struct GroupUserData : public BaseTemplateUserdata {
|
||||
C *instance;
|
||||
M method;
|
||||
U userdata;
|
||||
virtual void callback_indexed(uint32_t p_index) override {
|
||||
(instance->*method)(p_index, userdata);
|
||||
}
|
||||
};
|
||||
|
||||
void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task);
|
||||
|
||||
void _switch_runlevel(Runlevel p_runlevel);
|
||||
bool _handle_runlevel(ThreadData *p_thread_data, MutexLock<BinaryMutex> &p_lock);
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
static uint32_t _thread_enter_unlock_allowance_zone(THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &p_ulock);
|
||||
#endif
|
||||
|
||||
void _lock_unlockable_mutexes();
|
||||
void _unlock_unlockable_mutexes();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
template <typename C, typename M, typename U>
|
||||
TaskID add_template_task(C *p_instance, M p_method, U p_userdata, bool p_high_priority = false, const String &p_description = String()) {
|
||||
typedef TaskUserData<C, M, U> TUD;
|
||||
TUD *ud = memnew(TUD);
|
||||
ud->instance = p_instance;
|
||||
ud->method = p_method;
|
||||
ud->userdata = p_userdata;
|
||||
return _add_task(Callable(), nullptr, nullptr, ud, p_high_priority, p_description);
|
||||
}
|
||||
TaskID add_native_task(void (*p_func)(void *), void *p_userdata, bool p_high_priority = false, const String &p_description = String());
|
||||
TaskID add_task(const Callable &p_action, bool p_high_priority = false, const String &p_description = String(), bool p_pump_task = false);
|
||||
TaskID add_task_bind(const Callable &p_action, bool p_high_priority = false, const String &p_description = String());
|
||||
|
||||
bool is_task_completed(TaskID p_task_id) const;
|
||||
Error wait_for_task_completion(TaskID p_task_id);
|
||||
|
||||
void yield();
|
||||
void notify_yield_over(TaskID p_task_id);
|
||||
|
||||
template <typename C, typename M, typename U>
|
||||
GroupID add_template_group_task(C *p_instance, M p_method, U p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String()) {
|
||||
typedef GroupUserData<C, M, U> GroupUD;
|
||||
GroupUD *ud = memnew(GroupUD);
|
||||
ud->instance = p_instance;
|
||||
ud->method = p_method;
|
||||
ud->userdata = p_userdata;
|
||||
return _add_group_task(Callable(), nullptr, nullptr, ud, p_elements, p_tasks, p_high_priority, p_description);
|
||||
}
|
||||
GroupID add_native_group_task(void (*p_func)(void *, uint32_t), void *p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String());
|
||||
GroupID add_group_task(const Callable &p_action, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String());
|
||||
uint32_t get_group_processed_element_count(GroupID p_group) const;
|
||||
bool is_group_task_completed(GroupID p_group) const;
|
||||
void wait_for_group_task_completion(GroupID p_group);
|
||||
|
||||
_FORCE_INLINE_ int get_thread_count() const {
|
||||
#ifdef THREADS_ENABLED
|
||||
return threads.size();
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Note: Do not use this unless you know what you are doing, and it is absolutely necessary. Main thread pool (`get_singleton()`) should be preferred instead.
|
||||
static WorkerThreadPool *get_named_pool(const StringName &p_name);
|
||||
|
||||
static WorkerThreadPool *get_singleton() { return singleton; }
|
||||
int get_thread_index() const;
|
||||
TaskID get_caller_task_id() const;
|
||||
GroupID get_caller_group_id() const;
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
_ALWAYS_INLINE_ static uint32_t thread_enter_unlock_allowance_zone(const MutexLock<BinaryMutex> &p_lock) { return _thread_enter_unlock_allowance_zone(p_lock._get_lock()); }
|
||||
template <int Tag>
|
||||
_ALWAYS_INLINE_ static uint32_t thread_enter_unlock_allowance_zone(const SafeBinaryMutex<Tag> &p_mutex) { return _thread_enter_unlock_allowance_zone(p_mutex._get_lock()); }
|
||||
static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id);
|
||||
#else
|
||||
static uint32_t thread_enter_unlock_allowance_zone(const MutexLock<BinaryMutex> &p_lock) { return UINT32_MAX; }
|
||||
template <int Tag>
|
||||
static uint32_t thread_enter_unlock_allowance_zone(const SafeBinaryMutex<Tag> &p_mutex) { return UINT32_MAX; }
|
||||
static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {}
|
||||
#endif
|
||||
|
||||
void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3);
|
||||
void exit_languages_threads();
|
||||
void finish();
|
||||
WorkerThreadPool(bool p_singleton = true);
|
||||
~WorkerThreadPool();
|
||||
};
|
Reference in New Issue
Block a user