diff options
Diffstat (limited to 'core/object')
-rw-r--r-- | core/object/SCsub | 7 | ||||
-rw-r--r-- | core/object/callable_method_pointer.cpp | 93 | ||||
-rw-r--r-- | core/object/callable_method_pointer.h | 244 | ||||
-rw-r--r-- | core/object/class_db.cpp | 1571 | ||||
-rw-r--r-- | core/object/class_db.h | 427 | ||||
-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 | 110 | ||||
-rw-r--r-- | core/object/method_bind.h | 591 | ||||
-rw-r--r-- | core/object/object.cpp | 1984 | ||||
-rw-r--r-- | core/object/object.h | 815 | ||||
-rw-r--r-- | core/object/object_id.h | 63 | ||||
-rw-r--r-- | core/object/reference.cpp | 132 | ||||
-rw-r--r-- | core/object/reference.h | 301 | ||||
-rw-r--r-- | core/object/script_language.cpp | 598 | ||||
-rw-r--r-- | core/object/script_language.h | 460 | ||||
-rw-r--r-- | core/object/undo_redo.cpp | 520 | ||||
-rw-r--r-- | core/object/undo_redo.h | 135 |
18 files changed, 8523 insertions, 0 deletions
diff --git a/core/object/SCsub b/core/object/SCsub new file mode 100644 index 0000000000..5d429960e5 --- /dev/null +++ b/core/object/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +Import("env") + +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..21a917cbd7 --- /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-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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..ee6da6a8db --- /dev/null +++ b/core/object/callable_method_pointer.h @@ -0,0 +1,244 @@ +/*************************************************************************/ +/* callable_method_pointer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/os/copymem.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...)) { + zeromem(&data, 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...)) { + zeromem(&data, 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) { + zeromem(&data, 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..64ebeb427e --- /dev/null +++ b/core/object/class_db.cpp @@ -0,0 +1,1571 @@ +/*************************************************************************/ +/* class_db.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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) { + 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 (List<StringName>::Element *E = names.front(); E; E = E->next()) { + ClassInfo *t = classes.getptr(E->get()); + ERR_FAIL_COND_V_MSG(!t, 0, "Cannot get class '" + String(E->get()) + "'."); + 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.empty()); + + if (name[0] == '_') { + continue; // Ignore non-virtual methods that start with an underscore + } + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + MethodBind *mb = t->method_map[F->get()]; + 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 (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + hash = hash_djb2_one_64(F->get().hash(), hash); + hash = hash_djb2_one_64(t->constant_map[F->get()], hash); + } + } + + { //signals + + List<StringName> snames; + + k = nullptr; + + while ((k = t->signal_map.next(k))) { + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + MethodInfo &mi = t->signal_map[F->get()]; + hash = hash_djb2_one_64(F->get().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 (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + PropertySetGet *psg = t->property_setget.getptr(F->get()); + ERR_FAIL_COND_V(!psg, 0); + + hash = hash_djb2_one_64(F->get().hash(), hash); + hash = hash_djb2_one_64(psg->setter.hash(), hash); + hash = hash_djb2_one_64(psg->getter.hash(), hash); + } + } + + //property list + for (List<PropertyInfo>::Element *F = t->property_list.front(); F; F = F->next()) { + hash = hash_djb2_one_64(F->get().name.hash(), hash); + hash = hash_djb2_one_64(F->get().type, hash); + hash = hash_djb2_one_64(F->get().hint, hash); + hash = hash_djb2_one_64(F->get().hint_string.hash(), hash); + hash = hash_djb2_one_64(F->get().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; +} + +Object *ClassDB::instance(const StringName &p_class) { + ClassInfo *ti; + { + OBJTYPE_RLOCK; + ti = classes.getptr(p_class); + if (!ti || ti->disabled || !ti->creation_func) { + 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 + return ti->creation_func(); +} + +bool ClassDB::can_instance(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); +} + +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(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 (List<MethodInfo>::Element *E = type->virtual_methods.front(); E; E = E->next()) { + p_methods->push_back(E->get()); + } + + for (List<StringName>::Element *E = type->method_order.front(); E; E = E->next()) { + if (p_exclude_from_properties && type->methods_in_properties.has(E->get())) { + continue; + } + + MethodBind *method = type->method_map.get(E->get()); + 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(StringName p_class, 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(StringName p_class, 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 (List<StringName>::Element *E = type->constant_order.front(); E; E = E->next()) { + p_constants->push_back(E->get()); + } +#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(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(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(StringName p_class, 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(StringName p_class, 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(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(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)); +} + +void ClassDB::add_property(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(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::get_property_list(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 (List<PropertyInfo>::Element *E = check->property_list.front(); E; E = E->next()) { + if (p_validator) { + PropertyInfo pi = E->get(); + p_validator->_validate_property(pi); + p_list->push_back(pi); + } else { + p_list->push_back(E->get()); + } + } + + if (p_no_inheritance) { + return; + } + check = check->inherits_ptr; + } +} + +bool ClassDB::get_property_info(StringName p_class, 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) { + 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) { + 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(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(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(StringName p_class, 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(StringName p_class, 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; +} + +#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) { + 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; + } + 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 (List<MethodInfo>::Element *E = check->virtual_methods.front(); E; E = E->next()) { + p_methods->push_back(E->get()); + } + + if (p_no_inheritance) { + return; + } + check = check->inherits_ptr; + } + +#endif +} + +void ClassDB::set_class_enabled(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(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(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); + } +} + +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_instance(p_class)) { + c = ClassDB::instance(p_class); + cleanup_c = true; + } + + if (c) { + List<PropertyInfo> plist; + c->get_property_list(&plist); + for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { + if (E->get().usage & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR)) { + if (!default_values[p_class].has(E->get().name)) { + Variant v = c->get(E->get().name); + default_values[p_class][E->get().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; +} + +RWLock *ClassDB::lock = nullptr; + +void ClassDB::init() { + lock = RWLock::create(); +} + +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(); + + memdelete(lock); +} + +// diff --git a/core/object/class_db.h b/core/object/class_db.h new file mode 100644 index 0000000000..94f26da60d --- /dev/null +++ b/core/object/class_db.h @@ -0,0 +1,427 @@ +/*************************************************************************/ +/* class_db.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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_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; + + 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 + } + + 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_instance(const StringName &p_class); + static Object *instance(const StringName &p_class); + 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, 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 add_signal(StringName p_class, const MethodInfo &p_signal); + static bool has_signal(StringName p_class, StringName p_signal, bool p_no_inheritance = false); + static bool get_signal(StringName p_class, StringName p_signal, MethodInfo *r_signal); + static void get_signal_list(StringName p_class, List<MethodInfo> *p_signals, bool p_no_inheritance = false); + + static void add_property_group(StringName p_class, const String &p_name, const String &p_prefix = ""); + static void add_property_subgroup(StringName p_class, const String &p_name, const String &p_prefix = ""); + static void add_property(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(StringName p_class, const StringName &p_name, const Variant &p_default); + static void get_property_list(StringName p_class, List<PropertyInfo> *p_list, bool p_no_inheritance = false, const Object *p_validator = nullptr); + static bool get_property_info(StringName p_class, 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(StringName p_class, const StringName &p_property); + static StringName get_property_getter(StringName p_class, const StringName &p_property); + + static bool has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false); + static void set_method_flags(StringName p_class, StringName p_method, int p_flags); + + static void get_method_list(StringName p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false); + static bool get_method_info(StringName p_class, StringName p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false); + static MethodBind *get_method(StringName p_class, StringName p_name); + + static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true); + 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(StringName p_class, bool p_enable); + static bool is_class_enabled(StringName p_class); + + static bool is_class_exposed(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 void add_compatibility_class(const StringName &p_class, const StringName &p_fallback); + static void init(); + + 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 + +#endif // CLASS_DB_H diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp new file mode 100644 index 0000000000..f0d6786853 --- /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-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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..2901ab196a --- /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-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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..e6652ac09f --- /dev/null +++ b/core/object/method_bind.cpp @@ -0,0 +1,110 @@ +/*************************************************************************/ +/* method_bind.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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" + +#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..ab4ba90b94 --- /dev/null +++ b/core/object/method_bind.h @@ -0,0 +1,591 @@ +/*************************************************************************/ +/* method_bind.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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_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; + +#ifdef PTRCALL_ENABLED + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) = 0; +#endif + + 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); + + 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 + } + +#ifdef PTRCALL_ENABLED + virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) { + ERR_FAIL(); //can't call + } //todo +#endif + + 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(); + } + +#ifdef PTRCALL_ENABLED + 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 + } +#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(); + } + +#ifdef PTRCALL_ENABLED + 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 + } +#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; + } + +#ifdef PTRCALL_ENABLED + 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 + } +#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; + } + +#ifdef PTRCALL_ENABLED + 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 + } +#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..c3f49856ca --- /dev/null +++ b/core/object/object.cpp @@ -0,0 +1,1984 @@ +/*************************************************************************/ +/* object.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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; + } + } + + //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; + } + } + + //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.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.empty()); +} + +Variant Object::get_indexed(const Vector<StringName> &p_names, bool *r_valid) const { + if (p_names.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 (!is_class("Script")) { // can still be set, but this is for userfriendlyness +#ifdef TOOLS_ENABLED + p_list->push_back(PropertyInfo(Variant::NIL, "Script", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); +#endif + p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT)); + } + if (!metadata.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<Reference>(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: { + } + } + } + + 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); + } +} + +String Object::to_string() { + if (script_instance) { + bool valid; + String ret = script_instance->to_string(&valid); + if (valid) { + return ret; + } + } + return "[" + get_class() + ":" + itos(get_instance_id()) + "]"; +} + +void Object::_changed_callback(Object *p_changed, const char *p_prop) { +} + +void Object::add_change_receptor(Object *p_receptor) { + change_receptors.insert(p_receptor); +} + +void Object::remove_change_receptor(Object *p_receptor) { + change_receptors.erase(p_receptor); +} + +void Object::property_list_changed_notify() { + _change_notify(); +} + +void Object::cancel_delete() { + _predelete_ok = true; +} + +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_instance()) { + 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); + } + } + + _change_notify(); //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 String &p_name) const { + return metadata.has(p_name); +} + +void Object::set_meta(const String &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 String &p_name) const { + ERR_FAIL_COND_V(!metadata.has(p_name), Variant()); + return metadata[p_name]; +} + +void Object::remove_meta(const String &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<String> Object::_get_meta_list_bind() const { + Vector<String> _metaret; + + List<Variant> keys; + metadata.get_key_list(&keys); + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + _metaret.push_back(E->get()); + } + + return _metaret; +} + +void Object::get_meta_list(List<String> *p_list) const { + List<Variant> keys; + metadata.get_key_list(&keys); + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + p_list->push_back(E->get()); + } +} + +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.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 (List<MethodInfo>::Element *E = signal_list.front(); E; E = E->next()) { + ret.push_back(Dictionary(E->get())); + } + + return ret; +} + +Array Object::_get_signal_connection_list(const String &p_signal) const { + List<Connection> conns; + get_all_signal_connections(&conns); + + Array ret; + + for (List<Connection>::Element *E = conns.front(); E; E = E->next()) { + Connection &c = E->get(); + 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 List<Connection>::Element *E = connections.front(); E; E = E->next()) { + p_connections->push_back(E->get()); + } +} + +Error Object::connect_compat(const StringName &p_signal, Object *p_to_object, const StringName &p_to_method, const Vector<Variant> &p_binds, uint32_t p_flags) { + return connect(p_signal, Callable(p_to_object, p_to_method), p_binds, p_flags); +} + +Error Object::connect(const StringName &p_signal, const Callable &p_callable, const Vector<Variant> &p_binds, uint32_t p_flags) { + ERR_FAIL_COND_V(p_callable.is_null(), ERR_INVALID_PARAMETER); + + Object *target_object = p_callable.get_object(); + ERR_FAIL_COND_V(!target_object, ERR_INVALID_PARAMETER); + + 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_compat(const StringName &p_signal, Object *p_to_object, const StringName &p_to_method) const { + return is_connected(p_signal, Callable(p_to_object, p_to_method)); +} + +bool Object::is_connected(const StringName &p_signal, const Callable &p_callable) const { + ERR_FAIL_COND_V(p_callable.is_null(), false); + 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_compat(const StringName &p_signal, Object *p_to_object, const StringName &p_to_method) { + _disconnect(p_signal, Callable(p_to_object, p_to_method)); +} + +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(p_callable.is_null()); + + Object *target_object = p_callable.get_object(); + ERR_FAIL_COND(!target_object); + + SignalData *s = signal_map.getptr(p_signal); + ERR_FAIL_COND_MSG(!s, vformat("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.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 (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + _clear_internal_resource_paths(E->get()); + _clear_internal_resource_paths(d[E->get()]); + } + } 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 (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + _clear_internal_resource_paths(get(E->get().name)); + } +} + +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("property_list_changed_notify"), &Object::property_list_changed_notify); + + 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")); + + BIND_VMETHOD(MethodInfo("_notification", PropertyInfo(Variant::INT, "what"))); + BIND_VMETHOD(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_VMETHOD(miget); + + MethodInfo plget("_get_property_list"); + + plget.return_val.type = Variant::ARRAY; + BIND_VMETHOD(plget); + +#endif + BIND_VMETHOD(MethodInfo("_init")); + BIND_VMETHOD(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 (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { + if (!(E->get().usage & PROPERTY_USAGE_INTERNATIONALIZED)) { + continue; + } + + String text = get(E->get().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 types 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::get_script_instance_binding(int p_script_language_index) { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(p_script_language_index, MAX_SCRIPT_INSTANCE_BINDINGS, nullptr); +#endif + + //it's up to the script language to make this thread safe, if the function is called twice due to threads being out of syncro + //just return the same pointer. + //if you want to put a big lock in the entire function and keep allocated pointers in a map or something, feel free to do it + //as it should not really affect performance much (won't be called too often), as in far most caes the condition below will be false afterwards + + if (!_script_instance_bindings[p_script_language_index]) { + void *script_data = ScriptServer::get_language(p_script_language_index)->alloc_instance_binding_data(this); + if (script_data) { + atomic_increment(&instance_binding_count); + _script_instance_bindings[p_script_language_index] = script_data; + } + } + + return _script_instance_bindings[p_script_language_index]; +} + +bool Object::has_script_instance_binding(int p_script_language_index) { + return _script_instance_bindings[p_script_language_index] != nullptr; +} + +void Object::set_script_instance_binding(int p_script_language_index, void *p_data) { +#ifdef DEBUG_ENABLED + CRASH_COND(_script_instance_bindings[p_script_language_index] != nullptr); +#endif + _script_instance_bindings[p_script_language_index] = p_data; +} + +void Object::_construct_object(bool p_reference) { + type_is_reference = p_reference; + _instance_id = ObjectDB::add_instance(this); + memset(_script_instance_bindings, 0, sizeof(void *) * MAX_SCRIPT_INSTANCE_BINDINGS); + +#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; + + 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 (!ScriptServer::are_languages_finished()) { + for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++) { + if (_script_instance_bindings[i]) { + ScriptServer::get_language(i)->free_instance_binding_data(_script_instance_bindings[i]); + } + } + } +} + +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; i < slot_count; i++) { + uint32_t slot = object_slots[i].next_free; + p_func(object_slots[slot].object); + } + 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_reference = 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_reference = p_object->is_reference(); + 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_reference()) { + 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_reference = 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; i < slot_count; i++) { + uint32_t slot = object_slots[i].next_free; + Object *obj = object_slots[slot].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(slot) | (uint64_t(object_slots[slot].validator) << OBJECTDB_VALIDATOR_BITS) | (object_slots[slot].is_reference ? OBJECTDB_REFERENCE_BIT : 0); + print_line("Leaked instance: " + String(obj->get_class()) + ":" + itos(id) + extra_info); + } + 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..c79745cf74 --- /dev/null +++ b/core/object/object.h @@ -0,0 +1,815 @@ +/*************************************************************************/ +/* object.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/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/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() +#define VARIANT_ARG_PASS p_arg1, p_arg2, p_arg3, p_arg4, p_arg5 +#define VARIANT_ARG_DECLARE const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5 +#define VARIANT_ARG_MAX 5 +#define VARIANT_ARGPTRS const Variant *argptr[5] = { &p_arg1, &p_arg2, &p_arg3, &p_arg4, &p_arg5 }; +#define VARIANT_ARGPTRS_PASS *argptr[0], *argptr[1], *argptr[2], *argptr[3], *argptr[4] +#define VARIANT_ARGS_FROM_ARRAY(m_arr) m_arr[0], m_arr[1], m_arr[2], m_arr[3], m_arr[4] + +/** +@author Juan Linietsky <reduzio@gmail.com> +*/ + +enum PropertyHint { + PROPERTY_HINT_NONE, ///< no hint provided. + PROPERTY_HINT_RANGE, ///< hint_text = "min,max,step,slider; //slider is optional" + PROPERTY_HINT_EXP_RANGE, ///< hint_text = "min,max,step", exponential edit + PROPERTY_HINT_ENUM, ///< 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_3D_RENDER, + PROPERTY_HINT_LAYERS_3D_PHYSICS, + 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_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_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) + +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; + + _FORCE_INLINE_ PropertyInfo added_usage(int 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(Variant::Type p_type, const String p_name, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = "", 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; + +/* + 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 { \ + return String(#m_class); \ + } \ + virtual const StringName *_get_class_namev() const override { \ + 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 { 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: + enum { + MAX_SCRIPT_INSTANCE_BINDINGS = 8 + }; + +#ifdef DEBUG_ENABLED + friend struct _ObjectDebugLock; +#endif + friend bool predelete_handler(Object *); + friend void postinitialize_handler(Object *); + + 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; + Set<Object *> change_receptors; + 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; + + void property_list_changed_notify(); + + _FORCE_INLINE_ void _construct_object(bool p_reference); + + friend class Reference; + bool type_is_reference = false; + uint32_t instance_binding_count = 0; + void *_script_instance_bindings[MAX_SCRIPT_INSTANCE_BINDINGS]; + + Object(bool p_reference); + +protected: + 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); + + void cancel_delete(); + + virtual void _changed_callback(Object *p_changed, const char *p_prop); + + //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<String> _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: +#ifdef TOOLS_ENABLED + _FORCE_INLINE_ void _change_notify(const char *p_property = "") { + _edited = true; + for (Set<Object *>::Element *E = change_receptors.front(); E; E = E->next()) { + ((Object *)(E->get()))->_changed_callback(this, p_property); + } + } +#else + _FORCE_INLINE_ void _change_notify(const char *p_what = "") {} +#endif + 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; } + + // this is used for editors + void add_change_receptor(Object *p_receptor); + void remove_change_receptor(Object *p_receptor); + + 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 { return "Object"; } + virtual String get_save_class() const { return get_class(); } //class stored when saving + + virtual bool is_class(const String &p_class) const { 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 (!_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 String &p_name) const; + void set_meta(const String &p_name, const Variant &p_value); + void remove_meta(const String &p_name); + Variant get_meta(const String &p_name) const; + void get_meta_list(List<String> *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_compat(const StringName &p_signal, Object *p_to_object, const StringName &p_to_method, const Vector<Variant> &p_binds = Vector<Variant>(), uint32_t p_flags = 0); + void disconnect_compat(const StringName &p_signal, Object *p_to_object, const StringName &p_to_method); + bool is_connected_compat(const StringName &p_signal, Object *p_to_object, const StringName &p_to_method) 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_script_instance_binding(int p_script_language_index); + bool has_script_instance_binding(int p_script_language_index); + void set_script_instance_binding(int p_script_language_index, void *p_data); + + void clear_internal_resource_paths(); + + _ALWAYS_INLINE_ bool is_reference() 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_reference : 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..63b0c27af8 --- /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-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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_reference() 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/reference.cpp b/core/object/reference.cpp new file mode 100644 index 0000000000..ce95d83dfc --- /dev/null +++ b/core/object/reference.cpp @@ -0,0 +1,132 @@ +/*************************************************************************/ +/* reference.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "reference.h" + +#include "core/object/script_language.h" + +bool Reference::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 Reference::_bind_methods() { + ClassDB::bind_method(D_METHOD("init_ref"), &Reference::init_ref); + ClassDB::bind_method(D_METHOD("reference"), &Reference::reference); + ClassDB::bind_method(D_METHOD("unreference"), &Reference::unreference); +} + +int Reference::reference_get_count() const { + return refcount.get(); +} + +bool Reference::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 (instance_binding_count > 0 && !ScriptServer::are_languages_finished()) { + for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++) { + if (_script_instance_bindings[i]) { + ScriptServer::get_language(i)->refcount_incremented_instance_binding(this); + } + } + } + } + + return success; +} + +bool Reference::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 (instance_binding_count > 0 && !ScriptServer::are_languages_finished()) { + for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++) { + if (_script_instance_bindings[i]) { + bool script_ret = ScriptServer::get_language(i)->refcount_decremented_instance_binding(this); + die = die && script_ret; + } + } + } + } + + return die; +} + +Reference::Reference() : + 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(); + } + Reference *r = cast_to<Reference>(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/reference.h b/core/object/reference.h new file mode 100644 index 0000000000..575f1cd914 --- /dev/null +++ b/core/object/reference.h @@ -0,0 +1,301 @@ +/*************************************************************************/ +/* reference.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 REFERENCE_H +#define REFERENCE_H + +#include "core/object/class_db.h" +#include "core/templates/safe_refcount.h" + +class Reference : public Object { + GDCLASS(Reference, 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; + + Reference(); + ~Reference() {} +}; + +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 Reference * 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) { + Reference *refb = const_cast<Reference *>(static_cast<const Reference *>(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) { + Reference *refb = const_cast<Reference *>(static_cast<const Reference *>(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 instance() { + ref(memnew(T)); + } + + Ref() {} + + ~Ref() { + unref(); + } +}; + +typedef Ref<Reference> REF; + +class WeakRef : public Reference { + GDCLASS(WeakRef, Reference); + + 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() {} +}; + +#ifdef PTRCALL_ENABLED + +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))); + } + + _FORCE_INLINE_ static void encode(Ref<T> p_val, const void *p_ptr) { + *(Ref<Reference> *)p_ptr = p_val; + } +}; + +template <class T> +struct PtrToArg<const Ref<T> &> { + _FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) { + return Ref<T>((T *)p_ptr); + } +}; + +#endif // PTRCALL_ENABLED + +#ifdef DEBUG_METHODS_ENABLED + +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 // DEBUG_METHODS_ENABLED + +#endif // REFERENCE_H diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp new file mode 100644 index 0000000000..17ac75e19f --- /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-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 (List<PropertyInfo>::Element *E = list.front(); E; E = E->next()) { + ret.append(E->get().operator Dictionary()); + } + return ret; +} + +Array Script::_get_script_method_list() { + Array ret; + List<MethodInfo> list; + get_script_method_list(&list); + for (List<MethodInfo>::Element *E = list.front(); E; E = E->next()) { + ret.append(E->get().operator Dictionary()); + } + return ret; +} + +Array Script::_get_script_signal_list() { + Array ret; + List<MethodInfo> list; + get_script_signal_list(&list); + for (List<MethodInfo>::Element *E = list.front(); E; E = E->next()) { + ret.append(E->get().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_instance"), &Script::can_instance); + //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, "", 0), "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 (List<StringName>::Element *E = classes.front(); E; E = E->next()) { + r_global_classes->push_back(E->get()); + } +} + +void ScriptServer::save_global_classes() { + List<StringName> gc; + get_global_class_list(&gc); + Array gcarr; + for (List<StringName>::Element *E = gc.front(); E; E = E->next()) { + Dictionary d; + d["class"] = E->get(); + d["language"] = global_classes[E->get()].language; + d["path"] = global_classes[E->get()].path; + d["base"] = global_classes[E->get()].base; + gcarr.push_back(d); + } + + if (gcarr.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 (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + if (E->get().usage & PROPERTY_USAGE_STORAGE) { + Pair<StringName, Variant> p; + p.first = E->get().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("Quat"); + p_core_type_words->push_back("AABB"); + p_core_type_words->push_back("Basis"); + p_core_type_words->push_back("Transform"); + 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 List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + p_properties->push_back(E->get()); + } + } else { + for (const List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + PropertyInfo pinfo = E->get(); + if (!values.has(pinfo.name)) { + pinfo.usage |= PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE; + } + p_properties->push_back(E->get()); + } + } +} + +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 List<PropertyInfo>::Element *E = p_properties.front(); E; E = E->next()) { + StringName n = E->get().name; + new_values.insert(n); + + if (!values.has(n) || values[n].get_type() != E->get().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->get()) { + 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->_change_notify(); + } + //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 List<PropertyInfo>::Element *F = properties.front(); F; F = F->next()) { + if (F->get().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(); +} + +uint16_t PlaceHolderScriptInstance::get_rpc_method_id(const StringName &p_method) const { + return UINT16_MAX; +} + +uint16_t PlaceHolderScriptInstance::get_rset_property_id(const StringName &p_method) const { + return UINT16_MAX; +} + +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..447216f14f --- /dev/null +++ b/core/object/script_language.h @@ -0,0 +1,460 @@ +/*************************************************************************/ +/* script_language.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/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); + +struct ScriptNetData { + StringName name; + MultiplayerAPI::RPCMode mode; + bool operator==(ScriptNetData const &p_other) const { + return name == p_other.name; + } +}; + +struct SortNetData { + StringName::AlphCompare compare; + bool operator()(const ScriptNetData &p_a, const ScriptNetData &p_b) const { + return compare(p_a.name, p_b.name); + } +}; + +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_instance() 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; + + 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 Vector<ScriptNetData> get_rpc_methods() const = 0; + virtual uint16_t get_rpc_method_id(const StringName &p_method) const = 0; + virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const = 0; + + virtual Vector<ScriptNetData> get_rset_properties() const = 0; + virtual uint16_t get_rset_property_id(const StringName &p_property) const = 0; + virtual StringName get_rset_property(const uint16_t p_rset_property_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_rpc_method_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) 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 Vector<ScriptNetData> get_rpc_methods() const = 0; + virtual uint16_t get_rpc_method_id(const StringName &p_method) const = 0; + virtual StringName get_rpc_method(uint16_t p_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const = 0; + + virtual Vector<ScriptNetData> get_rset_properties() const = 0; + virtual uint16_t get_rset_property_id(const StringName &p_variable) const = 0; + virtual StringName get_rset_property(uint16_t p_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const = 0; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const = 0; + + virtual ScriptLanguage *get_language() = 0; + virtual ~ScriptInstance(); +}; + +struct ScriptCodeCompletionOption { + 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: + virtual RES get_cached_resource(const String &p_path) = 0; + + 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; + }; + + void get_core_type_words(List<String> *p_core_type_words) const; + virtual void get_reserved_words(List<String> *p_words) 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, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = 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 can_inherit_from_file() { 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; + 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 Vector<ScriptNetData> get_rpc_methods() const { return Vector<ScriptNetData>(); } + virtual uint16_t get_rpc_method_id(const StringName &p_method) const; + virtual StringName get_rpc_method(uint16_t p_id) const { return StringName(); } + virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(uint16_t p_id) const { return MultiplayerAPI::RPC_MODE_DISABLED; } + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const { return MultiplayerAPI::RPC_MODE_DISABLED; } + + virtual Vector<ScriptNetData> get_rset_properties() const { return Vector<ScriptNetData>(); } + virtual uint16_t get_rset_property_id(const StringName &p_variable) const; + virtual StringName get_rset_property(uint16_t p_id) const { return StringName(); } + virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(uint16_t p_id) const { return MultiplayerAPI::RPC_MODE_DISABLED; } + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const { return MultiplayerAPI::RPC_MODE_DISABLED; } + + 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..1dcbb0cd6b --- /dev/null +++ b/core/object/undo_redo.cpp @@ -0,0 +1,520 @@ +/*************************************************************************/ +/* undo_redo.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/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 (List<Operation>::Element *E = actions.write[i].do_ops.front(); E; E = E->next()) { + if (E->get().type == Operation::TYPE_REFERENCE) { + Object *obj = ObjectDB::get_instance(E->get().object); + if (obj) { + memdelete(obj); + } + } + } + //ERASE do data + } + + actions.resize(current_action + 1); +} + +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<Resource>(p_object)) { + do_op.resref = Ref<Resource>(Object::cast_to<Resource>(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<Resource>(p_object)) { + undo_op.resref = Ref<Resource>(Object::cast_to<Resource>(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<Resource>(p_object)) { + do_op.resref = Ref<Resource>(Object::cast_to<Resource>(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<Resource>(p_object)) { + undo_op.resref = Ref<Resource>(Object::cast_to<Resource>(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<Resource>(p_object)) { + do_op.resref = Ref<Resource>(Object::cast_to<Resource>(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<Resource>(p_object)) { + undo_op.resref = Ref<Resource>(Object::cast_to<Resource>(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 (List<Operation>::Element *E = actions.write[0].undo_ops.front(); E; E = E->next()) { + if (E->get().type == Operation::TYPE_REFERENCE) { + Object *obj = ObjectDB::get_instance(E->get().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() { + ERR_FAIL_COND(action_level <= 0); + action_level--; + if (action_level > 0) { + return; //still nested + } + + if (merging) { + version--; + merging = false; + } + + committing++; + redo(); // 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() { + ERR_FAIL_COND_V(action_level > 0, false); + + if ((current_action + 1) >= actions.size()) { + return false; //nothing to redo + } + + current_action++; + + _process_operation_list(actions.write[current_action].do_ops.front()); + version++; + emit_signal("version_changed"); + + return 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("version_changed"); + + return true; +} + +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("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() { + return current_action >= 0; +} + +bool UndoRedo::has_redo() { + 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 == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5"); + add_do_method(object, method, v[0], v[1], v[2], v[3], v[4]); + 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 == 5, "This code needs to be updated if VARIANT_ARG_MAX != 5"); + add_undo_method(object, method, v[0], v[1], v[2], v[3], v[4]); + 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"), &UndoRedo::commit_action); + 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("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..68d78e0d7d --- /dev/null +++ b/core/object/undo_redo.h @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* undo_redo.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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/io/resource.h" +#include "core/object/class_db.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<Resource> resref; + 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(); + + 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 redo(); + bool undo(); + String get_current_action_name() const; + void clear_history(bool p_increase_version = true); + + bool has_undo(); + bool has_redo(); + + 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 |