diff options
70 files changed, 1084 insertions, 357 deletions
diff --git a/core/core_constants.cpp b/core/core_constants.cpp index b1f56539e5..d88dda6609 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -33,14 +33,15 @@ #include "core/input/input_event.h" #include "core/object/class_db.h" #include "core/os/keyboard.h" +#include "core/templates/hash_set.h" #include "core/variant/variant.h" struct _CoreConstant { #ifdef DEBUG_METHODS_ENABLED - StringName enum_name; bool ignore_value_in_docs = false; bool is_bitfield = false; #endif + StringName enum_name; const char *name = nullptr; int64_t value = 0; @@ -48,14 +49,15 @@ struct _CoreConstant { #ifdef DEBUG_METHODS_ENABLED _CoreConstant(const StringName &p_enum_name, const char *p_name, int64_t p_value, bool p_ignore_value_in_docs = false, bool p_is_bitfield = false) : - enum_name(p_enum_name), ignore_value_in_docs(p_ignore_value_in_docs), is_bitfield(p_is_bitfield), + enum_name(p_enum_name), name(p_name), value(p_value) { } #else - _CoreConstant(const char *p_name, int64_t p_value) : + _CoreConstant(const StringName &p_enum_name, const char *p_name, int64_t p_value) : + enum_name(p_enum_name), name(p_name), value(p_value) { } @@ -63,84 +65,190 @@ struct _CoreConstant { }; static Vector<_CoreConstant> _global_constants; +static HashMap<StringName, int> _global_constants_map; +static HashMap<StringName, Vector<_CoreConstant>> _global_enums; #ifdef DEBUG_METHODS_ENABLED -#define BIND_CORE_CONSTANT(m_constant) \ - _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); +#define BIND_CORE_CONSTANT(m_constant) \ + _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; -#define BIND_CORE_ENUM_CONSTANT(m_constant) \ - _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant)); +#define BIND_CORE_ENUM_CONSTANT(m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_BITFIELD_FLAG(m_constant) \ - _global_constants.push_back(_CoreConstant(__constant_get_bitfield_name(m_constant, #m_constant), #m_constant, m_constant, false, true)); +#define BIND_CORE_BITFIELD_FLAG(m_constant) \ + { \ + StringName enum_name = __constant_get_bitfield_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant, false, true)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } // This just binds enum classes as if they were regular enum constants. -#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \ - _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member), #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); +#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \ + { \ + StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \ + _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \ - _global_constants.push_back(_CoreConstant(__constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member), #m_prefix "_" #m_member, (int64_t)m_enum::m_member, false, true)); +#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \ + { \ + StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member, false, true)); \ + _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \ - _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_enum::m_member, #m_name), #m_name, (int64_t)m_enum::m_member)); +#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \ + { \ + StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_name); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \ + _global_constants_map[#m_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \ - _global_constants.push_back(_CoreConstant(__constant_get_bitfield_name(m_enum::m_member, #m_name), #m_name, (int64_t)m_enum::m_member, false, true)); +#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \ + { \ + StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_name); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member, false, true)); \ + _global_constants_map[#m_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \ - _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member), #m_prefix "_" #m_member, (int64_t)m_enum::m_member, true)); +#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \ + { \ + StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member, true)); \ + _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \ - _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant)); +#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \ + _global_constants_map[m_custom_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \ - _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant, true)); +#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \ + _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant, true)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; -#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \ - _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant, true)); +#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant, true)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \ - _global_constants.push_back(_CoreConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant, true)); +#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant, true)); \ + _global_constants_map[m_custom_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } #else -#define BIND_CORE_CONSTANT(m_constant) \ - _global_constants.push_back(_CoreConstant(#m_constant, m_constant)); +#define BIND_CORE_CONSTANT(m_constant) \ + _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; -#define BIND_CORE_ENUM_CONSTANT(m_constant) \ - _global_constants.push_back(_CoreConstant(#m_constant, m_constant)); +#define BIND_CORE_ENUM_CONSTANT(m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_BITFIELD_FLAG(m_constant) \ - _global_constants.push_back(_CoreConstant(#m_constant, m_constant)); +#define BIND_CORE_BITFIELD_FLAG(m_constant) \ + { \ + StringName enum_name = __constant_get_bitfield_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } // This just binds enum classes as if they were regular enum constants. -#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \ - _global_constants.push_back(_CoreConstant(#m_prefix "_" #m_member, (int64_t)m_enum::m_member)); +#define BIND_CORE_ENUM_CLASS_CONSTANT(m_enum, m_prefix, m_member) \ + { \ + StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \ + _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \ - _global_constants.push_back(_CoreConstant(#m_prefix "_" #m_member, (int64_t)m_enum::m_member)); +#define BIND_CORE_BITFIELD_CLASS_FLAG(m_enum, m_prefix, m_member) \ + { \ + StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_prefix "_" #m_member); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \ + _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \ - _global_constants.push_back(_CoreConstant(#m_name, (int64_t)m_enum::m_member)); +#define BIND_CORE_ENUM_CLASS_CONSTANT_CUSTOM(m_enum, m_name, m_member) \ + { \ + StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_name); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \ + _global_constants_map[#m_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \ - _global_constants.push_back(_CoreConstant(#m_name, (int64_t)m_enum::m_member)); +#define BIND_CORE_BITFIELD_CLASS_FLAG_CUSTOM(m_enum, m_name, m_member) \ + { \ + StringName enum_name = __constant_get_bitfield_name(m_enum::m_member, #m_name); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_name, (int64_t)m_enum::m_member)); \ + _global_constants_map[#m_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \ - _global_constants.push_back(_CoreConstant(#m_prefix "_" #m_member, (int64_t)m_enum::m_member)); +#define BIND_CORE_ENUM_CLASS_CONSTANT_NO_VAL(m_enum, m_prefix, m_member) \ + { \ + StringName enum_name = __constant_get_enum_name(m_enum::m_member, #m_prefix "_" #m_member); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_prefix "_" #m_member, (int64_t)m_enum::m_member)); \ + _global_constants_map[#m_prefix "_" #m_member] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \ - _global_constants.push_back(_CoreConstant(m_custom_name, m_constant)); +#define BIND_CORE_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \ + _global_constants_map[m_custom_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \ - _global_constants.push_back(_CoreConstant(#m_constant, m_constant)); +#define BIND_CORE_CONSTANT_NO_VAL(m_constant) \ + _global_constants.push_back(_CoreConstant(StringName(), #m_constant, m_constant)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; -#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \ - _global_constants.push_back(_CoreConstant(#m_constant, m_constant)); +#define BIND_CORE_ENUM_CONSTANT_NO_VAL(m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, #m_constant, m_constant)); \ + _global_constants_map[#m_constant] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } -#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \ - _global_constants.push_back(_CoreConstant(m_custom_name, m_constant)); +#define BIND_CORE_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \ + { \ + StringName enum_name = __constant_get_enum_name(m_constant, #m_constant); \ + _global_constants.push_back(_CoreConstant(enum_name, m_custom_name, m_constant)); \ + _global_constants_map[m_custom_name] = _global_constants.size() - 1; \ + _global_enums[enum_name].push_back((_global_constants.ptr())[_global_constants.size() - 1]); \ + } #endif @@ -690,11 +798,11 @@ int CoreConstants::get_global_constant_count() { return _global_constants.size(); } -#ifdef DEBUG_METHODS_ENABLED StringName CoreConstants::get_global_constant_enum(int p_idx) { return _global_constants[p_idx].enum_name; } +#ifdef DEBUG_METHODS_ENABLED bool CoreConstants::is_global_constant_bitfield(int p_idx) { return _global_constants[p_idx].is_bitfield; } @@ -703,10 +811,6 @@ bool CoreConstants::get_ignore_value_in_docs(int p_idx) { return _global_constants[p_idx].ignore_value_in_docs; } #else -StringName CoreConstants::get_global_constant_enum(int p_idx) { - return StringName(); -} - bool CoreConstants::is_global_constant_bitfield(int p_idx) { return false; } @@ -723,3 +827,24 @@ const char *CoreConstants::get_global_constant_name(int p_idx) { int64_t CoreConstants::get_global_constant_value(int p_idx) { return _global_constants[p_idx].value; } + +bool CoreConstants::is_global_constant(const StringName &p_name) { + return _global_constants_map.has(p_name); +} + +int CoreConstants::get_global_constant_index(const StringName &p_name) { + ERR_FAIL_COND_V_MSG(!_global_constants_map.has(p_name), -1, "Trying to get index of non-existing constant."); + return _global_constants_map[p_name]; +} + +bool CoreConstants::is_global_enum(const StringName &p_enum) { + return _global_enums.has(p_enum); +} + +void CoreConstants::get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values) { + ERR_FAIL_NULL_MSG(p_values, "Trying to get enum values with null map."); + ERR_FAIL_COND_MSG(!_global_enums.has(p_enum), "Trying to get values of non-existing enum."); + for (const _CoreConstant &constant : _global_enums[p_enum]) { + (*p_values)[constant.name] = constant.value; + } +} diff --git a/core/core_constants.h b/core/core_constants.h index 5a5cd4394c..51842490c8 100644 --- a/core/core_constants.h +++ b/core/core_constants.h @@ -32,6 +32,7 @@ #define CORE_CONSTANTS_H #include "core/string/string_name.h" +#include "core/templates/hash_set.h" class CoreConstants { public: @@ -41,6 +42,10 @@ public: static bool get_ignore_value_in_docs(int p_idx); static const char *get_global_constant_name(int p_idx); static int64_t get_global_constant_value(int p_idx); + static bool is_global_constant(const StringName &p_name); + static int get_global_constant_index(const StringName &p_name); + static bool is_global_enum(const StringName &p_enum); + static void get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values); }; #endif // CORE_CONSTANTS_H diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index b2e6b57eb6..910778324c 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -669,6 +669,10 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { default_builtin_cache.insert("ui_text_select_word_under_caret", inputs); inputs = List<Ref<InputEvent>>(); + inputs.push_back(InputEventKey::create_reference(Key::G | KeyModifierMask::CTRL | KeyModifierMask::META)); + default_builtin_cache.insert("ui_text_select_word_under_caret.macos", inputs); + + inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::D | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_add_selection_for_next_occurrence", inputs); diff --git a/core/math/bvh.h b/core/math/bvh.h index ea8289607e..8fbbd8e7fd 100644 --- a/core/math/bvh.h +++ b/core/math/bvh.h @@ -55,7 +55,7 @@ #include "core/os/mutex.h" #define BVHTREE_CLASS BVH_Tree<T, NUM_TREES, 2, MAX_ITEMS, USER_PAIR_TEST_FUNCTION, USER_CULL_TEST_FUNCTION, USE_PAIRS, BOUNDS, POINT> -#define BVH_LOCKED_FUNCTION BVHLockedFunction(&_mutex, BVH_THREAD_SAFE &&_thread_safe); +#define BVH_LOCKED_FUNCTION BVHLockedFunction _lock_guard(&_mutex, BVH_THREAD_SAFE &&_thread_safe); template <class T, int NUM_TREES = 1, bool USE_PAIRS = false, int MAX_ITEMS = 32, class USER_PAIR_TEST_FUNCTION = BVH_DummyPairTestFunction<T>, class USER_CULL_TEST_FUNCTION = BVH_DummyCullTestFunction<T>, class BOUNDS = AABB, class POINT = Vector3, bool BVH_THREAD_SAFE = true> class BVH_Manager { @@ -779,11 +779,7 @@ private: // will be compiled out if not set in template if (p_thread_safe) { _mutex = p_mutex; - - if (!_mutex->try_lock()) { - WARN_PRINT("Info : multithread BVH access detected (benign)"); - _mutex->lock(); - } + _mutex->lock(); } else { _mutex = nullptr; diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index f45ddf2738..5693876194 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1001,6 +1001,7 @@ </member> <member name="rotation" type="float" setter="set_rotation" getter="get_rotation" default="0.0"> The node's rotation around its pivot, in radians. See [member pivot_offset] to change the pivot's position. + [b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [member rotation_degrees]. </member> <member name="rotation_degrees" type="float" setter="set_rotation_degrees" getter="get_rotation_degrees"> Helper property to access [member rotation] in degrees instead of radians. diff --git a/doc/classes/Node2D.xml b/doc/classes/Node2D.xml index 9d224f09b1..3b2c52c5bf 100644 --- a/doc/classes/Node2D.xml +++ b/doc/classes/Node2D.xml @@ -116,6 +116,7 @@ </member> <member name="rotation" type="float" setter="set_rotation" getter="get_rotation" default="0.0"> Rotation in radians, relative to the node's parent. + [b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [member rotation_degrees]. </member> <member name="rotation_degrees" type="float" setter="set_rotation_degrees" getter="get_rotation_degrees"> Helper property to access [member rotation] in degrees instead of radians. diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml index c199c1aae6..61e5c44251 100644 --- a/doc/classes/Node3D.xml +++ b/doc/classes/Node3D.xml @@ -289,6 +289,7 @@ <member name="rotation" type="Vector3" setter="set_rotation" getter="get_rotation" default="Vector3(0, 0, 0)"> Rotation part of the local transformation in radians, specified in terms of Euler angles. The angles construct a rotaton in the order specified by the [member rotation_order] property. [b]Note:[/b] In the mathematical sense, rotation is a matrix and not a vector. The three Euler angles, which are the three independent parameters of the Euler-angle parametrization of the rotation matrix, are stored in a [Vector3] data structure not because the rotation is a vector, but only because [Vector3] exists as a convenient data-structure to store 3 floating-point numbers. Therefore, applying affine operations on the rotation "vector" is not meaningful. + [b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [member rotation_degrees]. </member> <member name="rotation_degrees" type="Vector3" setter="set_rotation_degrees" getter="get_rotation_degrees"> Helper property to access [member rotation] in degrees instead of radians. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 3177780a26..5f6a679b30 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1112,6 +1112,9 @@ If no selection is currently active, selects the word currently under the caret in text fields. If a selection is currently active, deselects the current selection. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> + <member name="input/ui_text_select_word_under_caret.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to select the word currently under the caret. + </member> <member name="input/ui_text_submit" type="Dictionary" setter="" getter=""> Default [InputEventAction] to submit a text field. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index aadc71c5b8..9c6e6baaec 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -115,7 +115,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ if (state.canvas_instance_data_buffers[state.current_data_buffer_index].fence != GLsync()) { GLint syncStatus; - glGetSynciv(state.canvas_instance_data_buffers[state.current_data_buffer_index].fence, GL_SYNC_STATUS, sizeof(GLint), nullptr, &syncStatus); + glGetSynciv(state.canvas_instance_data_buffers[state.current_data_buffer_index].fence, GL_SYNC_STATUS, 1, nullptr, &syncStatus); if (syncStatus == GL_UNSIGNALED) { // If older than 2 frames, wait for sync OpenGL can have up to 3 frames in flight, any more and we need to sync anyway. if (state.canvas_instance_data_buffers[state.current_data_buffer_index].last_frame_used < RSG::rasterizer->get_frame_number() - 2) { diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 600aa908cc..a9ec48fcd5 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -281,7 +281,7 @@ void RasterizerGLES3::prepare_for_blitting_render_targets() { utils->capture_timestamps_end(); } -void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, DisplayServer::WindowID p_screen, const Rect2 &p_screen_rect, uint32_t p_layer) { +void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, DisplayServer::WindowID p_screen, const Rect2 &p_screen_rect, uint32_t p_layer, bool p_first) { GLES3::RenderTarget *rt = GLES3::TextureStorage::get_singleton()->get_render_target(p_render_target); ERR_FAIL_COND(!rt); @@ -307,12 +307,14 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display glReadBuffer(GL_COLOR_ATTACHMENT0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); - if (p_screen_rect.position != Vector2()) { - // Viewport doesn't cover entire window so clear window to black before blitting. + if (p_first) { Size2i win_size = DisplayServer::get_singleton()->window_get_size(); - glViewport(0, 0, win_size.width, win_size.height); - glClearColor(0.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); + if (p_screen_rect.position != Vector2() || p_screen_rect.size != rt->size) { + // Viewport doesn't cover entire window so clear window to black before blitting. + glViewport(0, 0, win_size.width, win_size.height); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + } } Vector2i screen_rect_end = p_screen_rect.get_end(); @@ -334,7 +336,7 @@ void RasterizerGLES3::blit_render_targets_to_screen(DisplayServer::WindowID p_sc RID rid_rt = blit.render_target; Rect2 dst_rect = blit.dst_rect; - _blit_render_target_to_screen(rid_rt, p_screen, dst_rect, blit.multi_view.use_layer ? blit.multi_view.layer : 0); + _blit_render_target_to_screen(rid_rt, p_screen, dst_rect, blit.multi_view.use_layer ? blit.multi_view.layer : 0, i == 0); } } diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h index 446c6af338..0ba84ce412 100644 --- a/drivers/gles3/rasterizer_gles3.h +++ b/drivers/gles3/rasterizer_gles3.h @@ -68,7 +68,7 @@ protected: RasterizerCanvasGLES3 *canvas = nullptr; RasterizerSceneGLES3 *scene = nullptr; - void _blit_render_target_to_screen(RID p_render_target, DisplayServer::WindowID p_screen, const Rect2 &p_screen_rect, uint32_t p_layer); + void _blit_render_target_to_screen(RID p_render_target, DisplayServer::WindowID p_screen, const Rect2 &p_screen_rect, uint32_t p_layer, bool p_first = true); public: RendererUtilities *get_utilities() { return utilities; } diff --git a/drivers/gles3/shaders/sky.glsl b/drivers/gles3/shaders/sky.glsl index e59bca8b07..2455ffb8e2 100644 --- a/drivers/gles3/shaders/sky.glsl +++ b/drivers/gles3/shaders/sky.glsl @@ -21,12 +21,13 @@ out vec2 uv_interp; /* clang-format on */ void main() { - uv_interp = vertex_attrib; #ifdef USE_INVERTED_Y - gl_Position = vec4(uv_interp, 1.0, 1.0); + uv_interp = vertex_attrib; #else - gl_Position = vec4(uv_interp.x, uv_interp.y * -1.0, 1.0, 1.0); + // We're doing clockwise culling so flip the order + uv_interp = vec2(vertex_attrib.x, vertex_attrib.y * -1.0); #endif + gl_Position = vec4(uv_interp, 1.0, 1.0); } /* clang-format off */ @@ -145,9 +146,6 @@ void main() { cube_normal.x = (uv_interp.x + projection.x) / projection.y; cube_normal.y = (-uv_interp.y - projection.z) / projection.w; #endif -#ifndef USE_INVERTED_Y - cube_normal.y *= -1.0; -#endif cube_normal = mat3(orientation) * cube_normal; cube_normal = normalize(cube_normal); diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 3de4379c5d..50826f572a 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -1705,6 +1705,11 @@ EditorFileDialog::EditorFileDialog() { ED_SHORTCUT("file_dialog/move_favorite_up", TTR("Move Favorite Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP); ED_SHORTCUT("file_dialog/move_favorite_down", TTR("Move Favorite Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN); + if (EditorSettings::get_singleton()) { + ED_SHORTCUT_OVERRIDE("file_dialog/toggle_favorite", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F); + ED_SHORTCUT_OVERRIDE("file_dialog/toggle_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::V); + } + HBoxContainer *pathhb = memnew(HBoxContainer); dir_prev = memnew(Button); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b854da8e4f..3555caac8a 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -7203,6 +7203,7 @@ EditorNode::EditorNode() { file_menu->add_separator(); file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN); + ED_SHORTCUT_OVERRIDE("editor/quick_open", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + Key::O); file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE); file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT); @@ -7274,7 +7275,7 @@ EditorNode::EditorNode() { project_menu->add_separator(); project_menu->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTR("Reload Current Project")), RELOAD_CURRENT_PROJECT); ED_SHORTCUT_AND_COMMAND("editor/quit_to_project_list", TTR("Quit to Project List"), KeyModifierMask::CTRL + KeyModifierMask::SHIFT + Key::Q); - ED_SHORTCUT_OVERRIDE("editor/quit_to_project_list", "macos", KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::Q); + ED_SHORTCUT_OVERRIDE("editor/quit_to_project_list", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + KeyModifierMask::ALT + Key::Q); project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true); // Spacer to center 2D / 3D / Script buttons. @@ -7996,10 +7997,10 @@ EditorNode::EditorNode() { ED_SHORTCUT_AND_COMMAND("editor/editor_script", TTR("Open Script Editor"), KeyModifierMask::CTRL | Key::F3); ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTR("Open Asset Library"), KeyModifierMask::CTRL | Key::F4); - ED_SHORTCUT_OVERRIDE("editor/editor_2d", "macos", KeyModifierMask::ALT | Key::KEY_1); - ED_SHORTCUT_OVERRIDE("editor/editor_3d", "macos", KeyModifierMask::ALT | Key::KEY_2); - ED_SHORTCUT_OVERRIDE("editor/editor_script", "macos", KeyModifierMask::ALT | Key::KEY_3); - ED_SHORTCUT_OVERRIDE("editor/editor_assetlib", "macos", KeyModifierMask::ALT | Key::KEY_4); + ED_SHORTCUT_OVERRIDE("editor/editor_2d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_1); + ED_SHORTCUT_OVERRIDE("editor/editor_3d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_2); + ED_SHORTCUT_OVERRIDE("editor/editor_script", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_3); + ED_SHORTCUT_OVERRIDE("editor/editor_assetlib", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_4); ED_SHORTCUT_AND_COMMAND("editor/editor_next", TTR("Open the next Editor")); ED_SHORTCUT_AND_COMMAND("editor/editor_prev", TTR("Open the previous Editor")); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index c90f8e9bf0..b4f5eeda84 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -1384,8 +1384,13 @@ float EditorSettings::get_auto_display_scale() const { // Shortcuts +void EditorSettings::_add_shortcut_default(const String &p_name, const Ref<Shortcut> &p_shortcut) { + shortcuts[p_name] = p_shortcut; +} + void EditorSettings::add_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut) { shortcuts[p_name] = p_shortcut; + shortcuts[p_name]->set_meta("customized", true); } bool EditorSettings::is_shortcut(const String &p_name, const Ref<InputEvent> &p_event) const { @@ -1489,12 +1494,12 @@ void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, c } } - // Override the existing shortcut only if it wasn't customized by the user (i.e. still "original"). - sc->set_meta("original", events.duplicate(true)); - - if (Shortcut::is_event_array_equal(sc->get_events(), sc->get_meta("original"))) { + // Override the existing shortcut only if it wasn't customized by the user. + if (!sc->has_meta("customized")) { sc->set_events(events); } + + sc->set_meta("original", events.duplicate(true)); } Ref<Shortcut> ED_SHORTCUT(const String &p_path, const String &p_name, Key p_keycode) { @@ -1543,7 +1548,7 @@ Ref<Shortcut> ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, cons sc->set_name(p_name); sc->set_events(events); sc->set_meta("original", events.duplicate(true)); //to compare against changes - EditorSettings::get_singleton()->add_shortcut(p_path, sc); + EditorSettings::get_singleton()->_add_shortcut_default(p_path, sc); return sc; } diff --git a/editor/editor_settings.h b/editor/editor_settings.h index e8775636a3..e1d3e757e0 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -170,6 +170,7 @@ public: String get_editor_layouts_config() const; float get_auto_display_scale() const; + void _add_shortcut_default(const String &p_name, const Ref<Shortcut> &p_shortcut); void add_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut); bool is_shortcut(const String &p_name, const Ref<InputEvent> &p_event) const; Ref<Shortcut> get_shortcut(const String &p_name) const; diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index 52482ecb16..ab061c4d5c 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -186,7 +186,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) { } } - int history_id = EditorUndoRedoManager::get_singleton()->get_history_for_object(current).id; + int history_id = EditorUndoRedoManager::get_singleton()->get_history_id_for_object(current); EditorUndoRedoManager::get_singleton()->clear_history(true, history_id); EditorNode::get_singleton()->edit_item(current, inspector); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index ccbc7c3d74..a584d357cd 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -3814,6 +3814,7 @@ ScriptEditor::ScriptEditor() { file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save", TTR("Save"), KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::S), FILE_SAVE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_as", TTR("Save As...")), FILE_SAVE_AS); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_all", TTR("Save All"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::S), FILE_SAVE_ALL); + ED_SHORTCUT_OVERRIDE("script_editor/save_all", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::S); file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reload_script_soft", TTR("Soft Reload Tool Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::R), FILE_TOOL_RELOAD_SOFT); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_path", TTR("Copy Script Path")), FILE_COPY_PATH); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 9fe1d8af99..5e70a407dd 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -2270,6 +2270,7 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/unindent", TTR("Unindent"), KeyModifierMask::SHIFT | Key::TAB); ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KeyModifierMask::CMD_OR_CTRL | Key::K); ED_SHORTCUT("script_text_editor/toggle_fold_line", TTR("Fold/Unfold Line"), KeyModifierMask::ALT | Key::F); + ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_fold_line", "macos", KeyModifierMask::CTRL | KeyModifierMask::META | Key::F); ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), Key::NONE); ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), Key::NONE); ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::SHIFT | KeyModifierMask::CTRL | Key::D); diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index eaf72d36ba..8404ea0969 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -245,7 +245,9 @@ bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const { void AtlasMergingDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { - _update_texture(); + if (is_visible()) { + _update_texture(); + } } break; } } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 1d510703b0..c00ef326d0 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -2240,11 +2240,11 @@ void ProjectManager::_open_selected_projects_ask() { return; } - const Size2i popup_min_width = Size2i(600.0 * EDSCALE, 0); + const Size2i popup_min_size = Size2i(600.0 * EDSCALE, 400.0 * EDSCALE); if (selected_list.size() > 1) { multi_open_ask->set_text(vformat(TTR("You requested to open %d projects in parallel. Do you confirm?\nNote that usual checks for engine version compatibility will be bypassed."), selected_list.size())); - multi_open_ask->popup_centered(popup_min_width); + multi_open_ask->popup_centered(popup_min_size); return; } @@ -2266,7 +2266,7 @@ void ProjectManager::_open_selected_projects_ask() { // Check if the config_version property was empty or 0. if (config_version == 0) { ask_update_settings->set_text(vformat(TTR("The selected project \"%s\" does not specify its supported Godot version in its configuration file (\"project.godot\").\n\nProject path: %s\n\nIf you proceed with opening it, it will be converted to Godot's current configuration file format.\n\nWarning: You won't be able to open the project with previous versions of the engine anymore."), project.project_name, project.path)); - ask_update_settings->popup_centered(popup_min_width); + ask_update_settings->popup_centered(popup_min_size); return; } // Check if we need to convert project settings from an earlier engine version. @@ -2279,14 +2279,14 @@ void ProjectManager::_open_selected_projects_ask() { ask_update_settings->set_text(vformat(TTR("The selected project \"%s\" was generated by an older engine version, and needs to be converted for this version.\n\nProject path: %s\n\nDo you want to convert it?\n\nWarning: You won't be able to open the project with previous versions of the engine anymore."), project.project_name, project.path)); ask_update_settings->get_ok_button()->set_text(TTR("Convert project.godot")); } - ask_update_settings->popup_centered(popup_min_width); + ask_update_settings->popup_centered(popup_min_size); ask_update_settings->get_cancel_button()->grab_focus(); // To prevent accidents. return; } // Check if the file was generated by a newer, incompatible engine version. if (config_version > ProjectSettings::CONFIG_VERSION) { dialog_error->set_text(vformat(TTR("Can't open project \"%s\" at the following path:\n\n%s\n\nThe project settings were created by a newer engine version, whose settings are not compatible with this version."), project.project_name, project.path)); - dialog_error->popup_centered(popup_min_width); + dialog_error->popup_centered(popup_min_size); return; } // Check if the project is using features not supported by this build of Godot. @@ -2315,7 +2315,7 @@ void ProjectManager::_open_selected_projects_ask() { warning_message += TTR("Open anyway? Project will be modified."); ask_update_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); ask_update_settings->set_text(warning_message); - ask_update_settings->popup_centered(popup_min_width); + ask_update_settings->popup_centered(popup_min_size); return; } @@ -2325,7 +2325,7 @@ void ProjectManager::_open_selected_projects_ask() { void ProjectManager::_full_convert_button_pressed() { ask_update_settings->hide(); - ask_full_convert_dialog->popup_centered(Size2i(600.0 * EDSCALE, 0)); + ask_full_convert_dialog->popup_centered(Size2i(600.0 * EDSCALE, 400.0 * EDSCALE)); ask_full_convert_dialog->get_cancel_button()->grab_focus(); } diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 0b7e4e50e6..08c8763493 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -121,6 +121,13 @@ [/codeblock] </description> </method> + <method name="is_instance_of"> + <return type="bool" /> + <param index="0" name="value" type="Variant" /> + <param index="1" name="type" type="Variant" /> + <description> + </description> + </method> <method name="len"> <return type="int" /> <param index="0" name="var" type="Variant" /> diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index c8dfdbdd68..38f9163f70 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -32,6 +32,7 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/core_constants.h" #include "core/core_string_names.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" @@ -48,7 +49,7 @@ #endif #define UNNAMED_ENUM "<anonymous enum>" -#define ENUM_SEPARATOR "::" +#define ENUM_SEPARATOR "." static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -137,12 +138,16 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co // For enums, native_type is only used to check compatibility in is_type_compatible() // We can set anything readable here for error messages, as long as it uniquely identifies the type of the enum - type.native_type = p_base_name + ENUM_SEPARATOR + p_enum_name; + if (p_base_name.is_empty()) { + type.native_type = p_enum_name; + } else { + type.native_type = p_base_name + ENUM_SEPARATOR + p_enum_name; + } return type; } -static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) { +static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) { // Find out which base class declared the enum, so the name is always the same even when coming from other contexts. StringName native_base = p_native_class; while (true && native_base != StringName()) { @@ -154,7 +159,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n GDScriptParser::DataType type = make_enum_type(p_enum_name, native_base, p_meta); if (p_meta) { - type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries + type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries. } List<StringName> enum_values; @@ -167,6 +172,22 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n return type; } +static GDScriptParser::DataType make_global_enum_type(const StringName &p_enum_name, const StringName &p_base, bool p_meta = true) { + GDScriptParser::DataType type = make_enum_type(p_enum_name, p_base, p_meta); + if (p_meta) { + type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries. + type.is_pseudo_type = true; + } + + HashMap<StringName, int64_t> enum_values; + CoreConstants::get_enum_values(type.native_type, &enum_values); + for (const KeyValue<StringName, int64_t> &element : enum_values) { + type.enum_values[element.key] = element.value; + } + + return type; +} + static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -562,7 +583,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.builtin_type = Variant::OBJECT; if (p_type->type_chain.is_empty()) { // void. @@ -575,15 +595,26 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type StringName first = p_type->type_chain[0]->name; if (first == SNAME("Variant")) { - if (p_type->type_chain.size() > 1) { - // TODO: Variant does actually have a nested Type though. - push_error(R"(Variant doesn't contain nested types.)", p_type->type_chain[1]); + if (p_type->type_chain.size() == 2) { + // May be nested enum. + StringName enum_name = p_type->type_chain[1]->name; + StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name); + if (CoreConstants::is_global_enum(qualified_name)) { + result = make_global_enum_type(enum_name, first, true); + return result; + } else { + push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]); + return bad_type; + } + } else if (p_type->type_chain.size() > 2) { + push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]); return bad_type; } result.kind = GDScriptParser::DataType::VARIANT; } else if (first == SNAME("Object")) { // Object is treated like a native type, not a built-in. result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; result.native_type = SNAME("Object"); } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. @@ -604,6 +635,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; result.native_type = first; } else if (ScriptServer::is_global_class(first)) { if (parser->script_path == ScriptServer::get_global_class_path(first)) { @@ -633,6 +665,12 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) { // Native enum in current class. result = make_native_enum_type(first, parser->current_class->base_type.native_type); + } else if (CoreConstants::is_global_enum(first)) { + if (p_type->type_chain.size() > 1) { + push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]); + return bad_type; + } + result = make_global_enum_type(first, StringName()); } else { // Classes in current scope. List<GDScriptParser::ClassNode *> script_classes; @@ -1338,6 +1376,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root case GDScriptParser::Node::SELF: case GDScriptParser::Node::SUBSCRIPT: case GDScriptParser::Node::TERNARY_OPERATOR: + case GDScriptParser::Node::TYPE_TEST: case GDScriptParser::Node::UNARY_OPERATOR: reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), p_is_root); break; @@ -2196,6 +2235,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::TERNARY_OPERATOR: reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root); break; + case GDScriptParser::Node::TYPE_TEST: + reduce_type_test(static_cast<GDScriptParser::TypeTestNode *>(p_expression)); + break; case GDScriptParser::Node::UNARY_OPERATOR: reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression)); break; @@ -2502,13 +2544,7 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) { reduce_expression(p_binary_op->left_operand); - - if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) { - reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true); - } else { - reduce_expression(p_binary_op->right_operand); - } - // TODO: Right operand must be a valid type with the `is` operator. Need to check here. + reduce_expression(p_binary_op->right_operand); GDScriptParser::DataType left_type; if (p_binary_op->left_operand) { @@ -2546,19 +2582,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o } } } else { - if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { - GDScriptParser::DataType test_type = right_type; - test_type.is_meta_type = false; - - if (!is_type_compatible(test_type, left_type)) { - push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); - p_binary_op->reduced_value = false; - } else { - p_binary_op->reduced_value = true; - } - } else { - ERR_PRINT("Parser bug: unknown binary operation."); - } + ERR_PRINT("Parser bug: unknown binary operation."); } p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value, p_binary_op)); @@ -2567,24 +2591,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType result; - if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { - GDScriptParser::DataType test_type = right_type; - test_type.is_meta_type = false; - - if (!is_type_compatible(test_type, left_type) && !is_type_compatible(left_type, test_type)) { - if (left_type.is_hard_type()) { - push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand); - } else { - // TODO: Warning. - mark_node_unsafe(p_binary_op); - } - } - - // "is" operator is always a boolean anyway. - result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.kind = GDScriptParser::DataType::BUILTIN; - result.builtin_type = Variant::BOOL; - } else if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) && + if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) && ((left_type.kind == GDScriptParser::DataType::BUILTIN && left_type.builtin_type == Variant::NIL) || (right_type.kind == GDScriptParser::DataType::BUILTIN && right_type.builtin_type == Variant::NIL))) { // "==" and "!=" operators always return a boolean when comparing to null. result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2599,6 +2606,8 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op); if (!valid) { push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); + } else if (result.type_source != GDScriptParser::DataType::ANNOTATED_EXPLICIT) { + mark_node_unsafe(p_binary_op); } } else { ERR_PRINT("Parser bug: unknown binary operation."); @@ -3661,6 +3670,20 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } + if (CoreConstants::is_global_constant(name)) { + int index = CoreConstants::get_global_constant_index(name); + StringName enum_name = CoreConstants::get_global_constant_enum(index); + int64_t value = CoreConstants::get_global_constant_value(index); + if (enum_name != StringName()) { + p_identifier->set_datatype(make_global_enum_type(enum_name, StringName(), false)); + } else { + p_identifier->set_datatype(type_from_variant(value, p_identifier)); + } + p_identifier->is_constant = true; + p_identifier->reduced_value = value; + return; + } + if (GDScriptLanguage::get_singleton()->has_any_global_constant(name)) { Variant constant = GDScriptLanguage::get_singleton()->get_any_global_constant(name); p_identifier->set_datatype(type_from_variant(constant, p_identifier)); @@ -3669,6 +3692,25 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident return; } + if (CoreConstants::is_global_enum(name)) { + p_identifier->set_datatype(make_global_enum_type(name, StringName(), true)); + if (!can_be_builtin) { + push_error(vformat(R"(Global enum "%s" cannot be used on its own.)", name), p_identifier); + } + return; + } + + // Allow "Variant" here since it might be used for nested enums. + if (can_be_builtin && name == SNAME("Variant")) { + GDScriptParser::DataType variant; + variant.kind = GDScriptParser::DataType::VARIANT; + variant.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + variant.is_meta_type = true; + variant.is_pseudo_type = true; + p_identifier->set_datatype(variant); + return; + } + // Not found. // Check if it's a builtin function. if (GDScriptUtilityFunctions::function_exists(name)) { @@ -3809,12 +3851,14 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { mark_lambda_use_self(); } -void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { +void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript, bool p_can_be_pseudo_type) { if (p_subscript->base == nullptr) { return; } if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); + } else if (p_subscript->base->type == GDScriptParser::Node::SUBSCRIPT) { + reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_subscript->base), true); } else { reduce_expression(p_subscript->base); } @@ -3838,14 +3882,28 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type = type_from_variant(value, p_subscript); } } else if (base_type.is_variant() || !base_type.is_hard_type()) { - valid = true; + valid = !base_type.is_pseudo_type || p_can_be_pseudo_type; result_type.kind = GDScriptParser::DataType::VARIANT; - mark_node_unsafe(p_subscript); + if (base_type.is_variant() && base_type.is_hard_type() && base_type.is_meta_type && base_type.is_pseudo_type) { + // Special case: it may be a global enum with pseudo base (e.g. Variant.Type). + String enum_name; + if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { + enum_name = String(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base)->name) + ENUM_SEPARATOR + String(p_subscript->attribute->name); + } + if (CoreConstants::is_global_enum(enum_name)) { + result_type = make_global_enum_type(enum_name, StringName()); + } else { + valid = false; + mark_node_unsafe(p_subscript); + } + } else { + mark_node_unsafe(p_subscript); + } } else { reduce_identifier_from_base(p_subscript->attribute, &base_type); GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); if (attr_type.is_set()) { - valid = true; + valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; result_type = attr_type; p_subscript->is_constant = p_subscript->attribute->is_constant; p_subscript->reduced_value = p_subscript->attribute->reduced_value; @@ -3861,7 +3919,12 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } if (!valid) { - push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute); + GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); + if (!p_can_be_pseudo_type && (attr_type.is_pseudo_type || result_type.is_pseudo_type)) { + push_error(vformat(R"(Type "%s" in base "%s" cannot be used on its own.)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute); + } else { + push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute); + } result_type.kind = GDScriptParser::DataType::VARIANT; } } else { @@ -4107,6 +4170,48 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar p_ternary_op->set_datatype(result); } +void GDScriptAnalyzer::reduce_type_test(GDScriptParser::TypeTestNode *p_type_test) { + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::BOOL; + p_type_test->set_datatype(result); + + if (!p_type_test->operand || !p_type_test->test_type) { + return; + } + + reduce_expression(p_type_test->operand); + GDScriptParser::DataType operand_type = p_type_test->operand->get_datatype(); + GDScriptParser::DataType test_type = type_from_metatype(resolve_datatype(p_type_test->test_type)); + p_type_test->test_datatype = test_type; + + if (!operand_type.is_set() || !test_type.is_set()) { + return; + } + + if (p_type_test->operand->is_constant) { + p_type_test->is_constant = true; + p_type_test->reduced_value = false; + + if (!is_type_compatible(test_type, operand_type)) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", operand_type.to_string(), test_type.to_string()), p_type_test->operand); + } else if (is_type_compatible(test_type, type_from_variant(p_type_test->operand->reduced_value, p_type_test->operand))) { + p_type_test->reduced_value = test_type.builtin_type != Variant::OBJECT || !p_type_test->operand->reduced_value.is_null(); + } + + return; + } + + if (!is_type_compatible(test_type, operand_type) && !is_type_compatible(operand_type, test_type)) { + if (operand_type.is_hard_type()) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", operand_type.to_string(), test_type.to_string()), p_type_test->operand); + } else { + downgrade_node_type_source(p_type_test->operand); + } + } +} + void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) { reduce_expression(p_unary_op->operand); @@ -4375,6 +4480,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) { GDScriptParser::DataType result = p_meta_type; result.is_meta_type = false; + result.is_pseudo_type = false; if (p_meta_type.kind == GDScriptParser::DataType::ENUM) { result.builtin_type = Variant::INT; } else { @@ -4428,11 +4534,16 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.set_container_element_type(elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. - if (p_property.class_name != StringName()) { - Vector<String> names = String(p_property.class_name).split("."); - if (names.size() == 2) { - result = make_native_enum_type(names[1], names[0], false); + if ((p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) && p_property.class_name != StringName()) { + if (CoreConstants::is_global_enum(p_property.class_name)) { + result = make_global_enum_type(p_property.class_name, StringName(), false); result.is_constant = false; + } else { + Vector<String> names = String(p_property.class_name).split(ENUM_SEPARATOR); + if (names.size() == 2) { + result = make_native_enum_type(names[1], names[0], false); + result.is_constant = false; + } } } } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index cdeba374c7..7a50b32d4c 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -98,8 +98,9 @@ class GDScriptAnalyzer { void reduce_literal(GDScriptParser::LiteralNode *p_literal); void reduce_preload(GDScriptParser::PreloadNode *p_preload); void reduce_self(GDScriptParser::SelfNode *p_self); - void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript); + void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript, bool p_can_be_pseudo_type = false); void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false); + void reduce_type_test(GDScriptParser::TypeTestNode *p_type_test); void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 45008b0e87..a13bf8009f 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -612,18 +612,44 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V append(p_operator); } -void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) { - append_opcode(GDScriptFunction::OPCODE_EXTENDS_TEST); - append(p_source); - append(p_type); - append(p_target); -} - -void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) { - append_opcode(GDScriptFunction::OPCODE_IS_BUILTIN); - append(p_source); - append(p_target); - append(p_type); +void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { + switch (p_type.kind) { + case GDScriptDataType::BUILTIN: { + if (p_type.builtin_type == Variant::ARRAY && p_type.has_container_element_type()) { + const GDScriptDataType &element_type = p_type.get_container_element_type(); + append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_ARRAY); + append(p_target); + append(p_source); + append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(element_type.builtin_type); + append(element_type.native_type); + } else { + append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN); + append(p_target); + append(p_source); + append(p_type.builtin_type); + } + } break; + case GDScriptDataType::NATIVE: { + append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_NATIVE); + append(p_target); + append(p_source); + append(p_type.native_type); + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + const Variant &script = p_type.script_type; + append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_SCRIPT); + append(p_target); + append(p_source); + append(get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + } break; + default: { + ERR_PRINT("Compiler bug: unresolved type in type test."); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_FALSE); + append(p_target); + } + } } void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 1d1b22e196..dc05de9fc6 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -481,8 +481,7 @@ public: virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) override; virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override; virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override; - virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override; - virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) override; + virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; virtual void write_and_left_operand(const Address &p_left_operand) override; virtual void write_and_right_operand(const Address &p_right_operand) override; virtual void write_end_and(const Address &p_target) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 6d42d152b9..7847ab28c7 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -90,8 +90,7 @@ public: virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) = 0; virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0; virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0; - virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0; - virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) = 0; + virtual void write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; virtual void write_and_left_operand(const Address &p_left_operand) = 0; virtual void write_and_right_operand(const Address &p_right_operand) = 0; virtual void write_end_and(const Address &p_target) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index b34be11169..efa75528fc 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -148,13 +148,8 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: - result.has_type = true; result.kind = GDScriptDataType::BUILTIN; - if (p_datatype.is_meta_type) { - result.builtin_type = Variant::DICTIONARY; - } else { - result.builtin_type = Variant::INT; - } + result.builtin_type = p_datatype.builtin_type; break; case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: { @@ -494,17 +489,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptParser::DataType og_cast_type = cn->get_datatype(); - GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result; if (cast_type.has_type) { - if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { - // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. - cast_type.kind = GDScriptDataType::BUILTIN; - cast_type.builtin_type = Variant::INT; - } - // Create temporary for result first since it will be deleted last. result = codegen.add_temporary(cast_type); @@ -817,28 +805,6 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->pop_temporary(); } } break; - case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: { - GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand); - - if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name) != Variant::VARIANT_MAX) { - // `is` with builtin type) - Variant::Type type = GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name); - gen->write_type_test_builtin(result, operand, type); - } else { - GDScriptCodeGenerator::Address type = _parse_expression(codegen, r_error, binary->right_operand); - if (r_error) { - return GDScriptCodeGenerator::Address(); - } - gen->write_type_test(result, operand, type); - if (type.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); - } - } - - if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); - } - } break; default: { GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); @@ -894,6 +860,28 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return result; } break; + case GDScriptParser::Node::TYPE_TEST: { + const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script)); + + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand); + GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + if (test_type.has_type) { + gen->write_type_test(result, operand, test_type); + } else { + gen->write_assign_true(result); + } + + if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + + return result; + } break; case GDScriptParser::Node::ASSIGNMENT: { const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index d4f4358ac1..0acc03be3d 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -135,23 +135,56 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; - case OPCODE_EXTENDS_TEST: { - text += "is object "; - text += DADDR(3); - text += " = "; + case OPCODE_TYPE_TEST_BUILTIN: { + text += "type test "; text += DADDR(1); - text += " is "; + text += " = "; text += DADDR(2); + text += " is "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3])); incr += 4; } break; - case OPCODE_IS_BUILTIN: { - text += "is builtin "; + case OPCODE_TYPE_TEST_ARRAY: { + text += "type test "; + text += DADDR(1); + text += " = "; text += DADDR(2); + text += " is Array["; + + Ref<Script> script_type = get_constant(_code_ptr[ip + 3] & GDScriptFunction::ADDR_MASK); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4]; + StringName native_type = get_global_name(_code_ptr[ip + 5]); + + if (script_type.is_valid() && script_type->is_valid()) { + text += script_type->get_path(); + } else if (native_type != StringName()) { + text += native_type; + } else { + text += Variant::get_type_name(builtin_type); + } + + text += "]"; + + incr += 6; + } break; + case OPCODE_TYPE_TEST_NATIVE: { + text += "type test "; + text += DADDR(1); text += " = "; + text += DADDR(2); + text += " is "; + text += get_global_name(_code_ptr[ip + 3]); + + incr += 4; + } break; + case OPCODE_TYPE_TEST_SCRIPT: { + text += "type test "; text += DADDR(1); + text += " = "; + text += DADDR(2); text += " is "; - text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3])); + text += DADDR(3); incr += 4; } break; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 8cfd48b52b..63dfd4d27c 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1353,6 +1353,21 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr return ci; } +#define MAX_COMPLETION_RECURSION 100 +struct RecursionCheck { + int *counter; + _FORCE_INLINE_ bool check() { + return (*counter) > MAX_COMPLETION_RECURSION; + } + RecursionCheck(int *p_counter) : + counter(p_counter) { + (*counter)++; + } + ~RecursionCheck() { + (*counter)--; + } +}; + static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); @@ -1385,6 +1400,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, return false; } + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + if (p_expression->is_constant) { // Already has a value, so just use that. r_type = _type_from_variant(p_expression->reduced_value); @@ -1855,6 +1876,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + // Look in blocks first. int last_assign_line = -1; const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; @@ -1918,21 +1945,19 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::BINARY_OPERATOR && static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition)->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { + if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::TYPE_TEST) { // Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common.. // Super dirty hack, but very useful. // Credit: Zylann. // TODO: this could be hacked to detect ANDed conditions too... - const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(suite->parent_if->condition); - if (op->left_operand && op->right_operand && op->left_operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(op->left_operand)->name == p_identifier) { + const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition); + if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier) { // Bingo. GDScriptParser::CompletionContext c = p_context; - c.current_line = op->left_operand->start_line; + c.current_line = type_test->operand->start_line; c.current_suite = suite; - GDScriptCompletionIdentifier is_type; - if (_guess_expression_type(c, op->right_operand, is_type)) { - id_type = is_type.type; - id_type.is_meta_type = false; + if ((!id_type.is_set() || id_type.is_variant()) && type_test->test_datatype.is_hard_type()) { + id_type = type_test->test_datatype; if (last_assign_line < c.current_line) { // Override last assignment. last_assign_line = c.current_line; @@ -2074,6 +2099,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) { + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; while (base_type.is_set()) { @@ -2287,6 +2318,12 @@ static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_cont } static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type) { + static int recursion_depth = 0; + RecursionCheck recursion(&recursion_depth); + if (unlikely(recursion.check())) { + ERR_FAIL_V_MSG(false, "Reached recursion limit while trying to guess type."); + } + GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 2624fb8dd9..1a5e9eef53 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -105,9 +105,10 @@ public: return false; } - Object *obj = p_variant.get_validated_object(); + bool was_freed = false; + Object *obj = p_variant.get_validated_object_with_check(was_freed); if (!obj) { - return false; + return !was_freed; } if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) { @@ -124,9 +125,10 @@ public: return false; } - Object *obj = p_variant.get_validated_object(); + bool was_freed = false; + Object *obj = p_variant.get_validated_object_with_check(was_freed); if (!obj) { - return false; + return !was_freed; } Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : nullptr; @@ -219,8 +221,10 @@ public: enum Opcode { OPCODE_OPERATOR, OPCODE_OPERATOR_VALIDATED, - OPCODE_EXTENDS_TEST, - OPCODE_IS_BUILTIN, + OPCODE_TYPE_TEST_BUILTIN, + OPCODE_TYPE_TEST_ARRAY, + OPCODE_TYPE_TEST_NATIVE, + OPCODE_TYPE_TEST_SCRIPT, OPCODE_SET_KEYED, OPCODE_SET_KEYED_VALIDATED, OPCODE_SET_INDEXED_VALIDATED, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index b5cb5a4680..b32313dad4 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2463,9 +2463,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression operation->operation = BinaryOpNode::OP_LOGIC_OR; operation->variant_op = Variant::OP_OR; break; - case GDScriptTokenizer::Token::IS: - operation->operation = BinaryOpNode::OP_TYPE_TEST; - break; case GDScriptTokenizer::Token::IN: operation->operation = BinaryOpNode::OP_CONTENT_TEST; operation->variant_op = Variant::OP_IN; @@ -3161,6 +3158,22 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p return lambda; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) { + TypeTestNode *type_test = alloc_node<TypeTestNode>(); + reset_extents(type_test, p_previous_operand); + update_extents(type_test); + + type_test->operand = p_previous_operand; + type_test->test_type = parse_type(); + complete_extents(type_test); + + if (type_test->test_type == nullptr) { + push_error(R"(Expected type specifier after "is".)"); + } + + return type_test; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) { push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)"); return nullptr; @@ -3529,7 +3542,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // EXTENDS, { &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC, { nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN, - { nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS, + { nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS, { nullptr, nullptr, PREC_NONE }, // NAMESPACE, { &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD, { &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF, @@ -4379,9 +4392,6 @@ void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) { case BinaryOpNode::OP_LOGIC_OR: push_text(" OR "); break; - case BinaryOpNode::OP_TYPE_TEST: - push_text(" IS "); - break; case BinaryOpNode::OP_CONTENT_TEST: push_text(" IN "); break; @@ -4584,6 +4594,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) case Node::TERNARY_OPERATOR: print_ternary_op(static_cast<TernaryOpNode *>(p_expression)); break; + case Node::TYPE_TEST: + print_type_test(static_cast<TypeTestNode *>(p_expression)); + break; case Node::UNARY_OPERATOR: print_unary_op(static_cast<UnaryOpNode *>(p_expression)); break; @@ -4943,6 +4956,12 @@ void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) { } } +void GDScriptParser::TreePrinter::print_type_test(TypeTestNode *p_test) { + print_expression(p_test->operand); + push_text(" IS "); + print_type(p_test->test_type); +} + void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) { // Surround in parenthesis for disambiguation. push_text("("); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 0ba0d5b6da..346c9bc45d 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -91,6 +91,7 @@ public: struct SuiteNode; struct TernaryOpNode; struct TypeNode; + struct TypeTestNode; struct UnaryOpNode; struct VariableNode; struct WhileNode; @@ -124,6 +125,7 @@ public: bool is_constant = false; bool is_read_only = false; bool is_meta_type = false; + bool is_pseudo_type = false; // For global names that can't be used standalone. bool is_coroutine = false; // For function calls. Variant::Type builtin_type = Variant::NIL; @@ -210,6 +212,7 @@ public: is_read_only = p_other.is_read_only; is_constant = p_other.is_constant; is_meta_type = p_other.is_meta_type; + is_pseudo_type = p_other.is_pseudo_type; is_coroutine = p_other.is_coroutine; builtin_type = p_other.builtin_type; native_type = p_other.native_type; @@ -288,6 +291,7 @@ public: SUITE, TERNARY_OPERATOR, TYPE, + TYPE_TEST, UNARY_OPERATOR, VARIABLE, WHILE, @@ -426,7 +430,6 @@ public: OP_BIT_XOR, OP_LOGIC_AND, OP_LOGIC_OR, - OP_TYPE_TEST, OP_CONTENT_TEST, OP_COMP_EQUAL, OP_COMP_NOT_EQUAL, @@ -1150,6 +1153,16 @@ public: } }; + struct TypeTestNode : public ExpressionNode { + ExpressionNode *operand = nullptr; + TypeNode *test_type = nullptr; + DataType test_datatype; + + TypeTestNode() { + type = TYPE_TEST; + } + }; + struct UnaryOpNode : public ExpressionNode { enum OpType { OP_POSITIVE, @@ -1460,6 +1473,7 @@ private: ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign); TypeNode *parse_type(bool p_allow_void = false); @@ -1541,8 +1555,9 @@ public: void print_statement(Node *p_statement); void print_subscript(SubscriptNode *p_subscript); void print_suite(SuiteNode *p_suite); - void print_type(TypeNode *p_type); void print_ternary_op(TernaryOpNode *p_ternary_op); + void print_type(TypeNode *p_type); + void print_type_test(TypeTestNode *p_type_test); void print_unary_op(UnaryOpNode *p_unary_op); void print_variable(VariableNode *p_variable); void print_while(WhileNode *p_while); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 758b61bb31..8862450121 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -523,6 +523,82 @@ struct GDScriptUtilityFunctionsDefinitions { } } } + + static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(2); + + if (p_args[1]->get_type() == Variant::INT) { + int builtin_type = *p_args[1]; + if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) { + *r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return; + } + *r_ret = p_args[0]->get_type() == builtin_type; + return; + } + + bool was_type_freed = false; + Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed); + if (was_type_freed) { + *r_ret = RTR("Type argument is a previously freed instance."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return; + } + if (!type_object) { + *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return; + } + + bool was_value_freed = false; + Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed); + if (was_value_freed) { + *r_ret = RTR("Value argument is a previously freed instance."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return; + } + if (!value_object) { + *r_ret = false; + return; + } + + GDScriptNativeClass *native_type = Object::cast_to<GDScriptNativeClass>(type_object); + if (native_type) { + *r_ret = ClassDB::is_parent_class(value_object->get_class_name(), native_type->get_name()); + return; + } + + Script *script_type = Object::cast_to<Script>(type_object); + if (script_type) { + bool result = false; + if (value_object->get_script_instance()) { + Script *script_ptr = value_object->get_script_instance()->get_script().ptr(); + while (script_ptr) { + if (script_ptr == script_type) { + result = true; + break; + } + script_ptr = script_ptr->get_base_script().ptr(); + } + } + *r_ret = result; + return; + } + + *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script."); + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + } }; struct GDScriptUtilityFunctionInfo { @@ -638,6 +714,7 @@ void GDScriptUtilityFunctions::register_functions() { REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL); REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY); REGISTER_FUNC(len, true, Variant::INT, VARARG("var")); + REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type")); } void GDScriptUtilityFunctions::unregister_functions() { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 9f7d27d841..ba400b8e15 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -201,8 +201,10 @@ void (*type_init_function_table[])(Variant *) = { static const void *switch_table_ops[] = { \ &&OPCODE_OPERATOR, \ &&OPCODE_OPERATOR_VALIDATED, \ - &&OPCODE_EXTENDS_TEST, \ - &&OPCODE_IS_BUILTIN, \ + &&OPCODE_TYPE_TEST_BUILTIN, \ + &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_NATIVE, \ + &&OPCODE_TYPE_TEST_SCRIPT, \ &&OPCODE_SET_KEYED, \ &&OPCODE_SET_KEYED_VALIDATED, \ &&OPCODE_SET_INDEXED_VALIDATED, \ @@ -743,91 +745,95 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; - OPCODE(OPCODE_EXTENDS_TEST) { + OPCODE(OPCODE_TYPE_TEST_BUILTIN) { CHECK_SPACE(4); - GET_VARIANT_PTR(a, 0); - GET_VARIANT_PTR(b, 1); - GET_VARIANT_PTR(dst, 2); - -#ifdef DEBUG_ENABLED - if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { - err_text = "Right operand of 'is' is not a class."; - OPCODE_BREAK; - } -#endif + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - bool extends_ok = false; - if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) { -#ifdef DEBUG_ENABLED - bool was_freed; - Object *obj_A = a->get_validated_object_with_check(was_freed); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3]; + GD_ERR_BREAK(builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX); - if (was_freed) { - err_text = "Left operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } + *dst = value->get_type() == builtin_type; + ip += 4; + } + DISPATCH_OPCODE; - Object *obj_B = b->get_validated_object_with_check(was_freed); + OPCODE(OPCODE_TYPE_TEST_ARRAY) { + CHECK_SPACE(6); - if (was_freed) { - err_text = "Right operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } -#else + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - Object *obj_A = *a; - Object *obj_B = *b; -#endif // DEBUG_ENABLED + GET_VARIANT_PTR(script_type, 2); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; - GDScript *scr_B = Object::cast_to<GDScript>(obj_B); + bool result = false; + if (value->get_type() == Variant::ARRAY) { + Array *array = VariantInternal::get_array(value); + result = array->get_typed_builtin() == ((uint32_t)builtin_type) && array->get_typed_class_name() == native_type && array->get_typed_script() == *script_type && array->get_typed_class_name() == native_type; + } - if (scr_B) { - //if B is a script, the only valid condition is that A has an instance which inherits from the script - //in other situation, this should return false. + *dst = result; + ip += 6; + } + DISPATCH_OPCODE; - if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { - GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); - //bool found=false; - while (cmp) { - if (cmp == scr_B) { - //inherits from script, all ok - extends_ok = true; - break; - } + OPCODE(OPCODE_TYPE_TEST_NATIVE) { + CHECK_SPACE(4); - cmp = cmp->_base; - } - } + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - } else { - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); + int native_type_idx = _code_ptr[ip + 3]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; -#ifdef DEBUG_ENABLED - if (!nc) { - err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; - OPCODE_BREAK; - } -#endif - extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); - } + bool was_freed = false; + Object *object = value->get_validated_object_with_check(was_freed); + if (was_freed) { + err_text = "Left operand of 'is' is a previously freed instance."; + OPCODE_BREAK; } - *dst = extends_ok; + *dst = object && ClassDB::is_parent_class(object->get_class_name(), native_type); ip += 4; } DISPATCH_OPCODE; - OPCODE(OPCODE_IS_BUILTIN) { + OPCODE(OPCODE_TYPE_TEST_SCRIPT) { CHECK_SPACE(4); - GET_VARIANT_PTR(value, 0); - GET_VARIANT_PTR(dst, 1); - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); - GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + GET_VARIANT_PTR(type, 2); + Script *script_type = Object::cast_to<Script>(type->operator Object *()); + GD_ERR_BREAK(!script_type); + + bool was_freed = false; + Object *object = value->get_validated_object_with_check(was_freed); + if (was_freed) { + err_text = "Left operand of 'is' is a previously freed instance."; + OPCODE_BREAK; + } + + bool result = false; + if (object && object->get_script_instance()) { + Script *script_ptr = object->get_script_instance()->get_script().ptr(); + while (script_ptr) { + if (script_ptr == script_type) { + result = true; + break; + } + script_ptr = script_ptr->get_base_script().ptr(); + } + } - *dst = value->get_type() == var_type; + *dst = result; ip += 4; } DISPATCH_OPCODE; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out index ddbdc17a42..1b6e11f6f2 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot find member "V3" in base "enum_bad_value.gd::Enum". +Cannot find member "V3" in base "enum_bad_value.gd.Enum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out index 84958f1aa2..d401675bcf 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd.MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out index e294f3496a..4b6b42b024 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd.MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out index a91189e2dd..adde630a0b 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot pass a value of type "enum_function_parameter_wrong_type.gd::MyOtherEnum" as "enum_function_parameter_wrong_type.gd::MyEnum". +Cannot pass a value of type "enum_function_parameter_wrong_type.gd.MyOtherEnum" as "enum_function_parameter_wrong_type.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out index 6b4eba3740..9ee3fb7c06 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot return a value of type "enum_function_return_wrong_type.gd::MyOtherEnum" as "enum_function_return_wrong_type.gd::MyEnum". +Cannot return a value of type "enum_function_return_wrong_type.gd.MyOtherEnum" as "enum_function_return_wrong_type.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out index 616358bb61..8de7bde50d 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" as "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass.MyEnum" as "enum_local_var_assign_outer_with_wrong_enum_type.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out index af3dde663f..e2139e75f6 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_local_var_assign_with_wrong_enum_type.gd.MyOtherEnum" as "enum_local_var_assign_with_wrong_enum_type.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out index 781b0bc85f..46c0553c28 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_init_with_wrong_enum_type.gd::MyEnum". +Cannot assign a value of type "enum_local_var_init_with_wrong_enum_type.gd.MyOtherEnum" as "enum_local_var_init_with_wrong_enum_type.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out index 49f041a2dd..4b1db77a35 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot find member "THIS_DOES_NOT_EXIST" in base "TileSet::TileShape". +Cannot find member "THIS_DOES_NOT_EXIST" in base "TileSet.TileShape". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out index e8c7f86c4f..ddd26e7399 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_value_from_parent.gd::<anonymous enum>" as "enum_preload_unnamed_assign_to_named.gd::MyEnum". +Cannot assign a value of type "enum_value_from_parent.gd.<anonymous enum>" as "enum_preload_unnamed_assign_to_named.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out index fb18c94d0b..3a65978aa3 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_unnamed_assign_to_named.gd::<anonymous enum>" as "enum_unnamed_assign_to_named.gd::MyEnum". +Cannot assign a value of type "enum_unnamed_assign_to_named.gd.<anonymous enum>" as "enum_unnamed_assign_to_named.gd.MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out index 08a973503f..21143f2ade 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "enum_from_outer.gd::Named" as "preload_enum_error.gd::LocalNamed". +Cannot assign a value of type "enum_from_outer.gd.Named" as "preload_enum_error.gd.LocalNamed". diff --git a/modules/gdscript/tests/scripts/analyzer/features/global_enums.gd b/modules/gdscript/tests/scripts/analyzer/features/global_enums.gd new file mode 100644 index 0000000000..67d48817e8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/global_enums.gd @@ -0,0 +1,30 @@ +func test(): + var type: Variant.Type + type = Variant.Type.TYPE_INT + print(type) + type = TYPE_FLOAT + print(type) + + var direction: ClockDirection + direction = ClockDirection.CLOCKWISE + print(direction) + direction = COUNTERCLOCKWISE + print(direction) + + var duper := Duper.new() + duper.set_type(Variant.Type.TYPE_INT) + duper.set_type(TYPE_FLOAT) + duper.set_direction(ClockDirection.CLOCKWISE) + duper.set_direction(COUNTERCLOCKWISE) + +class Super: + func set_type(type: Variant.Type) -> void: + print(type) + func set_direction(dir: ClockDirection) -> void: + print(dir) + +class Duper extends Super: + func set_type(type: Variant.Type) -> void: + print(type) + func set_direction(dir: ClockDirection) -> void: + print(dir) diff --git a/modules/gdscript/tests/scripts/analyzer/features/global_enums.out b/modules/gdscript/tests/scripts/analyzer/features/global_enums.out new file mode 100644 index 0000000000..b3b5bf57c1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/global_enums.out @@ -0,0 +1,9 @@ +GDTEST_OK +2 +3 +0 +1 +2 +3 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd new file mode 100644 index 0000000000..12dc0b93df --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd @@ -0,0 +1,127 @@ +class A extends RefCounted: + pass + +class B extends A: + pass + +@warning_ignore("assert_always_true") +func test(): + var builtin: Variant = 3 + assert((builtin is Variant) == true) + assert((builtin is int) == true) + assert(is_instance_of(builtin, TYPE_INT) == true) + assert((builtin is float) == false) + assert(is_instance_of(builtin, TYPE_FLOAT) == false) + + const const_builtin: Variant = 3 + assert((const_builtin is Variant) == true) + assert((const_builtin is int) == true) + assert(is_instance_of(const_builtin, TYPE_INT) == true) + assert((const_builtin is float) == false) + assert(is_instance_of(const_builtin, TYPE_FLOAT) == false) + + var int_array: Variant = [] as Array[int] + assert((int_array is Variant) == true) + assert((int_array is Array) == true) + assert(is_instance_of(int_array, TYPE_ARRAY) == true) + assert((int_array is Array[int]) == true) + assert((int_array is Array[float]) == false) + assert((int_array is int) == false) + assert(is_instance_of(int_array, TYPE_INT) == false) + + var const_int_array: Variant = [] as Array[int] + assert((const_int_array is Variant) == true) + assert((const_int_array is Array) == true) + assert(is_instance_of(const_int_array, TYPE_ARRAY) == true) + assert((const_int_array is Array[int]) == true) + assert((const_int_array is Array[float]) == false) + assert((const_int_array is int) == false) + assert(is_instance_of(const_int_array, TYPE_INT) == false) + + var b_array: Variant = [] as Array[B] + assert((b_array is Variant) == true) + assert((b_array is Array) == true) + assert(is_instance_of(b_array, TYPE_ARRAY) == true) + assert((b_array is Array[B]) == true) + assert((b_array is Array[A]) == false) + assert((b_array is Array[int]) == false) + assert((b_array is int) == false) + assert(is_instance_of(b_array, TYPE_INT) == false) + + var const_b_array: Variant = [] as Array[B] + assert((const_b_array is Variant) == true) + assert((const_b_array is Array) == true) + assert(is_instance_of(const_b_array, TYPE_ARRAY) == true) + assert((const_b_array is Array[B]) == true) + assert((const_b_array is Array[A]) == false) + assert((const_b_array is Array[int]) == false) + assert((const_b_array is int) == false) + assert(is_instance_of(const_b_array, TYPE_INT) == false) + + var native: Variant = RefCounted.new() + assert((native is Variant) == true) + assert((native is Object) == true) + assert(is_instance_of(native, TYPE_OBJECT) == true) + assert(is_instance_of(native, Object) == true) + assert((native is RefCounted) == true) + assert(is_instance_of(native, RefCounted) == true) + assert((native is Node) == false) + assert(is_instance_of(native, Node) == false) + assert((native is int) == false) + assert(is_instance_of(native, TYPE_INT) == false) + + var a_script: Variant = A.new() + assert((a_script is Variant) == true) + assert((a_script is Object) == true) + assert(is_instance_of(a_script, TYPE_OBJECT) == true) + assert(is_instance_of(a_script, Object) == true) + assert((a_script is RefCounted) == true) + assert(is_instance_of(a_script, RefCounted) == true) + assert((a_script is A) == true) + assert(is_instance_of(a_script, A) == true) + assert((a_script is B) == false) + assert(is_instance_of(a_script, B) == false) + assert((a_script is Node) == false) + assert(is_instance_of(a_script, Node) == false) + assert((a_script is int) == false) + assert(is_instance_of(a_script, TYPE_INT) == false) + + var b_script: Variant = B.new() + assert((b_script is Variant) == true) + assert((b_script is Object) == true) + assert(is_instance_of(b_script, TYPE_OBJECT) == true) + assert(is_instance_of(b_script, Object) == true) + assert((b_script is RefCounted) == true) + assert(is_instance_of(b_script, RefCounted) == true) + assert((b_script is A) == true) + assert(is_instance_of(b_script, A) == true) + assert((b_script is B) == true) + assert(is_instance_of(b_script, B) == true) + assert((b_script is Node) == false) + assert(is_instance_of(b_script, Node) == false) + assert((b_script is int) == false) + assert(is_instance_of(b_script, TYPE_INT) == false) + + var var_null: Variant = null + assert((var_null is Variant) == true) + assert((var_null is int) == false) + assert(is_instance_of(var_null, TYPE_INT) == false) + assert((var_null is Object) == false) + assert(is_instance_of(var_null, TYPE_OBJECT) == false) + assert((var_null is RefCounted) == false) + assert(is_instance_of(var_null, RefCounted) == false) + assert((var_null is A) == false) + assert(is_instance_of(var_null, A) == false) + + const const_null: Variant = null + assert((const_null is Variant) == true) + assert((const_null is int) == false) + assert(is_instance_of(const_null, TYPE_INT) == false) + assert((const_null is Object) == false) + assert(is_instance_of(const_null, TYPE_OBJECT) == false) + assert((const_null is RefCounted) == false) + assert(is_instance_of(const_null, RefCounted) == false) + assert((const_null is A) == false) + assert(is_instance_of(const_null, A) == false) + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out index 6e086a0918..6d8aeaf0b6 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 5 >> INT_AS_ENUM_WITHOUT_MATCH ->> Cannot cast 2 as Enum "cast_enum_bad_enum.gd::MyEnum": no enum member has matching value. +>> Cannot cast 2 as Enum "cast_enum_bad_enum.gd.MyEnum": no enum member has matching value. 2 diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out index c19d57f98e..b0e4af29a0 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 4 >> INT_AS_ENUM_WITHOUT_MATCH ->> Cannot cast 2 as Enum "cast_enum_bad_int.gd::MyEnum": no enum member has matching value. +>> Cannot cast 2 as Enum "cast_enum_bad_int.gd.MyEnum": no enum member has matching value. 2 diff --git a/modules/gdscript/tests/scripts/runtime/features/const_class_reference.gd b/modules/gdscript/tests/scripts/runtime/features/const_class_reference.gd new file mode 100644 index 0000000000..c7553769da --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/const_class_reference.gd @@ -0,0 +1,16 @@ +# https://github.com/godotengine/godot/issues/61636 + +const External := preload("const_class_reference_external.notest.gd") + +class Class1: + class Class2: + pass + +const Class1Alias = Class1 +const Class1Class2Alias = Class1.Class2 + +const ExternalAlias = External +const ExternalClassAlias = External.Class + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/runtime/features/const_class_reference.out b/modules/gdscript/tests/scripts/runtime/features/const_class_reference.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/const_class_reference.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/const_class_reference_external.notest.gd b/modules/gdscript/tests/scripts/runtime/features/const_class_reference_external.notest.gd new file mode 100644 index 0000000000..050e8a0960 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/const_class_reference_external.notest.gd @@ -0,0 +1,2 @@ +class Class: + pass diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd new file mode 100644 index 0000000000..277242156d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd @@ -0,0 +1,27 @@ +# https://github.com/godotengine/godot/issues/72967 + +class CustomNode: + extends Node + + static func test_custom_node(n: CustomNode): + if not n: + print("null node") + +func test(): + test_typed_argument_is_null() + +func get_custom_node() -> CustomNode: + return null + +func test_typed_argument_is_null(): + var node: Node = Node.new() + print_node_name(node.get_parent()) + node.free() + test_custom_node() + +func test_custom_node(): + CustomNode.test_custom_node(get_custom_node()) + +func print_node_name(n: Node): + if not n: + print("null node") diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out new file mode 100644 index 0000000000..41560003d6 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out @@ -0,0 +1,3 @@ +GDTEST_OK +null node +null node diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 8e48066623..f3b3e61501 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -35,8 +35,6 @@ </member> <member name="height" type="int" setter="set_height" getter="get_height" default="-1"> </member> - <member name="joint" type="bool" setter="set_joint" getter="get_joint" default="false"> - </member> <member name="light" type="int" setter="set_light" getter="get_light" default="-1"> </member> <member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1"> diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp index f8f458fcc7..5e7a8f4e69 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.cpp +++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp @@ -93,7 +93,7 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t Ref<GLTFState> state; state.instantiate(); print_verbose(vformat("glTF path: %s", sink)); - Error err = gltf->append_from_file(sink, state, p_flags); + Error err = gltf->append_from_file(sink, state, p_flags, p_path.get_base_dir()); if (err != OK) { if (r_err) { *r_err = FAILED; diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp index 66d1eaad51..30895034a9 100644 --- a/modules/gltf/structures/gltf_node.cpp +++ b/modules/gltf/structures/gltf_node.cpp @@ -45,8 +45,6 @@ void GLTFNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_skin", "skin"), &GLTFNode::set_skin); ClassDB::bind_method(D_METHOD("get_skeleton"), &GLTFNode::get_skeleton); ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &GLTFNode::set_skeleton); - ClassDB::bind_method(D_METHOD("get_joint"), &GLTFNode::get_joint); - ClassDB::bind_method(D_METHOD("set_joint", "joint"), &GLTFNode::set_joint); ClassDB::bind_method(D_METHOD("get_position"), &GLTFNode::get_position); ClassDB::bind_method(D_METHOD("set_position", "position"), &GLTFNode::set_position); ClassDB::bind_method(D_METHOD("get_rotation"), &GLTFNode::get_rotation); @@ -67,7 +65,6 @@ void GLTFNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "camera"), "set_camera", "get_camera"); // GLTFCameraIndex ADD_PROPERTY(PropertyInfo(Variant::INT, "skin"), "set_skin", "get_skin"); // GLTFSkinIndex ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton"), "set_skeleton", "get_skeleton"); // GLTFSkeletonIndex - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "joint"), "set_joint", "get_joint"); // bool ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position"), "set_position", "get_position"); // Vector3 ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "rotation"), "set_rotation", "get_rotation"); // Quaternion ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3 @@ -131,14 +128,6 @@ void GLTFNode::set_skeleton(GLTFSkeletonIndex p_skeleton) { skeleton = p_skeleton; } -bool GLTFNode::get_joint() { - return joint; -} - -void GLTFNode::set_joint(bool p_joint) { - joint = p_joint; -} - Vector3 GLTFNode::get_position() { return position; } diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h index d801a4cc2c..95c80861de 100644 --- a/modules/gltf/structures/gltf_node.h +++ b/modules/gltf/structures/gltf_node.h @@ -80,9 +80,6 @@ public: GLTFSkeletonIndex get_skeleton(); void set_skeleton(GLTFSkeletonIndex p_skeleton); - bool get_joint(); - void set_joint(bool p_joint); - Vector3 get_position(); void set_position(Vector3 p_position); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index fe0f4aae81..43d2779e41 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1147,6 +1147,7 @@ void CSharpLanguage::_editor_init_callback() { // Add plugin to EditorNode and enable it EditorNode::add_editor_plugin(godotsharp_editor); ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B); + ED_SHORTCUT_OVERRIDE("mono/build_solution", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::B); godotsharp_editor->enable_plugin(); get_singleton()->godotsharp_editor = godotsharp_editor; diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml index af7c345f15..717906bd2b 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml @@ -69,6 +69,11 @@ </member> </members> <signals> + <signal name="synchronized"> + <description> + Emitted when a new synchronization state is received by this synchronizer after the variables have been updated. + </description> + </signal> <signal name="visibility_changed"> <param index="0" name="for_peer" type="int" /> <description> diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index 10714db6df..458b6a664a 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -268,6 +268,7 @@ void MultiplayerSynchronizer::_bind_methods() { BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(VISIBILITY_PROCESS_NONE); + ADD_SIGNAL(MethodInfo("synchronized")); ADD_SIGNAL(MethodInfo("visibility_changed", PropertyInfo(Variant::INT, "for_peer"))); } diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 68b6bc4a24..c1d45636f1 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -775,6 +775,7 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu err = MultiplayerSynchronizer::set_state(props, node, vars); ERR_FAIL_COND_V(err, err); ofs += size; + sync->emit_signal(SNAME("synchronized")); #ifdef DEBUG_ENABLED _profile_node_data("sync_in", sync->get_instance_id(), size); #endif diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 14778b5f03..e8eb5b419b 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2024,7 +2024,7 @@ void DisplayServerMacOS::warp_mouse(const Point2i &p_position) { // Local point in window coords. const NSRect contentRect = [wd.window_view frame]; const float scale = screen_get_max_scale(); - NSRect pointInWindowRect = NSMakeRect(p_position.x / scale, contentRect.size.height - (p_position.y / scale - 1), 0, 0); + NSRect pointInWindowRect = NSMakeRect(p_position.x / scale, contentRect.size.height - (p_position.y / scale), scale, scale); NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; // Point in scren coords. diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index e7e3084037..9fa33e9d5c 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -217,8 +217,7 @@ bool SoftBody3D::_set_property_pinned_points_attachment(int p_item, const String if ("spatial_attachment_path" == p_what) { PinnedPoint *w = pinned_points.ptrw(); - pin_point(w[p_item].point_index, true, p_value); - _make_cache_dirty(); + callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value); } else if ("offset" == p_what) { PinnedPoint *w = pinned_points.ptrw(); w[p_item].offset = p_value; @@ -670,6 +669,11 @@ void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatia } } +void SoftBody3D::_pin_point_deferred(int p_point_index, bool pin, const NodePath p_spatial_attachment_path) { + pin_point(p_point_index, pin, p_spatial_attachment_path); + _make_cache_dirty(); +} + bool SoftBody3D::is_point_pinned(int p_point_index) const { return -1 != _has_pinned_point(p_point_index); } @@ -741,7 +745,11 @@ void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_ pinned_point->spatial_attachment_path = p_spatial_attachment_path; if (!p_spatial_attachment_path.is_empty() && has_node(p_spatial_attachment_path)) { - pinned_point->spatial_attachment = Object::cast_to<Node3D>(get_node(p_spatial_attachment_path)); + Node3D *attachment_node = Object::cast_to<Node3D>(get_node(p_spatial_attachment_path)); + + ERR_FAIL_NULL_MSG(attachment_node, "Attachment node path is invalid."); + + pinned_point->spatial_attachment = attachment_node; pinned_point->offset = (pinned_point->spatial_attachment->get_global_transform().affine_inverse() * get_global_transform()).xform(PhysicsServer3D::get_singleton()->soft_body_get_point_global_position(physics_rid, pinned_point->point_index)); } } diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h index d81011006b..0b75ae2cda 100644 --- a/scene/3d/soft_body_3d.h +++ b/scene/3d/soft_body_3d.h @@ -179,6 +179,8 @@ public: void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath()); bool is_point_pinned(int p_point_index) const; + void _pin_point_deferred(int p_point_index, bool pin, const NodePath p_spatial_attachment_path); + void set_ray_pickable(bool p_ray_pickable); bool is_ray_pickable() const; diff --git a/servers/physics_3d/godot_body_pair_3d.cpp b/servers/physics_3d/godot_body_pair_3d.cpp index 619e6c00be..3a91e5e480 100644 --- a/servers/physics_3d/godot_body_pair_3d.cpp +++ b/servers/physics_3d/godot_body_pair_3d.cpp @@ -167,6 +167,9 @@ void GodotBodyPair3D::validate_contacts() { // cast forward along motion vector to see if A is going to enter/pass B's collider next frame, only proceed if it does. // adjust the velocity of A down so that it will just slightly intersect the collider instead of blowing right past it. bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, const Transform3D &p_xform_A, GodotBody3D *p_B, int p_shape_B, const Transform3D &p_xform_B) { + GodotShape3D *shape_A_ptr = p_A->get_shape(p_shape_A); + GodotShape3D *shape_B_ptr = p_B->get_shape(p_shape_B); + Vector3 motion = p_A->get_linear_velocity() * p_step; real_t mlen = motion.length(); if (mlen < CMP_EPSILON) { @@ -176,7 +179,7 @@ bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, Vector3 mnormal = motion / mlen; real_t min = 0.0, max = 0.0; - p_A->get_shape(p_shape_A)->project_range(mnormal, p_xform_A, min, max); + shape_A_ptr->project_range(mnormal, p_xform_A, min, max); // Did it move enough in this direction to even attempt raycast? // Let's say it should move more than 1/3 the size of the object in that axis. @@ -187,33 +190,64 @@ bool GodotBodyPair3D::_test_ccd(real_t p_step, GodotBody3D *p_A, int p_shape_A, // A is moving fast enough that tunneling might occur. See if it's really about to collide. - // Cast a segment from support in motion normal, in the same direction of motion by motion length. - // Support point will the farthest forward collision point along the movement vector. - // i.e. the point that should hit B first if any collision does occur. - - // convert mnormal into body A's local xform because get_support requires (and returns) local coordinates. - Vector3 s = p_A->get_shape(p_shape_A)->get_support(p_xform_A.basis.xform_inv(mnormal).normalized()); - Vector3 from = p_xform_A.xform(s); - Vector3 to = from + motion; - - Transform3D from_inv = p_xform_B.affine_inverse(); - - // Back up 10% of the per-frame motion behind the support point and use that as the beginning of our cast. - // At high speeds, this may mean we're actually casting from well behind the body instead of inside it, which is odd. But it still works out. - Vector3 local_from = from_inv.xform(from - motion * 0.1); - Vector3 local_to = from_inv.xform(to); + // Support points are the farthest forward points on A in the direction of the motion vector. + // i.e. the candidate points of which one should hit B first if any collision does occur. + static const int max_supports = 16; + Vector3 supports_A[max_supports]; + int support_count_A; + GodotShape3D::FeatureType support_type_A; + // Convert mnormal into body A's local xform because get_supports requires (and returns) local coordinates. + shape_A_ptr->get_supports(p_xform_A.basis.xform_inv(mnormal).normalized(), max_supports, supports_A, support_count_A, support_type_A); + + // Cast a segment from each support point of A in the motion direction. + int segment_support_idx = -1; + float segment_hit_length = FLT_MAX; + Vector3 segment_hit_local; + for (int i = 0; i < support_count_A; i++) { + supports_A[i] = p_xform_A.xform(supports_A[i]); + + Vector3 from = supports_A[i]; + Vector3 to = from + motion; + + Transform3D from_inv = p_xform_B.affine_inverse(); + + // Back up 10% of the per-frame motion behind the support point and use that as the beginning of our cast. + // At high speeds, this may mean we're actually casting from well behind the body instead of inside it, which is odd. + // But it still works out. + Vector3 local_from = from_inv.xform(from - motion * 0.1); + Vector3 local_to = from_inv.xform(to); + + Vector3 rpos, rnorm; + if (shape_B_ptr->intersect_segment(local_from, local_to, rpos, rnorm, true)) { + float hit_length = local_from.distance_to(rpos); + if (hit_length < segment_hit_length) { + segment_support_idx = i; + segment_hit_length = hit_length; + segment_hit_local = rpos; + } + } + } - Vector3 rpos, rnorm; - if (!p_B->get_shape(p_shape_B)->intersect_segment(local_from, local_to, rpos, rnorm, true)) { - // there was no hit. Since the segment is the length of per-frame motion, this means the bodies will not + if (segment_support_idx == -1) { + // There was no hit. Since the segment is the length of per-frame motion, this means the bodies will not // actually collide yet on next frame. We'll probably check again next frame once they're closer. return false; } - // Shorten the linear velocity so it will collide next frame. - Vector3 hitpos = p_xform_B.xform(rpos); - - real_t newlen = hitpos.distance_to(from) + (max - min) * 0.01; // adding 1% of body length to the distance between collision and support point should cause body A's support point to arrive just within B's collider next frame. + Vector3 hitpos = p_xform_B.xform(segment_hit_local); + + real_t newlen = hitpos.distance_to(supports_A[segment_support_idx]); + if (shape_B_ptr->is_concave()) { + // Subtracting 5% of body length from the distance between collision and support point + // should cause body A's support point to arrive just before a face of B next frame. + newlen = MAX(newlen - (max - min) * 0.05, 0.0); + // NOTE: This may stop body A completely, without a proper collision response. + // We consider this preferable to tunneling. + } else { + // Adding 1% of body length to the distance between collision and support point + // should cause body A's support point to arrive just within B's collider next frame. + newlen += (max - min) * 0.01; + } p_A->set_linear_velocity((mnormal * newlen) / p_step); |