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

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

12
core/object/SCsub Normal file
View 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")

View 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);
}
}
}

View 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

File diff suppressed because it is too large Load Diff

576
core/object/class_db.h Normal file
View 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))

View 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)

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

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
View 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
View 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
View 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));
}

View 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;
}

View 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(); }
};

View 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() {
}

View 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();
};

View 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);
}
}

View 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();
};

View 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);
}

View 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
View 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
View 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);

View 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();
}
}

View 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();
};