diff options
Diffstat (limited to 'core/object')
-rw-r--r-- | core/object/SCsub | 12 | ||||
-rw-r--r-- | core/object/callable_method_pointer.cpp | 93 | ||||
-rw-r--r-- | core/object/callable_method_pointer.h | 240 | ||||
-rw-r--r-- | core/object/class_db.cpp | 1670 | ||||
-rw-r--r-- | core/object/class_db.h | 450 | ||||
-rw-r--r-- | core/object/make_virtuals.py | 162 | ||||
-rw-r--r-- | core/object/message_queue.cpp | 374 | ||||
-rw-r--r-- | core/object/message_queue.h | 98 | ||||
-rw-r--r-- | core/object/method_bind.cpp | 139 | ||||
-rw-r--r-- | core/object/method_bind.h | 581 | ||||
-rw-r--r-- | core/object/object.cpp | 2057 | ||||
-rw-r--r-- | core/object/object.h | 896 | ||||
-rw-r--r-- | core/object/object_id.h | 63 | ||||
-rw-r--r-- | core/object/ref_counted.cpp | 127 | ||||
-rw-r--r-- | core/object/ref_counted.h | 297 | ||||
-rw-r--r-- | core/object/script_language.cpp | 598 | ||||
-rw-r--r-- | core/object/script_language.h | 428 | ||||
-rw-r--r-- | core/object/undo_redo.cpp | 559 | ||||
-rw-r--r-- | core/object/undo_redo.h | 140 |
19 files changed, 8984 insertions, 0 deletions
diff --git a/core/object/SCsub b/core/object/SCsub new file mode 100644 index 0000000000..dc116aeb19 --- /dev/null +++ b/core/object/SCsub @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +Import("env") + +import make_virtuals +from platform_methods import run_in_subprocess + +env.CommandNoCache(["gdvirtual.gen.inc"], "make_virtuals.py", run_in_subprocess(make_virtuals.run)) + +env_object = env.Clone() + +env_object.add_source_files(env.core_sources, "*.cpp") diff --git a/core/object/callable_method_pointer.cpp b/core/object/callable_method_pointer.cpp new file mode 100644 index 0000000000..2bfaef744d --- /dev/null +++ b/core/object/callable_method_pointer.cpp @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* callable_method_pointer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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; + } + + for (uint32_t i = 0; i < a->comp_size; i++) { + if (a->comp_ptr[i] != b->comp_ptr[i]) { + return false; + } + } + + return true; +} + +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; + } + + for (uint32_t i = 0; i < a->comp_size; i++) { + if (a->comp_ptr[i] == b->comp_ptr[i]) { + continue; + } + + return a->comp_ptr[i] < b->comp_ptr[i]; + } + + return false; +} + +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_djb2_one_32(comp_ptr[i]); + } else { + h = hash_djb2_one_32(comp_ptr[i], h); + } + } +} diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h new file mode 100644 index 0000000000..8ba01be4e4 --- /dev/null +++ b/core/object/callable_method_pointer.h @@ -0,0 +1,240 @@ +/*************************************************************************/ +/* callable_method_pointer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef CALLABLE_METHOD_POINTER_H +#define CALLABLE_METHOD_POINTER_H + +#include "core/object/object.h" +#include "core/templates/hashfuncs.h" +#include "core/templates/simple_type.h" +#include "core/variant/binder_common.h" +#include "core/variant/callable.h" + +class CallableCustomMethodPointerBase : public CallableCustom { + uint32_t *comp_ptr; + uint32_t comp_size; + uint32_t h; +#ifdef DEBUG_METHODS_ENABLED + const char *text = ""; +#endif + 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: +#ifdef DEBUG_METHODS_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 + virtual CompareEqualFunc get_compare_equal_func() const; + virtual CompareLessFunc get_compare_less_func() const; + + virtual uint32_t hash() const; +}; + +template <class T, class... P> +class CallableCustomMethodPointer : public CallableCustomMethodPointerBase { + struct Data { + T *instance; +#ifdef DEBUG_ENABLED + uint64_t object_id; +#endif + void (T::*method)(P...); + } data; + +public: + virtual ObjectID get_object() const { +#ifdef DEBUG_ENABLED + if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) { + return ObjectID(); + } +#endif + return data.instance->get_instance_id(); + } + + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_MSG(ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr, "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); +#endif + call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error); + } + + CallableCustomMethodPointer(T *p_instance, void (T::*p_method)(P...)) { + memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. + data.instance = p_instance; +#ifdef DEBUG_ENABLED + data.object_id = p_instance->get_instance_id(); +#endif + data.method = p_method; + _setup((uint32_t *)&data, sizeof(Data)); + } +}; + +template <class T, class... P> +Callable create_custom_callable_function_pointer(T *p_instance, +#ifdef DEBUG_METHODS_ENABLED + const char *p_func_text, +#endif + void (T::*p_method)(P...)) { + typedef CallableCustomMethodPointer<T, P...> CCMP; // Messes with memnew otherwise. + CCMP *ccmp = memnew(CCMP(p_instance, p_method)); +#ifdef DEBUG_METHODS_ENABLED + ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. +#endif + return Callable(ccmp); +} + +// VERSION WITH RETURN + +template <class T, class R, class... P> +class CallableCustomMethodPointerRet : public CallableCustomMethodPointerBase { + struct Data { + T *instance; +#ifdef DEBUG_ENABLED + uint64_t object_id; +#endif + R(T::*method) + (P...); + } data; + +public: + virtual ObjectID get_object() const { +#ifdef DEBUG_ENABLED + if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) { + return ObjectID(); + } +#endif + return data.instance->get_instance_id(); + } + + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_MSG(ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr, "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); +#endif + call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); + } + + CallableCustomMethodPointerRet(T *p_instance, R (T::*p_method)(P...)) { + memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. + data.instance = p_instance; +#ifdef DEBUG_ENABLED + data.object_id = p_instance->get_instance_id(); +#endif + data.method = p_method; + _setup((uint32_t *)&data, sizeof(Data)); + } +}; + +template <class T, class R, class... P> +Callable create_custom_callable_function_pointer(T *p_instance, +#ifdef DEBUG_METHODS_ENABLED + const char *p_func_text, +#endif + R (T::*p_method)(P...)) { + typedef CallableCustomMethodPointerRet<T, R, P...> CCMP; // Messes with memnew otherwise. + CCMP *ccmp = memnew(CCMP(p_instance, p_method)); +#ifdef DEBUG_METHODS_ENABLED + ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. +#endif + return Callable(ccmp); +} + +// CONST VERSION WITH RETURN + +template <class T, class R, class... P> +class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase { + struct Data { + T *instance; +#ifdef DEBUG_ENABLED + uint64_t object_id; +#endif + R(T::*method) + (P...) const; + } data; + +public: + virtual ObjectID get_object() const { +#ifdef DEBUG_ENABLED + if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) { + return ObjectID(); + } +#endif + return data.instance->get_instance_id(); + } + + virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_MSG(ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr, "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); +#endif + call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); + } + + CallableCustomMethodPointerRetC(T *p_instance, R (T::*p_method)(P...) const) { + memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. + data.instance = p_instance; +#ifdef DEBUG_ENABLED + data.object_id = p_instance->get_instance_id(); +#endif + data.method = p_method; + _setup((uint32_t *)&data, sizeof(Data)); + } +}; + +template <class T, class R, class... P> +Callable create_custom_callable_function_pointer(T *p_instance, +#ifdef DEBUG_METHODS_ENABLED + const char *p_func_text, +#endif + R (T::*p_method)(P...) const) { + typedef CallableCustomMethodPointerRetC<T, R, P...> CCMP; // Messes with memnew otherwise. + CCMP *ccmp = memnew(CCMP(p_instance, p_method)); +#ifdef DEBUG_METHODS_ENABLED + ccmp->set_text(p_func_text + 1); // Try to get rid of the ampersand. +#endif + return Callable(ccmp); +} + +#ifdef DEBUG_METHODS_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 + +#endif // CALLABLE_METHOD_POINTER_H diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp new file mode 100644 index 0000000000..b29b2bd421 --- /dev/null +++ b/core/object/class_db.cpp @@ -0,0 +1,1670 @@ +/*************************************************************************/ +/* class_db.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "class_db.h" + +#include "core/config/engine.h" +#include "core/os/mutex.h" +#include "core/version.h" + +#define OBJTYPE_RLOCK RWLockRead _rw_lockr_(lock); +#define OBJTYPE_WLOCK RWLockWrite _rw_lockw_(lock); + +#ifdef DEBUG_METHODS_ENABLED + +MethodDefinition D_METHOD(const char *p_name) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.push_back(StaticCString::create(p_arg1)); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(2); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(3); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(4); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(5); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(6); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(7); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + md.args.write[6] = StaticCString::create(p_arg7); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(8); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + md.args.write[6] = StaticCString::create(p_arg7); + md.args.write[7] = StaticCString::create(p_arg8); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(9); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + md.args.write[6] = StaticCString::create(p_arg7); + md.args.write[7] = StaticCString::create(p_arg8); + md.args.write[8] = StaticCString::create(p_arg9); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(10); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + md.args.write[6] = StaticCString::create(p_arg7); + md.args.write[7] = StaticCString::create(p_arg8); + md.args.write[8] = StaticCString::create(p_arg9); + md.args.write[9] = StaticCString::create(p_arg10); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10, const char *p_arg11) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(11); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + md.args.write[6] = StaticCString::create(p_arg7); + md.args.write[7] = StaticCString::create(p_arg8); + md.args.write[8] = StaticCString::create(p_arg9); + md.args.write[9] = StaticCString::create(p_arg10); + md.args.write[10] = StaticCString::create(p_arg11); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10, const char *p_arg11, const char *p_arg12) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(12); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + md.args.write[6] = StaticCString::create(p_arg7); + md.args.write[7] = StaticCString::create(p_arg8); + md.args.write[8] = StaticCString::create(p_arg9); + md.args.write[9] = StaticCString::create(p_arg10); + md.args.write[10] = StaticCString::create(p_arg11); + md.args.write[11] = StaticCString::create(p_arg12); + return md; +} + +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10, const char *p_arg11, const char *p_arg12, const char *p_arg13) { + MethodDefinition md; + md.name = StaticCString::create(p_name); + md.args.resize(13); + md.args.write[0] = StaticCString::create(p_arg1); + md.args.write[1] = StaticCString::create(p_arg2); + md.args.write[2] = StaticCString::create(p_arg3); + md.args.write[3] = StaticCString::create(p_arg4); + md.args.write[4] = StaticCString::create(p_arg5); + md.args.write[5] = StaticCString::create(p_arg6); + md.args.write[6] = StaticCString::create(p_arg7); + md.args.write[7] = StaticCString::create(p_arg8); + md.args.write[8] = StaticCString::create(p_arg9); + md.args.write[9] = StaticCString::create(p_arg10); + md.args.write[10] = StaticCString::create(p_arg11); + md.args.write[11] = StaticCString::create(p_arg12); + md.args.write[12] = StaticCString::create(p_arg13); + return md; +} + +#endif + +ClassDB::APIType ClassDB::current_api = API_CORE; + +void ClassDB::set_current_api(APIType p_api) { + current_api = p_api; +} + +ClassDB::APIType ClassDB::get_current_api() { + return current_api; +} + +HashMap<StringName, ClassDB::ClassInfo> ClassDB::classes; +HashMap<StringName, StringName> ClassDB::resource_base_extensions; +HashMap<StringName, StringName> ClassDB::compat_classes; + +bool ClassDB::_is_parent_class(const StringName &p_class, const StringName &p_inherits) { + if (!classes.has(p_class)) { + return false; + } + + StringName inherits = p_class; + while (inherits.operator String().length()) { + if (inherits == p_inherits) { + return true; + } + inherits = _get_parent_class(inherits); + } + + return false; +} + +bool ClassDB::is_parent_class(const StringName &p_class, const StringName &p_inherits) { + OBJTYPE_RLOCK; + + return _is_parent_class(p_class, p_inherits); +} + +void ClassDB::get_class_list(List<StringName> *p_classes) { + OBJTYPE_RLOCK; + + const StringName *k = nullptr; + + while ((k = classes.next(k))) { + p_classes->push_back(*k); + } + + p_classes->sort(); +} + +void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) { + OBJTYPE_RLOCK; + + const StringName *k = nullptr; + + while ((k = classes.next(k))) { + if (*k != p_class && _is_parent_class(*k, p_class)) { + p_classes->push_back(*k); + } + } +} + +void ClassDB::get_direct_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) { + OBJTYPE_RLOCK; + + const StringName *k = nullptr; + + while ((k = classes.next(k))) { + if (*k != p_class && _get_parent_class(*k) == p_class) { + p_classes->push_back(*k); + } + } +} + +StringName ClassDB::get_parent_class_nocheck(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + if (!ti) { + return StringName(); + } + return ti->inherits; +} + +StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class) { + if (classes.has(p_class)) { + return p_class; + } + + if (compat_classes.has(p_class)) { + return compat_classes[p_class]; + } + + return p_class; +} + +StringName ClassDB::_get_parent_class(const StringName &p_class) { + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_COND_V_MSG(!ti, StringName(), "Cannot get class '" + String(p_class) + "'."); + return ti->inherits; +} + +StringName ClassDB::get_parent_class(const StringName &p_class) { + OBJTYPE_RLOCK; + + return _get_parent_class(p_class); +} + +ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + + ERR_FAIL_COND_V_MSG(!ti, API_NONE, "Cannot get class '" + String(p_class) + "'."); + return ti->api; +} + +uint64_t ClassDB::get_api_hash(APIType p_api) { + OBJTYPE_RLOCK; +#ifdef DEBUG_METHODS_ENABLED + + uint64_t hash = hash_djb2_one_64(HashMapHasherDefault::hash(VERSION_FULL_CONFIG)); + + List<StringName> names; + + const StringName *k = nullptr; + + while ((k = classes.next(k))) { + names.push_back(*k); + } + //must be alphabetically sorted for hash to compute + names.sort_custom<StringName::AlphCompare>(); + + for (const StringName &E : names) { + ClassInfo *t = classes.getptr(E); + ERR_FAIL_COND_V_MSG(!t, 0, "Cannot get class '" + String(E) + "'."); + if (t->api != p_api || !t->exposed) { + continue; + } + hash = hash_djb2_one_64(t->name.hash(), hash); + hash = hash_djb2_one_64(t->inherits.hash(), hash); + + { //methods + + List<StringName> snames; + + k = nullptr; + + while ((k = t->method_map.next(k))) { + String name = k->operator String(); + + ERR_CONTINUE(name.is_empty()); + + if (name[0] == '_') { + continue; // Ignore non-virtual methods that start with an underscore + } + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + for (const StringName &F : snames) { + MethodBind *mb = t->method_map[F]; + hash = hash_djb2_one_64(mb->get_name().hash(), hash); + hash = hash_djb2_one_64(mb->get_argument_count(), hash); + hash = hash_djb2_one_64(mb->get_argument_type(-1), hash); //return + + for (int i = 0; i < mb->get_argument_count(); i++) { + const PropertyInfo info = mb->get_argument_info(i); + hash = hash_djb2_one_64(info.type, hash); + hash = hash_djb2_one_64(info.name.hash(), hash); + hash = hash_djb2_one_64(info.hint, hash); + hash = hash_djb2_one_64(info.hint_string.hash(), hash); + } + + hash = hash_djb2_one_64(mb->get_default_argument_count(), hash); + + for (int i = 0; i < mb->get_default_argument_count(); i++) { + //hash should not change, i hope for tis + Variant da = mb->get_default_argument(i); + hash = hash_djb2_one_64(da.hash(), hash); + } + + hash = hash_djb2_one_64(mb->get_hint_flags(), hash); + } + } + + { //constants + + List<StringName> snames; + + k = nullptr; + + while ((k = t->constant_map.next(k))) { + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + for (const StringName &F : snames) { + hash = hash_djb2_one_64(F.hash(), hash); + hash = hash_djb2_one_64(t->constant_map[F], hash); + } + } + + { //signals + + List<StringName> snames; + + k = nullptr; + + while ((k = t->signal_map.next(k))) { + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + for (const StringName &F : snames) { + MethodInfo &mi = t->signal_map[F]; + hash = hash_djb2_one_64(F.hash(), hash); + for (int i = 0; i < mi.arguments.size(); i++) { + hash = hash_djb2_one_64(mi.arguments[i].type, hash); + } + } + } + + { //properties + + List<StringName> snames; + + k = nullptr; + + while ((k = t->property_setget.next(k))) { + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + for (const StringName &F : snames) { + PropertySetGet *psg = t->property_setget.getptr(F); + ERR_FAIL_COND_V(!psg, 0); + + hash = hash_djb2_one_64(F.hash(), hash); + hash = hash_djb2_one_64(psg->setter.hash(), hash); + hash = hash_djb2_one_64(psg->getter.hash(), hash); + } + } + + //property list + for (const PropertyInfo &F : t->property_list) { + hash = hash_djb2_one_64(F.name.hash(), hash); + hash = hash_djb2_one_64(F.type, hash); + hash = hash_djb2_one_64(F.hint, hash); + hash = hash_djb2_one_64(F.hint_string.hash(), hash); + hash = hash_djb2_one_64(F.usage, hash); + } + } + + return hash; +#else + return 0; +#endif +} + +bool ClassDB::class_exists(const StringName &p_class) { + OBJTYPE_RLOCK; + return classes.has(p_class); +} + +void ClassDB::add_compatibility_class(const StringName &p_class, const StringName &p_fallback) { + OBJTYPE_WLOCK; + compat_classes[p_class] = p_fallback; +} + +thread_local bool initializing_with_extension = false; +thread_local ObjectNativeExtension *initializing_extension = nullptr; +thread_local GDExtensionClassInstancePtr initializing_extension_instance = nullptr; + +void ClassDB::instance_get_native_extension_data(ObjectNativeExtension **r_extension, GDExtensionClassInstancePtr *r_extension_instance, Object *p_base) { + if (initializing_with_extension) { + *r_extension = initializing_extension; + *r_extension_instance = initializing_extension_instance; + initializing_with_extension = false; + initializing_extension->set_object_instance(*r_extension_instance, p_base); + } else { + *r_extension = nullptr; + *r_extension_instance = nullptr; + } +} + +Object *ClassDB::instantiate(const StringName &p_class) { + ClassInfo *ti; + { + OBJTYPE_RLOCK; + ti = classes.getptr(p_class); + if (!ti || ti->disabled || !ti->creation_func || (ti->native_extension && !ti->native_extension->create_instance)) { + if (compat_classes.has(p_class)) { + ti = classes.getptr(compat_classes[p_class]); + } + } + ERR_FAIL_COND_V_MSG(!ti, nullptr, "Cannot get class '" + String(p_class) + "'."); + ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled."); + ERR_FAIL_COND_V(!ti->creation_func, nullptr); + } +#ifdef TOOLS_ENABLED + if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) { + ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor."); + return nullptr; + } +#endif + if (ti->native_extension) { + initializing_with_extension = true; + initializing_extension = ti->native_extension; + initializing_extension_instance = ti->native_extension->create_instance(ti->native_extension->class_userdata); + } + return ti->creation_func(); +} + +bool ClassDB::can_instantiate(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_COND_V_MSG(!ti, false, "Cannot get class '" + String(p_class) + "'."); +#ifdef TOOLS_ENABLED + if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) { + return false; + } +#endif + return (!ti->disabled && ti->creation_func != nullptr && !(ti->native_extension && !ti->native_extension->create_instance)); +} + +void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherits) { + OBJTYPE_WLOCK; + + const StringName &name = p_class; + + ERR_FAIL_COND_MSG(classes.has(name), "Class '" + String(p_class) + "' already exists."); + + classes[name] = ClassInfo(); + ClassInfo &ti = classes[name]; + ti.name = name; + ti.inherits = p_inherits; + ti.api = current_api; + + if (ti.inherits) { + ERR_FAIL_COND(!classes.has(ti.inherits)); //it MUST be registered. + ti.inherits_ptr = &classes[ti.inherits]; + + } else { + ti.inherits_ptr = nullptr; + } +} + +#ifdef DEBUG_METHODS_ENABLED +static MethodInfo info_from_bind(MethodBind *p_method) { + MethodInfo minfo; + minfo.name = p_method->get_name(); + minfo.id = p_method->get_method_id(); + + for (int i = 0; i < p_method->get_argument_count(); i++) { + minfo.arguments.push_back(p_method->get_argument_info(i)); + } + + minfo.return_val = p_method->get_return_info(); + minfo.flags = p_method->get_hint_flags(); + + for (int i = 0; i < p_method->get_argument_count(); i++) { + if (p_method->has_default_argument(i)) { + minfo.default_arguments.push_back(p_method->get_default_argument(i)); + } + } + + return minfo; +} +#endif + +void ClassDB::get_method_list(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance, bool p_exclude_from_properties) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->disabled) { + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + continue; + } + +#ifdef DEBUG_METHODS_ENABLED + + for (const MethodInfo &E : type->virtual_methods) { + p_methods->push_back(E); + } + + for (const StringName &E : type->method_order) { + if (p_exclude_from_properties && type->methods_in_properties.has(E)) { + continue; + } + + MethodBind *method = type->method_map.get(E); + MethodInfo minfo = info_from_bind(method); + + p_methods->push_back(minfo); + } + +#else + + const StringName *K = nullptr; + + while ((K = type->method_map.next(K))) { + MethodBind *m = type->method_map[*K]; + MethodInfo mi; + mi.name = m->get_name(); + p_methods->push_back(mi); + } + +#endif + + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } +} + +bool ClassDB::get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance, bool p_exclude_from_properties) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->disabled) { + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + continue; + } + +#ifdef DEBUG_METHODS_ENABLED + MethodBind **method = type->method_map.getptr(p_method); + if (method && *method) { + if (r_info != nullptr) { + MethodInfo minfo = info_from_bind(*method); + *r_info = minfo; + } + return true; + } else if (type->virtual_methods_map.has(p_method)) { + if (r_info) { + *r_info = type->virtual_methods_map[p_method]; + } + return true; + } +#else + if (type->method_map.has(p_method)) { + if (r_info) { + MethodBind *m = type->method_map[p_method]; + MethodInfo mi; + mi.name = m->get_name(); + *r_info = mi; + } + return true; + } +#endif + + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } + + return false; +} + +MethodBind *ClassDB::get_method(const StringName &p_class, const StringName &p_name) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + MethodBind **method = type->method_map.getptr(p_name); + if (method && *method) { + return *method; + } + type = type->inherits_ptr; + } + return nullptr; +} + +void ClassDB::bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int p_constant) { + OBJTYPE_WLOCK; + + ClassInfo *type = classes.getptr(p_class); + + ERR_FAIL_COND(!type); + + if (type->constant_map.has(p_name)) { + ERR_FAIL(); + } + + type->constant_map[p_name] = p_constant; + + String enum_name = p_enum; + if (enum_name != String()) { + if (enum_name.find(".") != -1) { + enum_name = enum_name.get_slicec('.', 1); + } + + List<StringName> *constants_list = type->enum_map.getptr(enum_name); + + if (constants_list) { + constants_list->push_back(p_name); + } else { + List<StringName> new_list; + new_list.push_back(p_name); + type->enum_map[enum_name] = new_list; + } + } + +#ifdef DEBUG_METHODS_ENABLED + type->constant_order.push_back(p_name); +#endif +} + +void ClassDB::get_integer_constant_list(const StringName &p_class, List<String> *p_constants, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { +#ifdef DEBUG_METHODS_ENABLED + for (const StringName &E : type->constant_order) { + p_constants->push_back(E); + } +#else + const StringName *K = nullptr; + + while ((K = type->constant_map.next(K))) { + p_constants->push_back(*K); + } + +#endif + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } +} + +int ClassDB::get_integer_constant(const StringName &p_class, const StringName &p_name, bool *p_success) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + int *constant = type->constant_map.getptr(p_name); + if (constant) { + if (p_success) { + *p_success = true; + } + return *constant; + } + + type = type->inherits_ptr; + } + + if (p_success) { + *p_success = false; + } + + return 0; +} + +bool ClassDB::has_integer_constant(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->constant_map.has(p_name)) { + return true; + } + if (p_no_inheritance) { + return false; + } + + type = type->inherits_ptr; + } + + return false; +} + +StringName ClassDB::get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + const StringName *k = nullptr; + while ((k = type->enum_map.next(k))) { + List<StringName> &constants_list = type->enum_map.get(*k); + const List<StringName>::Element *found = constants_list.find(p_name); + if (found) { + return *k; + } + } + + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } + + return StringName(); +} + +void ClassDB::get_enum_list(const StringName &p_class, List<StringName> *p_enums, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + const StringName *k = nullptr; + while ((k = type->enum_map.next(k))) { + p_enums->push_back(*k); + } + + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } +} + +void ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_enum, List<StringName> *p_constants, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + const List<StringName> *constants = type->enum_map.getptr(p_enum); + + if (constants) { + for (const List<StringName>::Element *E = constants->front(); E; E = E->next()) { + p_constants->push_back(E->get()); + } + } + + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } +} + +bool ClassDB::has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->enum_map.has(p_name)) { + return true; + } + if (p_no_inheritance) { + return false; + } + + type = type->inherits_ptr; + } + + return false; +} + +void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal) { + OBJTYPE_WLOCK; + + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + StringName sname = p_signal.name; + +#ifdef DEBUG_METHODS_ENABLED + ClassInfo *check = type; + while (check) { + ERR_FAIL_COND_MSG(check->signal_map.has(sname), "Class '" + String(p_class) + "' already has signal '" + String(sname) + "'."); + check = check->inherits_ptr; + } +#endif + + type->signal_map[sname] = p_signal; +} + +void ClassDB::get_signal_list(const StringName &p_class, List<MethodInfo> *p_signals, bool p_no_inheritance) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + ClassInfo *check = type; + + while (check) { + const StringName *S = nullptr; + while ((S = check->signal_map.next(S))) { + p_signals->push_back(check->signal_map[*S]); + } + + if (p_no_inheritance) { + return; + } + + check = check->inherits_ptr; + } +} + +bool ClassDB::has_signal(const StringName &p_class, const StringName &p_signal, bool p_no_inheritance) { + OBJTYPE_RLOCK; + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + if (check->signal_map.has(p_signal)) { + return true; + } + if (p_no_inheritance) { + return false; + } + check = check->inherits_ptr; + } + + return false; +} + +bool ClassDB::get_signal(const StringName &p_class, const StringName &p_signal, MethodInfo *r_signal) { + OBJTYPE_RLOCK; + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + if (check->signal_map.has(p_signal)) { + if (r_signal) { + *r_signal = check->signal_map[p_signal]; + } + return true; + } + check = check->inherits_ptr; + } + + return false; +} + +void ClassDB::add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix) { + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_GROUP)); +} + +void ClassDB::add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix) { + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + type->property_list.push_back(PropertyInfo(Variant::NIL, p_name, PROPERTY_HINT_NONE, p_prefix, PROPERTY_USAGE_SUBGROUP)); +} + +// NOTE: For implementation simplicity reasons, this method doesn't allow setters to have optional arguments at the end. +void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index) { + lock.read_lock(); + ClassInfo *type = classes.getptr(p_class); + lock.read_unlock(); + + ERR_FAIL_COND(!type); + + MethodBind *mb_set = nullptr; + if (p_setter) { + mb_set = get_method(p_class, p_setter); +#ifdef DEBUG_METHODS_ENABLED + + ERR_FAIL_COND_MSG(!mb_set, "Invalid setter '" + p_class + "::" + p_setter + "' for property '" + p_pinfo.name + "'."); + + int exp_args = 1 + (p_index >= 0 ? 1 : 0); + ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, "Invalid function for setter '" + p_class + "::" + p_setter + " for property '" + p_pinfo.name + "'."); +#endif + } + + MethodBind *mb_get = nullptr; + if (p_getter) { + mb_get = get_method(p_class, p_getter); +#ifdef DEBUG_METHODS_ENABLED + + ERR_FAIL_COND_MSG(!mb_get, "Invalid getter '" + p_class + "::" + p_getter + "' for property '" + p_pinfo.name + "'."); + + int exp_args = 0 + (p_index >= 0 ? 1 : 0); + ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, "Invalid function for getter '" + p_class + "::" + p_getter + "' for property: '" + p_pinfo.name + "'."); +#endif + } + +#ifdef DEBUG_METHODS_ENABLED + ERR_FAIL_COND_MSG(type->property_setget.has(p_pinfo.name), "Object '" + p_class + "' already has property '" + p_pinfo.name + "'."); +#endif + + OBJTYPE_WLOCK + + type->property_list.push_back(p_pinfo); + type->property_map[p_pinfo.name] = p_pinfo; +#ifdef DEBUG_METHODS_ENABLED + if (mb_get) { + type->methods_in_properties.insert(p_getter); + } + if (mb_set) { + type->methods_in_properties.insert(p_setter); + } +#endif + PropertySetGet psg; + psg.setter = p_setter; + psg.getter = p_getter; + psg._setptr = mb_set; + psg._getptr = mb_get; + psg.index = p_index; + psg.type = p_pinfo.type; + + type->property_setget[p_pinfo.name] = psg; +} + +void ClassDB::set_property_default_value(const StringName &p_class, const StringName &p_name, const Variant &p_default) { + if (!default_values.has(p_class)) { + default_values[p_class] = HashMap<StringName, Variant>(); + } + default_values[p_class][p_name] = p_default; +} + +void ClassDB::add_linked_property(const StringName &p_class, const String &p_property, const String &p_linked_property) { +#ifdef TOOLS_ENABLED + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); + ERR_FAIL_COND(!type); + + ERR_FAIL_COND(!type->property_map.has(p_property)); + ERR_FAIL_COND(!type->property_map.has(p_linked_property)); + + PropertyInfo &pinfo = type->property_map[p_property]; + pinfo.linked_properties.push_back(p_linked_property); +#endif +} + +void ClassDB::get_property_list(const StringName &p_class, List<PropertyInfo> *p_list, bool p_no_inheritance, const Object *p_validator) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + for (const PropertyInfo &pi : check->property_list) { + if (p_validator) { + // Making a copy as we may modify it. + PropertyInfo pi_mut = pi; + p_validator->_validate_property(pi_mut); + p_list->push_back(pi_mut); + } else { + p_list->push_back(pi); + } + } + + if (p_no_inheritance) { + return; + } + check = check->inherits_ptr; + } +} + +bool ClassDB::get_property_info(const StringName &p_class, const StringName &p_property, PropertyInfo *r_info, bool p_no_inheritance, const Object *p_validator) { + OBJTYPE_RLOCK; + + ClassInfo *check = classes.getptr(p_class); + while (check) { + if (check->property_map.has(p_property)) { + PropertyInfo pinfo = check->property_map[p_property]; + if (p_validator) { + p_validator->_validate_property(pinfo); + } + if (r_info) { + *r_info = pinfo; + } + return true; + } + if (p_no_inheritance) { + break; + } + check = check->inherits_ptr; + } + + return false; +} + +bool ClassDB::set_property(Object *p_object, const StringName &p_property, const Variant &p_value, bool *r_valid) { + ERR_FAIL_NULL_V(p_object, false); + + ClassInfo *type = classes.getptr(p_object->get_class_name()); + ClassInfo *check = type; + while (check) { + const PropertySetGet *psg = check->property_setget.getptr(p_property); + if (psg) { + if (!psg->setter) { + if (r_valid) { + *r_valid = false; + } + return true; //return true but do nothing + } + + Callable::CallError ce; + + if (psg->index >= 0) { + Variant index = psg->index; + const Variant *arg[2] = { &index, &p_value }; + //p_object->call(psg->setter,arg,2,ce); + if (psg->_setptr) { + psg->_setptr->call(p_object, arg, 2, ce); + } else { + p_object->call(psg->setter, arg, 2, ce); + } + + } else { + const Variant *arg[1] = { &p_value }; + if (psg->_setptr) { + psg->_setptr->call(p_object, arg, 1, ce); + } else { + p_object->call(psg->setter, arg, 1, ce); + } + } + + if (r_valid) { + *r_valid = ce.error == Callable::CallError::CALL_OK; + } + + return true; + } + + check = check->inherits_ptr; + } + + return false; +} + +bool ClassDB::get_property(Object *p_object, const StringName &p_property, Variant &r_value) { + ERR_FAIL_NULL_V(p_object, false); + + ClassInfo *type = classes.getptr(p_object->get_class_name()); + ClassInfo *check = type; + while (check) { + const PropertySetGet *psg = check->property_setget.getptr(p_property); + if (psg) { + if (!psg->getter) { + return true; //return true but do nothing + } + + if (psg->index >= 0) { + Variant index = psg->index; + const Variant *arg[1] = { &index }; + Callable::CallError ce; + r_value = p_object->call(psg->getter, arg, 1, ce); + + } else { + Callable::CallError ce; + if (psg->_getptr) { + r_value = psg->_getptr->call(p_object, nullptr, 0, ce); + } else { + r_value = p_object->call(psg->getter, nullptr, 0, ce); + } + } + return true; + } + + const int *c = check->constant_map.getptr(p_property); //constants count + if (c) { + r_value = *c; + return true; + } + + if (check->method_map.has(p_property)) { //methods count + r_value = Callable(p_object, p_property); + return true; + } + + if (check->signal_map.has(p_property)) { //signals count + r_value = Signal(p_object, p_property); + return true; + } + + check = check->inherits_ptr; + } + + return false; +} + +int ClassDB::get_property_index(const StringName &p_class, const StringName &p_property, bool *r_is_valid) { + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + const PropertySetGet *psg = check->property_setget.getptr(p_property); + if (psg) { + if (r_is_valid) { + *r_is_valid = true; + } + + return psg->index; + } + + check = check->inherits_ptr; + } + if (r_is_valid) { + *r_is_valid = false; + } + + return -1; +} + +Variant::Type ClassDB::get_property_type(const StringName &p_class, const StringName &p_property, bool *r_is_valid) { + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + const PropertySetGet *psg = check->property_setget.getptr(p_property); + if (psg) { + if (r_is_valid) { + *r_is_valid = true; + } + + return psg->type; + } + + check = check->inherits_ptr; + } + if (r_is_valid) { + *r_is_valid = false; + } + + return Variant::NIL; +} + +StringName ClassDB::get_property_setter(const StringName &p_class, const StringName &p_property) { + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + const PropertySetGet *psg = check->property_setget.getptr(p_property); + if (psg) { + return psg->setter; + } + + check = check->inherits_ptr; + } + + return StringName(); +} + +StringName ClassDB::get_property_getter(const StringName &p_class, const StringName &p_property) { + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + const PropertySetGet *psg = check->property_setget.getptr(p_property); + if (psg) { + return psg->getter; + } + + check = check->inherits_ptr; + } + + return StringName(); +} + +bool ClassDB::has_property(const StringName &p_class, const StringName &p_property, bool p_no_inheritance) { + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + if (check->property_setget.has(p_property)) { + return true; + } + + if (p_no_inheritance) { + break; + } + check = check->inherits_ptr; + } + + return false; +} + +void ClassDB::set_method_flags(const StringName &p_class, const StringName &p_method, int p_flags) { + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + ERR_FAIL_COND(!check); + ERR_FAIL_COND(!check->method_map.has(p_method)); + check->method_map[p_method]->set_hint_flags(p_flags); +} + +bool ClassDB::has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance) { + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + if (check->method_map.has(p_method)) { + return true; + } + if (p_no_inheritance) { + return false; + } + check = check->inherits_ptr; + } + + return false; +} + +void ClassDB::bind_method_custom(const StringName &p_class, MethodBind *p_method) { + ClassInfo *type = classes.getptr(p_class); + if (!type) { + ERR_FAIL_MSG("Couldn't bind custom method '" + p_method->get_name() + "' for instance '" + p_class + "'."); + } + + if (type->method_map.has(p_method->get_name())) { + // overloading not supported + ERR_FAIL_MSG("Method already bound '" + p_class + "::" + p_method->get_name() + "'."); + } + +#ifdef DEBUG_METHODS_ENABLED + type->method_order.push_back(p_method->get_name()); +#endif + + type->method_map[p_method->get_name()] = p_method; +} + +#ifdef DEBUG_METHODS_ENABLED +MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount) { + StringName mdname = method_name.name; +#else +MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const char *method_name, const Variant **p_defs, int p_defcount) { + StringName mdname = StaticCString::create(method_name); +#endif + + OBJTYPE_WLOCK; + ERR_FAIL_COND_V(!p_bind, nullptr); + p_bind->set_name(mdname); + + String instance_type = p_bind->get_instance_class(); + +#ifdef DEBUG_ENABLED + + ERR_FAIL_COND_V_MSG(has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + "."); +#endif + + ClassInfo *type = classes.getptr(instance_type); + if (!type) { + memdelete(p_bind); + ERR_FAIL_V_MSG(nullptr, "Couldn't bind method '" + mdname + "' for instance '" + instance_type + "'."); + } + + if (type->method_map.has(mdname)) { + memdelete(p_bind); + // overloading not supported + ERR_FAIL_V_MSG(nullptr, "Method already bound '" + instance_type + "::" + mdname + "'."); + } + +#ifdef DEBUG_METHODS_ENABLED + + if (method_name.args.size() > p_bind->get_argument_count()) { + memdelete(p_bind); + ERR_FAIL_V_MSG(nullptr, "Method definition provides more arguments than the method actually has '" + instance_type + "::" + mdname + "'."); + } + + p_bind->set_argument_names(method_name.args); + + type->method_order.push_back(mdname); +#endif + + type->method_map[mdname] = p_bind; + + Vector<Variant> defvals; + + defvals.resize(p_defcount); + for (int i = 0; i < p_defcount; i++) { + defvals.write[i] = *p_defs[i]; + } + + p_bind->set_default_arguments(defvals); + p_bind->set_hint_flags(p_flags); + return p_bind; +} + +void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual, const Vector<String> &p_arg_names, bool p_object_core) { + ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'."); + + OBJTYPE_WLOCK; + +#ifdef DEBUG_METHODS_ENABLED + MethodInfo mi = p_method; + if (p_virtual) { + mi.flags |= METHOD_FLAG_VIRTUAL; + } + if (p_object_core) { + mi.flags |= METHOD_FLAG_OBJECT_CORE; + } + if (p_arg_names.size()) { + if (p_arg_names.size() != mi.arguments.size()) { + WARN_PRINT("Mismatch argument name count for virtual function: " + String(p_class) + "::" + p_method.name); + } else { + for (int i = 0; i < p_arg_names.size(); i++) { + mi.arguments[i].name = p_arg_names[i]; + } + } + } + + classes[p_class].virtual_methods.push_back(mi); + classes[p_class].virtual_methods_map[p_method.name] = mi; + +#endif +} + +void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance) { + ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'."); + +#ifdef DEBUG_METHODS_ENABLED + + ClassInfo *type = classes.getptr(p_class); + ClassInfo *check = type; + while (check) { + for (const MethodInfo &E : check->virtual_methods) { + p_methods->push_back(E); + } + + if (p_no_inheritance) { + return; + } + check = check->inherits_ptr; + } + +#endif +} + +void ClassDB::set_class_enabled(const StringName &p_class, bool p_enable) { + OBJTYPE_WLOCK; + + ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'."); + classes[p_class].disabled = !p_enable; +} + +bool ClassDB::is_class_enabled(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + if (!ti || !ti->creation_func) { + if (compat_classes.has(p_class)) { + ti = classes.getptr(compat_classes[p_class]); + } + } + + ERR_FAIL_COND_V_MSG(!ti, false, "Cannot get class '" + String(p_class) + "'."); + return !ti->disabled; +} + +bool ClassDB::is_class_exposed(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_COND_V_MSG(!ti, false, "Cannot get class '" + String(p_class) + "'."); + return ti->exposed; +} + +StringName ClassDB::get_category(const StringName &p_node) { + ERR_FAIL_COND_V(!classes.has(p_node), StringName()); +#ifdef DEBUG_ENABLED + return classes[p_node].category; +#else + return StringName(); +#endif +} + +void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) { + if (resource_base_extensions.has(p_extension)) { + return; + } + + resource_base_extensions[p_extension] = p_class; +} + +void ClassDB::get_resource_base_extensions(List<String> *p_extensions) { + const StringName *K = nullptr; + + while ((K = resource_base_extensions.next(K))) { + p_extensions->push_back(*K); + } +} + +bool ClassDB::is_resource_extension(const StringName &p_extension) { + return resource_base_extensions.has(p_extension); +} + +void ClassDB::get_extensions_for_type(const StringName &p_class, List<String> *p_extensions) { + const StringName *K = nullptr; + + while ((K = resource_base_extensions.next(K))) { + StringName cmp = resource_base_extensions[*K]; + if (is_parent_class(p_class, cmp) || is_parent_class(cmp, p_class)) { + p_extensions->push_back(*K); + } + } +} + +HashMap<StringName, HashMap<StringName, Variant>> ClassDB::default_values; +Set<StringName> ClassDB::default_values_cached; + +Variant ClassDB::class_get_default_property_value(const StringName &p_class, const StringName &p_property, bool *r_valid) { + if (!default_values_cached.has(p_class)) { + if (!default_values.has(p_class)) { + default_values[p_class] = HashMap<StringName, Variant>(); + } + + Object *c = nullptr; + bool cleanup_c = false; + + if (Engine::get_singleton()->has_singleton(p_class)) { + c = Engine::get_singleton()->get_singleton_object(p_class); + cleanup_c = false; + } else if (ClassDB::can_instantiate(p_class)) { + c = ClassDB::instantiate(p_class); + cleanup_c = true; + } + + if (c) { + List<PropertyInfo> plist; + c->get_property_list(&plist); + for (const PropertyInfo &E : plist) { + if (E.usage & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR)) { + if (!default_values[p_class].has(E.name)) { + Variant v = c->get(E.name); + default_values[p_class][E.name] = v; + } + } + } + + if (cleanup_c) { + memdelete(c); + } + } + + default_values_cached.insert(p_class); + } + + if (!default_values.has(p_class)) { + if (r_valid != nullptr) { + *r_valid = false; + } + return Variant(); + } + + if (!default_values[p_class].has(p_property)) { + if (r_valid != nullptr) { + *r_valid = false; + } + return Variant(); + } + + if (r_valid != nullptr) { + *r_valid = true; + } + + Variant var = default_values[p_class][p_property]; + +#ifdef DEBUG_ENABLED + // Some properties may have an instantiated Object as default value, + // (like Path2D's `curve` used to have), but that's not a good practice. + // Instead, those properties should use PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT + // to be auto-instantiated when created in the editor. + if (var.get_type() == Variant::OBJECT) { + Object *obj = var.get_validated_object(); + if (obj) { + WARN_PRINT(vformat("Instantiated %s used as default value for %s's \"%s\" property.", obj->get_class(), p_class, p_property)); + } + } +#endif + + return var; +} + +void ClassDB::register_extension_class(ObjectNativeExtension *p_extension) { + GLOBAL_LOCK_FUNCTION; + + ERR_FAIL_COND_MSG(classes.has(p_extension->class_name), "Class already registered: " + String(p_extension->class_name)); + ERR_FAIL_COND_MSG(!classes.has(p_extension->parent_class_name), "Parent class name for extension class not found: " + String(p_extension->parent_class_name)); + + ClassInfo *parent = classes.getptr(p_extension->parent_class_name); + + ClassInfo c; + c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION; + c.native_extension = p_extension; + c.name = p_extension->class_name; + c.creation_func = parent->creation_func; + c.inherits = parent->name; + c.class_ptr = parent->class_ptr; + c.inherits_ptr = parent; + c.exposed = true; + + classes[p_extension->class_name] = c; +} + +void ClassDB::unregister_extension_class(const StringName &p_class) { + ERR_FAIL_COND(!classes.has(p_class)); + classes.erase(p_class); +} + +RWLock ClassDB::lock; + +void ClassDB::cleanup_defaults() { + default_values.clear(); + default_values_cached.clear(); +} + +void ClassDB::cleanup() { + //OBJTYPE_LOCK; hah not here + + const StringName *k = nullptr; + + while ((k = classes.next(k))) { + ClassInfo &ti = classes[*k]; + + const StringName *m = nullptr; + while ((m = ti.method_map.next(m))) { + memdelete(ti.method_map[*m]); + } + } + classes.clear(); + resource_base_extensions.clear(); + compat_classes.clear(); +} + +// diff --git a/core/object/class_db.h b/core/object/class_db.h new file mode 100644 index 0000000000..39e523da27 --- /dev/null +++ b/core/object/class_db.h @@ -0,0 +1,450 @@ +/*************************************************************************/ +/* class_db.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef CLASS_DB_H +#define CLASS_DB_H + +#include "core/object/method_bind.h" +#include "core/object/object.h" +#include "core/string/print_string.h" + +/** To bind more then 6 parameters include this: + * + */ + +// 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" + +#define DEFVAL(m_defval) (m_defval) + +#ifdef DEBUG_METHODS_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_METHOD(const char *p_name); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10, const char *p_arg11); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10, const char *p_arg11, const char *p_arg12); +MethodDefinition D_METHOD(const char *p_name, const char *p_arg1, const char *p_arg2, const char *p_arg3, const char *p_arg4, const char *p_arg5, const char *p_arg6, const char *p_arg7, const char *p_arg8, const char *p_arg9, const char *p_arg10, const char *p_arg11, const char *p_arg12, const char *p_arg13); + +#else + +//#define NO_VARIADIC_MACROS + +#ifdef NO_VARIADIC_MACROS + +static _FORCE_INLINE_ const char *D_METHOD(const char *m_name, ...) { + return m_name; +} + +#else + +// When DEBUG_METHODS_ENABLED is set this will let the engine know +// the argument names for easier debugging. +#define D_METHOD(m_c, ...) m_c + +#endif + +#endif + +class ClassDB { +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; + MethodBind *_getptr; + Variant::Type type; + }; + + struct ClassInfo { + APIType api = API_NONE; + ClassInfo *inherits_ptr = nullptr; + void *class_ptr = nullptr; + + ObjectNativeExtension *native_extension = nullptr; + + HashMap<StringName, MethodBind *> method_map; + HashMap<StringName, int> constant_map; + HashMap<StringName, List<StringName>> enum_map; + HashMap<StringName, MethodInfo> signal_map; + List<PropertyInfo> property_list; + HashMap<StringName, PropertyInfo> property_map; +#ifdef DEBUG_METHODS_ENABLED + List<StringName> constant_order; + List<StringName> method_order; + Set<StringName> methods_in_properties; + List<MethodInfo> virtual_methods; + Map<StringName, MethodInfo> virtual_methods_map; + StringName category; +#endif + HashMap<StringName, PropertySetGet> property_setget; + + StringName inherits; + StringName name; + bool disabled = false; + bool exposed = false; + Object *(*creation_func)() = nullptr; + + ClassInfo() {} + ~ClassInfo() {} + }; + + template <class T> + static Object *creator() { + return memnew(T); + } + + static RWLock lock; + static HashMap<StringName, ClassInfo> classes; + static HashMap<StringName, StringName> resource_base_extensions; + static HashMap<StringName, StringName> compat_classes; + +#ifdef DEBUG_METHODS_ENABLED + static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount); +#else + static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const char *method_name, const Variant **p_defs, int p_defcount); +#endif + + static APIType current_api; + + static void _add_class2(const StringName &p_class, const StringName &p_inherits); + + static HashMap<StringName, HashMap<StringName, Variant>> default_values; + static Set<StringName> default_values_cached; + +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); + +public: + // DO NOT USE THIS!!!!!! NEEDS TO BE PUBLIC BUT DO NOT USE NO MATTER WHAT!!! + template <class T> + static void _add_class() { + _add_class2(T::get_class_static(), T::get_parent_class_static()); + } + + template <class T> + static void register_class() { + GLOBAL_LOCK_FUNCTION; + T::initialize_class(); + ClassInfo *t = classes.getptr(T::get_class_static()); + ERR_FAIL_COND(!t); + t->creation_func = &creator<T>; + t->exposed = true; + t->class_ptr = T::get_class_ptr_static(); + T::register_custom_data_to_otdb(); + } + + template <class T> + static void register_virtual_class() { + GLOBAL_LOCK_FUNCTION; + T::initialize_class(); + ClassInfo *t = classes.getptr(T::get_class_static()); + ERR_FAIL_COND(!t); + t->exposed = true; + t->class_ptr = T::get_class_ptr_static(); + //nothing + } + + static void register_extension_class(ObjectNativeExtension *p_extension); + static void unregister_extension_class(const StringName &p_class); + + template <class T> + static Object *_create_ptr_func() { + return T::create(); + } + + template <class T> + static void register_custom_instance_class() { + GLOBAL_LOCK_FUNCTION; + T::initialize_class(); + ClassInfo *t = classes.getptr(T::get_class_static()); + ERR_FAIL_COND(!t); + t->creation_func = &_create_ptr_func<T>; + t->exposed = true; + t->class_ptr = T::get_class_ptr_static(); + T::register_custom_data_to_otdb(); + } + + static void get_class_list(List<StringName> *p_classes); + static void get_inheriters_from_class(const StringName &p_class, List<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 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 Object *instantiate(const StringName &p_class); + static void instance_get_native_extension_data(ObjectNativeExtension **r_extension, GDExtensionClassInstancePtr *r_extension_instance, Object *p_base); + + static APIType get_api_type(const StringName &p_class); + + static uint64_t get_api_hash(APIType p_api); + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method) { + MethodBind *bind = create_method_bind(p_method); + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, nullptr, 0); //use static function, much smaller binary usage + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[1] = { &p_def1 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 1); + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1, const Variant &p_def2) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[2] = { &p_def1, &p_def2 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 2); + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1, const Variant &p_def2, const Variant &p_def3) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[3] = { &p_def1, &p_def2, &p_def3 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 3); + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1, const Variant &p_def2, const Variant &p_def3, const Variant &p_def4) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[4] = { &p_def1, &p_def2, &p_def3, &p_def4 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 4); + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1, const Variant &p_def2, const Variant &p_def3, const Variant &p_def4, const Variant &p_def5) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[5] = { &p_def1, &p_def2, &p_def3, &p_def4, &p_def5 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 5); + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1, const Variant &p_def2, const Variant &p_def3, const Variant &p_def4, const Variant &p_def5, const Variant &p_def6) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[6] = { &p_def1, &p_def2, &p_def3, &p_def4, &p_def5, &p_def6 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 6); + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1, const Variant &p_def2, const Variant &p_def3, const Variant &p_def4, const Variant &p_def5, const Variant &p_def6, const Variant &p_def7) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[7] = { &p_def1, &p_def2, &p_def3, &p_def4, &p_def5, &p_def6, &p_def7 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 7); + } + + template <class N, class M> + static MethodBind *bind_method(N p_method_name, M p_method, const Variant &p_def1, const Variant &p_def2, const Variant &p_def3, const Variant &p_def4, const Variant &p_def5, const Variant &p_def6, const Variant &p_def7, const Variant &p_def8) { + MethodBind *bind = create_method_bind(p_method); + const Variant *ptr[8] = { &p_def1, &p_def2, &p_def3, &p_def4, &p_def5, &p_def6, &p_def7, &p_def8 }; + + return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, ptr, 8); + } + + template <class 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) { + GLOBAL_LOCK_FUNCTION; + + MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant); + ERR_FAIL_COND_V(!bind, nullptr); + + bind->set_name(p_name); + bind->set_default_arguments(p_default_args); + + String instance_type = bind->get_instance_class(); + + ClassInfo *type = classes.getptr(instance_type); + if (!type) { + memdelete(bind); + ERR_FAIL_COND_V(!type, nullptr); + } + + if (type->method_map.has(p_name)) { + memdelete(bind); + // Overloading not supported + ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + "."); + } + type->method_map[p_name] = bind; +#ifdef DEBUG_METHODS_ENABLED + // FIXME: <reduz> set_return_type is no longer in MethodBind, so I guess it should be moved to vararg method bind + //bind->set_return_type("Variant"); + type->method_order.push_back(p_name); +#endif + + return bind; + } + + static void bind_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 = ""); + static void add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_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 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 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 MethodBind *get_method(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 get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false); + + static void bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int p_constant); + static void get_integer_constant_list(const StringName &p_class, List<String> *p_constants, bool p_no_inheritance = false); + static int 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 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 Variant class_get_default_property_value(const StringName &p_class, const StringName &p_property, bool *r_valid = nullptr); + + static StringName get_category(const StringName &p_node); + + 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 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 void set_current_api(APIType p_api); + static APIType get_current_api(); + static void cleanup_defaults(); + static void cleanup(); +}; + +#ifdef DEBUG_METHODS_ENABLED + +#define BIND_CONSTANT(m_constant) \ + ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); + +#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); + +#else + +#define BIND_CONSTANT(m_constant) \ + ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); + +#define BIND_ENUM_CONSTANT(m_constant) \ + ::ClassDB::bind_integer_constant(get_class_static(), StringName(), #m_constant, m_constant); + +#endif + +#ifdef TOOLS_ENABLED + +#define BIND_VMETHOD(m_method) \ + ::ClassDB::add_virtual_method(get_class_static(), m_method); + +#else + +#define BIND_VMETHOD(m_method) + +#endif + +#define GDREGISTER_CLASS(m_class) \ + if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \ + ::ClassDB::register_class<m_class>(); \ + } +#define GDREGISTER_VIRTUAL_CLASS(m_class) \ + if (!GD_IS_DEFINED(ClassDB_Disable_##m_class)) { \ + ::ClassDB::register_virtual_class<m_class>(); \ + } + +#include "core/disabled_classes.gen.h" + +#endif // CLASS_DB_H diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py new file mode 100644 index 0000000000..af90593140 --- /dev/null +++ b/core/object/make_virtuals.py @@ -0,0 +1,162 @@ +proto = """ +#define GDVIRTUAL$VER($RET m_name $ARG) \\ +StringName _gdvirtual_##m_name##_sn = #m_name;\\ +GDNativeExtensionClassCallVirtual _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, #m_name) : (GDNativeExtensionClassCallVirtual) nullptr;\\ +bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ + ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\ + if (script_instance) {\\ + Callable::CallError ce; \\ + $CALLSIARGS\\ + $CALLSIBEGINscript_instance->call(_gdvirtual_##m_name##_sn, $CALLSIARGPASS, ce);\\ + if (ce.error == Callable::CallError::CALL_OK) {\\ + $CALLSIRET\\ + return true;\\ + } \\ + }\\ + if (_gdvirtual_##m_name) {\\ + $CALLPTRARGS\\ + $CALLPTRRETDEF\\ + _gdvirtual_##m_name(_get_extension_instance(),$CALLPTRARGPASS,$CALLPTRRETPASS);\\ + $CALLPTRRET\\ + return true;\\ + }\\ +\\ + return false;\\ +}\\ +bool _gdvirtual_##m_name##_overriden() const { \\ + ScriptInstance *script_instance = ((Object*)(this))->get_script_instance();\\ + if (script_instance) {\\ + return script_instance->has_method(_gdvirtual_##m_name##_sn);\\ + }\\ + if (_gdvirtual_##m_name) {\\ + return true;\\ + }\\ + return false;\\ +}\\ +\\ +_FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() { \\ + MethodInfo method_info;\\ + method_info.name = #m_name;\\ + method_info.flags = METHOD_FLAG_VIRTUAL;\\ + $FILL_METHOD_INFO\\ + return method_info;\\ +} + + +""" + + +def generate_version(argcount, const=False, returns=False): + s = proto + sproto = str(argcount) + method_info = "" + if returns: + sproto += "R" + s = s.replace("$RET", "m_ret, ") + s = s.replace("$CALLPTRRETDEF", "PtrToArg<m_ret>::EncodeT ret;") + method_info += "\tmethod_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\\n" + else: + s = s.replace("$RET", "") + s = s.replace("$CALLPTRRETDEF", "") + + if const: + sproto += "C" + s = s.replace("$CONST", "const") + method_info += "\tmethod_info.flags|=METHOD_FLAG_CONST;\\\n" + else: + s = s.replace("$CONST", "") + + s = s.replace("$VER", sproto) + argtext = "" + callargtext = "" + callsiargs = "" + callsiargptrs = "" + callptrargsptr = "" + if argcount > 0: + argtext += ", " + callsiargs = "Variant vargs[" + str(argcount) + "]={" + callsiargptrs = "\t\tconst Variant *vargptrs[" + str(argcount) + "]={" + callptrargsptr = "\t\tconst GDNativeTypePtr argptrs[" + str(argcount) + "]={" + callptrargs = "" + for i in range(argcount): + if i > 0: + argtext += ", " + callargtext += ", " + callsiargs += ", " + callsiargptrs += ", " + callptrargs += "\t\t" + callptrargsptr += ", " + argtext += "m_type" + str(i + 1) + callargtext += "m_type" + str(i + 1) + " arg" + str(i + 1) + callsiargs += "Variant(arg" + str(i + 1) + ")" + callsiargptrs += "&vargs[" + str(i) + "]" + callptrargs += ( + "PtrToArg<m_type" + str(i + 1) + ">::EncodeT argval" + str(i + 1) + " = arg" + str(i + 1) + ";\\\n" + ) + callptrargsptr += "&argval" + str(i + 1) + method_info += "\tmethod_info.arguments.push_back(GetTypeInfo<m_type" + str(i + 1) + ">::get_class_info());\\\n" + + if argcount: + callsiargs += "};\\\n" + callsiargptrs += "};\\\n" + s = s.replace("$CALLSIARGS", callsiargs + callsiargptrs) + s = s.replace("$CALLSIARGPASS", "(const Variant **)vargptrs," + str(argcount)) + callptrargsptr += "};\\\n" + s = s.replace("$CALLPTRARGS", callptrargs + callptrargsptr) + s = s.replace("$CALLPTRARGPASS", "(const GDNativeTypePtr*)argptrs") + else: + s = s.replace("$CALLSIARGS", "") + s = s.replace("$CALLSIARGPASS", "nullptr, 0") + s = s.replace("$CALLPTRARGS", "") + 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 = ret;") + s = s.replace("$CALLPTRRETPASS", "&ret") + s = s.replace("$CALLPTRRET", "r_ret = ret;") + else: + s = s.replace("$CALLSIBEGIN", "") + s = s.replace("$CALLSIRET", "") + s = s.replace("$CALLPTRRETPASS", "nullptr") + s = s.replace("$CALLPTRRET", "") + + s = s.replace("$ARG", argtext) + s = s.replace("$CALLARGS", callargtext) + s = s.replace("$FILL_METHOD_INFO", method_info) + + return s + + +def run(target, source, env): + + max_versions = 12 + + txt = """ +#ifndef GDVIRTUAL_GEN_H +#define GDVIRTUAL_GEN_H + + +""" + + for i in range(max_versions + 1): + + txt += "/* " + str(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 += "#endif" + + with open(target[0], "w") as f: + f.write(txt) + + +if __name__ == "__main__": + from platform_methods import subprocess_main + + subprocess_main(globals()) diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp new file mode 100644 index 0000000000..4751c69f1e --- /dev/null +++ b/core/object/message_queue.cpp @@ -0,0 +1,374 @@ +/*************************************************************************/ +/* message_queue.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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/core_string_names.h" +#include "core/object/script_language.h" + +MessageQueue *MessageQueue::singleton = nullptr; + +MessageQueue *MessageQueue::get_singleton() { + return singleton; +} + +Error MessageQueue::push_call(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { + return push_callable(Callable(p_id, p_method), p_args, p_argcount, p_show_error); +} + +Error MessageQueue::push_call(ObjectID p_id, const StringName &p_method, VARIANT_ARG_DECLARE) { + VARIANT_ARGPTRS; + + int argc = 0; + + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + if (argptr[i]->get_type() == Variant::NIL) { + break; + } + argc++; + } + + return push_call(p_id, p_method, argptr, argc, false); +} + +Error MessageQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) { + _THREAD_SAFE_METHOD_ + + uint8_t room_needed = sizeof(Message) + sizeof(Variant); + + if ((buffer_end + room_needed) >= buffer_size) { + String type; + if (ObjectDB::get_instance(p_id)) { + type = ObjectDB::get_instance(p_id)->get_class(); + } + print_line("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id)); + statistics(); + ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_kb' in project settings."); + } + + Message *msg = memnew_placement(&buffer[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[buffer_end], Variant); + buffer_end += sizeof(Variant); + *v = p_value; + + return OK; +} + +Error MessageQueue::push_notification(ObjectID p_id, int p_notification) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER); + + uint8_t room_needed = sizeof(Message); + + if ((buffer_end + room_needed) >= buffer_size) { + print_line("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id)); + statistics(); + ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_kb' in project settings."); + } + + Message *msg = memnew_placement(&buffer[buffer_end], Message); + + msg->type = TYPE_NOTIFICATION; + msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it + //msg->target; + msg->notification = p_notification; + + buffer_end += sizeof(Message); + + return OK; +} + +Error MessageQueue::push_call(Object *p_object, const StringName &p_method, VARIANT_ARG_DECLARE) { + return push_call(p_object->get_instance_id(), p_method, VARIANT_ARG_PASS); +} + +Error MessageQueue::push_notification(Object *p_object, int p_notification) { + return push_notification(p_object->get_instance_id(), p_notification); +} + +Error MessageQueue::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 MessageQueue::push_callable(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) { + _THREAD_SAFE_METHOD_ + + int room_needed = sizeof(Message) + sizeof(Variant) * p_argcount; + + if ((buffer_end + room_needed) >= buffer_size) { + print_line("Failed method: " + p_callable); + statistics(); + ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_kb' in project settings."); + } + + Message *msg = memnew_placement(&buffer[buffer_end], Message); + msg->args = p_argcount; + msg->callable = p_callable; + msg->type = TYPE_CALL; + if (p_show_error) { + msg->type |= FLAG_SHOW_ERROR; + } + + buffer_end += sizeof(Message); + + for (int i = 0; i < p_argcount; i++) { + Variant *v = memnew_placement(&buffer[buffer_end], Variant); + buffer_end += sizeof(Variant); + *v = *p_args[i]; + } + + return OK; +} + +Error MessageQueue::push_callable(const Callable &p_callable, VARIANT_ARG_DECLARE) { + VARIANT_ARGPTRS; + + int argc = 0; + + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + if (argptr[i]->get_type() == Variant::NIL) { + break; + } + argc++; + } + + return push_callable(p_callable, argptr, argc); +} + +void MessageQueue::statistics() { + Map<StringName, int> set_count; + Map<int, int> notify_count; + Map<Callable, int> call_count; + int null_count = 0; + + uint32_t read_pos = 0; + while (read_pos < buffer_end) { + Message *message = (Message *)&buffer[read_pos]; + + Object *target = message->callable.get_object(); + + if (target != nullptr) { + switch (message->type & FLAG_MASK) { + case TYPE_CALL: { + if (!call_count.has(message->callable)) { + call_count[message->callable] = 0; + } + + call_count[message->callable]++; + + } break; + case TYPE_NOTIFICATION: { + if (!notify_count.has(message->notification)) { + notify_count[message->notification] = 0; + } + + notify_count[message->notification]++; + + } break; + case TYPE_SET: { + StringName t = message->callable.get_method(); + if (!set_count.has(t)) { + set_count[t] = 0; + } + + set_count[t]++; + + } break; + } + + } else { + //object was deleted + print_line("Object was deleted while awaiting a callback"); + + null_count++; + } + + read_pos += sizeof(Message); + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + read_pos += sizeof(Variant) * message->args; + } + } + + print_line("TOTAL BYTES: " + itos(buffer_end)); + print_line("NULL count: " + itos(null_count)); + + for (Map<StringName, int>::Element *E = set_count.front(); E; E = E->next()) { + print_line("SET " + E->key() + ": " + itos(E->get())); + } + + for (Map<Callable, int>::Element *E = call_count.front(); E; E = E->next()) { + print_line("CALL " + E->key() + ": " + itos(E->get())); + } + + for (Map<int, int>::Element *E = notify_count.front(); E; E = E->next()) { + print_line("NOTIFY " + itos(E->key()) + ": " + itos(E->get())); + } +} + +int MessageQueue::get_max_buffer_usage() const { + return buffer_max_used; +} + +void MessageQueue::_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.call(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) + "."); + } +} + +void MessageQueue::flush() { + if (buffer_end > buffer_max_used) { + buffer_max_used = buffer_end; + } + + uint32_t read_pos = 0; + + //using reverse locking strategy + _THREAD_SAFE_LOCK_ + + if (flushing) { + _THREAD_SAFE_UNLOCK_ + ERR_FAIL_COND(flushing); //already flushing, you did something odd + } + flushing = true; + + while (read_pos < buffer_end) { + //lock on each iteration, so a call can re-add itself to the message queue + + Message *message = (Message *)&buffer[read_pos]; + + uint32_t advance = sizeof(Message); + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + advance += sizeof(Variant) * message->args; + } + + //pre-advance so this function is reentrant + read_pos += advance; + + _THREAD_SAFE_UNLOCK_ + + Object *target = message->callable.get_object(); + + if (target != nullptr) { + switch (message->type & FLAG_MASK) { + case TYPE_CALL: { + Variant *args = (Variant *)(message + 1); + + // messages don't expect a return value + + _call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR); + + } break; + case TYPE_NOTIFICATION: { + // messages don't expect a return value + target->notification(message->notification); + + } break; + case TYPE_SET: { + Variant *arg = (Variant *)(message + 1); + // messages don't expect a return value + target->set(message->callable.get_method(), *arg); + + } break; + } + } + + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + Variant *args = (Variant *)(message + 1); + for (int i = 0; i < message->args; i++) { + args[i].~Variant(); + } + } + + message->~Message(); + + _THREAD_SAFE_LOCK_ + } + + buffer_end = 0; // reset buffer + flushing = false; + _THREAD_SAFE_UNLOCK_ +} + +bool MessageQueue::is_flushing() const { + return flushing; +} + +MessageQueue::MessageQueue() { + ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists."); + singleton = this; + + buffer_size = GLOBAL_DEF_RST("memory/limits/message_queue/max_size_kb", DEFAULT_QUEUE_SIZE_KB); + ProjectSettings::get_singleton()->set_custom_property_info("memory/limits/message_queue/max_size_kb", PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_kb", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); + buffer_size *= 1024; + buffer = memnew_arr(uint8_t, buffer_size); +} + +MessageQueue::~MessageQueue() { + uint32_t read_pos = 0; + + while (read_pos < buffer_end) { + Message *message = (Message *)&buffer[read_pos]; + Variant *args = (Variant *)(message + 1); + int argc = message->args; + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + for (int i = 0; i < argc; i++) { + args[i].~Variant(); + } + } + message->~Message(); + + read_pos += sizeof(Message); + if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { + read_pos += sizeof(Variant) * message->args; + } + } + + singleton = nullptr; + memdelete_arr(buffer); +} diff --git a/core/object/message_queue.h b/core/object/message_queue.h new file mode 100644 index 0000000000..1b8def62d3 --- /dev/null +++ b/core/object/message_queue.h @@ -0,0 +1,98 @@ +/*************************************************************************/ +/* message_queue.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef MESSAGE_QUEUE_H +#define MESSAGE_QUEUE_H + +#include "core/object/class_db.h" +#include "core/os/thread_safe.h" + +class MessageQueue { + _THREAD_SAFE_CLASS_ + + enum { + DEFAULT_QUEUE_SIZE_KB = 4096 + }; + + enum { + TYPE_CALL, + TYPE_NOTIFICATION, + TYPE_SET, + FLAG_SHOW_ERROR = 1 << 14, + FLAG_MASK = FLAG_SHOW_ERROR - 1 + + }; + + struct Message { + Callable callable; + int16_t type; + union { + int16_t notification; + int16_t args; + }; + }; + + uint8_t *buffer; + uint32_t buffer_end = 0; + uint32_t buffer_max_used = 0; + uint32_t buffer_size; + + void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error); + + static MessageQueue *singleton; + + bool flushing = false; + +public: + static MessageQueue *get_singleton(); + + Error push_call(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false); + Error push_call(ObjectID p_id, const StringName &p_method, VARIANT_ARG_LIST); + Error push_notification(ObjectID p_id, int p_notification); + Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value); + Error push_callable(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error = false); + Error push_callable(const Callable &p_callable, VARIANT_ARG_LIST); + + Error push_call(Object *p_object, const StringName &p_method, VARIANT_ARG_LIST); + Error push_notification(Object *p_object, int p_notification); + Error push_set(Object *p_object, const StringName &p_prop, const Variant &p_value); + + void statistics(); + void flush(); + + bool is_flushing() const; + + int get_max_buffer_usage() const; + + MessageQueue(); + ~MessageQueue(); +}; + +#endif // MESSAGE_QUEUE_H diff --git a/core/object/method_bind.cpp b/core/object/method_bind.cpp new file mode 100644 index 0000000000..c53104fe3f --- /dev/null +++ b/core/object/method_bind.cpp @@ -0,0 +1,139 @@ +/*************************************************************************/ +/* method_bind.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 { + uint32_t hash = hash_djb2_one_32(has_return() ? 1 : 0); + hash = hash_djb2_one_32(get_argument_count(), hash); + +#ifndef _MSC_VER +#warning This needs proper class name and argument type for hashing +#endif +#if 0 + + for (int i = (has_return() ? -1 : 0); i < get_argument_count(); i++) { + PropertyInfo pi = i == -1 ? get_return_info() : get_argument_info(i); + hash = hash_djb2_one_32(get_argument_type(i), hash); + if (pi.class_name != StringName()) { + hash = hash_djb2_one_32(pi.class_name.operator String().hash(), hash); + } + } +#endif + hash = hash_djb2_one_32(get_default_argument_count(), hash); + for (int i = 0; i < get_default_argument_count(); i++) { + Variant v = get_default_argument(i); + hash = hash_djb2_one_32(v.hash(), hash); + } + + hash = hash_djb2_one_32(is_const(), hash); + hash = hash_djb2_one_32(is_vararg(), hash); + + return hash; +} + +#ifdef DEBUG_METHODS_ENABLED +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); + info.name = p_argument < arg_names.size() ? String(arg_names[p_argument]) : String("arg" + itos(p_argument)); + return info; +} + +PropertyInfo MethodBind::get_return_info() const { + return _gen_argument_type_info(-1); +} + +#endif +void MethodBind::_set_const(bool p_const) { + _const = p_const; +} + +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_METHODS_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 + +void MethodBind::set_default_arguments(const Vector<Variant> &p_defargs) { + default_arguments = p_defargs; + default_argument_count = default_arguments.size(); +} + +#ifdef DEBUG_METHODS_ENABLED +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; +} + +#endif + +MethodBind::MethodBind() { + static int last_id = 0; + method_id = last_id++; +} + +MethodBind::~MethodBind() { +#ifdef DEBUG_METHODS_ENABLED + if (argument_types) { + memdelete_arr(argument_types); + } +#endif +} diff --git a/core/object/method_bind.h b/core/object/method_bind.h new file mode 100644 index 0000000000..b0b379873e --- /dev/null +++ b/core/object/method_bind.h @@ -0,0 +1,581 @@ +/*************************************************************************/ +/* method_bind.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef METHOD_BIND_H +#define METHOD_BIND_H + +#include "core/variant/binder_common.h" + +enum MethodFlags { + METHOD_FLAG_NORMAL = 1, + METHOD_FLAG_EDITOR = 2, + METHOD_FLAG_NOSCRIPT = 4, + METHOD_FLAG_CONST = 8, + METHOD_FLAG_REVERSE = 16, // used for events + METHOD_FLAG_VIRTUAL = 32, + METHOD_FLAG_FROM_SCRIPT = 64, + METHOD_FLAG_VARARG = 128, + METHOD_FLAG_STATIC = 256, + METHOD_FLAG_OBJECT_CORE = 512, + METHOD_FLAGS_DEFAULT = METHOD_FLAG_NORMAL, +}; + +VARIANT_ENUM_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 _const = false; + bool _returns = false; + +protected: +#ifdef DEBUG_METHODS_ENABLED + Variant::Type *argument_types = nullptr; + Vector<StringName> arg_names; +#endif + void _set_const(bool p_const); + void _set_returns(bool p_returns); +#ifdef DEBUG_METHODS_ENABLED + 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); + +#endif + 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]; + } + } + +#ifdef DEBUG_METHODS_ENABLED + _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; + + 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 + + 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); } + _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; }; + + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) = 0; + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) = 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 has_return() const { return _returns; } + virtual bool is_vararg() const { return false; } + + void set_default_arguments(const Vector<Variant> &p_defargs); + + uint32_t get_hash() const; + + MethodBind(); + virtual ~MethodBind(); +}; + +template <class T> +class MethodBindVarArg : public MethodBind { +public: + typedef Variant (T::*NativeCall)(const Variant **, int, Callable::CallError &); + +protected: + NativeCall call_method = nullptr; +#ifdef DEBUG_METHODS_ENABLED + MethodInfo arguments; +#endif + +public: +#ifdef DEBUG_METHODS_ENABLED + virtual PropertyInfo _gen_argument_type_info(int p_arg) const { + if (p_arg < 0) { + return arguments.return_val; + } else if (p_arg < arguments.arguments.size()) { + return arguments.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 { + return _gen_argument_type_info(p_arg).type; + } + + virtual GodotTypeInfo::Metadata get_argument_meta(int) const { + return GodotTypeInfo::METADATA_NONE; + } +#else + virtual Variant::Type _gen_argument_type(int p_arg) const { + return Variant::NIL; + } +#endif + + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + T *instance = static_cast<T *>(p_object); + return (instance->*call_method)(p_args, p_arg_count, r_error); + } + + void set_method_info(const MethodInfo &p_info, bool p_return_nil_is_variant) { + set_argument_count(p_info.arguments.size()); +#ifdef DEBUG_METHODS_ENABLED + Variant::Type *at = memnew_arr(Variant::Type, p_info.arguments.size() + 1); + at[0] = p_info.return_val.type; + if (p_info.arguments.size()) { + Vector<StringName> names; + names.resize(p_info.arguments.size()); + for (int i = 0; i < p_info.arguments.size(); i++) { + at[i + 1] = p_info.arguments[i].type; + names.write[i] = p_info.arguments[i].name; + } + + set_argument_names(names); + } + argument_types = at; + arguments = p_info; + if (p_return_nil_is_variant) { + arguments.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } +#endif + } + + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { + ERR_FAIL(); // Can't call. + } + + void set_method(NativeCall p_method) { call_method = p_method; } + virtual bool is_const() const { return false; } + + virtual bool is_vararg() const { return true; } + + MethodBindVarArg() { + _set_returns(true); + } +}; + +template <class T> +MethodBind *create_vararg_method_bind(Variant (T::*p_method)(const Variant **, int, Callable::CallError &), const MethodInfo &p_info, bool p_return_nil_is_variant) { + MethodBindVarArg<T> *a = memnew((MethodBindVarArg<T>)); + a->set_method(p_method); + a->set_method_info(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 <class T, class... P> +#else +template <class... P> +#endif +class MethodBindT : public MethodBind { + void (MB_T::*method)(P...); + +protected: +#ifdef DEBUG_METHODS_ENABLED +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual Variant::Type _gen_argument_type(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + return call_get_argument_type<P...>(p_arg); + } else { + return Variant::NIL; + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + + virtual PropertyInfo _gen_argument_type_info(int p_arg) const { + PropertyInfo pi; + call_get_argument_type_info<P...>(p_arg, pi); + return pi; + } +#endif + +public: +#ifdef DEBUG_METHODS_ENABLED + virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const { + return call_get_argument_metadata<P...>(p_arg); + } + +#endif + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { +#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((MB_T *)(p_object), method, p_args, p_arg_count, r_error, get_default_arguments()); +#endif + return Variant(); + } + + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { +#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...>((MB_T *)(p_object), method, p_args); +#endif + } + + MethodBindT(void (MB_T::*p_method)(P...)) { + method = p_method; +#ifdef DEBUG_METHODS_ENABLED + _generate_argument_types(sizeof...(P)); +#endif + set_argument_count(sizeof...(P)); + } +}; + +template <class T, class... 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, not const + +#ifdef TYPED_METHOD_BIND +template <class T, class... P> +#else +template <class... P> +#endif +class MethodBindTC : public MethodBind { + void (MB_T::*method)(P...) const; + +protected: +#ifdef DEBUG_METHODS_ENABLED +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual Variant::Type _gen_argument_type(int p_arg) const { + if (p_arg >= 0 && p_arg < (int)sizeof...(P)) { + return call_get_argument_type<P...>(p_arg); + } else { + return Variant::NIL; + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + + virtual PropertyInfo _gen_argument_type_info(int p_arg) const { + PropertyInfo pi; + call_get_argument_type_info<P...>(p_arg, pi); + return pi; + } +#endif + +public: +#ifdef DEBUG_METHODS_ENABLED + virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const { + return call_get_argument_metadata<P...>(p_arg); + } + +#endif + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { +#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((MB_T *)(p_object), method, p_args, p_arg_count, r_error, get_default_arguments()); +#endif + return Variant(); + } + + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { +#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...>((MB_T *)(p_object), method, p_args); +#endif + } + + MethodBindTC(void (MB_T::*p_method)(P...) const) { + method = p_method; + _set_const(true); +#ifdef DEBUG_METHODS_ENABLED + _generate_argument_types(sizeof...(P)); +#endif + set_argument_count(sizeof...(P)); + } +}; + +template <class T, class... 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 <class T, class R, class... P> +#else +template <class R, class... P> +#endif +class MethodBindTR : public MethodBind { + R(MB_T::*method) + (P...); + +protected: +#ifdef DEBUG_METHODS_ENABLED +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual Variant::Type _gen_argument_type(int p_arg) const { + 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 { + 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(); + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif +#endif + +public: +#ifdef DEBUG_METHODS_ENABLED + virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const { + if (p_arg >= 0) { + return call_get_argument_metadata<P...>(p_arg); + } else { + return GetTypeInfo<R>::METADATA; + } + } +#endif + + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + Variant ret; +#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((MB_T *)p_object, method, p_args, p_arg_count, ret, r_error, get_default_arguments()); +#endif + return ret; + } + + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { +#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...>((MB_T *)(p_object), method, p_args, r_ret); +#endif + } + + MethodBindTR(R (MB_T::*p_method)(P...)) { + method = p_method; + _set_returns(true); +#ifdef DEBUG_METHODS_ENABLED + _generate_argument_types(sizeof...(P)); +#endif + set_argument_count(sizeof...(P)); + } +}; + +template <class T, class R, class... 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 <class T, class R, class... P> +#else +template <class R, class... P> +#endif +class MethodBindTRC : public MethodBind { + R(MB_T::*method) + (P...) const; + +protected: +#ifdef DEBUG_METHODS_ENABLED +// GCC raises warnings in the case P = {} as the comparison is always false... +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wlogical-op" +#endif + virtual Variant::Type _gen_argument_type(int p_arg) const { + 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 { + 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(); + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif +#endif + +public: +#ifdef DEBUG_METHODS_ENABLED + virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const { + if (p_arg >= 0) { + return call_get_argument_metadata<P...>(p_arg); + } else { + return GetTypeInfo<R>::METADATA; + } + } +#endif + + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + Variant ret; +#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((MB_T *)(p_object), method, p_args, p_arg_count, ret, r_error, get_default_arguments()); +#endif + return ret; + } + + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { +#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...>((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); +#ifdef DEBUG_METHODS_ENABLED + _generate_argument_types(sizeof...(P)); +#endif + set_argument_count(sizeof...(P)); + } +}; + +template <class T, class R, class... 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; +} + +#endif // METHOD_BIND_H diff --git a/core/object/object.cpp b/core/object/object.cpp new file mode 100644 index 0000000000..3335942fb3 --- /dev/null +++ b/core/object/object.cpp @@ -0,0 +1,2057 @@ +/*************************************************************************/ +/* object.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "object.h" + +#include "core/core_string_names.h" +#include "core/io/resource.h" +#include "core/object/class_db.h" +#include "core/object/message_queue.h" +#include "core/object/script_language.h" +#include "core/os/os.h" +#include "core/string/print_string.h" +#include "core/string/translation.h" + +#ifdef DEBUG_ENABLED + +struct _ObjectDebugLock { + Object *obj; + + _ObjectDebugLock(Object *p_obj) { + obj = p_obj; + obj->_lock_index.ref(); + } + ~_ObjectDebugLock() { + obj->_lock_index.unref(); + } +}; + +#define OBJ_DEBUG_LOCK _ObjectDebugLock _debug_lock(this); + +#else + +#define OBJ_DEBUG_LOCK + +#endif + +PropertyInfo::operator Dictionary() const { + Dictionary d; + d["name"] = name; + d["class_name"] = class_name; + d["type"] = type; + d["hint"] = hint; + d["hint_string"] = hint_string; + d["usage"] = usage; + return d; +} + +PropertyInfo PropertyInfo::from_dict(const Dictionary &p_dict) { + PropertyInfo pi; + + if (p_dict.has("type")) { + pi.type = Variant::Type(int(p_dict["type"])); + } + + if (p_dict.has("name")) { + pi.name = p_dict["name"]; + } + + if (p_dict.has("class_name")) { + pi.class_name = p_dict["class_name"]; + } + + if (p_dict.has("hint")) { + pi.hint = PropertyHint(int(p_dict["hint"])); + } + + if (p_dict.has("hint_string")) { + pi.hint_string = p_dict["hint_string"]; + } + + if (p_dict.has("usage")) { + pi.usage = p_dict["usage"]; + } + + return pi; +} + +Array convert_property_list(const List<PropertyInfo> *p_list) { + Array va; + for (const List<PropertyInfo>::Element *E = p_list->front(); E; E = E->next()) { + va.push_back(Dictionary(E->get())); + } + + return va; +} + +MethodInfo::operator Dictionary() const { + Dictionary d; + d["name"] = name; + d["args"] = convert_property_list(&arguments); + Array da; + for (int i = 0; i < default_arguments.size(); i++) { + da.push_back(default_arguments[i]); + } + d["default_args"] = da; + d["flags"] = flags; + d["id"] = id; + Dictionary r = return_val; + d["return"] = r; + return d; +} + +MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { + MethodInfo mi; + + if (p_dict.has("name")) { + mi.name = p_dict["name"]; + } + Array args; + if (p_dict.has("args")) { + args = p_dict["args"]; + } + + for (int i = 0; i < args.size(); i++) { + Dictionary d = args[i]; + mi.arguments.push_back(PropertyInfo::from_dict(d)); + } + Array defargs; + if (p_dict.has("default_args")) { + defargs = p_dict["default_args"]; + } + for (int i = 0; i < defargs.size(); i++) { + mi.default_arguments.push_back(defargs[i]); + } + + if (p_dict.has("return")) { + mi.return_val = PropertyInfo::from_dict(p_dict["return"]); + } + + if (p_dict.has("flags")) { + mi.flags = p_dict["flags"]; + } + + return mi; +} + +MethodInfo::MethodInfo() : + flags(METHOD_FLAG_NORMAL) {} + +MethodInfo::MethodInfo(const String &p_name) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { +} + +MethodInfo::MethodInfo(const String &p_name, const PropertyInfo &p_param1) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); +} + +MethodInfo::MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); +} + +MethodInfo::MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); +} + +MethodInfo::MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); + arguments.push_back(p_param4); +} + +MethodInfo::MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); + arguments.push_back(p_param4); + arguments.push_back(p_param5); +} + +MethodInfo::MethodInfo(Variant::Type ret) : + flags(METHOD_FLAG_NORMAL) { + return_val.type = ret; +} + +MethodInfo::MethodInfo(Variant::Type ret, const String &p_name) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + return_val.type = ret; +} + +MethodInfo::MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + return_val.type = ret; + arguments.push_back(p_param1); +} + +MethodInfo::MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + return_val.type = ret; + arguments.push_back(p_param1); + arguments.push_back(p_param2); +} + +MethodInfo::MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + return_val.type = ret; + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); +} + +MethodInfo::MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + return_val.type = ret; + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); + arguments.push_back(p_param4); +} + +MethodInfo::MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5) : + name(p_name), + flags(METHOD_FLAG_NORMAL) { + return_val.type = ret; + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); + arguments.push_back(p_param4); + arguments.push_back(p_param5); +} + +MethodInfo::MethodInfo(const PropertyInfo &p_ret, const String &p_name) : + name(p_name), + return_val(p_ret), + flags(METHOD_FLAG_NORMAL) { +} + +MethodInfo::MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1) : + name(p_name), + return_val(p_ret), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); +} + +MethodInfo::MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2) : + name(p_name), + return_val(p_ret), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); +} + +MethodInfo::MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3) : + name(p_name), + return_val(p_ret), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); +} + +MethodInfo::MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4) : + name(p_name), + return_val(p_ret), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); + arguments.push_back(p_param4); +} + +MethodInfo::MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5) : + name(p_name), + return_val(p_ret), + flags(METHOD_FLAG_NORMAL) { + arguments.push_back(p_param1); + arguments.push_back(p_param2); + arguments.push_back(p_param3); + arguments.push_back(p_param4); + arguments.push_back(p_param5); +} + +Object::Connection::operator Variant() const { + Dictionary d; + d["signal"] = signal; + d["callable"] = callable; + d["flags"] = flags; + d["binds"] = binds; + return d; +} + +bool Object::Connection::operator<(const Connection &p_conn) const { + if (signal == p_conn.signal) { + return callable < p_conn.callable; + } else { + return signal < p_conn.signal; + } +} + +Object::Connection::Connection(const Variant &p_variant) { + Dictionary d = p_variant; + if (d.has("signal")) { + signal = d["signal"]; + } + if (d.has("callable")) { + callable = d["callable"]; + } + if (d.has("flags")) { + flags = d["flags"]; + } + if (d.has("binds")) { + binds = d["binds"]; + } +} + +bool Object::_predelete() { + _predelete_ok = 1; + notification(NOTIFICATION_PREDELETE, true); + if (_predelete_ok) { + _class_ptr = nullptr; //must restore so destructors can access class ptr correctly + } + return _predelete_ok; +} + +void Object::_postinitialize() { + _class_ptr = _get_class_namev(); + _initialize_classv(); + notification(NOTIFICATION_POSTINITIALIZE); +} + +void Object::get_valid_parents_static(List<String> *p_parents) { +} + +void Object::_get_valid_parents_static(List<String> *p_parents) { +} + +void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid) { +#ifdef TOOLS_ENABLED + + _edited = true; +#endif + + if (script_instance) { + if (script_instance->set(p_name, p_value)) { + if (r_valid) { + *r_valid = true; + } + return; + } + } + + if (_extension && _extension->set) { +// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#endif + if (_extension->set(_extension_instance, (const GDNativeStringNamePtr)&p_name, (const GDNativeVariantPtr)&p_value)) { + if (r_valid) { + *r_valid = true; + } + return; + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + } + + //try built-in setgetter + { + if (ClassDB::set_property(this, p_name, p_value, r_valid)) { + /* + if (r_valid) + *r_valid=true; + */ + return; + } + } + + if (p_name == CoreStringNames::get_singleton()->_script) { + set_script(p_value); + if (r_valid) { + *r_valid = true; + } + return; + + } else if (p_name == CoreStringNames::get_singleton()->_meta) { + //set_meta(p_name,p_value); + metadata = p_value.duplicate(); + if (r_valid) { + *r_valid = true; + } + return; + } + + //something inside the object... :| + bool success = _setv(p_name, p_value); + if (success) { + if (r_valid) { + *r_valid = true; + } + return; + } + +#ifdef TOOLS_ENABLED + if (script_instance) { + bool valid; + script_instance->property_set_fallback(p_name, p_value, &valid); + if (valid) { + if (r_valid) { + *r_valid = true; + } + return; + } + } +#endif + + if (r_valid) { + *r_valid = false; + } +} + +Variant Object::get(const StringName &p_name, bool *r_valid) const { + Variant ret; + + if (script_instance) { + if (script_instance->get(p_name, ret)) { + if (r_valid) { + *r_valid = true; + } + return ret; + } + } + if (_extension && _extension->get) { +// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#endif + + if (_extension->get(_extension_instance, (const GDNativeStringNamePtr)&p_name, (GDNativeVariantPtr)&ret)) { + if (r_valid) { + *r_valid = true; + } + return ret; + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + } + + //try built-in setgetter + { + if (ClassDB::get_property(const_cast<Object *>(this), p_name, ret)) { + if (r_valid) { + *r_valid = true; + } + return ret; + } + } + + if (p_name == CoreStringNames::get_singleton()->_script) { + ret = get_script(); + if (r_valid) { + *r_valid = true; + } + return ret; + + } else if (p_name == CoreStringNames::get_singleton()->_meta) { + ret = metadata; + if (r_valid) { + *r_valid = true; + } + return ret; + + } else { + //something inside the object... :| + bool success = _getv(p_name, ret); + if (success) { + if (r_valid) { + *r_valid = true; + } + return ret; + } + +#ifdef TOOLS_ENABLED + if (script_instance) { + bool valid; + ret = script_instance->property_get_fallback(p_name, &valid); + if (valid) { + if (r_valid) { + *r_valid = true; + } + return ret; + } + } +#endif + + if (r_valid) { + *r_valid = false; + } + return Variant(); + } +} + +void Object::set_indexed(const Vector<StringName> &p_names, const Variant &p_value, bool *r_valid) { + if (p_names.is_empty()) { + if (r_valid) { + *r_valid = false; + } + return; + } + if (p_names.size() == 1) { + set(p_names[0], p_value, r_valid); + return; + } + + bool valid = false; + if (!r_valid) { + r_valid = &valid; + } + + List<Variant> value_stack; + + value_stack.push_back(get(p_names[0], r_valid)); + + if (!*r_valid) { + value_stack.clear(); + return; + } + + for (int i = 1; i < p_names.size() - 1; i++) { + value_stack.push_back(value_stack.back()->get().get_named(p_names[i], valid)); + if (r_valid) { + *r_valid = valid; + } + + if (!valid) { + value_stack.clear(); + return; + } + } + + value_stack.push_back(p_value); // p_names[p_names.size() - 1] + + for (int i = p_names.size() - 1; i > 0; i--) { + value_stack.back()->prev()->get().set_named(p_names[i], value_stack.back()->get(), valid); + value_stack.pop_back(); + + if (r_valid) { + *r_valid = valid; + } + if (!valid) { + value_stack.clear(); + return; + } + } + + set(p_names[0], value_stack.back()->get(), r_valid); + value_stack.pop_back(); + + ERR_FAIL_COND(!value_stack.is_empty()); +} + +Variant Object::get_indexed(const Vector<StringName> &p_names, bool *r_valid) const { + if (p_names.is_empty()) { + if (r_valid) { + *r_valid = false; + } + return Variant(); + } + bool valid = false; + + Variant current_value = get(p_names[0], &valid); + for (int i = 1; i < p_names.size(); i++) { + current_value = current_value.get_named(p_names[i], valid); + + if (!valid) { + break; + } + } + if (r_valid) { + *r_valid = valid; + } + + return current_value; +} + +void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) const { + if (script_instance && p_reversed) { + p_list->push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + script_instance->get_property_list(p_list); + } + + _get_property_listv(p_list, p_reversed); + + if (_extension && _extension->get_property_list) { + uint32_t pcount; + const GDNativePropertyInfo *pinfo = _extension->get_property_list(_extension_instance, &pcount); + for (uint32_t i = 0; i < pcount; i++) { + p_list->push_back(PropertyInfo(Variant::Type(pinfo[i].type), pinfo[i].class_name, PropertyHint(pinfo[i].hint), pinfo[i].hint_string, pinfo[i].usage, pinfo[i].class_name)); + } + if (_extension->free_property_list) { + _extension->free_property_list(_extension_instance, pinfo); + } + } + + if (!is_class("Script")) { // can still be set, but this is for user-friendliness + p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT)); + } + if (!metadata.is_empty()) { + p_list->push_back(PropertyInfo(Variant::DICTIONARY, "__meta__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + } + if (script_instance && !p_reversed) { + p_list->push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + script_instance->get_property_list(p_list); + } +} + +void Object::_validate_property(PropertyInfo &property) const { +} + +void Object::get_method_list(List<MethodInfo> *p_list) const { + ClassDB::get_method_list(get_class_name(), p_list); + if (script_instance) { + script_instance->get_method_list(p_list); + } +} + +Variant Object::_call_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Variant(); + } + + if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING_NAME; + return Variant(); + } + + StringName method = *p_args[0]; + + return call(method, &p_args[1], p_argcount - 1, r_error); +} + +Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Variant(); + } + + if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING_NAME; + return Variant(); + } + + r_error.error = Callable::CallError::CALL_OK; + + StringName method = *p_args[0]; + + MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1, true); + + return Variant(); +} + +bool Object::has_method(const StringName &p_method) const { + if (p_method == CoreStringNames::get_singleton()->_free) { + return true; + } + + if (script_instance && script_instance->has_method(p_method)) { + return true; + } + + MethodBind *method = ClassDB::get_method(get_class_name(), p_method); + + return method != nullptr; +} + +Variant Object::getvar(const Variant &p_key, bool *r_valid) const { + if (r_valid) { + *r_valid = false; + } + + if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) { + return get(p_key, r_valid); + } + return Variant(); +} + +void Object::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid) { + if (r_valid) { + *r_valid = false; + } + if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) { + return set(p_key, p_value, r_valid); + } +} + +Variant Object::callv(const StringName &p_method, const Array &p_args) { + const Variant **argptrs = nullptr; + + if (p_args.size() > 0) { + argptrs = (const Variant **)alloca(sizeof(Variant *) * p_args.size()); + for (int i = 0; i < p_args.size(); i++) { + argptrs[i] = &p_args[i]; + } + } + + Callable::CallError ce; + Variant ret = call(p_method, argptrs, p_args.size(), ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + "."); + } + return ret; +} + +Variant Object::call(const StringName &p_name, VARIANT_ARG_DECLARE) { + VARIANT_ARGPTRS; + + int argc = 0; + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + if (argptr[i]->get_type() == Variant::NIL) { + break; + } + argc++; + } + + Callable::CallError error; + + Variant ret = call(p_name, argptr, argc, error); + return ret; +} + +Variant Object::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + r_error.error = Callable::CallError::CALL_OK; + + if (p_method == CoreStringNames::get_singleton()->_free) { +//free must be here, before anything, always ready +#ifdef DEBUG_ENABLED + if (p_argcount != 0) { + r_error.argument = 0; + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + return Variant(); + } + if (Object::cast_to<RefCounted>(this)) { + r_error.argument = 0; + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference."); + } + + if (_lock_index.get() > 1) { + r_error.argument = 0; + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + ERR_FAIL_V_MSG(Variant(), "Object is locked and can't be freed."); + } + +#endif + //must be here, must be before everything, + memdelete(this); + r_error.error = Callable::CallError::CALL_OK; + return Variant(); + } + + Variant ret; + OBJ_DEBUG_LOCK + + if (script_instance) { + ret = script_instance->call(p_method, p_args, p_argcount, r_error); + //force jumptable + switch (r_error.error) { + case Callable::CallError::CALL_OK: + return ret; + case Callable::CallError::CALL_ERROR_INVALID_METHOD: + break; + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + return ret; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: { + } + } + } + + //extension does not need this, because all methods are registered in MethodBind + + MethodBind *method = ClassDB::get_method(get_class_name(), p_method); + + if (method) { + ret = method->call(this, p_args, p_argcount, r_error); + } else { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } + + return ret; +} + +void Object::notification(int p_notification, bool p_reversed) { + _notificationv(p_notification, p_reversed); + + if (script_instance) { + script_instance->notification(p_notification); + } + + if (_extension && _extension->notification) { + _extension->notification(_extension_instance, p_notification); + } +} + +String Object::to_string() { + if (script_instance) { + bool valid; + String ret = script_instance->to_string(&valid); + if (valid) { + return ret; + } + } + if (_extension && _extension->to_string) { + return _extension->to_string(_extension_instance); + } + return "[" + get_class() + ":" + itos(get_instance_id()) + "]"; +} + +void Object::set_script_and_instance(const Variant &p_script, ScriptInstance *p_instance) { + //this function is not meant to be used in any of these ways + ERR_FAIL_COND(p_script.is_null()); + ERR_FAIL_COND(!p_instance); + ERR_FAIL_COND(script_instance != nullptr || !script.is_null()); + + script = p_script; + script_instance = p_instance; +} + +void Object::set_script(const Variant &p_script) { + if (script == p_script) { + return; + } + + if (script_instance) { + memdelete(script_instance); + script_instance = nullptr; + } + + script = p_script; + Ref<Script> s = script; + + if (!s.is_null()) { + if (s->can_instantiate()) { + OBJ_DEBUG_LOCK + script_instance = s->instance_create(this); + } else if (Engine::get_singleton()->is_editor_hint()) { + OBJ_DEBUG_LOCK + script_instance = s->placeholder_instance_create(this); + } + } + + notify_property_list_changed(); //scripts may add variables, so refresh is desired + emit_signal(CoreStringNames::get_singleton()->script_changed); +} + +void Object::set_script_instance(ScriptInstance *p_instance) { + if (script_instance == p_instance) { + return; + } + + if (script_instance) { + memdelete(script_instance); + } + + script_instance = p_instance; + + if (p_instance) { + script = p_instance->get_script(); + } else { + script = Variant(); + } +} + +Variant Object::get_script() const { + return script; +} + +bool Object::has_meta(const StringName &p_name) const { + return metadata.has(p_name); +} + +void Object::set_meta(const StringName &p_name, const Variant &p_value) { + if (p_value.get_type() == Variant::NIL) { + metadata.erase(p_name); + return; + } + + metadata[p_name] = p_value; +} + +Variant Object::get_meta(const StringName &p_name) const { + ERR_FAIL_COND_V_MSG(!metadata.has(p_name), Variant(), "The object does not have any 'meta' values with the key '" + p_name + "'."); + return metadata[p_name]; +} + +void Object::remove_meta(const StringName &p_name) { + metadata.erase(p_name); +} + +Array Object::_get_property_list_bind() const { + List<PropertyInfo> lpi; + get_property_list(&lpi); + return convert_property_list(&lpi); +} + +Array Object::_get_method_list_bind() const { + List<MethodInfo> ml; + get_method_list(&ml); + Array ret; + + for (List<MethodInfo>::Element *E = ml.front(); E; E = E->next()) { + Dictionary d = E->get(); + //va.push_back(d); + ret.push_back(d); + } + + return ret; +} + +Vector<StringName> Object::_get_meta_list_bind() const { + Vector<StringName> _metaret; + + List<Variant> keys; + metadata.get_key_list(&keys); + for (const Variant &E : keys) { + _metaret.push_back(E); + } + + return _metaret; +} + +void Object::get_meta_list(List<StringName> *p_list) const { + List<Variant> keys; + metadata.get_key_list(&keys); + for (const Variant &E : keys) { + p_list->push_back(E); + } +} + +void Object::add_user_signal(const MethodInfo &p_signal) { + ERR_FAIL_COND_MSG(p_signal.name == "", "Signal name cannot be empty."); + ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), "User signal's name conflicts with a built-in signal of '" + get_class_name() + "'."); + ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), "Trying to add already existing signal '" + p_signal.name + "'."); + SignalData s; + s.user = p_signal; + signal_map[p_signal.name] = s; +} + +bool Object::_has_user_signal(const StringName &p_name) const { + if (!signal_map.has(p_name)) { + return false; + } + return signal_map[p_name].user.name.length() > 0; +} + +struct _ObjectSignalDisconnectData { + StringName signal; + Callable callable; +}; + +Variant Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + + ERR_FAIL_COND_V(p_argcount < 1, Variant()); + if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING_NAME; + ERR_FAIL_COND_V(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING, Variant()); + } + + r_error.error = Callable::CallError::CALL_OK; + + StringName signal = *p_args[0]; + + const Variant **args = nullptr; + + int argc = p_argcount - 1; + if (argc) { + args = &p_args[1]; + } + + emit_signal(signal, args, argc); + + return Variant(); +} + +Error Object::emit_signal(const StringName &p_name, const Variant **p_args, int p_argcount) { + if (_block_signals) { + return ERR_CANT_ACQUIRE_RESOURCE; //no emit, signals blocked + } + + SignalData *s = signal_map.getptr(p_name); + if (!s) { +#ifdef DEBUG_ENABLED + bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_name); + //check in script + ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, "Can't emit non-existing signal " + String("\"") + p_name + "\"."); +#endif + //not connected? just return + return ERR_UNAVAILABLE; + } + + List<_ObjectSignalDisconnectData> disconnect_data; + + //copy on write will ensure that disconnecting the signal or even deleting the object will not affect the signal calling. + //this happens automatically and will not change the performance of calling. + //awesome, isn't it? + VMap<Callable, SignalData::Slot> slot_map = s->slot_map; + + int ssize = slot_map.size(); + + OBJ_DEBUG_LOCK + + Vector<const Variant *> bind_mem; + + Error err = OK; + + for (int i = 0; i < ssize; i++) { + const Connection &c = slot_map.getv(i).conn; + + Object *target = c.callable.get_object(); + if (!target) { + // Target might have been deleted during signal callback, this is expected and OK. + continue; + } + + const Variant **args = p_args; + int argc = p_argcount; + + if (c.binds.size()) { + //handle binds + bind_mem.resize(p_argcount + c.binds.size()); + + for (int j = 0; j < p_argcount; j++) { + bind_mem.write[j] = p_args[j]; + } + for (int j = 0; j < c.binds.size(); j++) { + bind_mem.write[p_argcount + j] = &c.binds[j]; + } + + args = (const Variant **)bind_mem.ptr(); + argc = bind_mem.size(); + } + + if (c.flags & CONNECT_DEFERRED) { + MessageQueue::get_singleton()->push_callable(c.callable, args, argc, true); + } else { + Callable::CallError ce; + _emitting = true; + Variant ret; + c.callable.call(args, argc, ret, ce); + _emitting = false; + + if (ce.error != Callable::CallError::CALL_OK) { +#ifdef DEBUG_ENABLED + if (c.flags & CONNECT_PERSIST && Engine::get_singleton()->is_editor_hint() && (script.is_null() || !Ref<Script>(script)->is_tool())) { + continue; + } +#endif + if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && !ClassDB::class_exists(target->get_class_name())) { + //most likely object is not initialized yet, do not throw error. + } else { + ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(c.callable, args, argc, ce) + "."); + err = ERR_METHOD_NOT_FOUND; + } + } + } + + bool disconnect = c.flags & CONNECT_ONESHOT; +#ifdef TOOLS_ENABLED + if (disconnect && (c.flags & CONNECT_PERSIST) && Engine::get_singleton()->is_editor_hint()) { + //this signal was connected from the editor, and is being edited. just don't disconnect for now + disconnect = false; + } +#endif + if (disconnect) { + _ObjectSignalDisconnectData dd; + dd.signal = p_name; + dd.callable = c.callable; + disconnect_data.push_back(dd); + } + } + + while (!disconnect_data.is_empty()) { + const _ObjectSignalDisconnectData &dd = disconnect_data.front()->get(); + + _disconnect(dd.signal, dd.callable); + disconnect_data.pop_front(); + } + + return err; +} + +Error Object::emit_signal(const StringName &p_name, VARIANT_ARG_DECLARE) { + VARIANT_ARGPTRS; + + int argc = 0; + + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + if (argptr[i]->get_type() == Variant::NIL) { + break; + } + argc++; + } + + return emit_signal(p_name, argptr, argc); +} + +void Object::_add_user_signal(const String &p_name, const Array &p_args) { + // this version of add_user_signal is meant to be used from scripts or external apis + // without access to ADD_SIGNAL in bind_methods + // added events are per instance, as opposed to the other ones, which are global + + MethodInfo mi; + mi.name = p_name; + + for (int i = 0; i < p_args.size(); i++) { + Dictionary d = p_args[i]; + PropertyInfo param; + + if (d.has("name")) { + param.name = d["name"]; + } + if (d.has("type")) { + param.type = (Variant::Type)(int)d["type"]; + } + + mi.arguments.push_back(param); + } + + add_user_signal(mi); +} + +Array Object::_get_signal_list() const { + List<MethodInfo> signal_list; + get_signal_list(&signal_list); + + Array ret; + for (const MethodInfo &E : signal_list) { + ret.push_back(Dictionary(E)); + } + + return ret; +} + +Array Object::_get_signal_connection_list(const String &p_signal) const { + List<Connection> conns; + get_all_signal_connections(&conns); + + Array ret; + + for (const Connection &c : conns) { + if (c.signal.get_name() == p_signal) { + ret.push_back(c); + } + } + + return ret; +} + +Array Object::_get_incoming_connections() const { + Array ret; + int connections_amount = connections.size(); + for (int idx_conn = 0; idx_conn < connections_amount; idx_conn++) { + ret.push_back(connections[idx_conn]); + } + + return ret; +} + +bool Object::has_signal(const StringName &p_name) const { + if (!script.is_null()) { + Ref<Script> scr = script; + if (scr.is_valid() && scr->has_script_signal(p_name)) { + return true; + } + } + + if (ClassDB::has_signal(get_class_name(), p_name)) { + return true; + } + + if (_has_user_signal(p_name)) { + return true; + } + + return false; +} + +void Object::get_signal_list(List<MethodInfo> *p_signals) const { + if (!script.is_null()) { + Ref<Script> scr = script; + if (scr.is_valid()) { + scr->get_script_signal_list(p_signals); + } + } + + ClassDB::get_signal_list(get_class_name(), p_signals); + //find maybe usersignals? + const StringName *S = nullptr; + + while ((S = signal_map.next(S))) { + if (signal_map[*S].user.name != "") { + //user signal + p_signals->push_back(signal_map[*S].user); + } + } +} + +void Object::get_all_signal_connections(List<Connection> *p_connections) const { + const StringName *S = nullptr; + + while ((S = signal_map.next(S))) { + const SignalData *s = &signal_map[*S]; + + for (int i = 0; i < s->slot_map.size(); i++) { + p_connections->push_back(s->slot_map.getv(i).conn); + } + } +} + +void Object::get_signal_connection_list(const StringName &p_signal, List<Connection> *p_connections) const { + const SignalData *s = signal_map.getptr(p_signal); + if (!s) { + return; //nothing + } + + for (int i = 0; i < s->slot_map.size(); i++) { + p_connections->push_back(s->slot_map.getv(i).conn); + } +} + +int Object::get_persistent_signal_connection_count() const { + int count = 0; + const StringName *S = nullptr; + + while ((S = signal_map.next(S))) { + const SignalData *s = &signal_map[*S]; + + for (int i = 0; i < s->slot_map.size(); i++) { + if (s->slot_map.getv(i).conn.flags & CONNECT_PERSIST) { + count += 1; + } + } + } + + return count; +} + +void Object::get_signals_connected_to_this(List<Connection> *p_connections) const { + for (const Connection &E : connections) { + p_connections->push_back(E); + } +} + +Error Object::connect(const StringName &p_signal, const Callable &p_callable, const Vector<Variant> &p_binds, uint32_t p_flags) { + ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null."); + + Object *target_object = p_callable.get_object(); + ERR_FAIL_COND_V_MSG(!target_object, ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null."); + + SignalData *s = signal_map.getptr(p_signal); + if (!s) { + bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal); + //check in script + if (!signal_is_valid && !script.is_null()) { + if (Ref<Script>(script)->has_script_signal(p_signal)) { + signal_is_valid = true; + } +#ifdef TOOLS_ENABLED + else { + //allow connecting signals anyway if script is invalid, see issue #17070 + if (!Ref<Script>(script)->is_valid()) { + signal_is_valid = true; + } + } +#endif + } + + ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, "In Object of type '" + String(get_class()) + "': Attempt to connect nonexistent signal '" + p_signal + "' to callable '" + p_callable + "'."); + + signal_map[p_signal] = SignalData(); + s = &signal_map[p_signal]; + } + + Callable target = p_callable; + + //compare with the base callable, so binds can be ignored + if (s->slot_map.has(*target.get_base_comparator())) { + if (p_flags & CONNECT_REFERENCE_COUNTED) { + s->slot_map[*target.get_base_comparator()].reference_count++; + return OK; + } else { + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Signal '" + p_signal + "' is already connected to given callable '" + p_callable + "' in that object."); + } + } + + SignalData::Slot slot; + + Connection conn; + conn.callable = target; + conn.signal = ::Signal(this, p_signal); + conn.flags = p_flags; + conn.binds = p_binds; + slot.conn = conn; + slot.cE = target_object->connections.push_back(conn); + if (p_flags & CONNECT_REFERENCE_COUNTED) { + slot.reference_count = 1; + } + + //use callable version as key, so binds can be ignored + s->slot_map[*target.get_base_comparator()] = slot; + + return OK; +} + +bool Object::is_connected(const StringName &p_signal, const Callable &p_callable) const { + ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot determine if connected to '" + p_signal + "': the provided callable is null."); + const SignalData *s = signal_map.getptr(p_signal); + if (!s) { + bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal); + if (signal_is_valid) { + return false; + } + + if (!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal)) { + return false; + } + + ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + "."); + } + + Callable target = p_callable; + + return s->slot_map.has(*target.get_base_comparator()); + //const Map<Signal::Target,Signal::Slot>::Element *E = s->slot_map.find(target); + //return (E!=nullptr ); +} + +void Object::disconnect(const StringName &p_signal, const Callable &p_callable) { + _disconnect(p_signal, p_callable); +} + +void Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) { + ERR_FAIL_COND_MSG(p_callable.is_null(), "Cannot disconnect from '" + p_signal + "': the provided callable is null."); + + Object *target_object = p_callable.get_object(); + ERR_FAIL_COND_MSG(!target_object, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null."); + + SignalData *s = signal_map.getptr(p_signal); + if (!s) { + bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal) || + (!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal)); + ERR_FAIL_COND_MSG(signal_is_valid, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'."); + } + ERR_FAIL_COND_MSG(!s, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string())); + + ERR_FAIL_COND_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), "Disconnecting nonexistent signal '" + p_signal + "', callable: " + p_callable + "."); + + SignalData::Slot *slot = &s->slot_map[p_callable]; + + if (!p_force) { + slot->reference_count--; // by default is zero, if it was not referenced it will go below it + if (slot->reference_count >= 0) { + return; + } + } + + target_object->connections.erase(slot->cE); + s->slot_map.erase(*p_callable.get_base_comparator()); + + if (s->slot_map.is_empty() && ClassDB::has_signal(get_class_name(), p_signal)) { + //not user signal, delete + signal_map.erase(p_signal); + } +} + +void Object::_set_bind(const String &p_set, const Variant &p_value) { + set(p_set, p_value); +} + +Variant Object::_get_bind(const String &p_name) const { + return get(p_name); +} + +void Object::_set_indexed_bind(const NodePath &p_name, const Variant &p_value) { + set_indexed(p_name.get_as_property_path().get_subnames(), p_value); +} + +Variant Object::_get_indexed_bind(const NodePath &p_name) const { + return get_indexed(p_name.get_as_property_path().get_subnames()); +} + +void Object::initialize_class() { + static bool initialized = false; + if (initialized) { + return; + } + ClassDB::_add_class<Object>(); + _bind_methods(); + initialized = true; +} + +String Object::tr(const StringName &p_message, const StringName &p_context) const { + if (!_can_translate || !TranslationServer::get_singleton()) { + return p_message; + } + return TranslationServer::get_singleton()->translate(p_message, p_context); +} + +String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + if (!_can_translate || !TranslationServer::get_singleton()) { + // Return message based on English plural rule if translation is not possible. + if (p_n == 1) { + return p_message; + } + return p_message_plural; + } + return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context); +} + +void Object::_clear_internal_resource_paths(const Variant &p_var) { + switch (p_var.get_type()) { + case Variant::OBJECT: { + RES r = p_var; + if (!r.is_valid()) { + return; + } + + if (!r->get_path().begins_with("res://") || r->get_path().find("::") == -1) { + return; //not an internal resource + } + + Object *object = p_var; + if (!object) { + return; + } + + r->set_path(""); + r->clear_internal_resource_paths(); + } break; + case Variant::ARRAY: { + Array a = p_var; + for (int i = 0; i < a.size(); i++) { + _clear_internal_resource_paths(a[i]); + } + + } break; + case Variant::DICTIONARY: { + Dictionary d = p_var; + List<Variant> keys; + d.get_key_list(&keys); + + for (const Variant &E : keys) { + _clear_internal_resource_paths(E); + _clear_internal_resource_paths(d[E]); + } + } break; + default: { + } + } +} + +#ifdef TOOLS_ENABLED +void Object::editor_set_section_unfold(const String &p_section, bool p_unfolded) { + set_edited(true); + if (p_unfolded) { + editor_section_folding.insert(p_section); + } else { + editor_section_folding.erase(p_section); + } +} + +bool Object::editor_is_section_unfolded(const String &p_section) { + return editor_section_folding.has(p_section); +} + +#endif + +void Object::clear_internal_resource_paths() { + List<PropertyInfo> pinfo; + + get_property_list(&pinfo); + + for (const PropertyInfo &E : pinfo) { + _clear_internal_resource_paths(get(E.name)); + } +} + +void Object::notify_property_list_changed() { + emit_signal(CoreStringNames::get_singleton()->property_list_changed); +} + +void Object::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_class"), &Object::get_class); + ClassDB::bind_method(D_METHOD("is_class", "class"), &Object::is_class); + ClassDB::bind_method(D_METHOD("set", "property", "value"), &Object::_set_bind); + ClassDB::bind_method(D_METHOD("get", "property"), &Object::_get_bind); + ClassDB::bind_method(D_METHOD("set_indexed", "property", "value"), &Object::_set_indexed_bind); + ClassDB::bind_method(D_METHOD("get_indexed", "property"), &Object::_get_indexed_bind); + ClassDB::bind_method(D_METHOD("get_property_list"), &Object::_get_property_list_bind); + ClassDB::bind_method(D_METHOD("get_method_list"), &Object::_get_method_list_bind); + ClassDB::bind_method(D_METHOD("notification", "what", "reversed"), &Object::notification, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("to_string"), &Object::to_string); + ClassDB::bind_method(D_METHOD("get_instance_id"), &Object::get_instance_id); + + ClassDB::bind_method(D_METHOD("set_script", "script"), &Object::set_script); + ClassDB::bind_method(D_METHOD("get_script"), &Object::get_script); + + ClassDB::bind_method(D_METHOD("set_meta", "name", "value"), &Object::set_meta); + ClassDB::bind_method(D_METHOD("remove_meta", "name"), &Object::remove_meta); + ClassDB::bind_method(D_METHOD("get_meta", "name"), &Object::get_meta); + ClassDB::bind_method(D_METHOD("has_meta", "name"), &Object::has_meta); + ClassDB::bind_method(D_METHOD("get_meta_list"), &Object::_get_meta_list_bind); + + ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal); + + { + MethodInfo mi; + mi.name = "emit_signal"; + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "signal")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "emit_signal", &Object::_emit_signal, mi, varray(), false); + } + + { + MethodInfo mi; + mi.name = "call"; + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call", &Object::_call_bind, mi); + } + + { + MethodInfo mi; + mi.name = "call_deferred"; + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_deferred", &Object::_call_deferred_bind, mi, varray(), false); + } + + ClassDB::bind_method(D_METHOD("set_deferred", "property", "value"), &Object::set_deferred); + + ClassDB::bind_method(D_METHOD("callv", "method", "arg_array"), &Object::callv); + + ClassDB::bind_method(D_METHOD("has_method", "method"), &Object::has_method); + + ClassDB::bind_method(D_METHOD("has_signal", "signal"), &Object::has_signal); + ClassDB::bind_method(D_METHOD("get_signal_list"), &Object::_get_signal_list); + ClassDB::bind_method(D_METHOD("get_signal_connection_list", "signal"), &Object::_get_signal_connection_list); + ClassDB::bind_method(D_METHOD("get_incoming_connections"), &Object::_get_incoming_connections); + + ClassDB::bind_method(D_METHOD("connect", "signal", "callable", "binds", "flags"), &Object::connect, DEFVAL(Array()), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("disconnect", "signal", "callable"), &Object::disconnect); + ClassDB::bind_method(D_METHOD("is_connected", "signal", "callable"), &Object::is_connected); + + ClassDB::bind_method(D_METHOD("set_block_signals", "enable"), &Object::set_block_signals); + ClassDB::bind_method(D_METHOD("is_blocking_signals"), &Object::is_blocking_signals); + ClassDB::bind_method(D_METHOD("notify_property_list_changed"), &Object::notify_property_list_changed); + + ClassDB::bind_method(D_METHOD("set_message_translation", "enable"), &Object::set_message_translation); + ClassDB::bind_method(D_METHOD("can_translate_messages"), &Object::can_translate_messages); + ClassDB::bind_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL("")); + ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL("")); + + ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion); + + ClassDB::add_virtual_method("Object", MethodInfo("free"), false); + + ADD_SIGNAL(MethodInfo("script_changed")); + ADD_SIGNAL(MethodInfo("property_list_changed")); + +#define BIND_OBJ_CORE_METHOD(m_method) \ + ::ClassDB::add_virtual_method(get_class_static(), m_method, true, Vector<String>(), true); + + BIND_OBJ_CORE_METHOD(MethodInfo("_notification", PropertyInfo(Variant::INT, "what"))); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_set", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value"))); +#ifdef TOOLS_ENABLED + MethodInfo miget("_get", PropertyInfo(Variant::STRING_NAME, "property")); + miget.return_val.name = "Variant"; + miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(miget); + + MethodInfo plget("_get_property_list"); + + plget.return_val.type = Variant::ARRAY; + BIND_OBJ_CORE_METHOD(plget); + +#endif + BIND_OBJ_CORE_METHOD(MethodInfo("_init")); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::STRING, "_to_string")); + + BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE); + BIND_CONSTANT(NOTIFICATION_PREDELETE); + + BIND_ENUM_CONSTANT(CONNECT_DEFERRED); + BIND_ENUM_CONSTANT(CONNECT_PERSIST); + BIND_ENUM_CONSTANT(CONNECT_ONESHOT); + BIND_ENUM_CONSTANT(CONNECT_REFERENCE_COUNTED); +} + +void Object::call_deferred(const StringName &p_method, VARIANT_ARG_DECLARE) { + MessageQueue::get_singleton()->push_call(this, p_method, VARIANT_ARG_PASS); +} + +void Object::set_deferred(const StringName &p_property, const Variant &p_value) { + MessageQueue::get_singleton()->push_set(this, p_property, p_value); +} + +void Object::set_block_signals(bool p_block) { + _block_signals = p_block; +} + +bool Object::is_blocking_signals() const { + return _block_signals; +} + +void Object::get_translatable_strings(List<String> *p_strings) const { + List<PropertyInfo> plist; + get_property_list(&plist); + + for (const PropertyInfo &E : plist) { + if (!(E.usage & PROPERTY_USAGE_INTERNATIONALIZED)) { + continue; + } + + String text = get(E.name); + + if (text == "") { + continue; + } + + p_strings->push_back(text); + } +} + +Variant::Type Object::get_static_property_type(const StringName &p_property, bool *r_valid) const { + bool valid; + Variant::Type t = ClassDB::get_property_type(get_class_name(), p_property, &valid); + if (valid) { + if (r_valid) { + *r_valid = true; + } + return t; + } + + if (get_script_instance()) { + return get_script_instance()->get_property_type(p_property, r_valid); + } + if (r_valid) { + *r_valid = false; + } + + return Variant::NIL; +} + +Variant::Type Object::get_static_property_type_indexed(const Vector<StringName> &p_path, bool *r_valid) const { + if (p_path.size() == 0) { + if (r_valid) { + *r_valid = false; + } + + return Variant::NIL; + } + + bool valid = false; + Variant::Type t = get_static_property_type(p_path[0], &valid); + if (!valid) { + if (r_valid) { + *r_valid = false; + } + + return Variant::NIL; + } + + Callable::CallError ce; + Variant check; + Variant::construct(t, check, nullptr, 0, ce); + + for (int i = 1; i < p_path.size(); i++) { + if (check.get_type() == Variant::OBJECT || check.get_type() == Variant::DICTIONARY || check.get_type() == Variant::ARRAY) { + // We cannot be sure about the type of properties this type can have + if (r_valid) { + *r_valid = false; + } + return Variant::NIL; + } + + check = check.get_named(p_path[i], valid); + + if (!valid) { + if (r_valid) { + *r_valid = false; + } + return Variant::NIL; + } + } + + if (r_valid) { + *r_valid = true; + } + + return check.get_type(); +} + +bool Object::is_queued_for_deletion() const { + return _is_queued_for_deletion; +} + +#ifdef TOOLS_ENABLED +void Object::set_edited(bool p_edited) { + _edited = p_edited; + _edited_version++; +} + +bool Object::is_edited() const { + return _edited; +} + +uint32_t Object::get_edited_version() const { + return _edited_version; +} +#endif + +void Object::set_instance_binding(void *p_token, void *p_binding, const GDNativeInstanceBindingCallbacks *p_callbacks) { + // This is only meant to be used on creation by the binder. + ERR_FAIL_COND(_instance_bindings != nullptr); + _instance_bindings = (InstanceBinding *)memalloc(sizeof(InstanceBinding)); + _instance_bindings[0].binding = p_binding; + _instance_bindings[0].free_callback = p_callbacks->free_callback; + _instance_bindings[0].reference_callback = p_callbacks->reference_callback; + _instance_bindings[0].token = p_token; + _instance_binding_count = 1; +} + +void *Object::get_instance_binding(void *p_token, const GDNativeInstanceBindingCallbacks *p_callbacks) { + void *binding = nullptr; + _instance_binding_mutex.lock(); + for (uint32_t i = 0; i < _instance_binding_count; i++) { + if (_instance_bindings[i].token == p_token) { + binding = _instance_bindings[i].binding; + break; + } + } + if (unlikely(!binding)) { + uint32_t current_size = next_power_of_2(_instance_binding_count); + uint32_t new_size = next_power_of_2(_instance_binding_count + 1); + + if (current_size == 0 || new_size > current_size) { + _instance_bindings = (InstanceBinding *)memrealloc(_instance_bindings, new_size * sizeof(InstanceBinding)); + } + + _instance_bindings[_instance_binding_count].free_callback = p_callbacks->free_callback; + _instance_bindings[_instance_binding_count].reference_callback = p_callbacks->reference_callback; + _instance_bindings[_instance_binding_count].token = p_token; + + binding = p_callbacks->create_callback(p_token, this); + _instance_bindings[_instance_binding_count].binding = binding; + + _instance_binding_count++; + } + + _instance_binding_mutex.unlock(); + + return binding; +} + +bool Object::has_instance_binding(void *p_token) { + bool found = false; + _instance_binding_mutex.lock(); + for (uint32_t i = 0; i < _instance_binding_count; i++) { + if (_instance_bindings[i].token == p_token) { + found = true; + break; + } + } + + _instance_binding_mutex.unlock(); + + return found; +} + +void Object::_construct_object(bool p_reference) { + type_is_reference = p_reference; + _instance_id = ObjectDB::add_instance(this); + + ClassDB::instance_get_native_extension_data(&_extension, &_extension_instance, this); + +#ifdef DEBUG_ENABLED + _lock_index.init(1); +#endif +} + +Object::Object(bool p_reference) { + _construct_object(p_reference); +} + +Object::Object() { + _construct_object(false); +} + +Object::~Object() { + if (script_instance) { + memdelete(script_instance); + } + script_instance = nullptr; + + if (_extension && _extension->free_instance) { + _extension->free_instance(_extension->class_userdata, _extension_instance); + _extension = nullptr; + _extension_instance = nullptr; + } + + const StringName *S = nullptr; + + if (_emitting) { + //@todo this may need to actually reach the debugger prioritarily somehow because it may crash before + ERR_PRINT("Object " + to_string() + " was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes."); + } + + while ((S = signal_map.next(nullptr))) { + SignalData *s = &signal_map[*S]; + + //brute force disconnect for performance + int slot_count = s->slot_map.size(); + const VMap<Callable, SignalData::Slot>::Pair *slot_list = s->slot_map.get_array(); + + for (int i = 0; i < slot_count; i++) { + slot_list[i].value.conn.callable.get_object()->connections.erase(slot_list[i].value.cE); + } + + signal_map.erase(*S); + } + + //signals from nodes that connect to this node + while (connections.size()) { + Connection c = connections.front()->get(); + c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true); + } + + ObjectDB::remove_instance(this); + _instance_id = ObjectID(); + _predelete_ok = 2; + + if (_instance_bindings != nullptr) { + for (uint32_t i = 0; i < _instance_binding_count; i++) { + if (_instance_bindings[i].free_callback) { + _instance_bindings[i].free_callback(_instance_bindings[i].token, this, _instance_bindings[i].binding); + } + } + memfree(_instance_bindings); + } +} + +bool predelete_handler(Object *p_object) { + return p_object->_predelete(); +} + +void postinitialize_handler(Object *p_object) { + p_object->_postinitialize(); +} + +void ObjectDB::debug_objects(DebugFunc p_func) { + spin_lock.lock(); + + for (uint32_t i = 0, count = slot_count; i < slot_max && count != 0; i++) { + if (object_slots[i].validator) { + p_func(object_slots[i].object); + count--; + } + } + spin_lock.unlock(); +} + +void Object::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { +} + +SpinLock ObjectDB::spin_lock; +uint32_t ObjectDB::slot_count = 0; +uint32_t ObjectDB::slot_max = 0; +ObjectDB::ObjectSlot *ObjectDB::object_slots = nullptr; +uint64_t ObjectDB::validator_counter = 0; + +int ObjectDB::get_object_count() { + return slot_count; +} + +ObjectID ObjectDB::add_instance(Object *p_object) { + spin_lock.lock(); + if (unlikely(slot_count == slot_max)) { + CRASH_COND(slot_count == (1 << OBJECTDB_SLOT_MAX_COUNT_BITS)); + + uint32_t new_slot_max = slot_max > 0 ? slot_max * 2 : 1; + object_slots = (ObjectSlot *)memrealloc(object_slots, sizeof(ObjectSlot) * new_slot_max); + for (uint32_t i = slot_max; i < new_slot_max; i++) { + object_slots[i].object = nullptr; + object_slots[i].is_ref_counted = false; + object_slots[i].next_free = i; + object_slots[i].validator = 0; + } + slot_max = new_slot_max; + } + + uint32_t slot = object_slots[slot_count].next_free; + if (object_slots[slot].object != nullptr) { + spin_lock.unlock(); + ERR_FAIL_COND_V(object_slots[slot].object != nullptr, ObjectID()); + } + object_slots[slot].object = p_object; + object_slots[slot].is_ref_counted = p_object->is_ref_counted(); + validator_counter = (validator_counter + 1) & OBJECTDB_VALIDATOR_MASK; + if (unlikely(validator_counter == 0)) { + validator_counter = 1; + } + object_slots[slot].validator = validator_counter; + + uint64_t id = validator_counter; + id <<= OBJECTDB_SLOT_MAX_COUNT_BITS; + id |= uint64_t(slot); + + if (p_object->is_ref_counted()) { + id |= OBJECTDB_REFERENCE_BIT; + } + + slot_count++; + + spin_lock.unlock(); + + return ObjectID(id); +} + +void ObjectDB::remove_instance(Object *p_object) { + uint64_t t = p_object->get_instance_id(); + uint32_t slot = t & OBJECTDB_SLOT_MAX_COUNT_MASK; //slot is always valid on valid object + + spin_lock.lock(); + +#ifdef DEBUG_ENABLED + + if (object_slots[slot].object != p_object) { + spin_lock.unlock(); + ERR_FAIL_COND(object_slots[slot].object != p_object); + } + { + uint64_t validator = (t >> OBJECTDB_SLOT_MAX_COUNT_BITS) & OBJECTDB_VALIDATOR_MASK; + if (object_slots[slot].validator != validator) { + spin_lock.unlock(); + ERR_FAIL_COND(object_slots[slot].validator != validator); + } + } + +#endif + //decrease slot count + slot_count--; + //set the free slot properly + object_slots[slot_count].next_free = slot; + //invalidate, so checks against it fail + object_slots[slot].validator = 0; + object_slots[slot].is_ref_counted = false; + object_slots[slot].object = nullptr; + + spin_lock.unlock(); +} + +void ObjectDB::setup() { + //nothing to do now +} + +void ObjectDB::cleanup() { + if (slot_count > 0) { + spin_lock.lock(); + + WARN_PRINT("ObjectDB instances leaked at exit (run with --verbose for details)."); + if (OS::get_singleton()->is_stdout_verbose()) { + // Ensure calling the native classes because if a leaked instance has a script + // that overrides any of those methods, it'd not be OK to call them at this point, + // now the scripting languages have already been terminated. + MethodBind *node_get_name = ClassDB::get_method("Node", "get_name"); + MethodBind *resource_get_path = ClassDB::get_method("Resource", "get_path"); + Callable::CallError call_error; + + for (uint32_t i = 0, count = slot_count; i < slot_max && count != 0; i++) { + if (object_slots[i].validator) { + Object *obj = object_slots[i].object; + + String extra_info; + if (obj->is_class("Node")) { + extra_info = " - Node name: " + String(node_get_name->call(obj, nullptr, 0, call_error)); + } + if (obj->is_class("Resource")) { + extra_info = " - Resource path: " + String(resource_get_path->call(obj, nullptr, 0, call_error)); + } + + uint64_t id = uint64_t(i) | (uint64_t(object_slots[i].validator) << OBJECTDB_VALIDATOR_BITS) | (object_slots[i].is_ref_counted ? OBJECTDB_REFERENCE_BIT : 0); + print_line("Leaked instance: " + String(obj->get_class()) + ":" + itos(id) + extra_info); + + count--; + } + } + print_line("Hint: Leaked instances typically happen when nodes are removed from the scene tree (with `remove_child()`) but not freed (with `free()` or `queue_free()`)."); + } + spin_lock.unlock(); + } + + if (object_slots) { + memfree(object_slots); + } +} diff --git a/core/object/object.h b/core/object/object.h new file mode 100644 index 0000000000..aede48343c --- /dev/null +++ b/core/object/object.h @@ -0,0 +1,896 @@ +/*************************************************************************/ +/* object.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OBJECT_H +#define OBJECT_H + +#include "core/extension/gdnative_interface.h" +#include "core/object/object_id.h" +#include "core/os/rw_lock.h" +#include "core/os/spin_lock.h" +#include "core/templates/hash_map.h" +#include "core/templates/list.h" +#include "core/templates/map.h" +#include "core/templates/safe_refcount.h" +#include "core/templates/set.h" +#include "core/templates/vmap.h" +#include "core/variant/callable_bind.h" +#include "core/variant/variant.h" + +#define VARIANT_ARG_LIST const Variant &p_arg1 = Variant(), const Variant &p_arg2 = Variant(), const Variant &p_arg3 = Variant(), const Variant &p_arg4 = Variant(), const Variant &p_arg5 = Variant(), const Variant &p_arg6 = Variant(), const Variant &p_arg7 = Variant(), const Variant &p_arg8 = Variant() +#define VARIANT_ARG_PASS p_arg1, p_arg2, p_arg3, p_arg4, p_arg5, p_arg6, p_arg7, p_arg8 +#define VARIANT_ARG_DECLARE const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8 +#define VARIANT_ARG_MAX 8 +#define VARIANT_ARGPTRS const Variant *argptr[8] = { &p_arg1, &p_arg2, &p_arg3, &p_arg4, &p_arg5, &p_arg6, &p_arg7, &p_arg8 }; +#define VARIANT_ARGPTRS_PASS *argptr[0], *argptr[1], *argptr[2], *argptr[3], *argptr[4], *argptr[5], *argptr[6]], *argptr[7] +#define VARIANT_ARGS_FROM_ARRAY(m_arr) m_arr[0], m_arr[1], m_arr[2], m_arr[3], m_arr[4], m_arr[5], m_arr[6], m_arr[7] + +/** +@author Juan Linietsky <reduzio@gmail.com> +*/ + +enum PropertyHint { + PROPERTY_HINT_NONE, ///< no hint provided. + PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_lesser][,noslider][,radians][,degrees][,exp][,suffix:<keyword>] range. + PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" + PROPERTY_HINT_ENUM_SUGGESTION, ///< hint_text= "val1,val2,val3,etc" + PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "full" to also include in/out. (ie: "attenuation,inout") + PROPERTY_HINT_LENGTH, ///< hint_text= "length" (as integer) + PROPERTY_HINT_KEY_ACCEL, ///< hint_text= "length" (as integer) + PROPERTY_HINT_FLAGS, ///< hint_text= "flag1,flag2,etc" (as bit flags) + PROPERTY_HINT_LAYERS_2D_RENDER, + PROPERTY_HINT_LAYERS_2D_PHYSICS, + PROPERTY_HINT_LAYERS_2D_NAVIGATION, + PROPERTY_HINT_LAYERS_3D_RENDER, + PROPERTY_HINT_LAYERS_3D_PHYSICS, + PROPERTY_HINT_LAYERS_3D_NAVIGATION, + PROPERTY_HINT_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," + PROPERTY_HINT_DIR, ///< a directory path must be passed + PROPERTY_HINT_GLOBAL_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc," + PROPERTY_HINT_GLOBAL_DIR, ///< a directory path must be passed + PROPERTY_HINT_RESOURCE_TYPE, ///< a resource object type + PROPERTY_HINT_MULTILINE_TEXT, ///< used for string properties that can contain multiple lines + PROPERTY_HINT_PLACEHOLDER_TEXT, ///< used to set a placeholder text for string properties + PROPERTY_HINT_COLOR_NO_ALPHA, ///< used for ignoring alpha component when editing a color + PROPERTY_HINT_IMAGE_COMPRESS_LOSSY, + PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS, + PROPERTY_HINT_OBJECT_ID, + PROPERTY_HINT_TYPE_STRING, ///< a type string, the hint is the base type to choose + PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE, ///< so something else can provide this (used in scripts) + PROPERTY_HINT_METHOD_OF_VARIANT_TYPE, ///< a method of a type + PROPERTY_HINT_METHOD_OF_BASE_TYPE, ///< a method of a base type + PROPERTY_HINT_METHOD_OF_INSTANCE, ///< a method of an instance + PROPERTY_HINT_METHOD_OF_SCRIPT, ///< a method of a script & base + PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE, ///< a property of a type + PROPERTY_HINT_PROPERTY_OF_BASE_TYPE, ///< a property of a base type + PROPERTY_HINT_PROPERTY_OF_INSTANCE, ///< a property of an instance + PROPERTY_HINT_PROPERTY_OF_SCRIPT, ///< a property of a script & base + PROPERTY_HINT_OBJECT_TOO_BIG, ///< object is too big to send + PROPERTY_HINT_NODE_PATH_VALID_TYPES, + PROPERTY_HINT_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog + PROPERTY_HINT_INT_IS_OBJECTID, + PROPERTY_HINT_ARRAY_TYPE, + PROPERTY_HINT_MAX, + // When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit +}; + +enum PropertyUsageFlags { + PROPERTY_USAGE_NONE = 0, + PROPERTY_USAGE_STORAGE = 1, + PROPERTY_USAGE_EDITOR = 2, + PROPERTY_USAGE_NETWORK = 4, + PROPERTY_USAGE_EDITOR_HELPER = 8, + PROPERTY_USAGE_CHECKABLE = 16, //used for editing global variables + PROPERTY_USAGE_CHECKED = 32, //used for editing global variables + PROPERTY_USAGE_INTERNATIONALIZED = 64, //hint for internationalized strings + PROPERTY_USAGE_GROUP = 128, //used for grouping props in the editor + PROPERTY_USAGE_CATEGORY = 256, + PROPERTY_USAGE_SUBGROUP = 512, + PROPERTY_USAGE_NO_INSTANCE_STATE = 2048, + PROPERTY_USAGE_RESTART_IF_CHANGED = 4096, + PROPERTY_USAGE_SCRIPT_VARIABLE = 8192, + PROPERTY_USAGE_STORE_IF_NULL = 16384, + PROPERTY_USAGE_ANIMATE_AS_TRIGGER = 32768, + PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED = 65536, + PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE = 1 << 17, + PROPERTY_USAGE_CLASS_IS_ENUM = 1 << 18, + PROPERTY_USAGE_NIL_IS_VARIANT = 1 << 19, + PROPERTY_USAGE_INTERNAL = 1 << 20, + PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE = 1 << 21, // If the object is duplicated also this property will be duplicated + PROPERTY_USAGE_HIGH_END_GFX = 1 << 22, + PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT = 1 << 23, + PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT = 1 << 24, + PROPERTY_USAGE_KEYING_INCREMENTS = 1 << 25, // Used in inspector to increment property when keyed in animation player + PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 26, // when loading, the resource for this property can be set at the end of loading + PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 27, // For Object properties, instantiate them when creating in editor. + PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 28, //for project or editor settings, show when basic settings are selected + + PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK, + PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK | PROPERTY_USAGE_INTERNATIONALIZED, + PROPERTY_USAGE_NOEDITOR = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_NETWORK, +}; + +#define ADD_SIGNAL(m_signal) ::ClassDB::add_signal(get_class_static(), m_signal) +#define ADD_PROPERTY(m_property, m_setter, m_getter) ::ClassDB::add_property(get_class_static(), m_property, _scs_create(m_setter), _scs_create(m_getter)) +#define ADD_PROPERTYI(m_property, m_setter, m_getter, m_index) ::ClassDB::add_property(get_class_static(), m_property, _scs_create(m_setter), _scs_create(m_getter), m_index) +#define ADD_PROPERTY_DEFAULT(m_property, m_default) ::ClassDB::set_property_default_value(get_class_static(), m_property, m_default) +#define ADD_GROUP(m_name, m_prefix) ::ClassDB::add_property_group(get_class_static(), m_name, m_prefix) +#define ADD_SUBGROUP(m_name, m_prefix) ::ClassDB::add_property_subgroup(get_class_static(), m_name, m_prefix) +#define ADD_LINKED_PROPERTY(m_property, m_linked_property) ::ClassDB::add_linked_property(get_class_static(), m_property, m_linked_property) + +struct PropertyInfo { + Variant::Type type = Variant::NIL; + String name; + StringName class_name; // For classes + PropertyHint hint = PROPERTY_HINT_NONE; + String hint_string; + uint32_t usage = PROPERTY_USAGE_DEFAULT; + +#ifdef TOOLS_ENABLED + Vector<String> linked_properties; +#endif + + _FORCE_INLINE_ PropertyInfo added_usage(uint32_t p_fl) const { + PropertyInfo pi = *this; + pi.usage |= p_fl; + return pi; + } + + operator Dictionary() const; + + static PropertyInfo from_dict(const Dictionary &p_dict); + + PropertyInfo() {} + + PropertyInfo(const Variant::Type p_type, const String p_name, const PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = "", const uint32_t p_usage = PROPERTY_USAGE_DEFAULT, const StringName &p_class_name = StringName()) : + type(p_type), + name(p_name), + hint(p_hint), + hint_string(p_hint_string), + usage(p_usage) { + if (hint == PROPERTY_HINT_RESOURCE_TYPE) { + class_name = hint_string; + } else { + class_name = p_class_name; + } + } + + PropertyInfo(const StringName &p_class_name) : + type(Variant::OBJECT), + class_name(p_class_name) {} + + bool operator==(const PropertyInfo &p_info) const { + return ((type == p_info.type) && + (name == p_info.name) && + (class_name == p_info.class_name) && + (hint == p_info.hint) && + (hint_string == p_info.hint_string) && + (usage == p_info.usage)); + } + + bool operator<(const PropertyInfo &p_info) const { + return name < p_info.name; + } +}; + +Array convert_property_list(const List<PropertyInfo> *p_list); + +struct MethodInfo { + String name; + PropertyInfo return_val; + uint32_t flags; // NOLINT - prevent clang-tidy to assign method_bind.h constant here, it should stay in .cpp. + int id = 0; + List<PropertyInfo> arguments; + Vector<Variant> default_arguments; + + inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id; } + inline bool operator<(const MethodInfo &p_method) const { return id == p_method.id ? (name < p_method.name) : (id < p_method.id); } + + operator Dictionary() const; + + static MethodInfo from_dict(const Dictionary &p_dict); + + MethodInfo(); + MethodInfo(const String &p_name); + MethodInfo(const String &p_name, const PropertyInfo &p_param1); + MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2); + MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3); + MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4); + MethodInfo(const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5); + MethodInfo(Variant::Type ret); + MethodInfo(Variant::Type ret, const String &p_name); + MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1); + MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2); + MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3); + MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4); + MethodInfo(Variant::Type ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5); + MethodInfo(const PropertyInfo &p_ret, const String &p_name); + MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1); + MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2); + MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3); + MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4); + MethodInfo(const PropertyInfo &p_ret, const String &p_name, const PropertyInfo &p_param1, const PropertyInfo &p_param2, const PropertyInfo &p_param3, const PropertyInfo &p_param4, const PropertyInfo &p_param5); +}; + +// old cast_to +//if ( is_type(T::get_class_static()) ) +//return static_cast<T*>(this); +////else +//return nullptr; + +// API used to extend in GDNative and other C compatible compiled languages +class MethodBind; + +struct ObjectNativeExtension { + ObjectNativeExtension *parent = nullptr; + List<ObjectNativeExtension *> children; + StringName parent_class_name; + StringName class_name; + bool editor_class = false; + GDNativeExtensionClassSet set; + GDNativeExtensionClassGet get; + GDNativeExtensionClassGetPropertyList get_property_list; + GDNativeExtensionClassFreePropertyList free_property_list; + GDNativeExtensionClassNotification notification; + GDNativeExtensionClassToString to_string; + GDNativeExtensionClassReference reference; + GDNativeExtensionClassReference unreference; + + _FORCE_INLINE_ bool is_class(const String &p_class) const { + const ObjectNativeExtension *e = this; + while (e) { + if (p_class == e->class_name.operator String()) { + return true; + } + e = e->parent; + } + return false; + } + void *class_userdata = nullptr; + + GDNativeExtensionClassCreateInstance create_instance; + GDNativeExtensionClassFreeInstance free_instance; + GDNativeExtensionClassObjectInstance set_object_instance; + GDNativeExtensionClassGetVirtual get_virtual; +}; + +#define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call(__VA_ARGS__) +#define GDVIRTUAL_CALL_PTR(m_obj, m_name, ...) m_obj->_gdvirtual_##m_name##_call(__VA_ARGS__) +#ifdef DEBUG_METHODS_ENABLED +#define GDVIRTUAL_BIND(m_name, ...) ::ClassDB::add_virtual_method(get_class_static(), _gdvirtual_##m_name##_get_method_info(), true, sarray(__VA_ARGS__)); +#else +#define GDVIRTUAL_BIND(m_name, ...) +#endif +#define GDVIRTUAL_IS_OVERRIDEN(m_name) _gdvirtual_##m_name##_overriden() +#define GDVIRTUAL_IS_OVERRIDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overriden() + +/* + the following is an incomprehensible blob of hacks and workarounds to compensate for many of the fallencies in C++. As a plus, this macro pretty much alone defines the object model. +*/ + +#define REVERSE_GET_PROPERTY_LIST \ +public: \ + _FORCE_INLINE_ bool _is_gpl_reversed() const { return true; }; \ + \ +private: + +#define UNREVERSE_GET_PROPERTY_LIST \ +public: \ + _FORCE_INLINE_ bool _is_gpl_reversed() const { return false; }; \ + \ +private: + +#define GDCLASS(m_class, m_inherits) \ +private: \ + void operator=(const m_class &p_rval) {} \ + mutable StringName _class_name; \ + friend class ::ClassDB; \ + \ +public: \ + virtual String get_class() const override { \ + if (_get_extension()) { \ + return _get_extension()->class_name.operator String(); \ + } \ + return String(#m_class); \ + } \ + virtual const StringName *_get_class_namev() const override { \ + if (_get_extension()) { \ + return &_get_extension()->class_name; \ + } \ + if (!_class_name) { \ + _class_name = get_class_static(); \ + } \ + return &_class_name; \ + } \ + static _FORCE_INLINE_ void *get_class_ptr_static() { \ + static int ptr; \ + return &ptr; \ + } \ + static _FORCE_INLINE_ String get_class_static() { \ + return String(#m_class); \ + } \ + static _FORCE_INLINE_ String get_parent_class_static() { \ + return m_inherits::get_class_static(); \ + } \ + static void get_inheritance_list_static(List<String> *p_inheritance_list) { \ + m_inherits::get_inheritance_list_static(p_inheritance_list); \ + p_inheritance_list->push_back(String(#m_class)); \ + } \ + static String get_category_static() { \ + String category = m_inherits::get_category_static(); \ + if (_get_category != m_inherits::_get_category) { \ + if (category != "") { \ + category += "/"; \ + } \ + category += _get_category(); \ + } \ + return category; \ + } \ + static String inherits_static() { \ + return String(#m_inherits); \ + } \ + virtual bool is_class(const String &p_class) const override { \ + if (_get_extension() && _get_extension()->is_class(p_class)) { \ + return true; \ + } \ + return (p_class == (#m_class)) ? true : m_inherits::is_class(p_class); \ + } \ + virtual bool is_class_ptr(void *p_ptr) const override { return (p_ptr == get_class_ptr_static()) ? true : m_inherits::is_class_ptr(p_ptr); } \ + \ + static void get_valid_parents_static(List<String> *p_parents) { \ + if (m_class::_get_valid_parents_static != m_inherits::_get_valid_parents_static) { \ + m_class::_get_valid_parents_static(p_parents); \ + } \ + \ + m_inherits::get_valid_parents_static(p_parents); \ + } \ + \ +protected: \ + _FORCE_INLINE_ static void (*_get_bind_methods())() { \ + return &m_class::_bind_methods; \ + } \ + \ +public: \ + static void initialize_class() { \ + static bool initialized = false; \ + if (initialized) { \ + return; \ + } \ + m_inherits::initialize_class(); \ + ::ClassDB::_add_class<m_class>(); \ + if (m_class::_get_bind_methods() != m_inherits::_get_bind_methods()) { \ + _bind_methods(); \ + } \ + initialized = true; \ + } \ + \ +protected: \ + virtual void _initialize_classv() override { \ + initialize_class(); \ + } \ + _FORCE_INLINE_ bool (Object::*_get_get() const)(const StringName &p_name, Variant &) const { \ + return (bool (Object::*)(const StringName &, Variant &) const) & m_class::_get; \ + } \ + virtual bool _getv(const StringName &p_name, Variant &r_ret) const override { \ + if (m_class::_get_get() != m_inherits::_get_get()) { \ + if (_get(p_name, r_ret)) { \ + return true; \ + } \ + } \ + return m_inherits::_getv(p_name, r_ret); \ + } \ + _FORCE_INLINE_ bool (Object::*_get_set() const)(const StringName &p_name, const Variant &p_property) { \ + return (bool (Object::*)(const StringName &, const Variant &)) & m_class::_set; \ + } \ + virtual bool _setv(const StringName &p_name, const Variant &p_property) override { \ + if (m_inherits::_setv(p_name, p_property)) { \ + return true; \ + } \ + if (m_class::_get_set() != m_inherits::_get_set()) { \ + return _set(p_name, p_property); \ + } \ + return false; \ + } \ + _FORCE_INLINE_ void (Object::*_get_get_property_list() const)(List<PropertyInfo> * p_list) const { \ + return (void (Object::*)(List<PropertyInfo> *) const) & m_class::_get_property_list; \ + } \ + virtual void _get_property_listv(List<PropertyInfo> *p_list, bool p_reversed) const override { \ + if (!p_reversed) { \ + m_inherits::_get_property_listv(p_list, p_reversed); \ + } \ + p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); \ + if (!_is_gpl_reversed()) { \ + ::ClassDB::get_property_list(#m_class, p_list, true, this); \ + } \ + if (m_class::_get_get_property_list() != m_inherits::_get_get_property_list()) { \ + _get_property_list(p_list); \ + } \ + if (_is_gpl_reversed()) { \ + ::ClassDB::get_property_list(#m_class, p_list, true, this); \ + } \ + if (p_reversed) { \ + m_inherits::_get_property_listv(p_list, p_reversed); \ + } \ + } \ + _FORCE_INLINE_ void (Object::*_get_notification() const)(int) { \ + return (void (Object::*)(int)) & m_class::_notification; \ + } \ + virtual void _notificationv(int p_notification, bool p_reversed) override { \ + if (!p_reversed) { \ + m_inherits::_notificationv(p_notification, p_reversed); \ + } \ + if (m_class::_get_notification() != m_inherits::_get_notification()) { \ + _notification(p_notification); \ + } \ + if (p_reversed) { \ + m_inherits::_notificationv(p_notification, p_reversed); \ + } \ + } \ + \ +private: + +#define OBJ_CATEGORY(m_category) \ +protected: \ + _FORCE_INLINE_ static String _get_category() { return m_category; } \ + \ +private: + +#define OBJ_SAVE_TYPE(m_class) \ +public: \ + virtual String get_save_class() const override { return #m_class; } \ + \ +private: + +class ScriptInstance; + +class Object { +public: + enum ConnectFlags { + CONNECT_DEFERRED = 1, + CONNECT_PERSIST = 2, // hint for scene to save this connection + CONNECT_ONESHOT = 4, + CONNECT_REFERENCE_COUNTED = 8, + }; + + struct Connection { + ::Signal signal; + Callable callable; + + uint32_t flags = 0; + Vector<Variant> binds; + bool operator<(const Connection &p_conn) const; + + operator Variant() const; + + Connection() {} + Connection(const Variant &p_variant); + }; + +private: +#ifdef DEBUG_ENABLED + friend struct _ObjectDebugLock; +#endif + friend bool predelete_handler(Object *); + friend void postinitialize_handler(Object *); + + ObjectNativeExtension *_extension = nullptr; + GDExtensionClassInstancePtr _extension_instance = nullptr; + + struct SignalData { + struct Slot { + int reference_count = 0; + Connection conn; + List<Connection>::Element *cE = nullptr; + }; + + MethodInfo user; + VMap<Callable, Slot> slot_map; + }; + + HashMap<StringName, SignalData> signal_map; + List<Connection> connections; +#ifdef DEBUG_ENABLED + SafeRefCount _lock_index; +#endif + bool _block_signals = false; + int _predelete_ok = 0; + ObjectID _instance_id; + bool _predelete(); + void _postinitialize(); + bool _can_translate = true; + bool _emitting = false; +#ifdef TOOLS_ENABLED + bool _edited = false; + uint32_t _edited_version = 0; + Set<String> editor_section_folding; +#endif + ScriptInstance *script_instance = nullptr; + Variant script; //reference does not yet exist, store it in a + Dictionary metadata; + mutable StringName _class_name; + mutable const StringName *_class_ptr = nullptr; + + void _add_user_signal(const String &p_name, const Array &p_args = Array()); + bool _has_user_signal(const StringName &p_name) const; + Variant _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + Array _get_signal_list() const; + Array _get_signal_connection_list(const String &p_signal) const; + Array _get_incoming_connections() const; + void _set_bind(const String &p_set, const Variant &p_value); + Variant _get_bind(const String &p_name) const; + void _set_indexed_bind(const NodePath &p_name, const Variant &p_value); + Variant _get_indexed_bind(const NodePath &p_name) const; + + _FORCE_INLINE_ void _construct_object(bool p_reference); + + friend class RefCounted; + bool type_is_reference = false; + + std::mutex _instance_binding_mutex; + struct InstanceBinding { + void *binding; + void *token; + GDNativeInstanceBindingFreeCallback free_callback = nullptr; + GDNativeInstanceBindingReferenceCallback reference_callback = nullptr; + }; + InstanceBinding *_instance_bindings = nullptr; + uint32_t _instance_binding_count = 0; + + Object(bool p_reference); + +protected: + _FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) { + bool can_die = true; + if (_instance_bindings) { + _instance_binding_mutex.lock(); + for (uint32_t i = 0; i < _instance_binding_count; i++) { + if (_instance_bindings[i].reference_callback) { + if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) { + can_die = false; + } + } + } + _instance_binding_mutex.unlock(); + } + return can_die; + } + friend class NativeExtensionMethodBind; + _ALWAYS_INLINE_ const ObjectNativeExtension *_get_extension() const { return _extension; } + _ALWAYS_INLINE_ GDExtensionClassInstancePtr _get_extension_instance() const { return _extension_instance; } + virtual void _initialize_classv() { initialize_class(); } + virtual bool _setv(const StringName &p_name, const Variant &p_property) { return false; }; + virtual bool _getv(const StringName &p_name, Variant &r_property) const { return false; }; + virtual void _get_property_listv(List<PropertyInfo> *p_list, bool p_reversed) const {}; + virtual void _notificationv(int p_notification, bool p_reversed) {} + + static String _get_category() { return ""; } + static void _bind_methods(); + bool _set(const StringName &p_name, const Variant &p_property) { return false; }; + bool _get(const StringName &p_name, Variant &r_property) const { return false; }; + void _get_property_list(List<PropertyInfo> *p_list) const {}; + void _notification(int p_notification) {} + + _FORCE_INLINE_ static void (*_get_bind_methods())() { + return &Object::_bind_methods; + } + _FORCE_INLINE_ bool (Object::*_get_get() const)(const StringName &p_name, Variant &r_ret) const { + return &Object::_get; + } + _FORCE_INLINE_ bool (Object::*_get_set() const)(const StringName &p_name, const Variant &p_property) { + return &Object::_set; + } + _FORCE_INLINE_ void (Object::*_get_get_property_list() const)(List<PropertyInfo> *p_list) const { + return &Object::_get_property_list; + } + _FORCE_INLINE_ void (Object::*_get_notification() const)(int) { + return &Object::_notification; + } + static void get_valid_parents_static(List<String> *p_parents); + static void _get_valid_parents_static(List<String> *p_parents); + + //Variant _call_bind(const StringName& p_name, const Variant& p_arg1 = Variant(), const Variant& p_arg2 = Variant(), const Variant& p_arg3 = Variant(), const Variant& p_arg4 = Variant()); + //void _call_deferred_bind(const StringName& p_name, const Variant& p_arg1 = Variant(), const Variant& p_arg2 = Variant(), const Variant& p_arg3 = Variant(), const Variant& p_arg4 = Variant()); + + Variant _call_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + Variant _call_deferred_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + + virtual const StringName *_get_class_namev() const { + if (!_class_name) { + _class_name = get_class_static(); + } + return &_class_name; + } + + Vector<StringName> _get_meta_list_bind() const; + Array _get_property_list_bind() const; + Array _get_method_list_bind() const; + + void _clear_internal_resource_paths(const Variant &p_var); + + friend class ClassDB; + virtual void _validate_property(PropertyInfo &property) const; + + void _disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force = false); + +public: //should be protected, but bug in clang++ + static void initialize_class(); + _FORCE_INLINE_ static void register_custom_data_to_otdb() {} + +public: + void notify_property_list_changed(); + + static void *get_class_ptr_static() { + static int ptr; + return &ptr; + } + + bool _is_gpl_reversed() const { return false; } + + _FORCE_INLINE_ ObjectID get_instance_id() const { return _instance_id; } + + template <class T> + static T *cast_to(Object *p_object) { +#ifndef NO_SAFE_CAST + return dynamic_cast<T *>(p_object); +#else + if (!p_object) { + return nullptr; + } + if (p_object->is_class_ptr(T::get_class_ptr_static())) { + return static_cast<T *>(p_object); + } else { + return nullptr; + } +#endif + } + + template <class T> + static const T *cast_to(const Object *p_object) { +#ifndef NO_SAFE_CAST + return dynamic_cast<const T *>(p_object); +#else + if (!p_object) { + return nullptr; + } + if (p_object->is_class_ptr(T::get_class_ptr_static())) { + return static_cast<const T *>(p_object); + } else { + return nullptr; + } +#endif + } + + enum { + NOTIFICATION_POSTINITIALIZE = 0, + NOTIFICATION_PREDELETE = 1 + }; + + /* TYPE API */ + static void get_inheritance_list_static(List<String> *p_inheritance_list) { p_inheritance_list->push_back("Object"); } + + static String get_class_static() { return "Object"; } + static String get_parent_class_static() { return String(); } + static String get_category_static() { return String(); } + + virtual String get_class() const { + if (_extension) + return _extension->class_name.operator String(); + return "Object"; + } + virtual String get_save_class() const { return get_class(); } //class stored when saving + + virtual bool is_class(const String &p_class) const { + if (_extension && _extension->is_class(p_class)) { + return true; + } + return (p_class == "Object"); + } + virtual bool is_class_ptr(void *p_ptr) const { return get_class_ptr_static() == p_ptr; } + + _FORCE_INLINE_ const StringName &get_class_name() const { + if (_extension) { + return _extension->class_name; + } + if (!_class_ptr) { + return *_get_class_namev(); + } else { + return *_class_ptr; + } + } + + /* IAPI */ + //void set(const String& p_name, const Variant& p_value); + //Variant get(const String& p_name) const; + + void set(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr); + Variant get(const StringName &p_name, bool *r_valid = nullptr) const; + void set_indexed(const Vector<StringName> &p_names, const Variant &p_value, bool *r_valid = nullptr); + Variant get_indexed(const Vector<StringName> &p_names, bool *r_valid = nullptr) const; + + void get_property_list(List<PropertyInfo> *p_list, bool p_reversed = false) const; + + bool has_method(const StringName &p_method) const; + void get_method_list(List<MethodInfo> *p_list) const; + Variant callv(const StringName &p_method, const Array &p_args); + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); + Variant call(const StringName &p_name, VARIANT_ARG_LIST); // C++ helper + + void notification(int p_notification, bool p_reversed = false); + virtual String to_string(); + + //used mainly by script, get and set all INCLUDING string + virtual Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const; + virtual void setvar(const Variant &p_key, const Variant &p_value, bool *r_valid = nullptr); + + /* SCRIPT */ + + void set_script(const Variant &p_script); + Variant get_script() const; + + /* SCRIPT */ + + bool has_meta(const StringName &p_name) const; + void set_meta(const StringName &p_name, const Variant &p_value); + void remove_meta(const StringName &p_name); + Variant get_meta(const StringName &p_name) const; + void get_meta_list(List<StringName> *p_list) const; + +#ifdef TOOLS_ENABLED + void set_edited(bool p_edited); + bool is_edited() const; + uint32_t get_edited_version() const; //this function is used to check when something changed beyond a point, it's used mainly for generating previews +#endif + + void set_script_instance(ScriptInstance *p_instance); + _FORCE_INLINE_ ScriptInstance *get_script_instance() const { return script_instance; } + + void set_script_and_instance(const Variant &p_script, ScriptInstance *p_instance); //some script languages can't control instance creation, so this function eases the process + + void add_user_signal(const MethodInfo &p_signal); + Error emit_signal(const StringName &p_name, VARIANT_ARG_LIST); + Error emit_signal(const StringName &p_name, const Variant **p_args, int p_argcount); + bool has_signal(const StringName &p_name) const; + void get_signal_list(List<MethodInfo> *p_signals) const; + void get_signal_connection_list(const StringName &p_signal, List<Connection> *p_connections) const; + void get_all_signal_connections(List<Connection> *p_connections) const; + int get_persistent_signal_connection_count() const; + void get_signals_connected_to_this(List<Connection> *p_connections) const; + + Error connect(const StringName &p_signal, const Callable &p_callable, const Vector<Variant> &p_binds = Vector<Variant>(), uint32_t p_flags = 0); + void disconnect(const StringName &p_signal, const Callable &p_callable); + bool is_connected(const StringName &p_signal, const Callable &p_callable) const; + + void call_deferred(const StringName &p_method, VARIANT_ARG_LIST); + void set_deferred(const StringName &p_property, const Variant &p_value); + + void set_block_signals(bool p_block); + bool is_blocking_signals() const; + + Variant::Type get_static_property_type(const StringName &p_property, bool *r_valid = nullptr) const; + Variant::Type get_static_property_type_indexed(const Vector<StringName> &p_path, bool *r_valid = nullptr) const; + + virtual void get_translatable_strings(List<String> *p_strings) const; + + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const; + + String tr(const StringName &p_message, const StringName &p_context = "") const; // translate message (internationalization) + String tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; + + bool _is_queued_for_deletion = false; // set to true by SceneTree::queue_delete() + bool is_queued_for_deletion() const; + + _FORCE_INLINE_ void set_message_translation(bool p_enable) { _can_translate = p_enable; } + _FORCE_INLINE_ bool can_translate_messages() const { return _can_translate; } + +#ifdef TOOLS_ENABLED + void editor_set_section_unfold(const String &p_section, bool p_unfolded); + bool editor_is_section_unfolded(const String &p_section); + const Set<String> &editor_get_section_folding() const { return editor_section_folding; } + void editor_clear_section_folding() { editor_section_folding.clear(); } + +#endif + + // Used by script languages to store binding data. + void *get_instance_binding(void *p_token, const GDNativeInstanceBindingCallbacks *p_callbacks); + // Used on creation by binding only. + void set_instance_binding(void *p_token, void *p_binding, const GDNativeInstanceBindingCallbacks *p_callbacks); + bool has_instance_binding(void *p_token); + + void clear_internal_resource_paths(); + + _ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; } + + Object(); + virtual ~Object(); +}; + +bool predelete_handler(Object *p_object); +void postinitialize_handler(Object *p_object); + +class ObjectDB { +//this needs to add up to 63, 1 bit is for reference +#define OBJECTDB_VALIDATOR_BITS 39 +#define OBJECTDB_VALIDATOR_MASK ((uint64_t(1) << OBJECTDB_VALIDATOR_BITS) - 1) +#define OBJECTDB_SLOT_MAX_COUNT_BITS 24 +#define OBJECTDB_SLOT_MAX_COUNT_MASK ((uint64_t(1) << OBJECTDB_SLOT_MAX_COUNT_BITS) - 1) +#define OBJECTDB_REFERENCE_BIT (uint64_t(1) << (OBJECTDB_SLOT_MAX_COUNT_BITS + OBJECTDB_VALIDATOR_BITS)) + + struct ObjectSlot { //128 bits per slot + uint64_t validator : OBJECTDB_VALIDATOR_BITS; + uint64_t next_free : OBJECTDB_SLOT_MAX_COUNT_BITS; + uint64_t is_ref_counted : 1; + Object *object; + }; + + static SpinLock spin_lock; + static uint32_t slot_count; + static uint32_t slot_max; + static ObjectSlot *object_slots; + static uint64_t validator_counter; + + friend class Object; + friend void unregister_core_types(); + static void cleanup(); + + static ObjectID add_instance(Object *p_object); + static void remove_instance(Object *p_object); + + friend void register_core_types(); + static void setup(); + +public: + typedef void (*DebugFunc)(Object *p_obj); + + _ALWAYS_INLINE_ static Object *get_instance(ObjectID p_instance_id) { + uint64_t id = p_instance_id; + uint32_t slot = id & OBJECTDB_SLOT_MAX_COUNT_MASK; + + ERR_FAIL_COND_V(slot >= slot_max, nullptr); //this should never happen unless RID is corrupted + + spin_lock.lock(); + + uint64_t validator = (id >> OBJECTDB_SLOT_MAX_COUNT_BITS) & OBJECTDB_VALIDATOR_MASK; + + if (unlikely(object_slots[slot].validator != validator)) { + spin_lock.unlock(); + return nullptr; + } + + Object *object = object_slots[slot].object; + + spin_lock.unlock(); + + return object; + } + static void debug_objects(DebugFunc p_func); + static int get_object_count(); +}; + +#endif // OBJECT_H diff --git a/core/object/object_id.h b/core/object/object_id.h new file mode 100644 index 0000000000..0666ec0855 --- /dev/null +++ b/core/object/object_id.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* object_id.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef OBJECT_ID_H +#define OBJECT_ID_H + +#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 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; } +}; + +#endif // OBJECT_ID_H diff --git a/core/object/ref_counted.cpp b/core/object/ref_counted.cpp new file mode 100644 index 0000000000..2833f774dc --- /dev/null +++ b/core/object/ref_counted.cpp @@ -0,0 +1,127 @@ +/*************************************************************************/ +/* ref_counted.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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); +} + +int RefCounted::reference_get_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()); + } + + die = die && _instance_binding_reference(false); + } + + 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(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 &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); +} diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h new file mode 100644 index 0000000000..f2dd2aa324 --- /dev/null +++ b/core/object/ref_counted.h @@ -0,0 +1,297 @@ +/*************************************************************************/ +/* ref_counted.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef REF_COUNTED_H +#define REF_COUNTED_H + +#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 reference_get_count() const; + + RefCounted(); + ~RefCounted() {} +}; + +template <class T> +class Ref { + T *reference = nullptr; + + void ref(const Ref &p_from) { + if (p_from.reference == reference) { + return; + } + + unref(); + + reference = p_from.reference; + if (reference) { + reference->reference(); + } + } + + void ref_pointer(T *p_ref) { + ERR_FAIL_COND(!p_ref); + + if (p_ref->init_ref()) { + reference = p_ref; + } + } + + //virtual RefCounted * get_reference() const { return reference; } +public: + _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; + } + + _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->() { + return reference; + } + + _FORCE_INLINE_ T *operator*() { + return reference; + } + + _FORCE_INLINE_ const T *operator->() const { + return reference; + } + + _FORCE_INLINE_ const T *ptr() const { + return reference; + } + _FORCE_INLINE_ T *ptr() { + return reference; + } + + _FORCE_INLINE_ const T *operator*() const { + return reference; + } + + operator Variant() const { + return Variant(reference); + } + + void operator=(const Ref &p_from) { + ref(p_from); + } + + template <class T_Other> + void operator=(const Ref<T_Other> &p_from) { + RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr())); + if (!refb) { + unref(); + return; + } + Ref r; + r.reference = Object::cast_to<T>(refb); + ref(r); + r.reference = nullptr; + } + + void operator=(const Variant &p_variant) { + Object *object = p_variant.get_validated_object(); + + if (object == reference) { + return; + } + + unref(); + + if (!object) { + return; + } + + T *r = Object::cast_to<T>(object); + if (r && r->reference()) { + reference = r; + } + } + + template <class T_Other> + void reference_ptr(T_Other *p_ptr) { + if (reference == p_ptr) { + return; + } + unref(); + + T *r = Object::cast_to<T>(p_ptr); + if (r) { + ref_pointer(r); + } + } + + Ref(const Ref &p_from) { + ref(p_from); + } + + template <class T_Other> + Ref(const Ref<T_Other> &p_from) { + RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr())); + if (!refb) { + unref(); + return; + } + Ref r; + r.reference = Object::cast_to<T>(refb); + ref(r); + r.reference = nullptr; + } + + Ref(T *p_reference) { + if (p_reference) { + ref_pointer(p_reference); + } + } + + Ref(const Variant &p_variant) { + Object *object = p_variant.get_validated_object(); + + if (!object) { + return; + } + + T *r = Object::cast_to<T>(object); + if (r && r->reference()) { + reference = r; + } + } + + 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 && reference->unreference()) { + memdelete(reference); + } + reference = nullptr; + } + + void instantiate() { + ref(memnew(T)); + } + + Ref() {} + + ~Ref() { + unref(); + } +}; + +typedef Ref<RefCounted> REF; + +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 &p_ref); + + WeakRef() {} +}; + +template <class T> +struct PtrToArg<Ref<T>> { + _FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) { + return Ref<T>(const_cast<T *>(reinterpret_cast<const T *>(p_ptr))); + } + + typedef Ref<T> EncodeT; + + _FORCE_INLINE_ static void encode(Ref<T> p_val, const void *p_ptr) { + *(Ref<RefCounted> *)p_ptr = p_val; + } +}; + +template <class T> +struct PtrToArg<const Ref<T> &> { + typedef Ref<T> EncodeT; + + _FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) { + return Ref<T>((T *)p_ptr); + } +}; + +template <class 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 <class T> +struct GetTypeInfo<const 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()); + } +}; + +#endif // REF_COUNTED_H diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp new file mode 100644 index 0000000000..0fb8c7350c --- /dev/null +++ b/core/object/script_language.cpp @@ -0,0 +1,598 @@ +/*************************************************************************/ +/* script_language.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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_string_names.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" + +#include <stdint.h> + +ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES]; +int ScriptServer::_language_count = 0; + +bool ScriptServer::scripting_enabled = true; +bool ScriptServer::reload_scripts_on_save = false; +bool ScriptServer::languages_finished = false; +ScriptEditRequestFunction ScriptServer::edit_request_func = nullptr; + +void Script::_notification(int p_what) { + if (p_what == NOTIFICATION_POSTINITIALIZE) { + if (EngineDebugger::is_active()) { + EngineDebugger::get_script_debugger()->set_break_language(get_language()); + } + } +} + +Variant Script::_get_property_default_value(const StringName &p_property) { + Variant ret; + get_property_default_value(p_property, ret); + return ret; +} + +Array Script::_get_script_property_list() { + Array ret; + List<PropertyInfo> list; + get_script_property_list(&list); + for (const PropertyInfo &E : list) { + ret.append(E.operator Dictionary()); + } + return ret; +} + +Array Script::_get_script_method_list() { + Array ret; + List<MethodInfo> list; + get_script_method_list(&list); + for (const MethodInfo &E : list) { + ret.append(E.operator Dictionary()); + } + return ret; +} + +Array Script::_get_script_signal_list() { + Array 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; + Map<StringName, Variant> map; + get_constants(&map); + for (Map<StringName, Variant>::Element *E = map.front(); E; E = E->next()) { + ret[E->key()] = E->value(); + } + return ret; +} + +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("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); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_source_code", "get_source_code"); +} + +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) { + ERR_FAIL_INDEX_V(p_idx, _language_count, nullptr); + + return _languages[p_idx]; +} + +void ScriptServer::register_language(ScriptLanguage *p_language) { + ERR_FAIL_COND(_language_count >= MAX_LANGUAGES); + _languages[_language_count++] = p_language; +} + +void ScriptServer::unregister_language(ScriptLanguage *p_language) { + 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; + } + } +} + +void ScriptServer::init_languages() { + { //load global classes + global_classes_clear(); + if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) { + Array script_classes = ProjectSettings::get_singleton()->get("_global_script_classes"); + + for (int i = 0; i < script_classes.size(); i++) { + Dictionary c = script_classes[i]; + if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { + continue; + } + add_global_class(c["class"], c["base"], c["language"], c["path"]); + } + } + } + + for (int i = 0; i < _language_count; i++) { + _languages[i]->init(); + } +} + +void ScriptServer::finish_languages() { + for (int i = 0; i < _language_count; i++) { + _languages[i]->finish(); + } + global_classes_clear(); + languages_finished = true; +} + +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() { + for (int i = 0; i < _language_count; i++) { + _languages[i]->thread_enter(); + } +} + +void ScriptServer::thread_exit() { + for (int i = 0; i < _language_count; i++) { + _languages[i]->thread_exit(); + } +} + +HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes; + +void ScriptServer::global_classes_clear() { + global_classes.clear(); +} + +void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path) { + 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 g; + g.language = p_language; + g.path = p_path; + g.base = p_base; + global_classes[p_class] = g; +} + +void ScriptServer::remove_global_class(const StringName &p_class) { + global_classes.erase(p_class); +} + +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; +} + +void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) { + const StringName *K = nullptr; + List<StringName> classes; + while ((K = global_classes.next(K))) { + classes.push_back(*K); + } + classes.sort_custom<StringName::AlphCompare>(); + for (const StringName &E : classes) { + r_global_classes->push_back(E); + } +} + +void ScriptServer::save_global_classes() { + List<StringName> gc; + get_global_class_list(&gc); + Array gcarr; + for (const StringName &E : gc) { + Dictionary d; + d["class"] = E; + d["language"] = global_classes[E].language; + d["path"] = global_classes[E].path; + d["base"] = global_classes[E].base; + gcarr.push_back(d); + } + + Array old; + if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) { + old = ProjectSettings::get_singleton()->get("_global_script_classes"); + } + if ((!old.is_empty() || gcarr.is_empty()) && gcarr.hash() == old.hash()) { + return; + } + + if (gcarr.is_empty()) { + if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) { + ProjectSettings::get_singleton()->clear("_global_script_classes"); + } + } else { + ProjectSettings::get_singleton()->set("_global_script_classes", gcarr); + } + ProjectSettings::get_singleton()->save(); +} + +//////////////////// +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); + } + } + } +} + +Variant ScriptInstance::call(const StringName &p_method, VARIANT_ARG_DECLARE) { + VARIANT_ARGPTRS; + int argc = 0; + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + if (argptr[i]->get_type() == Variant::NIL) { + break; + } + argc++; + } + + Callable::CallError error; + return call(p_method, argptr, argc, error); +} + +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(); +} + +ScriptInstance::~ScriptInstance() { +} + +ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton = nullptr; +ScriptCodeCompletionCache::ScriptCodeCompletionCache() { + singleton = this; +} + +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("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("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"); +} + +void ScriptLanguage::frame() { +} + +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)) { + if (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 (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; + if (!values.has(pinfo.name)) { + pinfo.usage |= PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE; + } + 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()) { + return script->has_method(p_method); + } + return false; +} + +void PlaceHolderScriptInstance::update(const List<PropertyInfo> &p_properties, const Map<StringName, Variant> &p_values) { + Set<StringName> new_values; + for (const PropertyInfo &E : p_properties) { + StringName n = E.name; + new_values.insert(n); + + if (!values.has(n) || values[n].get_type() != E.type) { + if (p_values.has(n)) { + values[n] = p_values[n]; + } + } + } + + properties = p_properties; + List<StringName> to_remove; + + for (Map<StringName, Variant>::Element *E = values.front(); E; E = E->next()) { + 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) { + 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()) { + Map<StringName, Variant>::Element *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) { + properties.push_back(PropertyInfo(p_value.get_type(), p_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | 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()) { + const Map<StringName, Variant>::Element *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); + } +} diff --git a/core/object/script_language.h b/core/object/script_language.h new file mode 100644 index 0000000000..385bf79c1a --- /dev/null +++ b/core/object/script_language.h @@ -0,0 +1,428 @@ +/*************************************************************************/ +/* script_language.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef SCRIPT_LANGUAGE_H +#define SCRIPT_LANGUAGE_H + +#include "core/doc_data.h" +#include "core/io/multiplayer_api.h" +#include "core/io/resource.h" +#include "core/templates/map.h" +#include "core/templates/pair.h" + +class ScriptLanguage; + +typedef void (*ScriptEditRequestFunction)(const String &p_path); + +class ScriptServer { + enum { + MAX_LANGUAGES = 16 + }; + + static ScriptLanguage *_languages[MAX_LANGUAGES]; + static int _language_count; + static bool scripting_enabled; + static bool reload_scripts_on_save; + static bool languages_finished; + + struct GlobalScriptClass { + StringName language; + String path; + String base; + }; + + static HashMap<StringName, GlobalScriptClass> global_classes; + +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 void register_language(ScriptLanguage *p_language); + static void unregister_language(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); + static void remove_global_class(const StringName &p_class); + 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 void get_global_class_list(List<StringName> *r_global_classes); + static void save_global_classes(); + + static void init_languages(); + static void finish_languages(); + + static bool are_languages_finished() { return languages_finished; } +}; + +class ScriptInstance; +class PlaceHolderScriptInstance; + +class Script : public Resource { + GDCLASS(Script, Resource); + OBJ_SAVE_TYPE(Script); + +protected: + virtual bool editor_can_reload_from_file() override { return false; } // this is handled by editor better + 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); + Array _get_script_property_list(); + Array _get_script_method_list(); + Array _get_script_signal_list(); + Dictionary _get_script_constant_map(); + +public: + virtual bool can_instantiate() const = 0; + + virtual Ref<Script> get_base_script() const = 0; //for script inheritance + + 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 const Vector<DocData::ClassDoc> &get_documentation() const = 0; +#endif // TOOLS_ENABLED + + virtual bool has_method(const StringName &p_method) const = 0; + virtual MethodInfo get_method_info(const StringName &p_method) const = 0; + + virtual bool is_tool() const = 0; + virtual bool is_valid() 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(Map<StringName, Variant> *p_constants) {} + virtual void get_members(Set<StringName> *p_constants) {} + + virtual bool is_placeholder_fallback_enabled() const { return false; } + + virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const = 0; + + Script() {} +}; + +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 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 Variant call(const StringName &p_method, VARIANT_ARG_LIST); + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0; + virtual void notification(int p_notification) = 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 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 Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const = 0; + + virtual ScriptLanguage *get_language() = 0; + virtual ~ScriptInstance(); +}; + +struct ScriptCodeCompletionOption { + /* Keep enum in Sync with: */ + /* /scene/gui/code_edit.h - CodeEdit::CodeCompletionKind */ + enum Kind { + KIND_CLASS, + KIND_FUNCTION, + KIND_SIGNAL, + KIND_VARIABLE, + KIND_MEMBER, + KIND_ENUM, + KIND_CONSTANT, + KIND_NODE_PATH, + KIND_FILE_PATH, + KIND_PLAIN_TEXT, + }; + Kind kind = KIND_PLAIN_TEXT; + String display; + String insert_text; + Color font_color; + RES icon; + Variant default_value; + + ScriptCodeCompletionOption() {} + + ScriptCodeCompletionOption(const String &p_text, Kind p_kind) { + display = p_text; + insert_text = p_text; + kind = p_kind; + } +}; + +class ScriptCodeCompletionCache { + static ScriptCodeCompletionCache *singleton; + +public: + static ScriptCodeCompletionCache *get_singleton() { return singleton; } + + ScriptCodeCompletionCache(); + + virtual ~ScriptCodeCompletionCache() {} +}; + +class ScriptLanguage { +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 Error execute_file(const String &p_path) = 0; + virtual void finish() = 0; + + /* EDITOR FUNCTIONS */ + struct Warning { + int start_line = -1, end_line = -1; + int leftmost_column = -1, rightmost_column = -1; + int code; + String string_code; + String message; + }; + + struct ScriptError { + int line = -1; + int column = -1; + String message; + }; + + void get_core_type_words(List<String> *p_core_type_words) const; + virtual void get_reserved_words(List<String> *p_words) const = 0; + virtual bool is_control_flow_keyword(String p_string) const = 0; + virtual void get_comment_delimiters(List<String> *p_delimiters) const = 0; + virtual void get_string_delimiters(List<String> *p_delimiters) const = 0; + virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const = 0; + virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {} + 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, Set<int> *r_safe_lines = nullptr) const = 0; + virtual String validate_path(const String &p_path) const { return ""; } + virtual Script *create_script() const = 0; + virtual bool has_named_classes() const = 0; + 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 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 Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; } + + struct LookupResult { + enum Type { + RESULT_SCRIPT_LOCATION, + RESULT_CLASS, + RESULT_CLASS_CONSTANT, + RESULT_CLASS_PROPERTY, + RESULT_CLASS_METHOD, + RESULT_CLASS_ENUM, + RESULT_CLASS_TBD_GLOBALSCOPE + }; + Type type; + Ref<Script> script; + String class_name; + String class_member; + String class_path; + int location; + }; + + 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_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; + + struct ProfilingInfo { + StringName signature; + uint64_t call_count; + uint64_t total_time; + uint64_t self_time; + }; + + virtual void profiling_start() = 0; + virtual void profiling_stop() = 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 *alloc_instance_binding_data(Object *p_object) { return nullptr; } //optional, not used by all languages + virtual void free_instance_binding_data(void *p_data) {} //optional, not used by all languages + virtual void refcount_incremented_instance_binding(Object *p_object) {} //optional, not used by all languages + virtual bool refcount_decremented_instance_binding(Object *p_object) { return true; } //return true if it can die //optional, not used by all languages + + 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) const { return String(); } + + virtual ~ScriptLanguage() {} +}; + +extern uint8_t script_encryption_key[32]; + +class PlaceHolderScriptInstance : public ScriptInstance { + Object *owner; + List<PropertyInfo> properties; + Map<StringName, Variant> values; + Map<StringName, Variant> constants; + ScriptLanguage *language; + Ref<Script> script; + +public: + virtual bool set(const StringName &p_name, const Variant &p_value); + virtual bool get(const StringName &p_name, Variant &r_ret) const; + virtual void get_property_list(List<PropertyInfo> *p_properties) const; + virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const; + + virtual void get_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName &p_method) const; + virtual Variant call(const StringName &p_method, VARIANT_ARG_LIST) { return Variant(); } + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); + } + virtual void notification(int p_notification) {} + + virtual Ref<Script> get_script() const { return script; } + + virtual ScriptLanguage *get_language() { return language; } + + Object *get_owner() { return owner; } + + void update(const List<PropertyInfo> &p_properties, const Map<StringName, Variant> &p_values); //likely changed in editor + + virtual bool is_placeholder() const { return true; } + + virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr); + virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid = nullptr); + + virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const { return Vector<MultiplayerAPI::RPCConfig>(); } + + PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner); + ~PlaceHolderScriptInstance(); +}; + +#endif // SCRIPT_LANGUAGE_H diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp new file mode 100644 index 0000000000..b7d2bac96d --- /dev/null +++ b/core/object/undo_redo.cpp @@ -0,0 +1,559 @@ +/*************************************************************************/ +/* undo_redo.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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" + +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) { + if (E.type == Operation::TYPE_REFERENCE) { + if (E.ref.is_valid()) { + E.ref.unref(); + } else { + Object *obj = ObjectDB::get_instance(E.object); + if (obj) { + memdelete(obj); + } + } + } + } + //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++; + if (p_execute) { + _process_operation_list(actions.write[current_action].do_ops.front()); + } + version++; + emit_signal(SNAME("version_changed")); + + return true; +} + +void UndoRedo::create_action(const String &p_name, MergeMode p_mode) { + uint32_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].last_tick + 800 > ticks) { + current_action = actions.size() - 2; + + if (p_mode == MERGE_ENDS) { + // Clear all do ops from last action, and delete all object references + List<Operation>::Element *E = actions.write[current_action + 1].do_ops.front(); + + while (E) { + if (E->get().type == Operation::TYPE_REFERENCE) { + Object *obj = ObjectDB::get_instance(E->get().object); + + if (obj) { + memdelete(obj); + } + } + + E = E->next(); + actions.write[current_action + 1].do_ops.pop_front(); + } + } + + actions.write[actions.size() - 1].last_tick = ticks; + + merge_mode = p_mode; + merging = true; + } else { + Action new_action; + new_action.name = p_name; + new_action.last_tick = ticks; + actions.push_back(new_action); + + merge_mode = MERGE_DISABLE; + } + } + + action_level++; +} + +void UndoRedo::add_do_method(Object *p_object, const StringName &p_method, VARIANT_ARG_DECLARE) { + VARIANT_ARGPTRS + ERR_FAIL_COND(p_object == nullptr); + 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_METHOD; + do_op.name = p_method; + + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + do_op.args[i] = *argptr[i]; + } + actions.write[current_action + 1].do_ops.push_back(do_op); +} + +void UndoRedo::add_undo_method(Object *p_object, const StringName &p_method, VARIANT_ARG_DECLARE) { + VARIANT_ARGPTRS + ERR_FAIL_COND(p_object == nullptr); + ERR_FAIL_COND(action_level <= 0); + ERR_FAIL_COND((current_action + 1) >= actions.size()); + + // No undo if the merge mode is MERGE_ENDS + if (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_METHOD; + undo_op.name = p_method; + + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + undo_op.args[i] = *argptr[i]; + } + 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_COND(p_object == nullptr); + 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.args[0] = 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_COND(p_object == nullptr); + ERR_FAIL_COND(action_level <= 0); + ERR_FAIL_COND((current_action + 1) >= actions.size()); + + // No undo if the merge mode is MERGE_ENDS + if (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.name = p_property; + undo_op.args[0] = p_value; + actions.write[current_action + 1].undo_ops.push_back(undo_op); +} + +void UndoRedo::add_do_reference(Object *p_object) { + ERR_FAIL_COND(p_object == nullptr); + 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_COND(p_object == nullptr); + ERR_FAIL_COND(action_level <= 0); + ERR_FAIL_COND((current_action + 1) >= actions.size()); + + // No undo if the merge mode is MERGE_ENDS + if (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; + actions.write[current_action + 1].undo_ops.push_back(undo_op); +} + +void UndoRedo::_pop_history_tail() { + _discard_redo(); + + if (!actions.size()) { + return; + } + + for (Operation &E : actions.write[0].undo_ops) { + if (E.type == Operation::TYPE_REFERENCE) { + if (E.ref.is_valid()) { + E.ref.unref(); + } else { + Object *obj = ObjectDB::get_instance(E.object); + if (obj) { + memdelete(obj); + } + } + } + } + + actions.remove(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 + } + + if (merging) { + version--; + merging = false; + } + + committing++; + _redo(p_execute); // perform action + committing--; + + if (callback && actions.size() > 0) { + callback(callback_ud, actions[actions.size() - 1].name); + } +} + +void UndoRedo::_process_operation_list(List<Operation>::Element *E) { + 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: { + Vector<const Variant *> argptrs; + argptrs.resize(VARIANT_ARG_MAX); + int argc = 0; + + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + if (op.args[i].get_type() == Variant::NIL) { + break; + } + argptrs.write[i] = &op.args[i]; + argc++; + } + argptrs.resize(argc); + + Callable::CallError ce; + obj->call(op.name, (const Variant **)argptrs.ptr(), argc, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT("Error calling method from signal '" + String(op.name) + "': " + Variant::get_call_error_text(obj, op.name, (const Variant **)argptrs.ptr(), argc, ce)); + } +#ifdef TOOLS_ENABLED + Resource *res = Object::cast_to<Resource>(obj); + if (res) { + res->set_edited(true); + } + +#endif + + if (method_callback) { + method_callback(method_callbck_ud, obj, op.name, VARIANT_ARGS_FROM_ARRAY(op.args)); + } + } break; + case Operation::TYPE_PROPERTY: { + obj->set(op.name, op.args[0]); +#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.args[0]); + } + } 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()); + 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; +} + +bool UndoRedo::has_undo() const { + return current_action >= 0; +} + +bool UndoRedo::has_redo() const { + return (current_action + 1) < actions.size(); +} + +uint64_t UndoRedo::get_version() const { + return version; +} + +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_callbck_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(); +} + +Variant UndoRedo::_add_do_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 2) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Variant(); + } + + if (p_args[0]->get_type() != Variant::OBJECT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; + return Variant(); + } + + if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::STRING_NAME; + return Variant(); + } + + r_error.error = Callable::CallError::CALL_OK; + + Object *object = *p_args[0]; + StringName method = *p_args[1]; + + Variant v[VARIANT_ARG_MAX]; + + for (int i = 0; i < MIN(VARIANT_ARG_MAX, p_argcount - 2); ++i) { + v[i] = *p_args[i + 2]; + } + + static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); + add_do_method(object, method, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); + return Variant(); +} + +Variant UndoRedo::_add_undo_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 2) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Variant(); + } + + if (p_args[0]->get_type() != Variant::OBJECT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::OBJECT; + return Variant(); + } + + if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::STRING_NAME; + return Variant(); + } + + r_error.error = Callable::CallError::CALL_OK; + + Object *object = *p_args[0]; + StringName method = *p_args[1]; + + Variant v[VARIANT_ARG_MAX]; + + for (int i = 0; i < MIN(VARIANT_ARG_MAX, p_argcount - 2); ++i) { + v[i] = *p_args[i + 2]; + } + + static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); + add_undo_method(object, method, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]); + return Variant(); +} + +void UndoRedo::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode"), &UndoRedo::create_action, DEFVAL(MERGE_DISABLE)); + 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); + + { + MethodInfo mi; + mi.name = "add_do_method"; + mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object")); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "add_do_method", &UndoRedo::_add_do_method, mi, varray(), false); + } + + { + MethodInfo mi; + mi.name = "add_undo_method"; + mi.arguments.push_back(PropertyInfo(Variant::OBJECT, "object")); + mi.arguments.push_back(PropertyInfo(Variant::STRING_NAME, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "add_undo_method", &UndoRedo::_add_undo_method, mi, varray(), false); + } + + 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("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("redo"), &UndoRedo::redo); + ClassDB::bind_method(D_METHOD("undo"), &UndoRedo::undo); + + ADD_SIGNAL(MethodInfo("version_changed")); + + BIND_ENUM_CONSTANT(MERGE_DISABLE); + BIND_ENUM_CONSTANT(MERGE_ENDS); + BIND_ENUM_CONSTANT(MERGE_ALL); +} diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h new file mode 100644 index 0000000000..d1ce252d86 --- /dev/null +++ b/core/object/undo_redo.h @@ -0,0 +1,140 @@ +/*************************************************************************/ +/* undo_redo.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef UNDO_REDO_H +#define UNDO_REDO_H + +#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); + Variant _add_do_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + Variant _add_undo_method(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + + typedef void (*MethodNotifyCallback)(void *p_ud, Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE); + 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 type; + Ref<RefCounted> ref; + ObjectID object; + StringName name; + Variant args[VARIANT_ARG_MAX]; + }; + + struct Action { + String name; + List<Operation> do_ops; + List<Operation> undo_ops; + uint64_t last_tick; + }; + + Vector<Action> actions; + int current_action = -1; + int action_level = 0; + MergeMode merge_mode = MERGE_DISABLE; + bool merging = false; + uint64_t version = 1; + + void _pop_history_tail(); + void _process_operation_list(List<Operation>::Element *E); + void _discard_redo(); + bool _redo(bool p_execute); + + CommitNotifyCallback callback = nullptr; + void *callback_ud = nullptr; + void *method_callbck_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); + + void add_do_method(Object *p_object, const StringName &p_method, VARIANT_ARG_LIST); + void add_undo_method(Object *p_object, const StringName &p_method, VARIANT_ARG_LIST); + 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); + + bool is_committing_action() const; + void commit_action(bool p_execute = true); + + bool redo(); + bool undo(); + String get_current_action_name() const; + + int get_history_count(); + int get_current_action(); + String get_action_name(int p_id); + void clear_history(bool p_increase_version = true); + + bool has_undo() const; + bool has_redo() const; + + uint64_t get_version() 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); + +#endif // UNDO_REDO_H |