summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/core_constants.cpp237
-rw-r--r--core/core_constants.h5
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--core/math/bvh.h8
-rw-r--r--doc/classes/Control.xml1
-rw-r--r--doc/classes/Node2D.xml1
-rw-r--r--doc/classes/Node3D.xml1
-rw-r--r--doc/classes/ProjectSettings.xml3
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp2
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp16
-rw-r--r--drivers/gles3/rasterizer_gles3.h2
-rw-r--r--drivers/gles3/shaders/sky.glsl10
-rw-r--r--editor/editor_file_dialog.cpp5
-rw-r--r--editor/editor_node.cpp11
-rw-r--r--editor/editor_settings.cpp15
-rw-r--r--editor/editor_settings.h1
-rw-r--r--editor/inspector_dock.cpp2
-rw-r--r--editor/plugins/script_editor_plugin.cpp1
-rw-r--r--editor/plugins/script_text_editor.cpp1
-rw-r--r--editor/plugins/tiles/atlas_merging_dialog.cpp4
-rw-r--r--editor/project_manager.cpp14
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml7
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp221
-rw-r--r--modules/gdscript/gdscript_analyzer.h3
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp50
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h3
-rw-r--r--modules/gdscript/gdscript_codegen.h3
-rw-r--r--modules/gdscript/gdscript_compiler.cpp60
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp49
-rw-r--r--modules/gdscript/gdscript_editor.cpp53
-rw-r--r--modules/gdscript/gdscript_function.h16
-rw-r--r--modules/gdscript/gdscript_parser.cpp33
-rw-r--r--modules/gdscript/gdscript_parser.h19
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp77
-rw-r--r--modules/gdscript/gdscript_vm.cpp134
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/global_enums.gd30
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/global_enums.out9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd127
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/type_test_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/const_class_reference.gd16
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/const_class_reference.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/const_class_reference_external.notest.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.gd27
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_argument_is_null.out3
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml2
-rw-r--r--modules/gltf/editor/editor_scene_importer_fbx.cpp2
-rw-r--r--modules/gltf/structures/gltf_node.cpp11
-rw-r--r--modules/gltf/structures/gltf_node.h3
-rw-r--r--modules/mono/csharp_script.cpp1
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml5
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp1
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp1
-rw-r--r--platform/macos/display_server_macos.mm2
-rw-r--r--scene/3d/soft_body_3d.cpp14
-rw-r--r--scene/3d/soft_body_3d.h2
-rw-r--r--servers/physics_3d/godot_body_pair_3d.cpp80
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);