summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/csg/csg_shape.cpp4
-rw-r--r--modules/enet/enet_multiplayer_peer.cpp8
-rw-r--r--modules/enet/enet_packet_peer.cpp2
-rw-r--r--modules/etcpak/SCsub1
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp9
-rw-r--r--modules/gdscript/README.md2
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml3
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp16
-rw-r--r--modules/gdscript/gdscript.cpp87
-rw-r--r--modules/gdscript/gdscript.h10
-rw-r--r--modules/gdscript/gdscript_cache.cpp55
-rw-r--r--modules/gdscript/gdscript_cache.h1
-rw-r--r--modules/gdscript/gdscript_editor.cpp191
-rw-r--r--modules/gdscript/gdscript_parser.cpp159
-rw-r--r--modules/gdscript/gdscript_parser.h7
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp69
-rw-r--r--modules/gdscript/gdscript_tokenizer.h58
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.cpp493
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.h93
-rw-r--r--modules/gdscript/gdscript_utility_callable.cpp5
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp2
-rw-r--r--modules/gdscript/register_types.cpp27
-rw-r--r--modules/gdscript/tests/README.md4
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp96
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.h13
-rw-r--r--modules/gdscript/tests/gdscript_test_runner_suite.h6
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd (renamed from modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd)0
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out (renamed from modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out)0
-rw-r--r--modules/gdscript/tests/scripts/parser/features/is_not_operator.gd11
-rw-r--r--modules/gdscript/tests/scripts/parser/features/is_not_operator.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/multiline_if.gd1
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp61
-rw-r--r--modules/gdscript/tests/test_gdscript.h1
-rw-r--r--modules/glslang/SCsub1
-rw-r--r--modules/glslang/register_types.cpp14
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsBody.xml2
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.cpp2
-rw-r--r--modules/gltf/gltf_document.cpp119
-rw-r--r--modules/gltf/structures/gltf_buffer_view.compat.inc61
-rw-r--r--modules/gltf/structures/gltf_buffer_view.cpp11
-rw-r--r--modules/gltf/structures/gltf_buffer_view.h19
-rw-r--r--modules/gltf/structures/gltf_node.cpp16
-rw-r--r--modules/gltf/structures/gltf_node.h6
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml4
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp58
-rw-r--r--modules/mbedtls/crypto_mbedtls.cpp26
-rw-r--r--modules/mbedtls/crypto_mbedtls.h26
-rw-r--r--modules/minimp3/resource_importer_mp3.cpp2
-rw-r--r--modules/mono/csharp_script.cpp123
-rw-r--r--modules/mono/csharp_script.h95
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs255
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs28
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs57
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs16
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp1
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h4
-rw-r--r--modules/multiplayer/doc_classes/SceneReplicationConfig.xml12
-rw-r--r--modules/multiplayer/multiplayer_debugger.cpp6
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp4
-rw-r--r--modules/multiplayer/multiplayer_spawner.h2
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp4
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.h2
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp4
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp2
-rw-r--r--modules/navigation/godot_navigation_server.cpp18
-rw-r--r--modules/navigation/godot_navigation_server.h4
-rw-r--r--modules/navigation/godot_navigation_server_2d.cpp4
-rw-r--r--modules/navigation/godot_navigation_server_2d.h1
-rw-r--r--modules/navigation/nav_map.cpp57
-rw-r--r--modules/navigation/nav_map.h15
-rw-r--r--modules/navigation/nav_mesh_generator_2d.cpp55
-rw-r--r--modules/navigation/nav_mesh_generator_2d.h1
-rw-r--r--modules/navigation/nav_mesh_generator_3d.cpp31
-rw-r--r--modules/navigation/nav_mesh_generator_3d.h1
-rw-r--r--modules/openxr/doc_classes/OpenXRAPIExtension.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml6
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper.h3
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp11
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.h4
-rw-r--r--modules/openxr/extensions/openxr_eye_gaze_interaction.cpp5
-rw-r--r--modules/openxr/extensions/openxr_eye_gaze_interaction.h2
-rw-r--r--modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp19
-rw-r--r--modules/openxr/extensions/openxr_htc_vive_tracker_extension.h2
-rw-r--r--modules/openxr/extensions/platform/openxr_vulkan_extension.cpp20
-rw-r--r--modules/openxr/extensions/platform/openxr_vulkan_extension.h16
-rw-r--r--modules/openxr/openxr_api.cpp2
-rw-r--r--modules/openxr/openxr_interface.cpp23
-rw-r--r--modules/openxr/openxr_platform_inc.h2
-rw-r--r--modules/svg/image_loader_svg.cpp2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp2
-rw-r--r--modules/upnp/upnp.cpp2
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp8
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.cpp14
-rw-r--r--modules/websocket/remote_debugger_peer_websocket.cpp2
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp4
-rw-r--r--modules/webxr/webxr_interface_js.cpp1
105 files changed, 2256 insertions, 655 deletions
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 1de76c60b5..7c93fbf081 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -800,7 +800,7 @@ CSGBrush *CSGMesh3D::_build_brush() {
if (arrays.size() == 0) {
_make_dirty();
- ERR_FAIL_COND_V(arrays.size() == 0, memnew(CSGBrush));
+ ERR_FAIL_COND_V(arrays.is_empty(), memnew(CSGBrush));
}
Vector<Vector3> avertices = arrays[Mesh::ARRAY_VERTEX];
@@ -1826,11 +1826,13 @@ CSGBrush *CSGPolygon3D::_build_brush() {
if (path) {
path->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
path->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
+ path->set_update_callback(Callable());
}
path = current_path;
if (path) {
path->connect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited));
path->connect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed));
+ path->set_update_callback(callable_mp(this, &CSGPolygon3D::_path_changed));
}
}
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp
index 63f12ea1c1..910c4ed242 100644
--- a/modules/enet/enet_multiplayer_peer.cpp
+++ b/modules/enet/enet_multiplayer_peer.cpp
@@ -40,20 +40,20 @@ void ENetMultiplayerPeer::set_target_peer(int p_peer) {
int ENetMultiplayerPeer::get_packet_peer() const {
ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
- ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
return incoming_packets.front()->get().from;
}
MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_packet_mode() const {
ERR_FAIL_COND_V_MSG(!_is_active(), TRANSFER_MODE_RELIABLE, "The multiplayer instance isn't currently active.");
- ERR_FAIL_COND_V(incoming_packets.size() == 0, TRANSFER_MODE_RELIABLE);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), TRANSFER_MODE_RELIABLE);
return incoming_packets.front()->get().transfer_mode;
}
int ENetMultiplayerPeer::get_packet_channel() const {
ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
- ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
int ch = incoming_packets.front()->get().channel;
if (ch >= SYSCH_MAX) { // First 2 channels are reserved.
return ch - SYSCH_MAX + 1;
@@ -321,7 +321,7 @@ int ENetMultiplayerPeer::get_available_packet_count() const {
}
Error ENetMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
- ERR_FAIL_COND_V_MSG(incoming_packets.size() == 0, ERR_UNAVAILABLE, "No incoming packets available.");
+ ERR_FAIL_COND_V_MSG(incoming_packets.is_empty(), ERR_UNAVAILABLE, "No incoming packets available.");
_pop_current_packet();
diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp
index f2bf5337ee..edb33fc96b 100644
--- a/modules/enet/enet_packet_peer.cpp
+++ b/modules/enet/enet_packet_peer.cpp
@@ -90,7 +90,7 @@ int ENetPacketPeer::get_available_packet_count() const {
Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED);
- ERR_FAIL_COND_V(!packet_queue.size(), ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V(packet_queue.is_empty(), ERR_UNAVAILABLE);
if (last_packet) {
enet_packet_destroy(last_packet);
last_packet = nullptr;
diff --git a/modules/etcpak/SCsub b/modules/etcpak/SCsub
index 3a4bff8e87..2d3b69be75 100644
--- a/modules/etcpak/SCsub
+++ b/modules/etcpak/SCsub
@@ -13,7 +13,6 @@ thirdparty_dir = "#thirdparty/etcpak/"
thirdparty_sources = [
"Dither.cpp",
"ProcessDxtc.cpp",
- "ProcessRgtc.cpp",
"ProcessRGB.cpp",
"Tables.cpp",
]
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp
index 9c04f5b40e..087d3a9314 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -35,7 +35,6 @@
#include <ProcessDxtc.hpp>
#include <ProcessRGB.hpp>
-#include <ProcessRgtc.hpp>
EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
switch (p_channels) {
@@ -246,11 +245,11 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
break;
case EtcpakType::ETCPAK_TYPE_ETC2_R:
- CompressEtc2R8(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w);
break;
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
- CompressEtc2RG8(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w);
break;
case EtcpakType::ETCPAK_TYPE_DXT1:
@@ -263,11 +262,11 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
break;
case EtcpakType::ETCPAK_TYPE_RGTC_R:
- CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w);
break;
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
- CompressRgtcRG(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w);
break;
default:
diff --git a/modules/gdscript/README.md b/modules/gdscript/README.md
index 30685e672c..865475d37d 100644
--- a/modules/gdscript/README.md
+++ b/modules/gdscript/README.md
@@ -69,7 +69,7 @@ The remaining steps of resolution, including member variable initialization code
In fully untyped code, very little static analysis is possible. For example, the analyzer cannot know whether `my_var.some_member` exists when it does not know the type of `my_var`. Therefore, it cannot emit a warning or error because `some_member` _could_ exist - or it could not. The analyzer must trust the programmer. If an error does occur, it will be at runtime.
However, GDScript is gradually typed, so all of these analyses must work when parts of the code are typed and others untyped. Static analysis in a gradually typed language is a best-effort situation: suppose there is a typed variable `var x : int`, and an untyped `var y = "some string"`. We can obviously tell this isn't going to work, but the analyzer will accept the assignment `x = y` without warnings or errors: it only knows that `y` is untyped and can therefore be anything, including the `int` that `x` expects. It must once again trust the programmer to have written code that works. In this instance, the code will error at runtime.
-In both these cases, the analyzer handles the uncertainty of untyped code by calling `mark_node_unsafe()` on the respective AST node. This means it didn't have enough information to know whether the code was fully safe or necessarily wrong. Lines with unsafe AST nodes are represented by grey line numbers in the GDScript editor. Green line numbers indicate a line of code without any unsafe nodes.
+In both these cases, the analyzer handles the uncertainty of untyped code by calling `mark_node_unsafe()` on the respective AST node. This means it didn't have enough information to know whether the code was fully safe or necessarily wrong. Lines with unsafe AST nodes are represented by gray line numbers in the GDScript editor. Green line numbers indicate a line of code without any unsafe nodes.
This analysis step is also where dependencies are introduced and that information stored for use later. If class `A` extends class `B` or contains a member with type `B` from some other script file, then the analyzer will attempt to load that second script. If `B` contains references to `A`, then a _cyclic_ dependency is introduced. This is OK in many cases, but impossible to resolve in others.
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 933bfba5ba..9982edfbd3 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -57,12 +57,11 @@
[/codeblock]
</description>
</method>
- <method name="convert" is_deprecated="true">
+ <method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
<param index="1" name="type" type="int" />
<description>
- [i]Deprecated.[/i] Use [method @GlobalScope.type_convert] instead.
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
var a = [4, 2.5, 1.2]
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 659140b9b1..601db5414b 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -268,7 +268,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
doc.tutorials.append(td);
}
doc.is_deprecated = p_class->doc_data.is_deprecated;
+ doc.deprecated_message = p_class->doc_data.deprecated_message;
doc.is_experimental = p_class->doc_data.is_experimental;
+ doc.experimental_message = p_class->doc_data.experimental_message;
for (const GDP::ClassNode::Member &member : p_class->members) {
switch (member.type) {
@@ -295,7 +297,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.is_value_valid = true;
const_doc.description = m_const->doc_data.description;
const_doc.is_deprecated = m_const->doc_data.is_deprecated;
+ const_doc.deprecated_message = m_const->doc_data.deprecated_message;
const_doc.is_experimental = m_const->doc_data.is_experimental;
+ const_doc.experimental_message = m_const->doc_data.experimental_message;
doc.constants.push_back(const_doc);
} break;
@@ -309,7 +313,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
method_doc.name = func_name;
method_doc.description = m_func->doc_data.description;
method_doc.is_deprecated = m_func->doc_data.is_deprecated;
+ method_doc.deprecated_message = m_func->doc_data.deprecated_message;
method_doc.is_experimental = m_func->doc_data.is_experimental;
+ method_doc.experimental_message = m_func->doc_data.experimental_message;
method_doc.qualifiers = m_func->is_static ? "static" : "";
if (m_func->return_type) {
@@ -349,7 +355,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
signal_doc.name = signal_name;
signal_doc.description = m_signal->doc_data.description;
signal_doc.is_deprecated = m_signal->doc_data.is_deprecated;
+ signal_doc.deprecated_message = m_signal->doc_data.deprecated_message;
signal_doc.is_experimental = m_signal->doc_data.is_experimental;
+ signal_doc.experimental_message = m_signal->doc_data.experimental_message;
for (const GDScriptParser::ParameterNode *p : m_signal->parameters) {
DocData::ArgumentDoc arg_doc;
@@ -371,7 +379,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
prop_doc.name = var_name;
prop_doc.description = m_var->doc_data.description;
prop_doc.is_deprecated = m_var->doc_data.is_deprecated;
+ prop_doc.deprecated_message = m_var->doc_data.deprecated_message;
prop_doc.is_experimental = m_var->doc_data.is_experimental;
+ prop_doc.experimental_message = m_var->doc_data.experimental_message;
_doctype_from_gdtype(m_var->get_datatype(), prop_doc.type, prop_doc.enumeration);
switch (m_var->property) {
@@ -417,7 +427,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
DocData::EnumDoc enum_doc;
enum_doc.description = m_enum->doc_data.description;
enum_doc.is_deprecated = m_enum->doc_data.is_deprecated;
+ enum_doc.deprecated_message = m_enum->doc_data.deprecated_message;
enum_doc.is_experimental = m_enum->doc_data.is_experimental;
+ enum_doc.experimental_message = m_enum->doc_data.experimental_message;
doc.enums[name] = enum_doc;
for (const GDP::EnumNode::Value &val : m_enum->values) {
@@ -428,7 +440,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.enumeration = name;
const_doc.description = val.doc_data.description;
const_doc.is_deprecated = val.doc_data.is_deprecated;
+ const_doc.deprecated_message = val.doc_data.deprecated_message;
const_doc.is_experimental = val.doc_data.is_experimental;
+ const_doc.experimental_message = val.doc_data.experimental_message;
doc.constants.push_back(const_doc);
}
@@ -448,7 +462,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
const_doc.enumeration = "@unnamed_enums";
const_doc.description = m_enum_val.doc_data.description;
const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
+ const_doc.deprecated_message = m_enum_val.doc_data.deprecated_message;
const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
+ const_doc.experimental_message = m_enum_val.doc_data.experimental_message;
doc.constants.push_back(const_doc);
} break;
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 0da7752940..e5ee1c0e39 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -35,6 +35,7 @@
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
#include "gdscript_rpc_callable.h"
+#include "gdscript_tokenizer_buffer.h"
#include "gdscript_warning.h"
#ifdef TOOLS_ENABLED
@@ -162,13 +163,14 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
_super_implicit_constructor(this, instance, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
+ String error_text = Variant::get_call_error_text(instance->owner, "@implicit_new", nullptr, 0, r_error);
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
}
- ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
+ ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance: " + error_text);
}
if (p_argcount < 0) {
@@ -179,13 +181,14 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
if (initializer != nullptr) {
initializer->call(instance, p_args, p_argcount, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
+ String error_text = Variant::get_call_error_text(instance->owner, "_init", p_args, p_argcount, r_error);
instance->script = Ref<GDScript>();
instance->owner->set_script_instance(nullptr);
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
instances.erase(p_owner);
}
- ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance.");
+ ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance: " + error_text);
}
}
//@TODO make thread safe
@@ -738,7 +741,12 @@ Error GDScript::reload(bool p_keep_state) {
valid = false;
GDScriptParser parser;
- Error err = parser.parse(source, path, false);
+ Error err;
+ if (!binary_tokens.is_empty()) {
+ err = parser.parse_binary(binary_tokens, path);
+ } else {
+ err = parser.parse(source, path, false);
+ }
if (err) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message);
@@ -1048,6 +1056,19 @@ Error GDScript::load_source_code(const String &p_path) {
return OK;
}
+void GDScript::set_binary_tokens_source(const Vector<uint8_t> &p_binary_tokens) {
+ binary_tokens = p_binary_tokens;
+}
+
+const Vector<uint8_t> &GDScript::get_binary_tokens_source() const {
+ return binary_tokens;
+}
+
+Vector<uint8_t> GDScript::get_as_binary_tokens() const {
+ GDScriptTokenizerBuffer tokenizer;
+ return tokenizer.parse_code_string(source, GDScriptTokenizerBuffer::COMPRESS_NONE);
+}
+
const HashMap<StringName, GDScriptFunction *> &GDScript::debug_get_member_functions() const {
return member_functions;
}
@@ -1149,7 +1170,7 @@ GDScript *GDScript::get_root_script() {
RBSet<GDScript *> GDScript::get_dependencies() {
RBSet<GDScript *> dependencies;
- _get_dependencies(dependencies, this);
+ _collect_dependencies(dependencies, this);
dependencies.erase(this);
return dependencies;
@@ -1255,52 +1276,55 @@ GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
return Object::cast_to<GDScript>(obj);
}
-void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
+void GDScript::_collect_function_dependencies(GDScriptFunction *p_func, RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
+ if (p_func == nullptr) {
+ return;
+ }
+ for (GDScriptFunction *lambda : p_func->lambdas) {
+ _collect_function_dependencies(lambda, p_dependencies, p_except);
+ }
+ for (const Variant &V : p_func->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_collect_dependencies(p_dependencies, p_except);
+ }
+ }
+}
+
+void GDScript::_collect_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
if (p_dependencies.has(this)) {
return;
}
- p_dependencies.insert(this);
+ if (this != p_except) {
+ p_dependencies.insert(this);
+ }
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
- if (E.value == nullptr) {
- continue;
- }
- for (const Variant &V : E.value->constants) {
- GDScript *scr = _get_gdscript_from_variant(V);
- if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
- }
- }
+ _collect_function_dependencies(E.value, p_dependencies, p_except);
}
if (implicit_initializer) {
- for (const Variant &V : implicit_initializer->constants) {
- GDScript *scr = _get_gdscript_from_variant(V);
- if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
- }
- }
+ _collect_function_dependencies(implicit_initializer, p_dependencies, p_except);
}
if (implicit_ready) {
- for (const Variant &V : implicit_ready->constants) {
- GDScript *scr = _get_gdscript_from_variant(V);
- if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
- }
- }
+ _collect_function_dependencies(implicit_ready, p_dependencies, p_except);
+ }
+
+ if (static_initializer) {
+ _collect_function_dependencies(static_initializer, p_dependencies, p_except);
}
for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
if (E.value != p_except) {
- E.value->_get_dependencies(p_dependencies, p_except);
+ E.value->_collect_dependencies(p_dependencies, p_except);
}
}
for (const KeyValue<StringName, Variant> &E : constants) {
GDScript *scr = _get_gdscript_from_variant(E.value);
if (scr != nullptr && scr != p_except) {
- scr->_get_dependencies(p_dependencies, p_except);
+ scr->_collect_dependencies(p_dependencies, p_except);
}
}
}
@@ -2573,7 +2597,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
}
}
-bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const {
+bool GDScriptLanguage::is_control_flow_keyword(const String &p_keyword) const {
// Please keep alphabetical order.
return p_keyword == "break" ||
p_keyword == "continue" ||
@@ -2803,6 +2827,7 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd");
+ p_extensions->push_back("gdc");
}
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
@@ -2811,7 +2836,7 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
- if (el == "gd") {
+ if (el == "gd" || el == "gdc") {
return "GDScript";
}
return "";
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 2da9b89eb9..860b2350e2 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -176,6 +176,7 @@ private:
bool clearing = false;
//exported members
String source;
+ Vector<uint8_t> binary_tokens;
String path;
bool path_valid = false; // False if using default path.
StringName local_name; // Inner class identifier or `class_name`.
@@ -212,7 +213,8 @@ private:
void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const;
GDScript *_get_gdscript_from_variant(const Variant &p_variant);
- void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
+ void _collect_function_dependencies(GDScriptFunction *p_func, RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
+ void _collect_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -296,6 +298,10 @@ public:
String get_script_path() const;
Error load_source_code(const String &p_path);
+ void set_binary_tokens_source(const Vector<uint8_t> &p_binary_tokens);
+ const Vector<uint8_t> &get_binary_tokens_source() const;
+ Vector<uint8_t> get_as_binary_tokens() const;
+
bool get_property_default_value(const StringName &p_property, Variant &r_value) const override;
virtual void get_script_method_list(List<MethodInfo> *p_list) const override;
@@ -534,7 +540,7 @@ public:
/* EDITOR FUNCTIONS */
virtual void get_reserved_words(List<String> *p_words) const override;
- virtual bool is_control_flow_keyword(String p_keywords) const override;
+ virtual bool is_control_flow_keyword(const String &p_keywords) const override;
virtual void get_comment_delimiters(List<String> *p_delimiters) const override;
virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const override;
virtual void get_string_delimiters(List<String> *p_delimiters) const override;
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 76f4e69ab9..ef783ab564 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -67,10 +67,15 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
while (p_new_status > status) {
switch (status) {
- case EMPTY:
+ case EMPTY: {
status = PARSED;
- result = parser->parse(GDScriptCache::get_source_code(path), path, false);
- break;
+ String remapped_path = ResourceLoader::path_remap(path);
+ if (remapped_path.get_extension().to_lower() == "gdc") {
+ result = parser->parse_binary(GDScriptCache::get_binary_tokens(remapped_path), path);
+ } else {
+ result = parser->parse(GDScriptCache::get_source_code(remapped_path), path, false);
+ }
+ } break;
case PARSED: {
status = INHERITANCE_SOLVED;
Error inheritance_result = get_analyzer()->resolve_inheritance();
@@ -205,7 +210,8 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP
return ref;
}
} else {
- if (!FileAccess::exists(p_path)) {
+ String remapped_path = ResourceLoader::path_remap(p_path);
+ if (!FileAccess::exists(remapped_path)) {
r_error = ERR_FILE_NOT_FOUND;
return ref;
}
@@ -239,6 +245,20 @@ String GDScriptCache::get_source_code(const String &p_path) {
return source;
}
+Vector<uint8_t> GDScriptCache::get_binary_tokens(const String &p_path) {
+ Vector<uint8_t> buffer;
+ Error err = OK;
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
+ ERR_FAIL_COND_V_MSG(err != OK, buffer, "Failed to open binary GDScript file '" + p_path + "'.");
+
+ uint64_t len = f->get_length();
+ buffer.resize(len);
+ uint64_t read = f->get_buffer(buffer.ptrw(), buffer.size());
+ ERR_FAIL_COND_V_MSG(read != len, Vector<uint8_t>(), "Failed to read binary GDScript file '" + p_path + "'.");
+
+ return buffer;
+}
+
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
@@ -251,10 +271,20 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
return singleton->shallow_gdscript_cache[p_path];
}
+ String remapped_path = ResourceLoader::path_remap(p_path);
+
Ref<GDScript> script;
script.instantiate();
script->set_path(p_path, true);
- r_error = script->load_source_code(p_path);
+ if (remapped_path.get_extension().to_lower() == "gdc") {
+ Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
+ if (buffer.is_empty()) {
+ r_error = ERR_FILE_CANT_READ;
+ }
+ script->set_binary_tokens_source(buffer);
+ } else {
+ r_error = script->load_source_code(remapped_path);
+ }
if (r_error) {
return Ref<GDScript>(); // Returns null and does not cache when the script fails to load.
@@ -294,9 +324,18 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
}
if (p_update_from_disk) {
- r_error = script->load_source_code(p_path);
- if (r_error) {
- return script;
+ if (p_path.get_extension().to_lower() == "gdc") {
+ Vector<uint8_t> buffer = get_binary_tokens(p_path);
+ if (buffer.is_empty()) {
+ r_error = ERR_FILE_CANT_READ;
+ return script;
+ }
+ script->set_binary_tokens_source(buffer);
+ } else {
+ r_error = script->load_source_code(p_path);
+ if (r_error) {
+ return script;
+ }
}
}
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index 0a0f403e44..0754e9feb6 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -99,6 +99,7 @@ public:
static void remove_script(const String &p_path);
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
static String get_source_code(const String &p_path);
+ static Vector<uint8_t> get_binary_tokens(const String &p_path);
static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String());
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
static Ref<GDScript> get_cached_script(const String &p_path);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 44e104da05..babd2c1772 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -210,7 +210,7 @@ bool GDScriptLanguage::supports_documentation() const {
}
int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const {
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(p_code);
int indent = 0;
GDScriptTokenizer::Token current = tokenizer.scan();
@@ -636,19 +636,6 @@ static int _get_method_location(const StringName &p_class, const StringName &p_m
return depth | ScriptLanguage::LOCATION_PARENT_MASK;
}
-static int _get_method_location(Ref<Script> p_script, const StringName &p_method) {
- int depth = 0;
- Ref<Script> scr = p_script;
- while (scr.is_valid()) {
- if (scr->get_member_line(p_method) != -1) {
- return depth | ScriptLanguage::LOCATION_PARENT_MASK;
- }
- depth++;
- scr = scr->get_base_script();
- }
- return depth + _get_method_location(p_script->get_instance_base_type(), p_method);
-}
-
static int _get_enum_constant_location(const StringName &p_class, const StringName &p_enum_constant) {
if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) {
return ScriptLanguage::LOCATION_OTHER;
@@ -791,18 +778,24 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer);
if (call->is_constant && call->reduced) {
def_val = call->function_name.operator String() + call->reduced_value.operator String();
+ } else {
+ def_val = call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)");
}
} break;
case GDScriptParser::Node::ARRAY: {
const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer);
if (arr->is_constant && arr->reduced) {
def_val = arr->reduced_value.operator String();
+ } else {
+ def_val = arr->elements.is_empty() ? "[]" : "[...]";
}
} break;
case GDScriptParser::Node::DICTIONARY: {
const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer);
if (dict->is_constant && dict->reduced) {
def_val = dict->reduced_value.operator String();
+ } else {
+ def_val = dict->elements.is_empty() ? "{}" : "{...}";
}
} break;
case GDScriptParser::Node::SUBSCRIPT: {
@@ -1020,9 +1013,9 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite,
}
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth);
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, bool p_types_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth);
-static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
+static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_types_only, bool p_static, bool p_parent_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
if (!p_parent_only) {
@@ -1036,13 +1029,13 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
ScriptLanguage::CodeCompletionOption option;
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE:
- if (p_only_functions || outer || (p_static && !member.variable->is_static)) {
+ if (p_types_only || p_only_functions || outer || (p_static && !member.variable->is_static)) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location);
break;
case GDScriptParser::ClassNode::Member::CONSTANT:
- if (p_only_functions) {
+ if (p_types_only || p_only_functions) {
continue;
}
if (r_result.has(member.constant->identifier->name)) {
@@ -1060,7 +1053,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
option = ScriptLanguage::CodeCompletionOption(member.m_class->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, location);
break;
case GDScriptParser::ClassNode::Member::ENUM_VALUE:
- if (p_only_functions) {
+ if (p_types_only || p_only_functions) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.enum_value.identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location);
@@ -1072,7 +1065,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
option = ScriptLanguage::CodeCompletionOption(member.m_enum->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location);
break;
case GDScriptParser::ClassNode::Member::FUNCTION:
- if (outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) {
+ if (p_types_only || outer || (p_static && !member.function->is_static) || member.function->identifier->name.operator String().begins_with("@")) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.function->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
@@ -1083,7 +1076,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
}
break;
case GDScriptParser::ClassNode::Member::SIGNAL:
- if (p_only_functions || outer || p_static) {
+ if (p_types_only || p_only_functions || outer || p_static) {
continue;
}
option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location);
@@ -1095,6 +1088,10 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
}
r_result.insert(option.display, option);
}
+ if (p_types_only) {
+ break; // Otherwise, it will fill the results with types from the outer class (which is undesired for that case).
+ }
+
outer = true;
clss = clss->outer;
classes_processed++;
@@ -1106,15 +1103,15 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
base_type.type = p_class->base_type;
base_type.type.is_meta_type = p_static;
- _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1);
+ _find_identifiers_in_base(base_type, p_only_functions, p_types_only, r_result, p_recursion_depth + 1);
}
-static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
+static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, bool p_types_only, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth) {
ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT);
GDScriptParser::DataType base_type = p_base.type;
- if (base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) {
+ if (!p_types_only && base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) {
ScriptLanguage::CodeCompletionOption option("new", ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_LOCAL);
option.insert_text += "(";
r_result.insert(option.display, option);
@@ -1123,14 +1120,16 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
- _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth);
+ _find_identifiers_in_class(base_type.class_type, p_only_functions, p_types_only, base_type.is_meta_type, false, r_result, p_recursion_depth);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
case GDScriptParser::DataType::SCRIPT: {
Ref<Script> scr = base_type.script_type;
if (scr.is_valid()) {
- if (!p_only_functions) {
+ if (p_types_only) {
+ // TODO: Need to implement Script::get_script_enum_list and retrieve the enum list from a script.
+ } else if (!p_only_functions) {
if (!base_type.is_meta_type) {
List<PropertyInfo> members;
scr->get_script_property_list(&members);
@@ -1163,20 +1162,22 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
}
- List<MethodInfo> methods;
- scr->get_script_method_list(&methods);
- for (const MethodInfo &E : methods) {
- if (E.name.begins_with("@")) {
- continue;
- }
- int location = p_recursion_depth + _get_method_location(scr, E.name);
- ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
- if (E.arguments.size()) {
- option.insert_text += "(";
- } else {
- option.insert_text += "()";
+ if (!p_types_only) {
+ List<MethodInfo> methods;
+ scr->get_script_method_list(&methods);
+ for (const MethodInfo &E : methods) {
+ if (E.name.begins_with("@")) {
+ continue;
+ }
+ int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name);
+ ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location);
+ if (E.arguments.size()) {
+ option.insert_text += "(";
+ } else {
+ option.insert_text += "()";
+ }
+ r_result.insert(option.display, option);
}
- r_result.insert(option.display, option);
}
Ref<Script> base_script = scr->get_base_script();
@@ -1197,6 +1198,16 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
return;
}
+ if (p_types_only) {
+ List<StringName> enums;
+ ClassDB::get_enum_list(type, &enums);
+ for (const StringName &E : enums) {
+ ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_ENUM);
+ r_result.insert(option.display, option);
+ }
+ return;
+ }
+
if (!p_only_functions) {
List<String> constants;
ClassDB::get_integer_constant_list(type, &constants);
@@ -1255,6 +1266,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
} break;
case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::BUILTIN: {
+ if (p_types_only) {
+ return;
+ }
+
Callable::CallError err;
Variant tmp;
Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err);
@@ -1322,7 +1337,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}
if (p_context.current_class) {
- _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth);
+ _find_identifiers_in_class(p_context.current_class, p_only_functions, false, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth);
}
List<StringName> functions;
@@ -3157,7 +3172,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
break;
}
- _find_identifiers_in_base(base, is_function, options, 0);
+ _find_identifiers_in_base(base, is_function, false, options, 0);
}
} break;
case GDScriptParser::COMPLETION_SUBSCRIPT: {
@@ -3167,7 +3182,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
break;
}
- _find_identifiers_in_base(base, false, options, 0);
+ _find_identifiers_in_base(base, false, false, options, 0);
} break;
case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: {
if (!completion_context.current_class) {
@@ -3175,25 +3190,41 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(completion_context.node);
bool found = true;
+
GDScriptCompletionIdentifier base;
base.type.kind = GDScriptParser::DataType::CLASS;
base.type.type_source = GDScriptParser::DataType::INFERRED;
base.type.is_constant = true;
- base.type.class_type = completion_context.current_class;
- base.value = completion_context.base;
- for (int i = 0; i < completion_context.current_argument; i++) {
- GDScriptCompletionIdentifier ci;
- if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) {
- found = false;
- break;
+ if (completion_context.current_argument == 1) {
+ StringName type_name = type->type_chain[0]->name;
+
+ if (ClassDB::class_exists(type_name)) {
+ base.type.kind = GDScriptParser::DataType::NATIVE;
+ base.type.native_type = type_name;
+ } else if (ScriptServer::is_global_class(type_name)) {
+ base.type.kind = GDScriptParser::DataType::SCRIPT;
+ String scr_path = ScriptServer::get_global_class_path(type_name);
+ base.type.script_type = ResourceLoader::load(scr_path);
+ }
+ }
+
+ if (base.type.kind == GDScriptParser::DataType::CLASS) {
+ base.type.class_type = completion_context.current_class;
+ base.value = completion_context.base;
+
+ for (int i = 0; i < completion_context.current_argument; i++) {
+ GDScriptCompletionIdentifier ci;
+ if (!_guess_identifier_type_from_base(completion_context, base, type->type_chain[i]->name, ci)) {
+ found = false;
+ break;
+ }
+ base = ci;
}
- base = ci;
}
- // TODO: Improve this to only list types.
if (found) {
- _find_identifiers_in_base(base, false, options, 0);
+ _find_identifiers_in_base(base, false, true, options, 0);
}
r_forced = true;
} break;
@@ -3303,9 +3334,17 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
opt = opt.substr(1);
}
- // The path needs quotes if it's not a valid identifier (with an exception
- // for "/" as path separator, which also doesn't require quotes).
- if (!opt.replace("/", "_").is_valid_identifier()) {
+ // The path needs quotes if at least one of its components (excluding `/` separations)
+ // is not a valid identifier.
+ bool path_needs_quote = false;
+ for (const String &part : opt.split("/")) {
+ if (!part.is_valid_identifier()) {
+ path_needs_quote = true;
+ break;
+ }
+ }
+
+ if (path_needs_quote) {
// Ignore quote_style and just use double quotes for paths with apostrophes.
// Double quotes don't need to be checked because they're not valid in node and property names.
opt = opt.quote(opt.contains("'") ? "\"" : quote_style); // Handle user preference.
@@ -3328,7 +3367,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
if (!completion_context.current_class) {
break;
}
- _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0);
+ _find_identifiers_in_class(completion_context.current_class, true, false, false, true, options, 0);
} break;
}
@@ -3633,14 +3672,50 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
analyzer.analyze();
if (context.current_class && context.current_class->extends.size() > 0) {
+ StringName class_name = context.current_class->extends[0]->name;
+
bool success = false;
- ClassDB::get_integer_constant(context.current_class->extends[0]->name, p_symbol, &success);
+ ClassDB::get_integer_constant(class_name, p_symbol, &success);
if (success) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
- r_result.class_name = context.current_class->extends[0]->name;
+ r_result.class_name = class_name;
r_result.class_member = p_symbol;
return OK;
}
+ do {
+ List<StringName> enums;
+ ClassDB::get_enum_list(class_name, &enums, true);
+ for (const StringName &enum_name : enums) {
+ if (enum_name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
+ r_result.class_name = class_name;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+ class_name = ClassDB::get_parent_class_nocheck(class_name);
+ } while (class_name != StringName());
+ }
+
+ const GDScriptParser::TypeNode *type_node = dynamic_cast<const GDScriptParser::TypeNode *>(context.node);
+ if (type_node != nullptr && !type_node->type_chain.is_empty()) {
+ StringName class_name = type_node->type_chain[0]->name;
+ if (ScriptServer::is_global_class(class_name)) {
+ class_name = ScriptServer::get_global_class_native_base(class_name);
+ }
+ do {
+ List<StringName> enums;
+ ClassDB::get_enum_list(class_name, &enums, true);
+ for (const StringName &enum_name : enums) {
+ if (enum_name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM;
+ r_result.class_name = class_name;
+ r_result.class_member = p_symbol;
+ return OK;
+ }
+ }
+ class_name = ClassDB::get_parent_class_nocheck(class_name);
+ } while (class_name != StringName());
}
bool is_function = false;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 2839d7b123..4625855329 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -31,6 +31,7 @@
#include "gdscript_parser.h"
#include "gdscript.h"
+#include "gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
@@ -226,7 +227,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
- if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
return;
}
CompletionContext context;
@@ -234,7 +235,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
- context.current_line = tokenizer.get_cursor_line();
+ context.current_line = tokenizer->get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
completion_context = context;
@@ -244,7 +245,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
- if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
return;
}
CompletionContext context;
@@ -252,7 +253,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
- context.current_line = tokenizer.get_cursor_line();
+ context.current_line = tokenizer->get_cursor_line();
context.builtin_type = p_builtin_type;
completion_context = context;
}
@@ -265,7 +266,7 @@ void GDScriptParser::push_completion_call(Node *p_call) {
call.call = p_call;
call.argument = 0;
completion_call_stack.push_back(call);
- if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) {
+ if (previous.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_BEGINNING) {
completion_call = call;
}
}
@@ -328,17 +329,21 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
source = source.replace_first(String::chr(0xFFFF), String());
}
- tokenizer.set_source_code(source);
- tokenizer.set_cursor_position(cursor_line, cursor_column);
- script_path = p_script_path;
- current = tokenizer.scan();
+ GDScriptTokenizerText *text_tokenizer = memnew(GDScriptTokenizerText);
+ text_tokenizer->set_source_code(source);
+
+ tokenizer = text_tokenizer;
+
+ tokenizer->set_cursor_position(cursor_line, cursor_column);
+ script_path = p_script_path.simplify_path();
+ current = tokenizer->scan();
// Avoid error or newline as the first token.
// The latter can mess with the parser when opening files filled exclusively with comments and newlines.
while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
if (current.type == GDScriptTokenizer::Token::ERROR) {
push_error(current.literal);
}
- current = tokenizer.scan();
+ current = tokenizer->scan();
}
#ifdef DEBUG_ENABLED
@@ -359,6 +364,9 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
parse_program();
pop_multiline();
+ memdelete(text_tokenizer);
+ tokenizer = nullptr;
+
#ifdef DEBUG_ENABLED
if (multiline_stack.size() > 0) {
ERR_PRINT("Parser bug: Imbalanced multiline stack.");
@@ -372,6 +380,41 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
}
}
+Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path) {
+ GDScriptTokenizerBuffer *buffer_tokenizer = memnew(GDScriptTokenizerBuffer);
+ Error err = buffer_tokenizer->set_code_buffer(p_binary);
+
+ if (err) {
+ memdelete(buffer_tokenizer);
+ return err;
+ }
+
+ tokenizer = buffer_tokenizer;
+ script_path = p_script_path;
+ current = tokenizer->scan();
+ // Avoid error or newline as the first token.
+ // The latter can mess with the parser when opening files filled exclusively with comments and newlines.
+ while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
+ if (current.type == GDScriptTokenizer::Token::ERROR) {
+ push_error(current.literal);
+ }
+ current = tokenizer->scan();
+ }
+
+ push_multiline(false); // Keep one for the whole parsing.
+ parse_program();
+ pop_multiline();
+
+ memdelete(buffer_tokenizer);
+ tokenizer = nullptr;
+
+ if (errors.is_empty()) {
+ return OK;
+ } else {
+ return ERR_PARSE_ERROR;
+ }
+}
+
GDScriptTokenizer::Token GDScriptParser::advance() {
lambda_ended = false; // Empty marker since we're past the end in any case.
@@ -379,16 +422,16 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
}
if (for_completion && !completion_call_stack.is_empty()) {
- if (completion_call.call == nullptr && tokenizer.is_past_cursor()) {
+ if (completion_call.call == nullptr && tokenizer->is_past_cursor()) {
completion_call = completion_call_stack.back()->get();
passed_cursor = true;
}
}
previous = current;
- current = tokenizer.scan();
+ current = tokenizer->scan();
while (current.type == GDScriptTokenizer::Token::ERROR) {
push_error(current.literal);
- current = tokenizer.scan();
+ current = tokenizer->scan();
}
if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.
for (Node *n : nodes_in_progress) {
@@ -457,19 +500,19 @@ void GDScriptParser::synchronize() {
void GDScriptParser::push_multiline(bool p_state) {
multiline_stack.push_back(p_state);
- tokenizer.set_multiline_mode(p_state);
+ tokenizer->set_multiline_mode(p_state);
if (p_state) {
// Consume potential whitespace tokens already waiting in line.
while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) {
- current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token.
+ current = tokenizer->scan(); // Don't call advance() here, as we don't want to change the previous token.
}
}
}
void GDScriptParser::pop_multiline() {
- ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value.");
+ ERR_FAIL_COND_MSG(multiline_stack.is_empty(), "Parser bug: trying to pop from multiline stack without available value.");
multiline_stack.pop_back();
- tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
+ tokenizer->set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
}
bool GDScriptParser::is_statement_end_token() const {
@@ -588,7 +631,7 @@ void GDScriptParser::parse_program() {
complete_extents(head);
#ifdef TOOLS_ENABLED
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = MIN(max_script_doc_line, head->end_line);
while (line > 0) {
if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {
@@ -597,6 +640,7 @@ void GDScriptParser::parse_program() {
}
line--;
}
+
#endif // TOOLS_ENABLED
if (!check(GDScriptTokenizer::Token::TK_EOF)) {
@@ -793,7 +837,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
if (has_comment(member->start_line, true)) {
// Inline doc comment.
member->doc_data = parse_class_doc_comment(member->start_line, true);
- } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ } else if (has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
// Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.
// This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.
member->doc_data = parse_class_doc_comment(doc_comment_line);
@@ -802,7 +846,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
if (has_comment(member->start_line, true)) {
// Inline doc comment.
member->doc_data = parse_doc_comment(member->start_line, true);
- } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
// Normal doc comment.
member->doc_data = parse_doc_comment(doc_comment_line);
}
@@ -1357,7 +1401,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) {
doc_data = parse_doc_comment(enum_value_line, true);
}
- } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
// Normal doc comment.
doc_data = parse_doc_comment(doc_comment_line);
}
@@ -2346,6 +2390,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
+ if (identifier->name.operator String().is_empty()) {
+ print_line("Empty identifier found.");
+ }
identifier->suite = current_suite;
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
@@ -3050,7 +3097,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
// Allow for trailing comma.
break;
}
- bool use_identifier_completion = current.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE;
+ bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE;
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
push_error(R"(Expected expression as the function argument.)");
@@ -3220,7 +3267,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
// Reset the multiline stack since we don't want the multiline mode one in the lambda body.
push_multiline(false);
if (multiline_context) {
- tokenizer.push_expression_indented_block();
+ tokenizer->push_expression_indented_block();
}
push_multiline(true); // For the parameters.
@@ -3267,9 +3314,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
if (multiline_context) {
// If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens.
while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) {
- current = tokenizer.scan(); // Not advance() since we don't want to change the previous token.
+ current = tokenizer->scan(); // Not advance() since we don't want to change the previous token.
}
- tokenizer.pop_expression_indented_block();
+ tokenizer->pop_expression_indented_block();
}
current_function = previous_function;
@@ -3285,6 +3332,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
}
GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ // x is not int
+ // ^ ^^^ ExpressionNode, TypeNode
+ // ^^^^^^^^^^^^ TypeTestNode
+ // ^^^^^^^^^^^^ UnaryOpNode
+ UnaryOpNode *not_node = nullptr;
+ if (match(GDScriptTokenizer::Token::NOT)) {
+ not_node = alloc_node<UnaryOpNode>();
+ not_node->operation = UnaryOpNode::OP_LOGIC_NOT;
+ not_node->variant_op = Variant::OP_NOT;
+ reset_extents(not_node, p_previous_operand);
+ update_extents(not_node);
+ }
+
TypeTestNode *type_test = alloc_node<TypeTestNode>();
reset_extents(type_test, p_previous_operand);
update_extents(type_test);
@@ -3293,8 +3353,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *
type_test->test_type = parse_type();
complete_extents(type_test);
+ if (not_node != nullptr) {
+ not_node->operand = type_test;
+ complete_extents(not_node);
+ }
+
if (type_test->test_type == nullptr) {
- push_error(R"(Expected type specifier after "is".)");
+ if (not_node == nullptr) {
+ push_error(R"(Expected type specifier after "is".)");
+ } else {
+ push_error(R"(Expected type specifier after "is not".)");
+ }
+ }
+
+ if (not_node != nullptr) {
+ return not_node;
}
return type_test;
@@ -3492,20 +3565,20 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons
}
bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
- bool has_comment = tokenizer.get_comments().has(p_line);
+ bool has_comment = tokenizer->get_comments().has(p_line);
// If there are no comments or if we don't care whether the comment
// is a docstring, we have our result.
if (!p_must_be_doc || !has_comment) {
return has_comment;
}
- return tokenizer.get_comments()[p_line].comment.begins_with("##");
+ return tokenizer->get_comments()[p_line].comment.begins_with("##");
}
GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {
ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData());
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = p_line;
if (!p_single_line) {
@@ -3536,11 +3609,17 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
if (state == DOC_LINE_NORMAL) {
String stripped_line = doc_line.strip_edges();
- if (stripped_line.begins_with("@deprecated")) {
+ if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) {
result.is_deprecated = true;
+ if (stripped_line.begins_with("@deprecated:")) {
+ result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges();
+ }
continue;
- } else if (stripped_line.begins_with("@experimental")) {
+ } else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) {
result.is_experimental = true;
+ if (stripped_line.begins_with("@experimental:")) {
+ result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges();
+ }
continue;
}
}
@@ -3554,7 +3633,7 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) {
ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData());
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = p_line;
if (!p_single_line) {
@@ -3639,11 +3718,17 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
result.tutorials.append(Pair<String, String>(title, link));
continue;
- } else if (stripped_line.begins_with("@deprecated")) {
+ } else if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) {
result.is_deprecated = true;
+ if (stripped_line.begins_with("@deprecated:")) {
+ result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges();
+ }
continue;
- } else if (stripped_line.begins_with("@experimental")) {
+ } else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) {
result.is_experimental = true;
+ if (stripped_line.begins_with("@experimental:")) {
+ result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges();
+ }
continue;
}
}
@@ -5001,6 +5086,9 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const
for (const AnnotationNode *E : p_function->annotations) {
print_annotation(E);
}
+ if (p_function->is_static) {
+ push_text("Static ");
+ }
push_text(p_context);
push_text(" ");
if (p_function->identifier) {
@@ -5345,6 +5433,9 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
print_annotation(E);
}
+ if (p_variable->is_static) {
+ push_text("Static ");
+ }
push_text("Variable ");
print_identifier(p_variable->identifier);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 11c5e51b9a..6664e6df04 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -274,13 +274,17 @@ public:
String description;
Vector<Pair<String, String>> tutorials;
bool is_deprecated = false;
+ String deprecated_message;
bool is_experimental = false;
+ String experimental_message;
};
struct MemberDocData {
String description;
bool is_deprecated = false;
+ String deprecated_message;
bool is_experimental = false;
+ String experimental_message;
};
#endif // TOOLS_ENABLED
@@ -1336,7 +1340,7 @@ private:
HashSet<int> unsafe_lines;
#endif
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizer *tokenizer = nullptr;
GDScriptTokenizer::Token previous;
GDScriptTokenizer::Token current;
@@ -1540,6 +1544,7 @@ private:
public:
Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion);
+ Error parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path);
ClassNode *get_tree() const { return head; }
bool is_tool() const { return _is_tool; }
ClassNode *find_class(const String &p_qualified_name) const;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 29cf7bc6ca..2940af585d 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -256,7 +256,7 @@ String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
return token_names[p_token_type];
}
-void GDScriptTokenizer::set_source_code(const String &p_source_code) {
+void GDScriptTokenizerText::set_source_code(const String &p_source_code) {
source = p_source_code;
if (source.is_empty()) {
_source = U"";
@@ -270,34 +270,34 @@ void GDScriptTokenizer::set_source_code(const String &p_source_code) {
position = 0;
}
-void GDScriptTokenizer::set_cursor_position(int p_line, int p_column) {
+void GDScriptTokenizerText::set_cursor_position(int p_line, int p_column) {
cursor_line = p_line;
cursor_column = p_column;
}
-void GDScriptTokenizer::set_multiline_mode(bool p_state) {
+void GDScriptTokenizerText::set_multiline_mode(bool p_state) {
multiline_mode = p_state;
}
-void GDScriptTokenizer::push_expression_indented_block() {
+void GDScriptTokenizerText::push_expression_indented_block() {
indent_stack_stack.push_back(indent_stack);
}
-void GDScriptTokenizer::pop_expression_indented_block() {
- ERR_FAIL_COND(indent_stack_stack.size() == 0);
+void GDScriptTokenizerText::pop_expression_indented_block() {
+ ERR_FAIL_COND(indent_stack_stack.is_empty());
indent_stack = indent_stack_stack.back()->get();
indent_stack_stack.pop_back();
}
-int GDScriptTokenizer::get_cursor_line() const {
+int GDScriptTokenizerText::get_cursor_line() const {
return cursor_line;
}
-int GDScriptTokenizer::get_cursor_column() const {
+int GDScriptTokenizerText::get_cursor_column() const {
return cursor_column;
}
-bool GDScriptTokenizer::is_past_cursor() const {
+bool GDScriptTokenizerText::is_past_cursor() const {
if (line < cursor_line) {
return false;
}
@@ -310,7 +310,7 @@ bool GDScriptTokenizer::is_past_cursor() const {
return true;
}
-char32_t GDScriptTokenizer::_advance() {
+char32_t GDScriptTokenizerText::_advance() {
if (unlikely(_is_at_end())) {
return '\0';
}
@@ -329,11 +329,11 @@ char32_t GDScriptTokenizer::_advance() {
return _peek(-1);
}
-void GDScriptTokenizer::push_paren(char32_t p_char) {
+void GDScriptTokenizerText::push_paren(char32_t p_char) {
paren_stack.push_back(p_char);
}
-bool GDScriptTokenizer::pop_paren(char32_t p_expected) {
+bool GDScriptTokenizerText::pop_paren(char32_t p_expected) {
if (paren_stack.is_empty()) {
return false;
}
@@ -343,13 +343,13 @@ bool GDScriptTokenizer::pop_paren(char32_t p_expected) {
return actual == p_expected;
}
-GDScriptTokenizer::Token GDScriptTokenizer::pop_error() {
+GDScriptTokenizer::Token GDScriptTokenizerText::pop_error() {
Token error = error_stack.back()->get();
error_stack.pop_back();
return error;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_token(Token::Type p_type) {
Token token(p_type);
token.start_line = start_line;
token.end_line = line;
@@ -408,35 +408,35 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
return token;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_literal(const Variant &p_literal) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_literal(const Variant &p_literal) {
Token token = make_token(Token::LITERAL);
token.literal = p_literal;
return token;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_identifier(const StringName &p_identifier) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_identifier(const StringName &p_identifier) {
Token identifier = make_token(Token::IDENTIFIER);
identifier.literal = p_identifier;
return identifier;
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_error(const String &p_message) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_error(const String &p_message) {
Token error = make_token(Token::ERROR);
error.literal = p_message;
return error;
}
-void GDScriptTokenizer::push_error(const String &p_message) {
+void GDScriptTokenizerText::push_error(const String &p_message) {
Token error = make_error(p_message);
error_stack.push_back(error);
}
-void GDScriptTokenizer::push_error(const Token &p_error) {
+void GDScriptTokenizerText::push_error(const Token &p_error) {
error_stack.push_back(p_error);
}
-GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(char32_t p_paren) {
+GDScriptTokenizer::Token GDScriptTokenizerText::make_paren_error(char32_t p_paren) {
if (paren_stack.is_empty()) {
return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren));
}
@@ -445,7 +445,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(char32_t p_paren) {
return error;
}
-GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, Token::Type p_double_type) {
+GDScriptTokenizer::Token GDScriptTokenizerText::check_vcs_marker(char32_t p_test, Token::Type p_double_type) {
const char32_t *next = _current + 1;
int chars = 2; // Two already matched.
@@ -469,7 +469,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To
}
}
-GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
+GDScriptTokenizer::Token GDScriptTokenizerText::annotation() {
if (is_unicode_identifier_start(_peek())) {
_advance(); // Consume start character.
} else {
@@ -550,7 +550,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
#define MAX_KEYWORD_LENGTH 10
#ifdef DEBUG_ENABLED
-void GDScriptTokenizer::make_keyword_list() {
+void GDScriptTokenizerText::make_keyword_list() {
#define KEYWORD_LINE(keyword, token_type) keyword,
#define KEYWORD_GROUP_IGNORE(group)
keyword_list = {
@@ -561,7 +561,7 @@ void GDScriptTokenizer::make_keyword_list() {
}
#endif // DEBUG_ENABLED
-GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() {
bool only_ascii = _peek(-1) < 128;
// Consume all identifier characters.
@@ -611,7 +611,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
static_assert(keyword_length <= MAX_KEYWORD_LENGTH, "There's a keyword longer than the defined maximum length"); \
static_assert(keyword_length >= MIN_KEYWORD_LENGTH, "There's a keyword shorter than the defined minimum length"); \
if (keyword_length == len && name == keyword) { \
- return make_token(token_type); \
+ Token kw = make_token(token_type); \
+ kw.literal = name; \
+ return kw; \
} \
}
@@ -646,7 +648,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#undef MIN_KEYWORD_LENGTH
#undef KEYWORDS
-void GDScriptTokenizer::newline(bool p_make_token) {
+void GDScriptTokenizerText::newline(bool p_make_token) {
// Don't overwrite previous newline, nor create if we want a line continuation.
if (p_make_token && !pending_newline && !line_continuation) {
Token newline(Token::NEWLINE);
@@ -667,7 +669,7 @@ void GDScriptTokenizer::newline(bool p_make_token) {
leftmost_column = 1;
}
-GDScriptTokenizer::Token GDScriptTokenizer::number() {
+GDScriptTokenizer::Token GDScriptTokenizerText::number() {
int base = 10;
bool has_decimal = false;
bool has_exponent = false;
@@ -868,7 +870,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
}
}
-GDScriptTokenizer::Token GDScriptTokenizer::string() {
+GDScriptTokenizer::Token GDScriptTokenizerText::string() {
enum StringType {
STRING_REGULAR,
STRING_NAME,
@@ -1154,7 +1156,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
return make_literal(string);
}
-void GDScriptTokenizer::check_indent() {
+void GDScriptTokenizerText::check_indent() {
ERR_FAIL_COND_MSG(column != 1, "Checking tokenizer indentation in the middle of a line.");
if (_is_at_end()) {
@@ -1323,13 +1325,13 @@ void GDScriptTokenizer::check_indent() {
}
}
-String GDScriptTokenizer::_get_indent_char_name(char32_t ch) {
+String GDScriptTokenizerText::_get_indent_char_name(char32_t ch) {
ERR_FAIL_COND_V(ch != ' ' && ch != '\t', String(&ch, 1).c_escape());
return ch == ' ' ? "space" : "tab";
}
-void GDScriptTokenizer::_skip_whitespace() {
+void GDScriptTokenizerText::_skip_whitespace() {
if (pending_indents != 0) {
// Still have some indent/dedent tokens to give.
return;
@@ -1391,7 +1393,7 @@ void GDScriptTokenizer::_skip_whitespace() {
}
}
-GDScriptTokenizer::Token GDScriptTokenizer::scan() {
+GDScriptTokenizer::Token GDScriptTokenizerText::scan() {
if (has_error()) {
return pop_error();
}
@@ -1453,6 +1455,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (_peek() != '\n') {
return make_error("Expected new line after \"\\\".");
}
+ continuation_lines.push_back(line);
_advance();
newline(false);
line_continuation = true;
@@ -1673,7 +1676,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
}
}
-GDScriptTokenizer::GDScriptTokenizer() {
+GDScriptTokenizerText::GDScriptTokenizerText() {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index a64aaf6820..5d76375173 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -181,14 +181,13 @@ public:
bool can_precede_bin_op() const;
bool is_identifier() const;
bool is_node_name() const;
- StringName get_identifier() const { return source; }
+ StringName get_identifier() const { return literal; }
Token(Type p_type) {
type = p_type;
}
- Token() {
- }
+ Token() {}
};
#ifdef TOOLS_ENABLED
@@ -203,12 +202,26 @@ public:
new_line = p_new_line;
}
};
- const HashMap<int, CommentData> &get_comments() const {
- return comments;
- }
+ virtual const HashMap<int, CommentData> &get_comments() const = 0;
#endif // TOOLS_ENABLED
-private:
+ static String get_token_name(Token::Type p_token_type);
+
+ virtual int get_cursor_line() const = 0;
+ virtual int get_cursor_column() const = 0;
+ virtual void set_cursor_position(int p_line, int p_column) = 0;
+ virtual void set_multiline_mode(bool p_state) = 0;
+ virtual bool is_past_cursor() const = 0;
+ virtual void push_expression_indented_block() = 0; // For lambdas, or blocks inside expressions.
+ virtual void pop_expression_indented_block() = 0; // For lambdas, or blocks inside expressions.
+ virtual bool is_text() = 0;
+
+ virtual Token scan() = 0;
+
+ virtual ~GDScriptTokenizer() {}
+};
+
+class GDScriptTokenizerText : public GDScriptTokenizer {
String source;
const char32_t *_source = nullptr;
const char32_t *_current = nullptr;
@@ -235,6 +248,7 @@ private:
char32_t indent_char = '\0';
int position = 0;
int length = 0;
+ Vector<int> continuation_lines;
#ifdef DEBUG_ENABLED
Vector<String> keyword_list;
#endif // DEBUG_ENABLED
@@ -275,20 +289,28 @@ private:
Token annotation();
public:
- Token scan();
-
void set_source_code(const String &p_source_code);
- int get_cursor_line() const;
- int get_cursor_column() const;
- void set_cursor_position(int p_line, int p_column);
- void set_multiline_mode(bool p_state);
- bool is_past_cursor() const;
- static String get_token_name(Token::Type p_token_type);
- void push_expression_indented_block(); // For lambdas, or blocks inside expressions.
- void pop_expression_indented_block(); // For lambdas, or blocks inside expressions.
+ const Vector<int> &get_continuation_lines() const { return continuation_lines; }
+
+ virtual int get_cursor_line() const override;
+ virtual int get_cursor_column() const override;
+ virtual void set_cursor_position(int p_line, int p_column) override;
+ virtual void set_multiline_mode(bool p_state) override;
+ virtual bool is_past_cursor() const override;
+ virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual bool is_text() override { return true; }
+
+#ifdef TOOLS_ENABLED
+ virtual const HashMap<int, CommentData> &get_comments() const override {
+ return comments;
+ }
+#endif // TOOLS_ENABLED
+
+ virtual Token scan() override;
- GDScriptTokenizer();
+ GDScriptTokenizerText();
};
#endif // GDSCRIPT_TOKENIZER_H
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp
new file mode 100644
index 0000000000..db523ea941
--- /dev/null
+++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp
@@ -0,0 +1,493 @@
+/**************************************************************************/
+/* gdscript_tokenizer_buffer.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "gdscript_tokenizer_buffer.h"
+
+#include "core/io/compression.h"
+#include "core/io/marshalls.h"
+
+#define TOKENIZER_VERSION 100
+
+int GDScriptTokenizerBuffer::_token_to_binary(const Token &p_token, Vector<uint8_t> &r_buffer, int p_start, HashMap<StringName, uint32_t> &r_identifiers_map, HashMap<Variant, uint32_t, VariantHasher, VariantComparator> &r_constants_map) {
+ int pos = p_start;
+
+ int token_type = p_token.type & TOKEN_MASK;
+
+ switch (p_token.type) {
+ case GDScriptTokenizer::Token::ANNOTATION:
+ case GDScriptTokenizer::Token::IDENTIFIER: {
+ // Add identifier to map.
+ int identifier_pos;
+ StringName id = p_token.get_identifier();
+ if (r_identifiers_map.has(id)) {
+ identifier_pos = r_identifiers_map[id];
+ } else {
+ identifier_pos = r_identifiers_map.size();
+ r_identifiers_map[id] = identifier_pos;
+ }
+ token_type |= identifier_pos << TOKEN_BITS;
+ } break;
+ case GDScriptTokenizer::Token::ERROR:
+ case GDScriptTokenizer::Token::LITERAL: {
+ // Add literal to map.
+ int constant_pos;
+ if (r_constants_map.has(p_token.literal)) {
+ constant_pos = r_constants_map[p_token.literal];
+ } else {
+ constant_pos = r_constants_map.size();
+ r_constants_map[p_token.literal] = constant_pos;
+ }
+ token_type |= constant_pos << TOKEN_BITS;
+ } break;
+ default:
+ break;
+ }
+
+ // Encode token.
+ int token_len;
+ if (token_type & TOKEN_MASK) {
+ token_len = 8;
+ r_buffer.resize(pos + token_len);
+ encode_uint32(token_type | TOKEN_BYTE_MASK, &r_buffer.write[pos]);
+ pos += 4;
+ } else {
+ token_len = 5;
+ r_buffer.resize(pos + token_len);
+ r_buffer.write[pos] = token_type;
+ pos++;
+ }
+ encode_uint32(p_token.start_line, &r_buffer.write[pos]);
+ return token_len;
+}
+
+GDScriptTokenizer::Token GDScriptTokenizerBuffer::_binary_to_token(const uint8_t *p_buffer) {
+ Token token;
+ const uint8_t *b = p_buffer;
+
+ uint32_t token_type = decode_uint32(b);
+ token.type = (Token::Type)(token_type & TOKEN_MASK);
+ if (token_type & TOKEN_BYTE_MASK) {
+ b += 4;
+ } else {
+ b++;
+ }
+ token.start_line = decode_uint32(b);
+ token.end_line = token.start_line;
+
+ token.literal = token.get_name();
+ if (token.type == Token::CONST_NAN) {
+ token.literal = String("NAN"); // Special case since name and notation are different.
+ }
+
+ switch (token.type) {
+ case GDScriptTokenizer::Token::ANNOTATION:
+ case GDScriptTokenizer::Token::IDENTIFIER: {
+ // Get name from map.
+ int identifier_pos = token_type >> TOKEN_BITS;
+ if (unlikely(identifier_pos >= identifiers.size())) {
+ Token error;
+ error.type = Token::ERROR;
+ error.literal = "Identifier index out of bounds.";
+ return error;
+ }
+ token.literal = identifiers[identifier_pos];
+ } break;
+ case GDScriptTokenizer::Token::ERROR:
+ case GDScriptTokenizer::Token::LITERAL: {
+ // Get literal from map.
+ int constant_pos = token_type >> TOKEN_BITS;
+ if (unlikely(constant_pos >= constants.size())) {
+ Token error;
+ error.type = Token::ERROR;
+ error.literal = "Constant index out of bounds.";
+ return error;
+ }
+ token.literal = constants[constant_pos];
+ } break;
+ default:
+ break;
+ }
+
+ return token;
+}
+
+Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) {
+ const uint8_t *buf = p_buffer.ptr();
+ ERR_FAIL_COND_V(p_buffer.size() < 12 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA);
+
+ int version = decode_uint32(&buf[4]);
+ ERR_FAIL_COND_V_MSG(version > TOKENIZER_VERSION, ERR_INVALID_DATA, "Binary GDScript is too recent! Please use a newer engine version.");
+
+ int decompressed_size = decode_uint32(&buf[8]);
+
+ Vector<uint8_t> contents;
+ if (decompressed_size == 0) {
+ contents = p_buffer.slice(12);
+ } else {
+ contents.resize(decompressed_size);
+ int result = Compression::decompress(contents.ptrw(), contents.size(), &buf[12], p_buffer.size() - 12, Compression::MODE_ZSTD);
+ ERR_FAIL_COND_V_MSG(result != decompressed_size, ERR_INVALID_DATA, "Error decompressing GDScript tokenizer buffer.");
+ }
+
+ int total_len = contents.size();
+ buf = contents.ptr();
+ uint32_t identifier_count = decode_uint32(&buf[0]);
+ uint32_t constant_count = decode_uint32(&buf[4]);
+ uint32_t token_line_count = decode_uint32(&buf[8]);
+ uint32_t token_count = decode_uint32(&buf[16]);
+
+ const uint8_t *b = &buf[20];
+ total_len -= 20;
+
+ identifiers.resize(identifier_count);
+ for (uint32_t i = 0; i < identifier_count; i++) {
+ uint32_t len = decode_uint32(b);
+ total_len -= 4;
+ ERR_FAIL_COND_V((len * 4u) > (uint32_t)total_len, ERR_INVALID_DATA);
+ b += 4;
+ Vector<uint32_t> cs;
+ cs.resize(len);
+ for (uint32_t j = 0; j < len; j++) {
+ uint8_t tmp[4];
+ for (uint32_t k = 0; k < 4; k++) {
+ tmp[k] = b[j * 4 + k] ^ 0xb6;
+ }
+ cs.write[j] = decode_uint32(tmp);
+ }
+
+ String s(reinterpret_cast<const char32_t *>(cs.ptr()), len);
+ b += len * 4;
+ total_len -= len * 4;
+ identifiers.write[i] = s;
+ }
+
+ constants.resize(constant_count);
+ for (uint32_t i = 0; i < constant_count; i++) {
+ Variant v;
+ int len;
+ Error err = decode_variant(v, b, total_len, &len, false);
+ if (err) {
+ return err;
+ }
+ b += len;
+ total_len -= len;
+ constants.write[i] = v;
+ }
+
+ for (uint32_t i = 0; i < token_line_count; i++) {
+ ERR_FAIL_COND_V(total_len < 8, ERR_INVALID_DATA);
+ uint32_t token_index = decode_uint32(b);
+ b += 4;
+ uint32_t line = decode_uint32(b);
+ b += 4;
+ total_len -= 8;
+ token_lines[token_index] = line;
+ }
+ for (uint32_t i = 0; i < token_line_count; i++) {
+ ERR_FAIL_COND_V(total_len < 8, ERR_INVALID_DATA);
+ uint32_t token_index = decode_uint32(b);
+ b += 4;
+ uint32_t column = decode_uint32(b);
+ b += 4;
+ total_len -= 8;
+ token_columns[token_index] = column;
+ }
+
+ tokens.resize(token_count);
+ for (uint32_t i = 0; i < token_count; i++) {
+ int token_len = 5;
+ if ((*b) & TOKEN_BYTE_MASK) {
+ token_len = 8;
+ }
+ ERR_FAIL_COND_V(total_len < token_len, ERR_INVALID_DATA);
+ Token token = _binary_to_token(b);
+ b += token_len;
+ ERR_FAIL_INDEX_V(token.type, Token::TK_MAX, ERR_INVALID_DATA);
+ tokens.write[i] = token;
+ total_len -= token_len;
+ }
+
+ ERR_FAIL_COND_V(total_len > 0, ERR_INVALID_DATA);
+
+ return OK;
+}
+
+Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code, CompressMode p_compress_mode) {
+ HashMap<StringName, uint32_t> identifier_map;
+ HashMap<Variant, uint32_t, VariantHasher, VariantComparator> constant_map;
+ Vector<uint8_t> token_buffer;
+ HashMap<uint32_t, uint32_t> token_lines;
+ HashMap<uint32_t, uint32_t> token_columns;
+
+ GDScriptTokenizerText tokenizer;
+ tokenizer.set_source_code(p_code);
+ tokenizer.set_multiline_mode(true); // Ignore whitespace tokens.
+ Token current = tokenizer.scan();
+ int token_pos = 0;
+ int last_token_line = 0;
+ int token_counter = 0;
+
+ while (current.type != Token::TK_EOF) {
+ int token_len = _token_to_binary(current, token_buffer, token_pos, identifier_map, constant_map);
+ token_pos += token_len;
+ if (token_counter > 0 && current.start_line > last_token_line) {
+ token_lines[token_counter] = current.start_line;
+ token_columns[token_counter] = current.start_column;
+ }
+ last_token_line = current.end_line;
+
+ current = tokenizer.scan();
+ token_counter++;
+ }
+
+ // Reverse maps.
+ Vector<StringName> rev_identifier_map;
+ rev_identifier_map.resize(identifier_map.size());
+ for (const KeyValue<StringName, uint32_t> &E : identifier_map) {
+ rev_identifier_map.write[E.value] = E.key;
+ }
+ Vector<Variant> rev_constant_map;
+ rev_constant_map.resize(constant_map.size());
+ for (const KeyValue<Variant, uint32_t> &E : constant_map) {
+ rev_constant_map.write[E.value] = E.key;
+ }
+ HashMap<uint32_t, uint32_t> rev_token_lines;
+ for (const KeyValue<uint32_t, uint32_t> &E : token_lines) {
+ rev_token_lines[E.value] = E.key;
+ }
+
+ // Remove continuation lines from map.
+ for (int line : tokenizer.get_continuation_lines()) {
+ if (rev_token_lines.has(line + 1)) {
+ token_lines.erase(rev_token_lines[line + 1]);
+ token_columns.erase(rev_token_lines[line + 1]);
+ }
+ }
+
+ Vector<uint8_t> contents;
+ contents.resize(20);
+ encode_uint32(identifier_map.size(), &contents.write[0]);
+ encode_uint32(constant_map.size(), &contents.write[4]);
+ encode_uint32(token_lines.size(), &contents.write[8]);
+ encode_uint32(token_counter, &contents.write[16]);
+
+ int buf_pos = 20;
+
+ // Save identifiers.
+ for (const StringName &id : rev_identifier_map) {
+ String s = id.operator String();
+ int len = s.length();
+
+ contents.resize(buf_pos + (len + 1) * 4);
+
+ encode_uint32(len, &contents.write[buf_pos]);
+ buf_pos += 4;
+
+ for (int i = 0; i < len; i++) {
+ uint8_t tmp[4];
+ encode_uint32(s[i], tmp);
+
+ for (int b = 0; b < 4; b++) {
+ contents.write[buf_pos + b] = tmp[b] ^ 0xb6;
+ }
+
+ buf_pos += 4;
+ }
+ }
+
+ // Save constants.
+ for (const Variant &v : rev_constant_map) {
+ int len;
+ // Objects cannot be constant, never encode objects.
+ Error err = encode_variant(v, nullptr, len, false);
+ ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant.");
+ contents.resize(buf_pos + len);
+ encode_variant(v, &contents.write[buf_pos], len, false);
+ buf_pos += len;
+ }
+
+ // Save lines and columns.
+ contents.resize(buf_pos + token_lines.size() * 16);
+ for (const KeyValue<uint32_t, uint32_t> &e : token_lines) {
+ encode_uint32(e.key, &contents.write[buf_pos]);
+ buf_pos += 4;
+ encode_uint32(e.value, &contents.write[buf_pos]);
+ buf_pos += 4;
+ }
+ for (const KeyValue<uint32_t, uint32_t> &e : token_columns) {
+ encode_uint32(e.key, &contents.write[buf_pos]);
+ buf_pos += 4;
+ encode_uint32(e.value, &contents.write[buf_pos]);
+ buf_pos += 4;
+ }
+
+ // Store tokens.
+ contents.append_array(token_buffer);
+
+ Vector<uint8_t> buf;
+
+ // Save header.
+ buf.resize(12);
+ buf.write[0] = 'G';
+ buf.write[1] = 'D';
+ buf.write[2] = 'S';
+ buf.write[3] = 'C';
+ encode_uint32(TOKENIZER_VERSION, &buf.write[4]);
+
+ switch (p_compress_mode) {
+ case COMPRESS_NONE:
+ encode_uint32(0u, &buf.write[8]);
+ buf.append_array(contents);
+ break;
+
+ case COMPRESS_ZSTD: {
+ encode_uint32(contents.size(), &buf.write[8]);
+ Vector<uint8_t> compressed;
+ int max_size = Compression::get_max_compressed_buffer_size(contents.size(), Compression::MODE_ZSTD);
+ compressed.resize(max_size);
+
+ int compressed_size = Compression::compress(compressed.ptrw(), contents.ptr(), contents.size(), Compression::MODE_ZSTD);
+ ERR_FAIL_COND_V_MSG(compressed_size < 0, Vector<uint8_t>(), "Error compressing GDScript tokenizer buffer.");
+ compressed.resize(compressed_size);
+
+ buf.append_array(compressed);
+ } break;
+ }
+
+ return buf;
+}
+
+int GDScriptTokenizerBuffer::get_cursor_line() const {
+ return 0;
+}
+
+int GDScriptTokenizerBuffer::get_cursor_column() const {
+ return 0;
+}
+
+void GDScriptTokenizerBuffer::set_cursor_position(int p_line, int p_column) {
+}
+
+void GDScriptTokenizerBuffer::set_multiline_mode(bool p_state) {
+ multiline_mode = p_state;
+}
+
+bool GDScriptTokenizerBuffer::is_past_cursor() const {
+ return false;
+}
+
+void GDScriptTokenizerBuffer::push_expression_indented_block() {
+ indent_stack_stack.push_back(indent_stack);
+}
+
+void GDScriptTokenizerBuffer::pop_expression_indented_block() {
+ ERR_FAIL_COND(indent_stack_stack.is_empty());
+ indent_stack = indent_stack_stack.back()->get();
+ indent_stack_stack.pop_back();
+}
+
+GDScriptTokenizer::Token GDScriptTokenizerBuffer::scan() {
+ // Add final newline.
+ if (current >= tokens.size() && !last_token_was_newline) {
+ Token newline;
+ newline.type = Token::NEWLINE;
+ newline.start_line = current_line;
+ newline.end_line = current_line;
+ last_token_was_newline = true;
+ return newline;
+ }
+
+ // Resolve pending indentation change.
+ if (pending_indents > 0) {
+ pending_indents--;
+ Token indent;
+ indent.type = Token::INDENT;
+ indent.start_line = current_line;
+ indent.end_line = current_line;
+ return indent;
+ } else if (pending_indents < 0) {
+ pending_indents++;
+ Token dedent;
+ dedent.type = Token::DEDENT;
+ dedent.start_line = current_line;
+ dedent.end_line = current_line;
+ return dedent;
+ }
+
+ if (current >= tokens.size()) {
+ if (!indent_stack.is_empty()) {
+ pending_indents -= indent_stack.size();
+ indent_stack.clear();
+ return scan();
+ }
+ Token eof;
+ eof.type = Token::TK_EOF;
+ return eof;
+ };
+
+ if (!last_token_was_newline && token_lines.has(current)) {
+ current_line = token_lines[current];
+ uint32_t current_column = token_columns[current];
+
+ // Check if there's a need to indent/dedent.
+ if (!multiline_mode) {
+ uint32_t previous_indent = 0;
+ if (!indent_stack.is_empty()) {
+ previous_indent = indent_stack.back()->get();
+ }
+ if (current_column - 1 > previous_indent) {
+ pending_indents++;
+ indent_stack.push_back(current_column - 1);
+ } else {
+ while (current_column - 1 < previous_indent) {
+ pending_indents--;
+ indent_stack.pop_back();
+ if (indent_stack.is_empty()) {
+ break;
+ }
+ previous_indent = indent_stack.back()->get();
+ }
+ }
+
+ Token newline;
+ newline.type = Token::NEWLINE;
+ newline.start_line = current_line;
+ newline.end_line = current_line;
+ last_token_was_newline = true;
+
+ return newline;
+ }
+ }
+
+ last_token_was_newline = false;
+
+ Token token = tokens[current++];
+ return token;
+}
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.h b/modules/gdscript/gdscript_tokenizer_buffer.h
new file mode 100644
index 0000000000..55df66e50f
--- /dev/null
+++ b/modules/gdscript/gdscript_tokenizer_buffer.h
@@ -0,0 +1,93 @@
+/**************************************************************************/
+/* gdscript_tokenizer_buffer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GDSCRIPT_TOKENIZER_BUFFER_H
+#define GDSCRIPT_TOKENIZER_BUFFER_H
+
+#include "gdscript_tokenizer.h"
+
+class GDScriptTokenizerBuffer : public GDScriptTokenizer {
+public:
+ enum CompressMode {
+ COMPRESS_NONE,
+ COMPRESS_ZSTD,
+ };
+
+ enum {
+ TOKEN_BYTE_MASK = 0x80,
+ TOKEN_BITS = 8,
+ TOKEN_MASK = (1 << (TOKEN_BITS - 1)) - 1,
+ };
+
+ Vector<StringName> identifiers;
+ Vector<Variant> constants;
+ Vector<int> continuation_lines;
+ HashMap<int, int> token_lines;
+ HashMap<int, int> token_columns;
+ Vector<Token> tokens;
+ int current = 0;
+ uint32_t current_line = 1;
+
+ bool multiline_mode = false;
+ List<int> indent_stack;
+ List<List<int>> indent_stack_stack; // For lambdas, which require manipulating the indentation point.
+ int pending_indents = 0;
+ bool last_token_was_newline = false;
+
+#ifdef TOOLS_ENABLED
+ HashMap<int, CommentData> dummy;
+#endif // TOOLS_ENABLED
+
+ static int _token_to_binary(const Token &p_token, Vector<uint8_t> &r_buffer, int p_start, HashMap<StringName, uint32_t> &r_identifiers_map, HashMap<Variant, uint32_t, VariantHasher, VariantComparator> &r_constants_map);
+ Token _binary_to_token(const uint8_t *p_buffer);
+
+public:
+ Error set_code_buffer(const Vector<uint8_t> &p_buffer);
+ static Vector<uint8_t> parse_code_string(const String &p_code, CompressMode p_compress_mode);
+
+ virtual int get_cursor_line() const override;
+ virtual int get_cursor_column() const override;
+ virtual void set_cursor_position(int p_line, int p_column) override;
+ virtual void set_multiline_mode(bool p_state) override;
+ virtual bool is_past_cursor() const override;
+ virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
+ virtual bool is_text() override { return false; };
+
+#ifdef TOOLS_ENABLED
+ virtual const HashMap<int, CommentData> &get_comments() const override {
+ return dummy;
+ }
+#endif // TOOLS_ENABLED
+
+ virtual Token scan() override;
+};
+
+#endif // GDSCRIPT_TOKENIZER_BUFFER_H
diff --git a/modules/gdscript/gdscript_utility_callable.cpp b/modules/gdscript/gdscript_utility_callable.cpp
index 74d2c477c2..7708a18044 100644
--- a/modules/gdscript/gdscript_utility_callable.cpp
+++ b/modules/gdscript/gdscript_utility_callable.cpp
@@ -83,7 +83,10 @@ ObjectID GDScriptUtilityCallable::get_object() const {
void GDScriptUtilityCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
switch (type) {
case TYPE_INVALID:
- ERR_PRINT(vformat(R"(Trying to call invalid utility function "%s".)", function_name));
+ r_return_value = vformat(R"(Trying to call invalid utility function "%s".)", function_name);
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
+ r_call_error.argument = 0;
+ r_call_error.expected = 0;
break;
case TYPE_GLOBAL:
Variant::call_utility_function(function_name, &r_return_value, p_arguments, p_argcount, r_call_error);
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 0f8648e9a3..ad7af34bf1 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -191,7 +191,7 @@ void ExtendGDScriptParser::update_symbols() {
void ExtendGDScriptParser::update_document_links(const String &p_code) {
document_links.clear();
- GDScriptTokenizer scr_tokenizer;
+ GDScriptTokenizerText scr_tokenizer;
Ref<FileAccess> fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
scr_tokenizer.set_source_code(p_code);
while (true) {
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 605e82be6e..5ff1c78ac9 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -34,6 +34,7 @@
#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
+#include "gdscript_tokenizer_buffer.h"
#include "gdscript_utility_functions.h"
#ifdef TOOLS_ENABLED
@@ -83,18 +84,33 @@ class EditorExportGDScript : public EditorExportPlugin {
public:
virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) override {
- String script_key;
+ int script_mode = EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED;
const Ref<EditorExportPreset> &preset = get_export_preset();
if (preset.is_valid()) {
- script_key = preset->get_script_encryption_key().to_lower();
+ script_mode = preset->get_script_export_mode();
}
- if (!p_path.ends_with(".gd")) {
+ if (!p_path.ends_with(".gd") || script_mode == EditorExportPreset::MODE_SCRIPT_TEXT) {
return;
}
+ Vector<uint8_t> file = FileAccess::get_file_as_bytes(p_path);
+ if (file.is_empty()) {
+ return;
+ }
+
+ String source;
+ source.parse_utf8(reinterpret_cast<const char *>(file.ptr()), file.size());
+ GDScriptTokenizerBuffer::CompressMode compress_mode = script_mode == EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED ? GDScriptTokenizerBuffer::COMPRESS_ZSTD : GDScriptTokenizerBuffer::COMPRESS_NONE;
+ file = GDScriptTokenizerBuffer::parse_code_string(source, compress_mode);
+ if (file.is_empty()) {
+ return;
+ }
+
+ add_file(p_path.get_basename() + ".gdc", file, true);
+
return;
}
@@ -185,6 +201,10 @@ void test_tokenizer() {
GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER);
}
+void test_tokenizer_buffer() {
+ GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER_BUFFER);
+}
+
void test_parser() {
GDScriptTests::test(GDScriptTests::TestType::TEST_PARSER);
}
@@ -198,6 +218,7 @@ void test_bytecode() {
}
REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer);
+REGISTER_TEST_COMMAND("gdscript-tokenizer-buffer", &test_tokenizer_buffer);
REGISTER_TEST_COMMAND("gdscript-parser", &test_parser);
REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler);
REGISTER_TEST_COMMAND("gdscript-bytecode", &test_bytecode);
diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md
index cea251bab5..72b5316532 100644
--- a/modules/gdscript/tests/README.md
+++ b/modules/gdscript/tests/README.md
@@ -32,9 +32,9 @@ Tests will only test against entries in `[output]` that were specified.
## Writing autocompletion tests
-To avoid failing edge cases a certain behaviour needs to be tested multiple times. Some things that tests should account for:
+To avoid failing edge cases a certain behavior needs to be tested multiple times. Some things that tests should account for:
-- All possible types: Test with all possible types that apply to the tested behaviour. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.)
+- All possible types: Test with all possible types that apply to the tested behavior. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.)
- `BUILTIN`
- `NATIVE`
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 4d93a6fc18..a0329eb8d2 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -34,6 +34,7 @@
#include "../gdscript_analyzer.h"
#include "../gdscript_compiler.h"
#include "../gdscript_parser.h"
+#include "../gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/core_globals.h"
@@ -131,10 +132,11 @@ void finish_language() {
StringName GDScriptTestRunner::test_function_name;
-GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames) {
+GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames, bool p_use_binary_tokens) {
test_function_name = StaticCString::create("test");
do_init_languages = p_init_language;
print_filenames = p_print_filenames;
+ binary_tokens = p_use_binary_tokens;
source_dir = p_source_dir;
if (!source_dir.ends_with("/")) {
@@ -277,6 +279,9 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
if (next.ends_with(".notest.gd")) {
next = dir->get_next();
continue;
+ } else if (binary_tokens && next.ends_with(".textonly.gd")) {
+ next = dir->get_next();
+ continue;
} else if (next.get_extension().to_lower() == "gd") {
#ifndef DEBUG_ENABLED
// On release builds, skip tests marked as debug only.
@@ -299,6 +304,9 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
ERR_FAIL_V_MSG(false, "Could not find output file for " + next);
}
GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
+ if (binary_tokens) {
+ test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
+ }
tests.push_back(test);
}
}
@@ -321,24 +329,65 @@ bool GDScriptTestRunner::make_tests() {
return make_tests_for_dir(dir->get_current_dir());
}
-bool GDScriptTestRunner::generate_class_index() {
+static bool generate_class_index_recursive(const String &p_dir) {
+ Error err = OK;
+ Ref<DirAccess> dir(DirAccess::open(p_dir, &err));
+
+ if (err != OK) {
+ return false;
+ }
+
+ String current_dir = dir->get_current_dir();
+
+ dir->list_dir_begin();
+ String next = dir->get_next();
+
StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name();
- for (int i = 0; i < tests.size(); i++) {
- GDScriptTest test = tests[i];
- String base_type;
+ while (!next.is_empty()) {
+ if (dir->current_is_dir()) {
+ if (next == "." || next == ".." || next == "completion" || next == "lsp") {
+ next = dir->get_next();
+ continue;
+ }
+ if (!generate_class_index_recursive(current_dir.path_join(next))) {
+ return false;
+ }
+ } else {
+ if (!next.ends_with(".gd")) {
+ next = dir->get_next();
+ continue;
+ }
+ String base_type;
+ String source_file = current_dir.path_join(next);
+ String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type);
+ if (class_name.is_empty()) {
+ next = dir->get_next();
+ continue;
+ }
+ ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
+ "Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
- String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(test.get_source_file(), &base_type);
- if (class_name.is_empty()) {
- continue;
+ ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file);
}
- ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
- "Class name '" + class_name + "' from " + test.get_source_file() + " is already used in " + ScriptServer::get_global_class_path(class_name));
- ScriptServer::add_global_class(class_name, base_type, gdscript_name, test.get_source_file());
+ next = dir->get_next();
}
+
+ dir->list_dir_end();
+
return true;
}
+bool GDScriptTestRunner::generate_class_index() {
+ Error err = OK;
+ Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
+
+ ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
+
+ source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
+ return generate_class_index_recursive(dir->get_current_dir());
+}
+
GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) {
source_file = p_source_path;
output_file = p_output_path;
@@ -484,7 +533,15 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
Ref<GDScript> script;
script.instantiate();
script->set_path(source_file);
- err = script->load_source_code(source_file);
+ if (tokenizer_mode == TOKENIZER_TEXT) {
+ err = script->load_source_code(source_file);
+ } else {
+ String code = FileAccess::get_file_as_string(source_file, &err);
+ if (!err) {
+ Vector<uint8_t> buffer = GDScriptTokenizerBuffer::parse_code_string(code, GDScriptTokenizerBuffer::COMPRESS_ZSTD);
+ script->set_binary_tokens_source(buffer);
+ }
+ }
if (err != OK) {
enable_stdout();
result.status = GDTEST_LOAD_ERROR;
@@ -494,7 +551,11 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
// Test parsing.
GDScriptParser parser;
- err = parser.parse(script->get_source_code(), source_file, false);
+ if (tokenizer_mode == TOKENIZER_TEXT) {
+ err = parser.parse(script->get_source_code(), source_file, false);
+ } else {
+ err = parser.parse_binary(script->get_binary_tokens_source(), source_file);
+ }
if (err != OK) {
enable_stdout();
result.status = GDTEST_PARSER_ERROR;
@@ -583,7 +644,14 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
add_print_handler(&_print_handler);
add_error_handler(&_error_handler);
- script->reload();
+ err = script->reload();
+ if (err) {
+ enable_stdout();
+ result.status = GDTEST_LOAD_ERROR;
+ result.output = "";
+ result.passed = false;
+ ERR_FAIL_V_MSG(result, "\nCould not reload script: '" + source_file + "'");
+ }
// Create object instance for test.
Object *obj = ClassDB::instantiate(script->get_native()->get_name());
diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h
index b1190604ad..57e3ac86f9 100644
--- a/modules/gdscript/tests/gdscript_test_runner.h
+++ b/modules/gdscript/tests/gdscript_test_runner.h
@@ -62,6 +62,11 @@ public:
bool passed;
};
+ enum TokenizerMode {
+ TOKENIZER_TEXT,
+ TOKENIZER_BUFFER,
+ };
+
private:
struct ErrorHandlerData {
TestResult *result = nullptr;
@@ -79,6 +84,8 @@ private:
PrintHandlerList _print_handler;
ErrorHandlerList _error_handler;
+ TokenizerMode tokenizer_mode = TOKENIZER_TEXT;
+
void enable_stdout();
void disable_stdout();
bool check_output(const String &p_output) const;
@@ -96,6 +103,9 @@ public:
const String get_source_relative_filepath() const { return source_file.trim_prefix(base_dir); }
const String &get_output_file() const { return output_file; }
+ void set_tokenizer_mode(TokenizerMode p_tokenizer_mode) { tokenizer_mode = p_tokenizer_mode; }
+ TokenizerMode get_tokenizer_mode() const { return tokenizer_mode; }
+
GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir);
GDScriptTest() :
GDScriptTest(String(), String(), String()) {} // Needed to use in Vector.
@@ -108,6 +118,7 @@ class GDScriptTestRunner {
bool is_generating = false;
bool do_init_languages = false;
bool print_filenames; // Whether filenames should be printed when generated/running tests
+ bool binary_tokens; // Test with buffer tokenizer.
bool make_tests();
bool make_tests_for_dir(const String &p_dir);
@@ -120,7 +131,7 @@ public:
int run_tests();
bool generate_outputs();
- GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false);
+ GDScriptTestRunner(const String &p_source_dir, bool p_init_language, bool p_print_filenames = false, bool p_use_binary_tokens = false);
~GDScriptTestRunner();
};
diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h
index 5fd7d942d2..5acf436e42 100644
--- a/modules/gdscript/tests/gdscript_test_runner_suite.h
+++ b/modules/gdscript/tests/gdscript_test_runner_suite.h
@@ -38,12 +38,10 @@
namespace GDScriptTests {
TEST_SUITE("[Modules][GDScript]") {
- // GDScript 2.0 is still under heavy construction.
- // Allow the tests to fail, but do not ignore errors during development.
- // Update the scripts and expected output as needed.
TEST_CASE("Script compilation and runtime") {
bool print_filenames = OS::get_singleton()->get_cmdline_args().find("--print-filenames") != nullptr;
- GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames);
+ bool use_binary_tokens = OS::get_singleton()->get_cmdline_args().find("--use-binary-tokens") != nullptr;
+ GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames, use_binary_tokens);
int fail_count = runner.run_tests();
INFO("Make sure `*.out` files have expected results.");
REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass.");
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd
index 9ad77f1432..9ad77f1432 100644
--- a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.gd
diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out
index 31bed2dbc7..31bed2dbc7 100644
--- a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out
+++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.textonly.out
diff --git a/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd b/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd
new file mode 100644
index 0000000000..b744e6170b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/is_not_operator.gd
@@ -0,0 +1,11 @@
+func test():
+ var i: Variant = 123
+ var s: Variant = "str"
+ prints(i is int, i is not int)
+ prints(s is int, s is not int)
+
+ var a: Variant = false
+ var b: Variant = true
+ prints(a == b is int, a == b is not int)
+ prints(a == (b is int), a == (b is not int))
+ prints((a == b) is int, (a == b) is not int)
diff --git a/modules/gdscript/tests/scripts/parser/features/is_not_operator.out b/modules/gdscript/tests/scripts/parser/features/is_not_operator.out
new file mode 100644
index 0000000000..f0535f9c83
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/is_not_operator.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+true false
+false true
+true false
+true false
+false true
diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
index 86152f4543..7b82d9b1da 100644
--- a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
+++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd
@@ -9,6 +9,7 @@ func test():
# Alternatively, backslashes can be used.
if 1 == 1 \
+ \
and 2 == 2 and \
3 == 3:
pass
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index 467bedc4b2..f6965cf7cf 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -34,6 +34,7 @@
#include "../gdscript_compiler.h"
#include "../gdscript_parser.h"
#include "../gdscript_tokenizer.h"
+#include "../gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
@@ -50,7 +51,7 @@
namespace GDScriptTests {
static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
- GDScriptTokenizer tokenizer;
+ GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(p_code);
int tab_size = 4;
@@ -107,6 +108,53 @@ static void test_tokenizer(const String &p_code, const Vector<String> &p_lines)
print_line(current.get_name()); // Should be EOF
}
+static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines);
+
+static void test_tokenizer_buffer(const String &p_code, const Vector<String> &p_lines) {
+ Vector<uint8_t> binary = GDScriptTokenizerBuffer::parse_code_string(p_code, GDScriptTokenizerBuffer::COMPRESS_NONE);
+ test_tokenizer_buffer(binary, p_lines);
+}
+
+static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines) {
+ GDScriptTokenizerBuffer tokenizer;
+ tokenizer.set_code_buffer(p_buffer);
+
+ int tab_size = 4;
+#ifdef TOOLS_ENABLED
+ if (EditorSettings::get_singleton()) {
+ tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
+ }
+#endif // TOOLS_ENABLED
+ String tab = String(" ").repeat(tab_size);
+
+ GDScriptTokenizer::Token current = tokenizer.scan();
+ while (current.type != GDScriptTokenizer::Token::TK_EOF) {
+ StringBuilder token;
+ token += " --> "; // Padding for line number.
+
+ for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {
+ print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
+ }
+
+ token += current.get_name();
+
+ if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
+ token += "(";
+ token += Variant::get_type_name(current.literal.get_type());
+ token += ") ";
+ token += current.literal;
+ }
+
+ print_line(token.as_string());
+
+ print_line("-------------------------------------------------------");
+
+ current = tokenizer.scan();
+ }
+
+ print_line(current.get_name()); // Should be EOF
+}
+
static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
GDScriptParser parser;
Error err = parser.parse(p_code, p_script_path, false);
@@ -119,7 +167,7 @@ static void test_parser(const String &p_code, const String &p_script_path, const
}
GDScriptAnalyzer analyzer(&parser);
- analyzer.analyze();
+ err = analyzer.analyze();
if (err != OK) {
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
@@ -212,7 +260,7 @@ void test(TestType p_type) {
}
String test = cmdlargs.back()->get();
- if (!test.ends_with(".gd")) {
+ if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {
print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
return;
}
@@ -255,6 +303,13 @@ void test(TestType p_type) {
case TEST_TOKENIZER:
test_tokenizer(code, lines);
break;
+ case TEST_TOKENIZER_BUFFER:
+ if (test.ends_with(".gdc")) {
+ test_tokenizer_buffer(buf, lines);
+ } else {
+ test_tokenizer_buffer(code, lines);
+ }
+ break;
case TEST_PARSER:
test_parser(code, test, lines);
break;
diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h
index b39dfe2b5a..32f278d5ce 100644
--- a/modules/gdscript/tests/test_gdscript.h
+++ b/modules/gdscript/tests/test_gdscript.h
@@ -39,6 +39,7 @@ namespace GDScriptTests {
enum TestType {
TEST_TOKENIZER,
+ TEST_TOKENIZER_BUFFER,
TEST_PARSER,
TEST_COMPILER,
TEST_BYTECODE,
diff --git a/modules/glslang/SCsub b/modules/glslang/SCsub
index a34a97d230..3068377e60 100644
--- a/modules/glslang/SCsub
+++ b/modules/glslang/SCsub
@@ -43,7 +43,6 @@ if env["builtin_glslang"]:
"glslang/MachineIndependent/SymbolTable.cpp",
"glslang/MachineIndependent/Versions.cpp",
"glslang/ResourceLimits/ResourceLimits.cpp",
- "OGLCompilersDLL/InitializeDll.cpp",
"SPIRV/disassemble.cpp",
"SPIRV/doc.cpp",
"SPIRV/GlslangToSpv.cpp",
diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp
index d7e5ddd32c..09ad1d6777 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -39,7 +39,7 @@
#include <glslang/SPIRV/GlslangToSpv.h>
static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage, const String &p_source_code, RenderingDevice::ShaderLanguage p_language, String *r_error, const RenderingDevice *p_render_device) {
- const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
+ const RDD::Capabilities &capabilities = p_render_device->get_device_capabilities();
Vector<uint8_t> ret;
ERR_FAIL_COND_V(p_language == RenderingDevice::SHADER_LANGUAGE_HLSL, ret);
@@ -57,17 +57,17 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
glslang::EShTargetClientVersion ClientVersion = glslang::EShTargetVulkan_1_2;
glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5;
- if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) {
- if (capabilities->version_major == 1 && capabilities->version_minor == 0) {
+ if (capabilities.device_family == RDD::DEVICE_VULKAN) {
+ if (capabilities.version_major == 1 && capabilities.version_minor == 0) {
ClientVersion = glslang::EShTargetVulkan_1_0;
TargetVersion = glslang::EShTargetSpv_1_0;
- } else if (capabilities->version_major == 1 && capabilities->version_minor == 1) {
+ } else if (capabilities.version_major == 1 && capabilities.version_minor == 1) {
ClientVersion = glslang::EShTargetVulkan_1_1;
TargetVersion = glslang::EShTargetSpv_1_3;
} else {
// use defaults
}
- } else if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_DIRECTX) {
+ } else if (capabilities.device_family == RDD::DEVICE_DIRECTX) {
// NIR-DXIL is Vulkan 1.1-conformant.
ClientVersion = glslang::EShTargetVulkan_1_1;
// The SPIR-V part of Mesa supports 1.6, but:
@@ -186,9 +186,9 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
}
static String _get_cache_key_function_glsl(const RenderingDevice *p_render_device) {
- const RD::Capabilities *capabilities = p_render_device->get_device_capabilities();
+ const RenderingDeviceDriver::Capabilities &capabilities = p_render_device->get_device_capabilities();
String version;
- version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
+ version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities.version_major) + ", minor=" + itos(capabilities.version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled());
return version;
}
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 1b52a82298..1f172633da 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -10,7 +10,7 @@
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="glTF 'What the duck?' guide">https://www.khronos.org/files/gltf20-reference-guide.pdf</link>
+ <link title="glTF &apos;What the duck?&apos; guide">https://www.khronos.org/files/gltf20-reference-guide.pdf</link>
<link title="Khronos glTF specification">https://registry.khronos.org/glTF/</link>
</tutorials>
<methods>
diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
index ca66cd54b0..5cfc22f6b2 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
@@ -55,7 +55,7 @@
<member name="inertia_orientation" type="Quaternion" setter="set_inertia_orientation" getter="get_inertia_orientation" default="Quaternion(0, 0, 0, 1)">
The inertia orientation of the physics body. This defines the rotation of the inertia's principle axes relative to the object's local axes. This is only used when the body type is "rigid" or "vehicle" and [member inertia_diagonal] is set to a non-zero value.
</member>
- <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)" is_deprecated="true">
+ <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)" deprecated="">
The inertia tensor of the physics body, in kilogram meter squared (kg⋅m²). This is only used when the body type is "rigid" or "vehicle".
When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically.
</member>
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
index 37b8ae0634..a9033de7ae 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
@@ -360,7 +360,7 @@ void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Re
gltf_shape->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh));
}
}
- if (cast_to<Area3D>(_get_ancestor_collision_object(p_scene_node))) {
+ if (cast_to<Area3D>(_get_ancestor_collision_object(p_scene_node->get_parent()))) {
p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), gltf_shape);
} else {
p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), gltf_shape);
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 73e26fbef5..ff2f290e4f 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -250,7 +250,7 @@ Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> p_state) const {
}
Error GLTFDocument::_serialize_scenes(Ref<GLTFState> p_state) {
- ERR_FAIL_COND_V_MSG(p_state->root_nodes.size() == 0, ERR_INVALID_DATA, "GLTF export: The scene must have at least one root node.");
+ ERR_FAIL_COND_V_MSG(p_state->root_nodes.is_empty(), ERR_INVALID_DATA, "GLTF export: The scene must have at least one root node.");
// Godot only supports one scene per glTF file.
Array scenes;
Dictionary scene_dict;
@@ -430,20 +430,22 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
}
if (gltf_node->skeleton != -1 && gltf_node->skin < 0) {
}
- if (gltf_node->xform != Transform3D()) {
- node["matrix"] = _xform_to_array(gltf_node->xform);
- }
-
- if (!gltf_node->rotation.is_equal_approx(Quaternion())) {
- node["rotation"] = _quaternion_to_array(gltf_node->rotation);
- }
-
- if (!gltf_node->scale.is_equal_approx(Vector3(1.0f, 1.0f, 1.0f))) {
- node["scale"] = _vec3_to_arr(gltf_node->scale);
- }
-
- if (!gltf_node->position.is_zero_approx()) {
- node["translation"] = _vec3_to_arr(gltf_node->position);
+ if (gltf_node->transform.basis.is_orthogonal()) {
+ // An orthogonal transform is decomposable into TRS, so prefer that.
+ const Vector3 position = gltf_node->get_position();
+ if (!position.is_zero_approx()) {
+ node["translation"] = _vec3_to_arr(position);
+ }
+ const Quaternion rotation = gltf_node->get_rotation();
+ if (!rotation.is_equal_approx(Quaternion())) {
+ node["rotation"] = _quaternion_to_array(rotation);
+ }
+ const Vector3 scale = gltf_node->get_scale();
+ if (!scale.is_equal_approx(Vector3(1.0f, 1.0f, 1.0f))) {
+ node["scale"] = _vec3_to_arr(scale);
+ }
+ } else {
+ node["matrix"] = _xform_to_array(gltf_node->transform);
}
if (gltf_node->children.size()) {
Array children;
@@ -609,20 +611,17 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
node->skin = n["skin"];
}
if (n.has("matrix")) {
- node->xform = _arr_to_xform(n["matrix"]);
+ node->transform = _arr_to_xform(n["matrix"]);
} else {
if (n.has("translation")) {
- node->position = _arr_to_vec3(n["translation"]);
+ node->set_position(_arr_to_vec3(n["translation"]));
}
if (n.has("rotation")) {
- node->rotation = _arr_to_quaternion(n["rotation"]);
+ node->set_rotation(_arr_to_quaternion(n["rotation"]));
}
if (n.has("scale")) {
- node->scale = _arr_to_vec3(n["scale"]);
+ node->set_scale(_arr_to_vec3(n["scale"]));
}
-
- node->xform.basis.set_quaternion_scale(node->rotation, node->scale);
- node->xform.origin = node->position;
}
if (n.has("extensions")) {
@@ -807,7 +806,7 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> p_state, const String &p_base_
uri = uri.uri_decode();
uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows.
buffer_data = FileAccess::get_file_as_bytes(uri);
- ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri);
+ ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri);
}
ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR);
@@ -1545,7 +1544,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
}
}
- ERR_FAIL_COND_V(attribs.size() == 0, -1);
+ ERR_FAIL_COND_V(attribs.is_empty(), -1);
Ref<GLTFAccessor> accessor;
accessor.instantiate();
@@ -1904,7 +1903,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat
_calc_accessor_min_max(i, element_count, type_max, attribs, type_min);
}
- ERR_FAIL_COND_V(!attribs.size(), -1);
+ ERR_FAIL_COND_V(attribs.is_empty(), -1);
Ref<GLTFAccessor> accessor;
accessor.instantiate();
@@ -2222,7 +2221,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Dictionary attributes;
{
Vector<Vector3> a = array[Mesh::ARRAY_VERTEX];
- ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(a.is_empty(), ERR_INVALID_DATA);
attributes["POSITION"] = _encode_accessor_as_vec3(p_state, a, true);
vertex_num = a.size();
}
@@ -2789,7 +2788,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
} else if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
//generate indices because they need to be swapped for CW/CCW
const Vector<Vector3> &vertices = array[Mesh::ARRAY_VERTEX];
- ERR_FAIL_COND_V(vertices.size() == 0, ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(vertices.is_empty(), ERR_PARSE_ERROR);
Vector<int> indices;
const int vs = vertices.size();
indices.resize(vs);
@@ -2920,7 +2919,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
if (t.has("TANGENT")) {
const Vector<Vector3> tangents_v3 = _decode_accessor_as_vec3(p_state, t["TANGENT"], true);
const Vector<float> src_tangents = array[Mesh::ARRAY_TANGENT];
- ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(src_tangents.is_empty(), ERR_PARSE_ERROR);
Vector<float> tangents_v4;
@@ -4415,7 +4414,7 @@ Error GLTFDocument::_verify_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) {
out_roots.sort();
- ERR_FAIL_COND_V(out_roots.size() == 0, FAILED);
+ ERR_FAIL_COND_V(out_roots.is_empty(), FAILED);
// Make sure the roots are the exact same (they better be)
ERR_FAIL_COND_V(out_roots.size() != p_skin->roots.size(), FAILED);
@@ -4801,10 +4800,10 @@ Error GLTFDocument::_create_skeletons(Ref<GLTFState> p_state) {
node->set_name(_gen_unique_bone_name(p_state, skel_i, node->get_name()));
skeleton->add_bone(node->get_name());
- skeleton->set_bone_rest(bone_index, node->xform);
- skeleton->set_bone_pose_position(bone_index, node->position);
- skeleton->set_bone_pose_rotation(bone_index, node->rotation.normalized());
- skeleton->set_bone_pose_scale(bone_index, node->scale);
+ skeleton->set_bone_rest(bone_index, node->transform);
+ skeleton->set_bone_pose_position(bone_index, node->get_position());
+ skeleton->set_bone_pose_rotation(bone_index, node->get_rotation());
+ skeleton->set_bone_pose_scale(bone_index, node->get_scale());
if (node->parent >= 0 && p_state->nodes[node->parent]->skeleton == skel_i) {
const int bone_parent = skeleton->find_bone(p_state->nodes[node->parent]->get_name());
@@ -5413,14 +5412,13 @@ BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> p_state
GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshInstance3D *p_mesh_instance) {
ERR_FAIL_NULL_V(p_mesh_instance, -1);
- if (p_mesh_instance->get_mesh().is_null()) {
- return -1;
- }
+ ERR_FAIL_COND_V_MSG(p_mesh_instance->get_mesh().is_null(), -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but it has no mesh. This node will be exported without a mesh.");
+ Ref<Mesh> mesh_resource = p_mesh_instance->get_mesh();
+ ERR_FAIL_COND_V_MSG(mesh_resource->get_surface_count() == 0, -1, "glTF: Tried to export a MeshInstance3D node named " + p_mesh_instance->get_name() + ", but its mesh has no surfaces. This node will be exported without a mesh.");
- Ref<Mesh> import_mesh = p_mesh_instance->get_mesh();
- Ref<ImporterMesh> current_mesh = _mesh_to_importer_mesh(import_mesh);
+ Ref<ImporterMesh> current_mesh = _mesh_to_importer_mesh(mesh_resource);
Vector<float> blend_weights;
- int32_t blend_count = import_mesh->get_blend_shape_count();
+ int32_t blend_count = mesh_resource->get_blend_shape_count();
blend_weights.resize(blend_count);
for (int32_t blend_i = 0; blend_i < blend_count; blend_i++) {
blend_weights.write[blend_i] = 0.0f;
@@ -5510,10 +5508,7 @@ GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> p_state, Light3D *p_l
}
void GLTFDocument::_convert_spatial(Ref<GLTFState> p_state, Node3D *p_spatial, Ref<GLTFNode> p_node) {
- Transform3D xform = p_spatial->get_transform();
- p_node->scale = xform.basis.get_scale();
- p_node->rotation = xform.basis.get_rotation_quaternion();
- p_node->position = xform.origin;
+ p_node->transform = p_spatial->get_transform();
}
Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index) {
@@ -5531,6 +5526,12 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
if (retflag) {
return;
}
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint() && p_gltf_root != -1 && p_current->get_owner() == nullptr) {
+ WARN_VERBOSE("glTF export warning: Node '" + p_current->get_name() + "' has no owner. This is likely a temporary node generated by a @tool script. This would not be saved when saving the Godot scene, therefore it will not be exported to glTF.");
+ return;
+ }
+#endif // TOOLS_ENABLED
Ref<GLTFNode> gltf_node;
gltf_node.instantiate();
gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
@@ -5627,7 +5628,7 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
GLTFMeshIndex mesh_i = p_state->meshes.size();
p_state->meshes.push_back(gltf_mesh);
p_gltf_node->mesh = mesh_i;
- p_gltf_node->xform = csg->get_meshes()[0];
+ p_gltf_node->transform = csg->get_meshes()[0];
p_gltf_node->set_name(_gen_unique_name(p_state, csg->get_name()));
}
#endif // MODULE_CSG_ENABLED
@@ -5703,7 +5704,7 @@ void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex
gltf_mesh->set_mesh(_mesh_to_importer_mesh(p_grid_map->get_mesh_library()->get_item_mesh(cell)));
new_gltf_node->mesh = p_state->meshes.size();
p_state->meshes.push_back(gltf_mesh);
- new_gltf_node->xform = cell_xform * p_grid_map->get_transform();
+ new_gltf_node->transform = cell_xform * p_grid_map->get_transform();
new_gltf_node->set_name(_gen_unique_name(p_state, p_grid_map->get_mesh_library()->get_item_name(cell)));
}
}
@@ -5771,7 +5772,7 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf(
Ref<GLTFNode> new_gltf_node;
new_gltf_node.instantiate();
new_gltf_node->mesh = mesh_index;
- new_gltf_node->xform = transform;
+ new_gltf_node->transform = transform;
new_gltf_node->set_name(_gen_unique_name(p_state, p_multi_mesh_instance->get_name()));
p_gltf_node->children.push_back(p_state->nodes.size());
p_state->nodes.push_back(new_gltf_node);
@@ -5796,10 +5797,7 @@ void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFS
// Note that we cannot use _gen_unique_bone_name here, because glTF spec requires all node
// names to be unique regardless of whether or not they are used as joints.
joint_node->set_name(_gen_unique_name(p_state, skeleton->get_bone_name(bone_i)));
- Transform3D xform = skeleton->get_bone_pose(bone_i);
- joint_node->scale = xform.basis.get_scale();
- joint_node->rotation = xform.basis.get_rotation_quaternion();
- joint_node->position = xform.origin;
+ joint_node->transform = skeleton->get_bone_pose(bone_i);
joint_node->joint = true;
GLTFNodeIndex current_node_i = p_state->nodes.size();
p_state->scene_nodes.insert(current_node_i, skeleton);
@@ -5948,7 +5946,7 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
Array args;
args.append(p_scene_root);
current_node->propagate_call(StringName("set_owner"), args);
- current_node->set_transform(gltf_node->xform);
+ current_node->set_transform(gltf_node->transform);
}
p_state->scene_nodes.insert(p_node_index, current_node);
@@ -6106,7 +6104,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> {
template <class T>
T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) {
- ERR_FAIL_COND_V(!p_values.size(), T());
+ ERR_FAIL_COND_V(p_values.is_empty(), T());
if (p_times.size() != (p_values.size() / (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE ? 3 : 1))) {
ERR_PRINT_ONCE("The interpolated values are not corresponding to its times.");
return p_values[0];
@@ -6280,7 +6278,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
if (track.position_track.values.size()) {
bool is_default = true; //discard the track if all it contains is default values
if (p_remove_immutable_tracks) {
- Vector3 base_pos = p_state->nodes[track_i.key]->position;
+ Vector3 base_pos = gltf_node->get_position();
for (int i = 0; i < track.position_track.times.size(); i++) {
int value_index = track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i;
ERR_FAIL_COND_MSG(value_index >= track.position_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements.");
@@ -6305,7 +6303,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
if (track.rotation_track.values.size()) {
bool is_default = true; //discard the track if all it contains is default values
if (p_remove_immutable_tracks) {
- Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized();
+ Quaternion base_rot = gltf_node->get_rotation();
for (int i = 0; i < track.rotation_track.times.size(); i++) {
int value_index = track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i;
ERR_FAIL_COND_MSG(value_index >= track.rotation_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements.");
@@ -6330,7 +6328,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
if (track.scale_track.values.size()) {
bool is_default = true; //discard the track if all it contains is default values
if (p_remove_immutable_tracks) {
- Vector3 base_scale = p_state->nodes[track_i.key]->scale;
+ Vector3 base_scale = gltf_node->get_scale();
for (int i = 0; i < track.scale_track.times.size(); i++) {
int value_index = track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i;
ERR_FAIL_COND_MSG(value_index >= track.scale_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements.");
@@ -6361,15 +6359,15 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
Vector3 base_scale = Vector3(1, 1, 1);
if (rotation_idx == -1) {
- base_rot = p_state->nodes[track_i.key]->rotation.normalized();
+ base_rot = gltf_node->get_rotation();
}
if (position_idx == -1) {
- base_pos = p_state->nodes[track_i.key]->position;
+ base_pos = gltf_node->get_position();
}
if (scale_idx == -1) {
- base_scale = p_state->nodes[track_i.key]->scale;
+ base_scale = gltf_node->get_scale();
}
bool last = false;
@@ -6476,10 +6474,7 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> p_state) {
if (!mi) {
continue;
}
- Transform3D mi_xform = mi->get_transform();
- node->scale = mi_xform.basis.get_scale();
- node->rotation = mi_xform.basis.get_rotation_quaternion();
- node->position = mi_xform.origin;
+ node->transform = mi->get_transform();
Node *skel_node = mi->get_node_or_null(mi->get_skeleton_path());
Skeleton3D *godot_skeleton = Object::cast_to<Skeleton3D>(skel_node);
diff --git a/modules/gltf/structures/gltf_buffer_view.compat.inc b/modules/gltf/structures/gltf_buffer_view.compat.inc
new file mode 100644
index 0000000000..db2600a071
--- /dev/null
+++ b/modules/gltf/structures/gltf_buffer_view.compat.inc
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* gltf_buffer_view.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+GLTFBufferIndex GLTFBufferView::_get_buffer_bind_compat_86907() {
+ return get_buffer();
+}
+
+int GLTFBufferView::_get_byte_offset_bind_compat_86907() {
+ return get_byte_offset();
+}
+
+int GLTFBufferView::_get_byte_length_bind_compat_86907() {
+ return get_byte_length();
+}
+
+int GLTFBufferView::_get_byte_stride_bind_compat_86907() {
+ return get_byte_stride();
+}
+
+bool GLTFBufferView::_get_indices_bind_compat_86907() {
+ return get_indices();
+}
+
+void GLTFBufferView::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("get_buffer"), &GLTFBufferView::_get_buffer_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_byte_offset"), &GLTFBufferView::_get_byte_offset_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_byte_length"), &GLTFBufferView::_get_byte_length_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_byte_stride"), &GLTFBufferView::_get_byte_stride_bind_compat_86907);
+ ClassDB::bind_compatibility_method(D_METHOD("get_indices"), &GLTFBufferView::_get_indices_bind_compat_86907);
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/modules/gltf/structures/gltf_buffer_view.cpp b/modules/gltf/structures/gltf_buffer_view.cpp
index 8588de0752..997c219bf0 100644
--- a/modules/gltf/structures/gltf_buffer_view.cpp
+++ b/modules/gltf/structures/gltf_buffer_view.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "gltf_buffer_view.h"
+#include "gltf_buffer_view.compat.inc"
#include "../gltf_state.h"
@@ -53,7 +54,7 @@ void GLTFBufferView::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indices"), "set_indices", "get_indices"); // bool
}
-GLTFBufferIndex GLTFBufferView::get_buffer() {
+GLTFBufferIndex GLTFBufferView::get_buffer() const {
return buffer;
}
@@ -61,7 +62,7 @@ void GLTFBufferView::set_buffer(GLTFBufferIndex p_buffer) {
buffer = p_buffer;
}
-int GLTFBufferView::get_byte_offset() {
+int GLTFBufferView::get_byte_offset() const {
return byte_offset;
}
@@ -69,7 +70,7 @@ void GLTFBufferView::set_byte_offset(int p_byte_offset) {
byte_offset = p_byte_offset;
}
-int GLTFBufferView::get_byte_length() {
+int GLTFBufferView::get_byte_length() const {
return byte_length;
}
@@ -77,7 +78,7 @@ void GLTFBufferView::set_byte_length(int p_byte_length) {
byte_length = p_byte_length;
}
-int GLTFBufferView::get_byte_stride() {
+int GLTFBufferView::get_byte_stride() const {
return byte_stride;
}
@@ -85,7 +86,7 @@ void GLTFBufferView::set_byte_stride(int p_byte_stride) {
byte_stride = p_byte_stride;
}
-bool GLTFBufferView::get_indices() {
+bool GLTFBufferView::get_indices() const {
return indices;
}
diff --git a/modules/gltf/structures/gltf_buffer_view.h b/modules/gltf/structures/gltf_buffer_view.h
index e4b7168130..1c7bd5c5c7 100644
--- a/modules/gltf/structures/gltf_buffer_view.h
+++ b/modules/gltf/structures/gltf_buffer_view.h
@@ -49,20 +49,29 @@ private:
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ GLTFBufferIndex _get_buffer_bind_compat_86907();
+ int _get_byte_offset_bind_compat_86907();
+ int _get_byte_length_bind_compat_86907();
+ int _get_byte_stride_bind_compat_86907();
+ bool _get_indices_bind_compat_86907();
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
public:
- GLTFBufferIndex get_buffer();
+ GLTFBufferIndex get_buffer() const;
void set_buffer(GLTFBufferIndex p_buffer);
- int get_byte_offset();
+ int get_byte_offset() const;
void set_byte_offset(int p_byte_offset);
- int get_byte_length();
+ int get_byte_length() const;
void set_byte_length(int p_byte_length);
- int get_byte_stride();
+ int get_byte_stride() const;
void set_byte_stride(int p_byte_stride);
- bool get_indices();
+ bool get_indices() const;
void set_indices(bool p_indices);
Vector<uint8_t> load_buffer_view_data(const Ref<GLTFState> p_state) const;
diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp
index 30895034a9..03f71525fd 100644
--- a/modules/gltf/structures/gltf_node.cpp
+++ b/modules/gltf/structures/gltf_node.cpp
@@ -89,11 +89,11 @@ void GLTFNode::set_height(int p_height) {
}
Transform3D GLTFNode::get_xform() {
- return xform;
+ return transform;
}
void GLTFNode::set_xform(Transform3D p_xform) {
- xform = p_xform;
+ transform = p_xform;
}
GLTFMeshIndex GLTFNode::get_mesh() {
@@ -129,27 +129,27 @@ void GLTFNode::set_skeleton(GLTFSkeletonIndex p_skeleton) {
}
Vector3 GLTFNode::get_position() {
- return position;
+ return transform.origin;
}
void GLTFNode::set_position(Vector3 p_position) {
- position = p_position;
+ transform.origin = p_position;
}
Quaternion GLTFNode::get_rotation() {
- return rotation;
+ return transform.basis.get_rotation_quaternion();
}
void GLTFNode::set_rotation(Quaternion p_rotation) {
- rotation = p_rotation;
+ transform.basis.set_quaternion_scale(p_rotation, transform.basis.get_scale());
}
Vector3 GLTFNode::get_scale() {
- return scale;
+ return transform.basis.get_scale();
}
void GLTFNode::set_scale(Vector3 p_scale) {
- scale = p_scale;
+ transform.basis = transform.basis.orthonormalized() * Basis::from_scale(p_scale);
}
Vector<int> GLTFNode::get_children() {
diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h
index c2d2f64495..aba7374127 100644
--- a/modules/gltf/structures/gltf_node.h
+++ b/modules/gltf/structures/gltf_node.h
@@ -40,18 +40,14 @@ class GLTFNode : public Resource {
friend class GLTFDocument;
private:
- // matrices need to be transformed to this
GLTFNodeIndex parent = -1;
int height = -1;
- Transform3D xform;
+ Transform3D transform;
GLTFMeshIndex mesh = -1;
GLTFCameraIndex camera = -1;
GLTFSkinIndex skin = -1;
GLTFSkeletonIndex skeleton = -1;
bool joint = false;
- Vector3 position;
- Quaternion rotation;
- Vector3 scale = Vector3(1, 1, 1);
Vector<int> children;
GLTFLightIndex light = -1;
Dictionary additional_data;
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index 3094a7bf80..da11cd216f 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -138,11 +138,11 @@
Returns the position of a grid cell in the GridMap's local coordinate space. To convert the returned value into global coordinates, use [method Node3D.to_global]. See also [method map_to_local].
</description>
</method>
- <method name="resource_changed" is_deprecated="true">
+ <method name="resource_changed" deprecated="Use [signal Resource.changed] instead.">
<return type="void" />
<param index="0" name="resource" type="Resource" />
<description>
- [i]Obsoleted.[/i] Use [signal Resource.changed] instead.
+ This method does nothing.
</description>
</method>
<method name="set_cell_item">
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 02b5aefc9f..5d22cb1301 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -41,6 +41,10 @@
#include "editor/editor_settings.h"
#include "servers/rendering/rendering_device_binds.h"
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_context_driver_vulkan.h"
+#endif
+
//uncomment this if you want to see textures from all the process saved
//#define DEBUG_TEXTURES
@@ -49,7 +53,7 @@ void LightmapperRD::add_mesh(const MeshData &p_mesh) {
ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->is_empty());
ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_width() != p_mesh.emission_on_uv2->get_width());
ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_height() != p_mesh.emission_on_uv2->get_height());
- ERR_FAIL_COND(p_mesh.points.size() == 0);
+ ERR_FAIL_COND(p_mesh.points.is_empty());
MeshInstance mi;
mi.data = p_mesh;
mesh_instances.push_back(mi);
@@ -1017,7 +1021,35 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}
#endif
- RenderingDevice *rd = RenderingDevice::get_singleton()->create_local_device();
+ // Attempt to create a local device by requesting it from rendering server first.
+ // If that fails because the current renderer is not implemented on top of RD, we fall back to creating
+ // a local rendering device manually depending on the current platform.
+ Error err;
+ RenderingContextDriver *rcd = nullptr;
+ RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
+ if (rd == nullptr) {
+#if defined(RD_ENABLED)
+#if defined(VULKAN_ENABLED)
+ rcd = memnew(RenderingContextDriverVulkan);
+ rd = memnew(RenderingDevice);
+#endif
+#endif
+ if (rcd != nullptr && rd != nullptr) {
+ err = rcd->initialize();
+ if (err == OK) {
+ err = rd->initialize(rcd);
+ }
+
+ if (err != OK) {
+ memdelete(rd);
+ memdelete(rcd);
+ rd = nullptr;
+ rcd = nullptr;
+ }
+ }
+ }
+
+ ERR_FAIL_NULL_V(rd, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
RID albedo_array_tex;
RID emission_array_tex;
@@ -1187,7 +1219,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
//shaders
Ref<RDShaderFile> raster_shader;
raster_shader.instantiate();
- Error err = raster_shader->parse_versions_from_text(lm_raster_shader_glsl);
+ err = raster_shader->parse_versions_from_text(lm_raster_shader_glsl);
if (err != OK) {
raster_shader->print_errors("raster_shader");
@@ -1195,6 +1227,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
FREE_BUFFERS
memdelete(rd);
+
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
@@ -1367,6 +1403,11 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
FREE_BUFFERS
FREE_RASTER_RESOURCES
memdelete(rd);
+
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
+
compute_shader->print_errors("compute_shader");
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
@@ -1789,6 +1830,11 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
FREE_RASTER_RESOURCES
FREE_COMPUTE_RESOURCES
memdelete(rd);
+
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
+
blendseams_shader->print_errors("blendseams_shader");
}
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
@@ -1964,6 +2010,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
memdelete(rd);
+ if (rcd != nullptr) {
+ memdelete(rcd);
+ }
+
return BAKE_OK;
}
@@ -1986,7 +2036,7 @@ Variant LightmapperRD::get_bake_mesh_userdata(int p_index) const {
}
Rect2 LightmapperRD::get_bake_mesh_uv_scale(int p_index) const {
- ERR_FAIL_COND_V(bake_textures.size() == 0, Rect2());
+ ERR_FAIL_COND_V(bake_textures.is_empty(), Rect2());
Rect2 uv_ofs;
Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height());
uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size;
diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp
index 381ed42fe1..859278d65e 100644
--- a/modules/mbedtls/crypto_mbedtls.cpp
+++ b/modules/mbedtls/crypto_mbedtls.cpp
@@ -53,7 +53,7 @@ CryptoKey *CryptoKeyMbedTLS::create() {
return memnew(CryptoKeyMbedTLS);
}
-Error CryptoKeyMbedTLS::load(String p_path, bool p_public_only) {
+Error CryptoKeyMbedTLS::load(const String &p_path, bool p_public_only) {
ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Key is in use");
PackedByteArray out;
@@ -79,7 +79,7 @@ Error CryptoKeyMbedTLS::load(String p_path, bool p_public_only) {
return OK;
}
-Error CryptoKeyMbedTLS::save(String p_path, bool p_public_only) {
+Error CryptoKeyMbedTLS::save(const String &p_path, bool p_public_only) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_INVALID_PARAMETER, "Cannot save CryptoKeyMbedTLS file '" + p_path + "'.");
@@ -103,7 +103,7 @@ Error CryptoKeyMbedTLS::save(String p_path, bool p_public_only) {
return OK;
}
-Error CryptoKeyMbedTLS::load_from_string(String p_string_key, bool p_public_only) {
+Error CryptoKeyMbedTLS::load_from_string(const String &p_string_key, bool p_public_only) {
int ret = 0;
if (p_public_only) {
ret = mbedtls_pk_parse_public_key(&pkey, (unsigned char *)p_string_key.utf8().get_data(), p_string_key.utf8().size());
@@ -138,7 +138,7 @@ X509Certificate *X509CertificateMbedTLS::create() {
return memnew(X509CertificateMbedTLS);
}
-Error X509CertificateMbedTLS::load(String p_path) {
+Error X509CertificateMbedTLS::load(const String &p_path) {
ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Certificate is already in use.");
PackedByteArray out;
@@ -170,7 +170,7 @@ Error X509CertificateMbedTLS::load_from_memory(const uint8_t *p_buffer, int p_le
return OK;
}
-Error X509CertificateMbedTLS::save(String p_path) {
+Error X509CertificateMbedTLS::save(const String &p_path) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_INVALID_PARAMETER, vformat("Cannot save X509CertificateMbedTLS file '%s'.", p_path));
@@ -235,7 +235,7 @@ HMACContext *HMACContextMbedTLS::create() {
return memnew(HMACContextMbedTLS);
}
-Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByteArray p_key) {
+Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, const PackedByteArray &p_key) {
ERR_FAIL_COND_V_MSG(ctx != nullptr, ERR_FILE_ALREADY_IN_USE, "HMACContext already started.");
// HMAC keys can be any size.
@@ -255,7 +255,7 @@ Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByte
return ret ? FAILED : OK;
}
-Error HMACContextMbedTLS::update(PackedByteArray p_data) {
+Error HMACContextMbedTLS::update(const PackedByteArray &p_data) {
ERR_FAIL_NULL_V_MSG(ctx, ERR_INVALID_DATA, "Start must be called before update.");
ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_PARAMETER, "Src must not be empty.");
@@ -338,7 +338,7 @@ X509CertificateMbedTLS *CryptoMbedTLS::get_default_certificates() {
return default_certs;
}
-void CryptoMbedTLS::load_default_certificates(String p_path) {
+void CryptoMbedTLS::load_default_certificates(const String &p_path) {
ERR_FAIL_COND(default_certs != nullptr);
default_certs = memnew(X509CertificateMbedTLS);
@@ -380,7 +380,7 @@ Ref<CryptoKey> CryptoMbedTLS::generate_rsa(int p_bytes) {
return out;
}
-Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) {
+Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoKey> p_key, const String &p_issuer_name, const String &p_not_before, const String &p_not_after) {
Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key);
ERR_FAIL_COND_V_MSG(key.is_null(), nullptr, "Invalid private key argument.");
mbedtls_x509write_cert crt;
@@ -452,7 +452,7 @@ mbedtls_md_type_t CryptoMbedTLS::md_type_from_hashtype(HashingContext::HashType
}
}
-Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key) {
+Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, Ref<CryptoKey> p_key) {
int size;
mbedtls_md_type_t type = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, size);
ERR_FAIL_COND_V_MSG(type == MBEDTLS_MD_NONE, Vector<uint8_t>(), "Invalid hash type.");
@@ -470,7 +470,7 @@ Vector<uint8_t> CryptoMbedTLS::sign(HashingContext::HashType p_hash_type, Vector
return out;
}
-bool CryptoMbedTLS::verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key) {
+bool CryptoMbedTLS::verify(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, const Vector<uint8_t> &p_signature, Ref<CryptoKey> p_key) {
int size;
mbedtls_md_type_t type = CryptoMbedTLS::md_type_from_hashtype(p_hash_type, size);
ERR_FAIL_COND_V_MSG(type == MBEDTLS_MD_NONE, false, "Invalid hash type.");
@@ -480,7 +480,7 @@ bool CryptoMbedTLS::verify(HashingContext::HashType p_hash_type, Vector<uint8_t>
return mbedtls_pk_verify(&(key->pkey), type, p_hash.ptr(), size, p_signature.ptr(), p_signature.size()) == 0;
}
-Vector<uint8_t> CryptoMbedTLS::encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) {
+Vector<uint8_t> CryptoMbedTLS::encrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_plaintext) {
Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key);
ERR_FAIL_COND_V_MSG(!key.is_valid(), Vector<uint8_t>(), "Invalid key provided.");
uint8_t buf[1024];
@@ -493,7 +493,7 @@ Vector<uint8_t> CryptoMbedTLS::encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_p
return out;
}
-Vector<uint8_t> CryptoMbedTLS::decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) {
+Vector<uint8_t> CryptoMbedTLS::decrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_ciphertext) {
Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS>>(p_key);
ERR_FAIL_COND_V_MSG(!key.is_valid(), Vector<uint8_t>(), "Invalid key provided.");
ERR_FAIL_COND_V_MSG(key->is_public_only(), Vector<uint8_t>(), "Invalid key provided. Cannot decrypt using a public_only key.");
diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h
index 0168e1f663..60a413ed7c 100644
--- a/modules/mbedtls/crypto_mbedtls.h
+++ b/modules/mbedtls/crypto_mbedtls.h
@@ -51,10 +51,10 @@ public:
static void make_default() { CryptoKey::_create = create; }
static void finalize() { CryptoKey::_create = nullptr; }
- virtual Error load(String p_path, bool p_public_only);
- virtual Error save(String p_path, bool p_public_only);
+ virtual Error load(const String &p_path, bool p_public_only);
+ virtual Error save(const String &p_path, bool p_public_only);
virtual String save_to_string(bool p_public_only);
- virtual Error load_from_string(String p_string_key, bool p_public_only);
+ virtual Error load_from_string(const String &p_string_key, bool p_public_only);
virtual bool is_public_only() const { return public_only; };
CryptoKeyMbedTLS() {
@@ -82,9 +82,9 @@ public:
static void make_default() { X509Certificate::_create = create; }
static void finalize() { X509Certificate::_create = nullptr; }
- virtual Error load(String p_path);
+ virtual Error load(const String &p_path);
virtual Error load_from_memory(const uint8_t *p_buffer, int p_len);
- virtual Error save(String p_path);
+ virtual Error save(const String &p_path);
virtual String save_to_string();
virtual Error load_from_string(const String &p_string_key);
@@ -116,8 +116,8 @@ public:
static bool is_md_type_allowed(mbedtls_md_type_t p_md_type);
- virtual Error start(HashingContext::HashType p_hash_type, PackedByteArray p_key);
- virtual Error update(PackedByteArray p_data);
+ virtual Error start(HashingContext::HashType p_hash_type, const PackedByteArray &p_key);
+ virtual Error update(const PackedByteArray &p_data);
virtual PackedByteArray finish();
HMACContextMbedTLS() {}
@@ -135,16 +135,16 @@ public:
static void initialize_crypto();
static void finalize_crypto();
static X509CertificateMbedTLS *get_default_certificates();
- static void load_default_certificates(String p_path);
+ static void load_default_certificates(const String &p_path);
static mbedtls_md_type_t md_type_from_hashtype(HashingContext::HashType p_hash_type, int &r_size);
virtual PackedByteArray generate_random_bytes(int p_bytes);
virtual Ref<CryptoKey> generate_rsa(int p_bytes);
- virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after);
- virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Ref<CryptoKey> p_key);
- virtual bool verify(HashingContext::HashType p_hash_type, Vector<uint8_t> p_hash, Vector<uint8_t> p_signature, Ref<CryptoKey> p_key);
- virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext);
- virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext);
+ virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, const String &p_issuer_name, const String &p_not_before, const String &p_not_after);
+ virtual Vector<uint8_t> sign(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, Ref<CryptoKey> p_key);
+ virtual bool verify(HashingContext::HashType p_hash_type, const Vector<uint8_t> &p_hash, const Vector<uint8_t> &p_signature, Ref<CryptoKey> p_key);
+ virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_plaintext);
+ virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, const Vector<uint8_t> &p_ciphertext);
CryptoMbedTLS();
~CryptoMbedTLS();
diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp
index 33c926689a..e4b54ef050 100644
--- a/modules/minimp3/resource_importer_mp3.cpp
+++ b/modules/minimp3/resource_importer_mp3.cpp
@@ -110,7 +110,7 @@ Ref<AudioStreamMP3> ResourceImporterMP3::import_mp3(const String &p_path) {
mp3_stream.instantiate();
mp3_stream->set_data(data);
- ERR_FAIL_COND_V(!mp3_stream->get_data().size(), Ref<AudioStreamMP3>());
+ ERR_FAIL_COND_V(mp3_stream->get_data().is_empty(), Ref<AudioStreamMP3>());
return mp3_stream;
}
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index e5ed8e9857..0345eebef6 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -307,7 +307,7 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
}
}
-bool CSharpLanguage::is_control_flow_keyword(String p_keyword) const {
+bool CSharpLanguage::is_control_flow_keyword(const String &p_keyword) const {
return p_keyword == "break" ||
p_keyword == "case" ||
p_keyword == "catch" ||
@@ -547,42 +547,9 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const {
}
String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
- Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type());
- // Always assign r_base_type and r_icon_path, even if the script
- // is not a global one. In the case that it is not a global script,
- // return an empty string AFTER assigning the return parameters.
- // See GDScriptLanguage::get_global_class_name() in modules/gdscript/gdscript.cpp
-
- if (!scr.is_valid() || !scr->valid) {
- // Invalid script.
- return String();
- }
-
- if (r_icon_path) {
- if (scr->icon_path.is_empty() || scr->icon_path.is_absolute_path()) {
- *r_icon_path = scr->icon_path.simplify_path();
- } else if (scr->icon_path.is_relative_path()) {
- *r_icon_path = p_path.get_base_dir().path_join(scr->icon_path).simplify_path();
- }
- }
- if (r_base_type) {
- bool found_global_base_script = false;
- const CSharpScript *top = scr->base_script.ptr();
- while (top != nullptr) {
- if (top->global_class) {
- *r_base_type = top->class_name;
- found_global_base_script = true;
- break;
- }
-
- top = top->base_script.ptr();
- }
- if (!found_global_base_script) {
- *r_base_type = scr->get_instance_base_type();
- }
- }
-
- return scr->global_class ? scr->class_name : String();
+ String class_name;
+ GDMonoCache::managed_callbacks.ScriptManagerBridge_GetGlobalClassName(&p_path, r_base_type, r_icon_path, &class_name);
+ return class_name;
}
String CSharpLanguage::debug_get_error() const {
@@ -686,25 +653,19 @@ struct CSharpScriptDepSort {
// Shouldn't happen but just in case...
return false;
}
- const CSharpScript *I = get_base_script(B.ptr()).ptr();
+ const Script *I = B->get_base_script().ptr();
while (I) {
if (I == A.ptr()) {
// A is a base of B
return true;
}
- I = get_base_script(I).ptr();
+ I = I->get_base_script().ptr();
}
// A isn't a base of B
return false;
}
-
- // Special fix for constructed generic types.
- Ref<CSharpScript> get_base_script(const CSharpScript *p_script) const {
- Ref<CSharpScript> base_script = p_script->base_script;
- return base_script.is_valid() && !base_script->class_name.is_empty() ? base_script : nullptr;
- }
};
void CSharpLanguage::reload_all_scripts() {
@@ -716,20 +677,15 @@ void CSharpLanguage::reload_all_scripts() {
}
void CSharpLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
- CRASH_COND(!Engine::get_singleton()->is_editor_hint());
-
- bool has_csharp_script = false;
- for (int i = 0; i < p_scripts.size(); ++i) {
- Ref<CSharpScript> cs_script = p_scripts[i];
- if (cs_script.is_valid()) {
- has_csharp_script = true;
- break;
- }
+#ifdef GD_MONO_HOT_RELOAD
+ if (is_assembly_reloading_needed()) {
+ reload_assemblies(p_soft_reload);
}
+#endif
+}
- if (!has_csharp_script) {
- return;
- }
+void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
+ CRASH_COND(!Engine::get_singleton()->is_editor_hint());
#ifdef TOOLS_ENABLED
get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
@@ -742,12 +698,6 @@ void CSharpLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload)
#endif
}
-void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
- Array scripts;
- scripts.push_back(p_script);
- reload_scripts(scripts, p_soft_reload);
-}
-
#ifdef GD_MONO_HOT_RELOAD
bool CSharpLanguage::is_assembly_reloading_needed() {
ERR_FAIL_NULL_V(gdmono, false);
@@ -937,7 +887,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
obj->set_script(Ref<RefCounted>()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
- scr->was_tool_before_reload = scr->tool;
+ scr->was_tool_before_reload = scr->type_info.is_tool;
scr->_clear();
}
@@ -997,7 +947,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->exports_invalidated = true;
#endif
- if (!scr->get_path().is_empty()) {
+ if (!scr->get_path().is_empty() && !scr->get_path().begins_with("csharp://")) {
scr->reload(p_soft_reload);
if (!scr->valid) {
@@ -1839,6 +1789,7 @@ bool CSharpInstance::_internal_new_managed() {
ERR_FAIL_NULL_V(owner, false);
ERR_FAIL_COND_V(script.is_null(), false);
+ ERR_FAIL_COND_V(!script->can_instantiate(), false);
bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance(
script.ptr(), owner, nullptr, 0);
@@ -2161,7 +2112,7 @@ void GD_CLR_STDCALL CSharpScript::_add_property_info_list_callback(CSharpScript
#ifdef TOOLS_ENABLED
p_script->exported_members_cache.push_back(PropertyInfo(
- Variant::NIL, *p_current_class_name, PROPERTY_HINT_NONE,
+ Variant::NIL, p_script->type_info.class_name, PROPERTY_HINT_NONE,
p_script->get_path(), PROPERTY_USAGE_CATEGORY));
#endif
@@ -2334,9 +2285,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
// Extract information about the script using the mono class.
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
- bool tool = false;
- bool global_class = false;
- bool abstract_class = false;
+ TypeInfo type_info;
// TODO: Use GDExtension godot_dictionary
Array methods_array;
@@ -2346,18 +2295,12 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
Dictionary signals_dict;
signals_dict.~Dictionary();
- String class_name;
- String icon_path;
Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
- p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path,
+ p_script.ptr(), &type_info,
&methods_array, &rpc_functions_dict, &signals_dict, &base_script);
- p_script->class_name = class_name;
- p_script->tool = tool;
- p_script->global_class = global_class;
- p_script->abstract_class = abstract_class;
- p_script->icon_path = icon_path;
+ p_script->type_info = type_info;
p_script->rpc_config.clear();
p_script->rpc_config = rpc_functions_dict;
@@ -2436,7 +2379,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool CSharpScript::can_instantiate() const {
#ifdef TOOLS_ENABLED
- bool extra_cond = tool || ScriptServer::is_scripting_enabled();
+ bool extra_cond = type_info.is_tool || ScriptServer::is_scripting_enabled();
#else
bool extra_cond = true;
#endif
@@ -2445,10 +2388,10 @@ bool CSharpScript::can_instantiate() const {
// For tool scripts, this will never fire if the class is not found. That's because we
// don't know if it's a tool script if we can't find the class to access the attributes.
if (extra_cond && !valid) {
- ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive).");
+ ERR_FAIL_V_MSG(false, "Cannot instantiate C# script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive).");
}
- return valid && !abstract_class && extra_cond;
+ return valid && type_info.can_instantiate() && extra_cond;
}
StringName CSharpScript::get_instance_base_type() const {
@@ -2458,6 +2401,8 @@ StringName CSharpScript::get_instance_base_type() const {
}
CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) {
+ ERR_FAIL_COND_V_MSG(!type_info.can_instantiate(), nullptr, "Cannot instantiate C# script. Script: '" + get_path() + "'.");
+
/* STEP 1, CREATE */
Ref<RefCounted> ref;
@@ -2772,11 +2717,11 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
}
Ref<Script> CSharpScript::get_base_script() const {
- return base_script.is_valid() && !base_script->get_path().is_empty() ? base_script : nullptr;
+ return base_script;
}
StringName CSharpScript::get_global_name() const {
- return global_class ? StringName(class_name) : StringName();
+ return type_info.is_global_class ? StringName(type_info.class_name) : StringName();
}
void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
@@ -2833,7 +2778,7 @@ Error CSharpScript::load_source_code(const String &p_path) {
}
void CSharpScript::_clear() {
- tool = false;
+ type_info = TypeInfo();
valid = false;
reload_invalidated = true;
}
@@ -2881,17 +2826,25 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
// TODO ignore anything inside bin/ and obj/ in tools builds?
+ String real_path = p_path;
+ if (p_path.begins_with("csharp://")) {
+ // This is a virtual path used by generic types, extract the real path.
+ real_path = "res://" + p_path.trim_prefix("csharp://");
+ real_path = real_path.substr(0, real_path.rfind(":"));
+ }
+
Ref<CSharpScript> scr;
if (GDMonoCache::godot_api_cache_updated) {
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr);
+ ERR_FAIL_NULL_V_MSG(scr, Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
} else {
scr = Ref<CSharpScript>(memnew(CSharpScript));
}
#if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED)
- Error err = scr->load_source_code(p_path);
- ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + p_path + "'.");
+ Error err = scr->load_source_code(real_path);
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + real_path + "'.");
#endif
// Only one instance of a C# script is allowed to exist.
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 58af93fc28..99e6ebf2e3 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -60,14 +60,88 @@ class CSharpScript : public Script {
friend class CSharpInstance;
friend class CSharpLanguage;
- friend struct CSharpScriptDepSort;
- bool tool = false;
- bool global_class = false;
- bool abstract_class = false;
+public:
+ struct TypeInfo {
+ /**
+ * Name of the C# class.
+ */
+ String class_name;
+
+ /**
+ * Path to the icon that will be used for this class by the editor.
+ */
+ String icon_path;
+
+ /**
+ * Script is marked as tool and runs in the editor.
+ */
+ bool is_tool = false;
+
+ /**
+ * Script is marked as global class and will be registered in the editor.
+ * Registered classes can be created using certain editor dialogs and
+ * can be referenced by name from other languages that support the feature.
+ */
+ bool is_global_class = false;
+
+ /**
+ * Script is declared abstract.
+ */
+ bool is_abstract = false;
+
+ /**
+ * The C# type that corresponds to this script is a constructed generic type.
+ * E.g.: `Dictionary<int, string>`
+ */
+ bool is_constructed_generic_type = false;
+
+ /**
+ * The C# type that corresponds to this script is a generic type definition.
+ * E.g.: `Dictionary<,>`
+ */
+ bool is_generic_type_definition = false;
+
+ /**
+ * The C# type that corresponds to this script contains generic type parameters,
+ * regardless of whether the type parameters are bound or not.
+ */
+ bool is_generic() const {
+ return is_constructed_generic_type || is_generic_type_definition;
+ }
+
+ /**
+ * Check if the script can be instantiated.
+ * C# types can't be instantiated if they are abstract or contain generic
+ * type parameters, but a CSharpScript is still created for them.
+ */
+ bool can_instantiate() const {
+ return !is_abstract && !is_generic_type_definition;
+ }
+ };
+
+private:
+ /**
+ * Contains the C# type information for this script.
+ */
+ TypeInfo type_info;
+
+ /**
+ * Scripts are valid when the corresponding C# class is found and used
+ * to extract the script info using the [update_script_class_info] method.
+ */
bool valid = false;
+ /**
+ * Scripts extract info from the C# class in the reload methods but,
+ * if the reload is not invalidated, then the current extracted info
+ * is still valid and there's no need to reload again.
+ */
bool reload_invalidated = false;
+ /**
+ * Base script that this script derives from, or null if it derives from a
+ * native Godot class.
+ */
Ref<CSharpScript> base_script;
HashSet<Object *> instances;
@@ -88,9 +162,10 @@ class CSharpScript : public Script {
HashSet<ObjectID> pending_replace_placeholders;
#endif
+ /**
+ * Script source code.
+ */
String source;
- String class_name;
- String icon_path;
SelfList<CSharpScript> script_list = this;
@@ -167,7 +242,7 @@ public:
return docs;
}
virtual String get_class_icon_path() const override {
- return icon_path;
+ return type_info.icon_path;
}
#endif // TOOLS_ENABLED
@@ -185,13 +260,13 @@ public:
void get_members(HashSet<StringName> *p_members) override;
bool is_tool() const override {
- return tool;
+ return type_info.is_tool;
}
bool is_valid() const override {
return valid;
}
bool is_abstract() const override {
- return abstract_class;
+ return type_info.is_abstract;
}
bool inherits_script(const Ref<Script> &p_script) const override;
@@ -418,7 +493,7 @@ public:
/* EDITOR FUNCTIONS */
void get_reserved_words(List<String> *p_words) const override;
- bool is_control_flow_keyword(String p_keyword) const override;
+ bool is_control_flow_keyword(const String &p_keyword) const override;
void get_comment_delimiters(List<String> *p_delimiters) const override;
void get_doc_comment_delimiters(List<String> *p_delimiters) const override;
void get_string_delimiters(List<String> *p_delimiters) const override;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
index 01aafe9c74..625a6f9921 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -54,11 +54,9 @@ namespace Godot.SourceGenerators
)
.Where(x =>
// Ignore classes whose name is not the same as the file name
- Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name &&
- // Ignore generic classes
- !x.symbol.IsGenericType)
- .GroupBy(x => x.symbol)
- .ToDictionary(g => g.Key, g => g.Select(x => x.cds));
+ Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name)
+ .GroupBy<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol), INamedTypeSymbol>(x => x.symbol, SymbolEqualityComparer.Default)
+ .ToDictionary<IGrouping<INamedTypeSymbol, (ClassDeclarationSyntax cds, INamedTypeSymbol symbol)>, INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>>(g => g.Key, g => g.Select(x => x.cds), SymbolEqualityComparer.Default);
foreach (var godotClass in godotClasses)
{
@@ -160,6 +158,8 @@ namespace Godot.SourceGenerators
first = false;
sourceBuilder.Append("typeof(");
sourceBuilder.Append(qualifiedName);
+ if (godotClass.Key.IsGenericType)
+ sourceBuilder.Append($"<{new string(',', godotClass.Key.TypeParameters.Count() - 1)}>");
sourceBuilder.Append(")");
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 7cf98b8f1f..6e99483a1c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -24,6 +24,20 @@ namespace GodotTools.Build
public static event Action<string> StdOutputReceived;
public static event Action<string> StdErrorReceived;
+ public static DateTime LastValidBuildDateTime { get; private set; }
+
+ static BuildManager()
+ {
+ UpdateLastValidBuildDateTime();
+ }
+
+ public static void UpdateLastValidBuildDateTime()
+ {
+ var dllName = $"{GodotSharpDirs.ProjectAssemblyName}.dll";
+ var path = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "Debug", dllName);
+ LastValidBuildDateTime = File.GetLastWriteTime(path);
+ }
+
private static void RemoveOldIssuesFile(BuildInfo buildInfo)
{
string issuesFile = GetIssuesFilePath(buildInfo);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index bae87dd1dd..9a02c9ca88 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -89,7 +89,10 @@ namespace GodotTools.Build
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
private void RebuildProject()
@@ -107,7 +110,10 @@ namespace GodotTools.Build
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
private void CleanProject()
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index c634d9c1ac..c4f0b77ecf 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -389,10 +389,10 @@ namespace GodotTools.Export
if (filterDir(dir))
{
addEntry(dir, false);
- }
- else if (recurseDir(dir))
- {
- RecursePublishContents(dir, filterDir, filterFile, recurseDir, addEntry);
+ if (recurseDir(dir))
+ {
+ RecursePublishContents(dir, filterDir, filterFile, recurseDir, addEntry);
+ }
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 5408b9b13e..44422eb862 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -54,8 +54,6 @@ namespace GodotTools
public bool SkipBuildBeforePlaying { get; set; } = false;
- public DateTime LastValidBuildDateTime { get; private set; }
-
[UsedImplicitly]
private bool CreateProjectSolutionIfNeeded()
{
@@ -441,21 +439,6 @@ namespace GodotTools
}
}
- private void UpdateLastValidBuildDateTime()
- {
- var dllName = $"{GodotSharpDirs.ProjectAssemblyName}.dll";
- var path = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "Debug", dllName);
- LastValidBuildDateTime = File.GetLastWriteTime(path);
- }
-
- private void BuildFinished(BuildResult buildResult)
- {
- if (buildResult == BuildResult.Success)
- {
- UpdateLastValidBuildDateTime();
- }
- }
-
private void BuildStateChanged()
{
if (_bottomPanelBtn != null)
@@ -466,8 +449,6 @@ namespace GodotTools
{
base._EnablePlugin();
- UpdateLastValidBuildDateTime();
-
ProjectSettings.SettingsChanged += GodotSharpDirs.DetermineProjectLocation;
if (Instance != null)
@@ -640,7 +621,6 @@ namespace GodotTools
var inspectorPlugin = new InspectorPlugin();
AddInspectorPlugin(inspectorPlugin);
_inspectorPluginWeak = WeakRef(inspectorPlugin);
- BuildManager.BuildFinished += BuildFinished;
BuildManager.Initialize();
RiderPathManager.Initialize();
@@ -657,7 +637,6 @@ namespace GodotTools
// Custom signals aren't automatically disconnected currently.
MSBuildPanel.BuildStateChanged -= BuildStateChanged;
- BuildManager.BuildFinished -= BuildFinished;
}
public override void _ExitTree()
diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
index eb42f01b3a..66717d8636 100644
--- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
@@ -1,7 +1,7 @@
using Godot;
+using GodotTools.Build;
using GodotTools.Internals;
using JetBrains.Annotations;
-using static GodotTools.Internals.Globals;
namespace GodotTools
{
@@ -16,14 +16,20 @@ namespace GodotTools
RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
}
private void TimerTimeout()
{
if (Internal.IsAssembliesReloadingNeeded())
+ {
+ BuildManager.UpdateLastValidBuildDateTime();
Internal.ReloadAssemblies(softReload: false);
+ }
}
[UsedImplicitly]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs
index 8282ca6ea6..b86a2b8b24 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Inspector/InspectorPlugin.cs
@@ -1,5 +1,7 @@
+using System;
using System.Collections.Generic;
using Godot;
+using GodotTools.Build;
using GodotTools.Utils;
namespace GodotTools.Inspector
@@ -22,9 +24,19 @@ namespace GodotTools.Inspector
{
foreach (var script in EnumerateScripts(godotObject))
{
- if (script is not CSharpScript) continue;
+ if (script is not CSharpScript)
+ continue;
- if (File.GetLastWriteTime(script.ResourcePath) > GodotSharpEditor.Instance.LastValidBuildDateTime)
+ string scriptPath = script.ResourcePath;
+ if (scriptPath.StartsWith("csharp://"))
+ {
+ // This is a virtual path used by generic types, extract the real path.
+ var scriptPathSpan = scriptPath.AsSpan("csharp://".Length);
+ scriptPathSpan = scriptPathSpan[..scriptPathSpan.IndexOf(':')];
+ scriptPath = $"res://{scriptPathSpan}";
+ }
+
+ if (File.GetLastWriteTime(scriptPath) > BuildManager.LastValidBuildDateTime)
{
AddCustomControl(new InspectorOutOfSyncWarning());
break;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index a78cb0bba9..6c34d7c29d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -18,6 +18,7 @@ namespace Godot.Bridge
public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
public delegate* unmanaged<IntPtr, godot_string_name*, void> ScriptManagerBridge_GetScriptNativeName;
+ public delegate* unmanaged<godot_string*, godot_string*, godot_string*, godot_string*, void> ScriptManagerBridge_GetGlobalClassName;
public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagerBridge_SetGodotObjectPtr;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits;
@@ -25,7 +26,7 @@ namespace Godot.Bridge
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
- public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
+ public delegate* unmanaged<IntPtr, godot_csharp_type_info*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
@@ -60,6 +61,7 @@ namespace Godot.Bridge
ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding,
ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance,
ScriptManagerBridge_GetScriptNativeName = &ScriptManagerBridge.GetScriptNativeName,
+ ScriptManagerBridge_GetGlobalClassName = &ScriptManagerBridge.GetGlobalClassName,
ScriptManagerBridge_SetGodotObjectPtr = &ScriptManagerBridge.SetGodotObjectPtr,
ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal,
ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index e344dc84c7..0f271d6547 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Serialization;
+using System.Text;
using Godot.NativeInterop;
namespace Godot.Bridge
@@ -29,7 +30,7 @@ namespace Godot.Bridge
foreach (var type in typesInAlc.Keys)
{
if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) &&
- !_pathTypeBiMap.TryGetScriptPath(type, out _))
+ (!_pathTypeBiMap.TryGetScriptPath(type, out string? scriptPath) || scriptPath.StartsWith("csharp://")))
{
// For scripts without a path, we need to keep the class qualified name for reloading
_scriptDataForReload.TryAdd(scriptPtr,
@@ -221,6 +222,71 @@ namespace Godot.Bridge
}
[UnmanagedCallersOnly]
+ internal static unsafe void GetGlobalClassName(godot_string* scriptPath, godot_string* outBaseType, godot_string* outIconPath, godot_string* outClassName)
+ {
+ // This method must always return the outBaseType for every script, even if the script is
+ // not a global class. But if the script is not a global class it must return an empty
+ // outClassName string since it should not have a name.
+ string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
+ Debug.Assert(!string.IsNullOrEmpty(scriptPathStr), "Script path can't be empty.");
+
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
+ {
+ // Script at the given path does not exist, or it's not a C# type.
+ // This is fine, it may be a path to a generic script and those can't be global classes.
+ *outClassName = default;
+ return;
+ }
+
+ if (outIconPath != null)
+ {
+ var iconAttr = scriptType.GetCustomAttributes(inherit: false)
+ .OfType<IconAttribute>()
+ .FirstOrDefault();
+
+ *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+ }
+
+ if (outBaseType != null)
+ {
+ bool foundGlobalBaseScript = false;
+
+ Type native = GodotObject.InternalGetClassNativeBase(scriptType);
+ Type? top = scriptType.BaseType;
+
+ while (top != null && top != native)
+ {
+ if (IsGlobalClass(top))
+ {
+ *outBaseType = Marshaling.ConvertStringToNative(top.Name);
+ foundGlobalBaseScript = true;
+ break;
+ }
+
+ top = top.BaseType;
+ }
+ if (!foundGlobalBaseScript)
+ {
+ *outBaseType = Marshaling.ConvertStringToNative(native.Name);
+ }
+ }
+
+ if (!IsGlobalClass(scriptType))
+ {
+ // Scripts that are not global classes should not have a name.
+ // Return an empty string to prevent the class from being registered
+ // as a global class in the editor.
+ *outClassName = default;
+ return;
+ }
+
+ *outClassName = Marshaling.ConvertStringToNative(scriptType.Name);
+
+ static bool IsGlobalClass(Type scriptType) =>
+ scriptType.IsDefined(typeof(GlobalClassAttribute), inherit: false);
+ }
+
+ [UnmanagedCallersOnly]
internal static void SetGodotObjectPtr(IntPtr gcHandlePtr, IntPtr newPtr)
{
try
@@ -333,7 +399,7 @@ namespace Godot.Bridge
foreach (var type in assembly.GetTypes())
{
- if (type.IsNested || type.IsGenericType)
+ if (type.IsNested)
continue;
if (!typeOfGodotObject.IsAssignableFrom(type))
@@ -352,9 +418,6 @@ namespace Godot.Bridge
{
foreach (var type in scriptTypes)
{
- if (type.IsGenericType)
- continue;
-
LookupScriptForClass(type);
}
}
@@ -422,20 +485,8 @@ namespace Godot.Bridge
{
try
{
- lock (_scriptTypeBiMap.ReadWriteLock)
- {
- if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
- {
- string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
-
- if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
- return godot_bool.False;
-
- _scriptTypeBiMap.Add(scriptPtr, scriptType);
- }
- }
-
- return godot_bool.True;
+ string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
+ return AddScriptBridgeCore(scriptPtr, scriptPathStr).ToGodotBool();
}
catch (Exception e)
{
@@ -444,6 +495,22 @@ namespace Godot.Bridge
}
}
+ private static unsafe bool AddScriptBridgeCore(IntPtr scriptPtr, string scriptPath)
+ {
+ lock (_scriptTypeBiMap.ReadWriteLock)
+ {
+ if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
+ {
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPath, out Type? scriptType))
+ return false;
+
+ _scriptTypeBiMap.Add(scriptPtr, scriptType);
+ }
+ }
+
+ return true;
+ }
+
[UnmanagedCallersOnly]
internal static unsafe void GetOrCreateScriptBridgeForPath(godot_string* scriptPath, godot_ref* outScript)
{
@@ -455,6 +522,8 @@ namespace Godot.Bridge
return;
}
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Cannot get or create script for a generic type definition '{scriptType.FullName}'. Path: '{scriptPathStr}'.");
+
GetOrCreateScriptBridgeForType(scriptType, outScript);
}
@@ -494,16 +563,51 @@ namespace Godot.Bridge
if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath))
return true;
+ if (scriptType.IsConstructedGenericType)
+ {
+ // If the script type is generic, also try looking for the path of the generic type definition
+ // since we can use it to create the script.
+ Type genericTypeDefinition = scriptType.GetGenericTypeDefinition();
+ if (_pathTypeBiMap.TryGetGenericTypeDefinitionPath(genericTypeDefinition, out scriptPath))
+ return true;
+ }
+
CreateScriptBridgeForType(scriptType, outScript);
scriptPath = null;
return false;
}
}
+ static string GetVirtualConstructedGenericTypeScriptPath(Type scriptType, string scriptPath)
+ {
+ // Constructed generic types all have the same path which is not allowed by Godot
+ // (every Resource must have a unique path). So we create a unique "virtual" path
+ // for each type.
+
+ if (!scriptPath.StartsWith("res://"))
+ {
+ throw new ArgumentException("Script path must start with 'res://'.", nameof(scriptPath));
+ }
+
+ scriptPath = scriptPath.Substring("res://".Length);
+ return $"csharp://{scriptPath}:{scriptType}.cs";
+ }
+
if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath))
{
// This path is slower, but it's only executed for the first instantiation of the type
+ if (scriptType.IsConstructedGenericType && !scriptPath.StartsWith("csharp://"))
+ {
+ // If the script type is generic it can't be loaded using the real script path.
+ // Construct a virtual path unique to this constructed generic type and add it
+ // to the path bimap so they can be found later by their virtual path.
+ // IMPORTANT: The virtual path must be added to _pathTypeBiMap before the first
+ // load of the script, otherwise the loaded script won't be added to _scriptTypeBiMap.
+ scriptPath = GetVirtualConstructedGenericTypeScriptPath(scriptType, scriptPath);
+ _pathTypeBiMap.Add(scriptPath, scriptType);
+ }
+
// This must be done outside the read-write lock, as the script resource loading can lock it
using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath);
if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool())
@@ -514,11 +618,23 @@ namespace Godot.Bridge
// with no path, as we do for types without an associated script file.
GetOrCreateScriptBridgeForType(scriptType, outScript);
}
+
+ if (scriptType.IsConstructedGenericType)
+ {
+ // When reloading generic scripts they won't be added to the script bimap because their
+ // virtual path won't be in the path bimap yet. The current method executes when a derived type
+ // is trying to get or create the script for their base type. The code above has now added
+ // the virtual path to the path bimap and loading the script with that path should retrieve
+ // any existing script, so now we have a chance to make sure it's added to the script bimap.
+ AddScriptBridgeCore(outScript->Reference, scriptPath);
+ }
}
}
private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
{
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Script type must be a constructed generic type or not generic at all. Type: {scriptType}.");
+
NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
IntPtr scriptPtr = outScript->Reference;
@@ -605,45 +721,82 @@ namespace Godot.Bridge
}
}
- [UnmanagedCallersOnly]
- internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_string* outClassName,
- godot_bool* outTool, godot_bool* outGlobal, godot_bool* outAbstract, godot_string* outIconPath,
- godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest,
- godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
+ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_info* outTypeInfo)
{
- try
+ Type native = GodotObject.InternalGetClassNativeBase(scriptType);
+
+ string typeName = scriptType.Name;
+ if (scriptType.IsGenericType)
{
- // Performance is not critical here as this will be replaced with source generators.
- var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
+ var sb = new StringBuilder();
+ AppendTypeName(sb, scriptType);
+ typeName = sb.ToString();
+ }
- *outClassName = Marshaling.ConvertStringToNative(scriptType.Name);
+ godot_string className = Marshaling.ConvertStringToNative(typeName);
- *outTool = scriptType.GetCustomAttributes(inherit: false)
- .OfType<ToolAttribute>()
- .Any().ToGodotBool();
+ bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
- if (!(*outTool).ToBool() && scriptType.IsNested)
- {
- *outTool = (scriptType.DeclaringType?.GetCustomAttributes(inherit: false)
- .OfType<ToolAttribute>()
- .Any() ?? false).ToGodotBool();
- }
+ // If the type is nested and the parent type is a tool script,
+ // consider the nested type a tool script as well.
+ if (!isTool && scriptType.IsNested)
+ {
+ isTool = scriptType.DeclaringType?.IsDefined(typeof(ToolAttribute), inherit: false) ?? false;
+ }
- if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
- *outTool = godot_bool.True;
+ // Every script in the GodotTools assembly is a tool script.
+ if (!isTool && scriptType.Assembly.GetName().Name == "GodotTools")
+ {
+ isTool = true;
+ }
- var globalAttr = scriptType.GetCustomAttributes(inherit: false)
- .OfType<GlobalClassAttribute>()
- .FirstOrDefault();
+ bool isGlobalClass = scriptType.IsDefined(typeof(GlobalClassAttribute), inherit: false);
- *outGlobal = (globalAttr != null).ToGodotBool();
+ var iconAttr = scriptType.GetCustomAttributes(inherit: false)
+ .OfType<IconAttribute>()
+ .FirstOrDefault();
- var iconAttr = scriptType.GetCustomAttributes(inherit: false)
- .OfType<IconAttribute>()
- .FirstOrDefault();
- *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+ godot_string iconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
- *outAbstract = scriptType.IsAbstract.ToGodotBool();
+ outTypeInfo->ClassName = className;
+ outTypeInfo->IconPath = iconPath;
+ outTypeInfo->IsTool = isTool.ToGodotBool();
+ outTypeInfo->IsGlobalClass = isGlobalClass.ToGodotBool();
+ outTypeInfo->IsAbstract = scriptType.IsAbstract.ToGodotBool();
+ outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
+ outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.ToGodotBool();
+
+ static void AppendTypeName(StringBuilder sb, Type type)
+ {
+ sb.Append(type.Name);
+ if (type.IsGenericType)
+ {
+ sb.Append('<');
+ for (int i = 0; i < type.GenericTypeArguments.Length; i++)
+ {
+ Type typeArg = type.GenericTypeArguments[i];
+ AppendTypeName(sb, typeArg);
+ if (i != type.GenericTypeArguments.Length - 1)
+ {
+ sb.Append(", ");
+ }
+ }
+ sb.Append('>');
+ }
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_csharp_type_info* outTypeInfo,
+ godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
+ {
+ try
+ {
+ // Performance is not critical here as this will be replaced with source generators.
+ var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"Script type must be a constructed generic type or not generic at all. Type: {scriptType}.");
+
+ GetScriptTypeInfo(scriptType, outTypeInfo);
// Methods
@@ -820,11 +973,7 @@ namespace Godot.Bridge
catch (Exception e)
{
ExceptionUtils.LogException(e);
- *outClassName = default;
- *outTool = godot_bool.False;
- *outGlobal = godot_bool.False;
- *outAbstract = godot_bool.False;
- *outIconPath = default;
+ *outTypeInfo = default;
*outMethodsDest = NativeFuncs.godotsharp_array_new();
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
*outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new();
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
index 7fa3498b92..1ec1a75516 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -19,6 +20,8 @@ public static partial class ScriptManagerBridge
{
// TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading?
+ Debug.Assert(!scriptType.IsGenericTypeDefinition, $"A generic type definition must never be added to the script type map. Type: {scriptType}.");
+
_scriptTypeMap.Add(scriptPtr, scriptType);
_typeScriptMap.Add(scriptType, scriptPtr);
@@ -85,10 +88,29 @@ public static partial class ScriptManagerBridge
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) =>
- _pathTypeMap.TryGetValue(scriptPath, out scriptType);
+ // This must never return true for a generic type definition, we only consider script types
+ // the types that can be attached to a Node/Resource (non-generic or constructed generic types).
+ _pathTypeMap.TryGetValue(scriptPath, out scriptType) && !scriptType.IsGenericTypeDefinition;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath)
+ {
+ if (scriptType.IsGenericTypeDefinition)
+ {
+ // This must never return true for a generic type definition, we only consider script types
+ // the types that can be attached to a Node/Resource (non-generic or constructed generic types).
+ scriptPath = null;
+ return false;
+ }
+
+ return _typePathMap.TryGetValue(scriptType, out scriptPath);
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath) =>
- _typePathMap.TryGetValue(scriptType, out scriptPath);
+ public bool TryGetGenericTypeDefinitionPath(Type genericTypeDefinition, [MaybeNullWhen(false)] out string scriptPath)
+ {
+ Debug.Assert(genericTypeDefinition.IsGenericTypeDefinition);
+ return _typePathMap.TryGetValue(genericTypeDefinition, out scriptPath);
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
index c806263edb..a8642a916c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
@@ -105,6 +105,61 @@ namespace Godot.NativeInterop
}
}
+ [StructLayout(LayoutKind.Sequential)]
+ // ReSharper disable once InconsistentNaming
+ public ref struct godot_csharp_type_info
+ {
+ private godot_string _className;
+ private godot_string _iconPath;
+ private godot_bool _isTool;
+ private godot_bool _isGlobalClass;
+ private godot_bool _isAbstract;
+ private godot_bool _isConstructedGenericType;
+ private godot_bool _isGenericTypeDefinition;
+
+ public godot_string ClassName
+ {
+ readonly get => _className;
+ set => _className = value;
+ }
+
+ public godot_string IconPath
+ {
+ readonly get => _iconPath;
+ set => _iconPath = value;
+ }
+
+ public godot_bool IsTool
+ {
+ readonly get => _isTool;
+ set => _isTool = value;
+ }
+
+ public godot_bool IsGlobalClass
+ {
+ readonly get => _isGlobalClass;
+ set => _isGlobalClass = value;
+ }
+
+ public godot_bool IsAbstract
+ {
+ readonly get => _isAbstract;
+ set => _isAbstract = value;
+ }
+
+ public godot_bool IsConstructedGenericType
+ {
+ readonly get => _isConstructedGenericType;
+ set => _isConstructedGenericType = value;
+ }
+
+ public godot_bool IsGenericTypeDefinition
+ {
+ readonly get => _isGenericTypeDefinition;
+ set => _isGenericTypeDefinition = value;
+ }
+ }
+
[StructLayout(LayoutKind.Sequential, Pack = 8)]
// ReSharper disable once InconsistentNaming
public ref struct godot_variant
@@ -907,7 +962,7 @@ namespace Godot.NativeInterop
public readonly unsafe int Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _ptr != null ? *(_ptr - 1) : 0;
+ get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0;
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
index 4c9e21fb79..a646caad3e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs
@@ -254,7 +254,7 @@ namespace Godot
/// <param name="near">The near clipping distance.</param>
/// <param name="far">The far clipping distance.</param>
/// <returns>The created projection.</returns>
- public static Projection CreateFrustum(real_t left, real_t right, real_t bottom, real_t top, real_t near, real_t far)
+ public static Projection CreateFrustum(real_t left, real_t right, real_t bottom, real_t top, real_t depth_near, real_t depth_far)
{
if (right <= left)
{
@@ -264,18 +264,18 @@ namespace Godot
{
throw new ArgumentException("top is less or equal to bottom.");
}
- if (far <= near)
+ if (depth_far <= depth_near)
{
throw new ArgumentException("far is less or equal to near.");
}
- real_t x = 2 * near / (right - left);
- real_t y = 2 * near / (top - bottom);
+ real_t x = 2 * depth_near / (right - left);
+ real_t y = 2 * depth_near / (top - bottom);
real_t a = (right + left) / (right - left);
real_t b = (top + bottom) / (top - bottom);
- real_t c = -(far + near) / (far - near);
- real_t d = -2 * far * near / (far - near);
+ real_t c = -(depth_far + depth_near) / (depth_far - depth_near);
+ real_t d = -2 * depth_far * depth_near / (depth_far - depth_near);
return new Projection(
new Vector4(x, 0, 0, 0),
@@ -297,13 +297,13 @@ namespace Godot
/// <param name="far">The far clipping distance.</param>
/// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param>
/// <returns>The created projection.</returns>
- public static Projection CreateFrustumAspect(real_t size, real_t aspect, Vector2 offset, real_t near, real_t far, bool flipFov)
+ public static Projection CreateFrustumAspect(real_t size, real_t aspect, Vector2 offset, real_t depth_near, real_t depth_far, bool flipFov)
{
if (!flipFov)
{
size *= aspect;
}
- return CreateFrustum(-size / 2 + offset.X, +size / 2 + offset.X, -size / aspect / 2 + offset.Y, +size / aspect / 2 + offset.Y, near, far);
+ return CreateFrustum(-size / 2 + offset.X, +size / 2 + offset.X, -size / aspect / 2 + offset.Y, +size / aspect / 2 + offset.Y, depth_near, depth_far);
}
/// <summary>
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index ef4e32e4a7..145f4cee90 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -59,6 +59,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectScriptInstance);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetScriptNativeName);
+ CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetGlobalClassName);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SetGodotObjectPtr);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RaiseEventSignal);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, ScriptIsOrInherits);
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index dac8cdcaef..46e9ab10cb 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -84,6 +84,7 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *);
using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t);
using FuncScriptManagerBridge_GetScriptNativeName = void(GD_CLR_STDCALL *)(const CSharpScript *, StringName *);
+ using FuncScriptManagerBridge_GetGlobalClassName = void(GD_CLR_STDCALL *)(const String *, String *, String *, String *);
using FuncScriptManagerBridge_SetGodotObjectPtr = void(GD_CLR_STDCALL *)(GCHandleIntPtr, Object *);
using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, bool *);
using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *);
@@ -91,7 +92,7 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
- using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
+ using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, CSharpScript::TypeInfo *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);
@@ -120,6 +121,7 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding;
FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
FuncScriptManagerBridge_GetScriptNativeName ScriptManagerBridge_GetScriptNativeName;
+ FuncScriptManagerBridge_GetGlobalClassName ScriptManagerBridge_GetGlobalClassName;
FuncScriptManagerBridge_SetGodotObjectPtr ScriptManagerBridge_SetGodotObjectPtr;
FuncScriptManagerBridge_RaiseEventSignal ScriptManagerBridge_RaiseEventSignal;
FuncScriptManagerBridge_ScriptIsOrInherits ScriptManagerBridge_ScriptIsOrInherits;
diff --git a/modules/multiplayer/doc_classes/SceneReplicationConfig.xml b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
index 1a51e4b6e9..8b9203d316 100644
--- a/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
+++ b/modules/multiplayer/doc_classes/SceneReplicationConfig.xml
@@ -51,20 +51,18 @@
Returns whether the property identified by the given [param path] is configured to be synchronized on spawn.
</description>
</method>
- <method name="property_get_sync" is_deprecated="true">
+ <method name="property_get_sync" deprecated="Use [method property_get_replication_mode] instead.">
<return type="bool" />
<param index="0" name="path" type="NodePath" />
<description>
Returns whether the property identified by the given [param path] is configured to be synchronized on process.
- [i]Deprecated.[/i] Use [method property_get_replication_mode] instead.
</description>
</method>
- <method name="property_get_watch" is_deprecated="true">
+ <method name="property_get_watch" deprecated="Use [method property_get_replication_mode] instead.">
<return type="bool" />
<param index="0" name="path" type="NodePath" />
<description>
Returns whether the property identified by the given [param path] is configured to be reliably synchronized when changes are detected on process.
- [i]Deprecated.[/i] Use [method property_get_replication_mode] instead.
</description>
</method>
<method name="property_set_replication_mode">
@@ -83,22 +81,20 @@
Sets whether the property identified by the given [param path] is configured to be synchronized on spawn.
</description>
</method>
- <method name="property_set_sync" is_deprecated="true">
+ <method name="property_set_sync" deprecated="Use [method property_set_replication_mode] with [constant REPLICATION_MODE_ALWAYS] instead.">
<return type="void" />
<param index="0" name="path" type="NodePath" />
<param index="1" name="enabled" type="bool" />
<description>
Sets whether the property identified by the given [param path] is configured to be synchronized on process.
- [i]Deprecated.[/i] Use [method property_set_replication_mode] with [constant REPLICATION_MODE_ALWAYS] instead.
</description>
</method>
- <method name="property_set_watch" is_deprecated="true">
+ <method name="property_set_watch" deprecated="Use [method property_set_replication_mode] with [constant REPLICATION_MODE_ON_CHANGE] instead.">
<return type="void" />
<param index="0" name="path" type="NodePath" />
<param index="1" name="enabled" type="bool" />
<description>
Sets whether the property identified by the given [param path] is configured to be reliably synchronized when changes are detected on process.
- [i]Deprecated.[/i] Use [method property_set_replication_mode] with [constant REPLICATION_MODE_ON_CHANGE] instead.
</description>
</method>
<method name="remove_property">
diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp
index a4d2aed2d6..c816bd3b6b 100644
--- a/modules/multiplayer/multiplayer_debugger.cpp
+++ b/modules/multiplayer/multiplayer_debugger.cpp
@@ -89,7 +89,7 @@ Error MultiplayerDebugger::_capture(void *p_user, const String &p_msg, const Arr
// BandwidthProfiler
int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
- ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
+ ERR_FAIL_COND_V(p_buffer.is_empty(), 0);
int total_bandwidth = 0;
uint64_t timestamp = OS::get_singleton()->get_ticks_msec();
@@ -174,7 +174,7 @@ Array MultiplayerDebugger::RPCFrame::serialize() {
}
bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) {
- ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ ERR_FAIL_COND_V(p_arr.is_empty(), false);
uint32_t size = p_arr[0];
ERR_FAIL_COND_V(size % 6, false);
ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
@@ -279,7 +279,7 @@ Array MultiplayerDebugger::ReplicationFrame::serialize() {
}
bool MultiplayerDebugger::ReplicationFrame::deserialize(const Array &p_arr) {
- ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ ERR_FAIL_COND_V(p_arr.is_empty(), false);
uint32_t size = p_arr[0];
ERR_FAIL_COND_V(size % 7, false);
ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 264a2e9c8e..fecefc7e71 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -87,8 +87,8 @@ void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const {
}
#endif
-PackedStringArray MultiplayerSpawner::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+Array MultiplayerSpawner::get_configuration_warnings() const {
+ Array warnings = Node::get_configuration_warnings();
if (spawn_path.is_empty() || !has_node(spawn_path)) {
warnings.push_back(RTR("A valid NodePath must be set in the \"Spawn Path\" property in order for MultiplayerSpawner to be able to spawn Nodes."));
diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h
index 0e94b781ea..6cd2946df7 100644
--- a/modules/multiplayer/multiplayer_spawner.h
+++ b/modules/multiplayer/multiplayer_spawner.h
@@ -91,7 +91,7 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
#endif
public:
- PackedStringArray get_configuration_warnings() const override;
+ Array get_configuration_warnings() const override;
Node *get_spawn_node() const {
return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr;
diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp
index 02e3a11964..16df9da78e 100644
--- a/modules/multiplayer/multiplayer_synchronizer.cpp
+++ b/modules/multiplayer/multiplayer_synchronizer.cpp
@@ -143,8 +143,8 @@ bool MultiplayerSynchronizer::update_inbound_sync_time(uint16_t p_network_time)
return true;
}
-PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+Array MultiplayerSynchronizer::get_configuration_warnings() const {
+ Array warnings = Node::get_configuration_warnings();
if (root_path.is_empty() || !has_node(root_path)) {
warnings.push_back(RTR("A valid NodePath must be set in the \"Root Path\" property in order for MultiplayerSynchronizer to be able to synchronize properties."));
diff --git a/modules/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h
index 192d7a5920..f5f3993f0d 100644
--- a/modules/multiplayer/multiplayer_synchronizer.h
+++ b/modules/multiplayer/multiplayer_synchronizer.h
@@ -91,7 +91,7 @@ public:
bool update_outbound_sync_time(uint64_t p_usec);
bool update_inbound_sync_time(uint16_t p_network_time);
- PackedStringArray get_configuration_warnings() const override;
+ Array get_configuration_warnings() const override;
void set_replication_interval(double p_interval);
double get_replication_interval() const;
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index 665b246bc5..5655467df7 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -440,7 +440,7 @@ void SceneMultiplayer::disconnect_peer(int p_id) {
}
Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) {
- ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet.");
+ ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_DATA, "Trying to send an empty raw packet.");
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a multiplayer peer which is not connected.");
@@ -460,7 +460,7 @@ Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer
Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) {
ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!pending_peers.has(p_to), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_data.is_empty(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(pending_peers[p_to].local, ERR_FILE_CANT_WRITE, "The authentication session was previously marked as completed, no more authentication data can be sent.");
ERR_FAIL_COND_V_MSG(pending_peers[p_to].remote, ERR_FILE_CANT_WRITE, "The remote peer notified that the authentication session was completed, no more authentication data can be sent.");
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index 35ff62dd06..b61cf0bf1d 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -783,7 +783,7 @@ Error SceneReplicationInterface::on_delta_receive(int p_from, const uint8_t *p_b
ERR_CONTINUE_MSG(true, "Ignoring delta for non-authority or invalid synchronizer.");
}
List<NodePath> props = sync->get_delta_properties(indexes);
- ERR_FAIL_COND_V(props.size() == 0, ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(props.is_empty(), ERR_INVALID_DATA);
Vector<Variant> vars;
vars.resize(props.size());
int consumed = 0;
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index 5a27f315b9..14c72f3db4 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -180,6 +180,20 @@ real_t GodotNavigationServer::map_get_cell_height(RID p_map) const {
return map->get_cell_height();
}
+COMMAND_2(map_set_merge_rasterizer_cell_scale, RID, p_map, float, p_value) {
+ NavMap *map = map_owner.get_or_null(p_map);
+ ERR_FAIL_NULL(map);
+
+ map->set_merge_rasterizer_cell_scale(p_value);
+}
+
+float GodotNavigationServer::map_get_merge_rasterizer_cell_scale(RID p_map) const {
+ NavMap *map = map_owner.get_or_null(p_map);
+ ERR_FAIL_NULL_V(map, false);
+
+ return map->get_merge_rasterizer_cell_scale();
+}
+
COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled) {
NavMap *map = map_owner.get_or_null(p_map);
ERR_FAIL_NULL(map);
@@ -1116,6 +1130,10 @@ void GodotNavigationServer::bake_from_source_geometry_data_async(const Ref<Navig
#endif // _3D_DISABLED
}
+bool GodotNavigationServer::is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const {
+ return NavMeshGenerator3D::get_singleton()->is_baking(p_navigation_mesh);
+}
+
COMMAND_1(free, RID, p_object) {
if (map_owner.owns(p_object)) {
NavMap *map = map_owner.get_or_null(p_object);
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index f19450db27..f3bc1185d8 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -117,6 +117,9 @@ public:
COMMAND_2(map_set_cell_height, RID, p_map, real_t, p_cell_height);
virtual real_t map_get_cell_height(RID p_map) const override;
+ COMMAND_2(map_set_merge_rasterizer_cell_scale, RID, p_map, float, p_value);
+ virtual float map_get_merge_rasterizer_cell_scale(RID p_map) const override;
+
COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled);
virtual bool map_get_use_edge_connections(RID p_map) const override;
@@ -258,6 +261,7 @@ public:
virtual void parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual bool is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const override;
COMMAND_1(free, RID, p_object);
diff --git a/modules/navigation/godot_navigation_server_2d.cpp b/modules/navigation/godot_navigation_server_2d.cpp
index 76bfd3a101..5bd4a37fd7 100644
--- a/modules/navigation/godot_navigation_server_2d.cpp
+++ b/modules/navigation/godot_navigation_server_2d.cpp
@@ -221,6 +221,10 @@ void GodotNavigationServer2D::bake_from_source_geometry_data_async(const Ref<Nav
#endif // CLIPPER2_ENABLED
}
+bool GodotNavigationServer2D::is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const {
+ return NavMeshGenerator2D::get_singleton()->is_baking(p_navigation_polygon);
+}
+
GodotNavigationServer2D::GodotNavigationServer2D() {}
GodotNavigationServer2D::~GodotNavigationServer2D() {}
diff --git a/modules/navigation/godot_navigation_server_2d.h b/modules/navigation/godot_navigation_server_2d.h
index 2f473da1ab..08f1730441 100644
--- a/modules/navigation/godot_navigation_server_2d.h
+++ b/modules/navigation/godot_navigation_server_2d.h
@@ -250,6 +250,7 @@ public:
virtual void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override;
+ virtual bool is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const override;
};
#endif // GODOT_NAVIGATION_SERVER_2D_H
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 6429513b53..9482da39ef 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -67,6 +67,7 @@ void NavMap::set_cell_size(real_t p_cell_size) {
return;
}
cell_size = p_cell_size;
+ _update_merge_rasterizer_cell_dimensions();
regenerate_polygons = true;
}
@@ -75,6 +76,16 @@ void NavMap::set_cell_height(real_t p_cell_height) {
return;
}
cell_height = p_cell_height;
+ _update_merge_rasterizer_cell_dimensions();
+ regenerate_polygons = true;
+}
+
+void NavMap::set_merge_rasterizer_cell_scale(float p_value) {
+ if (merge_rasterizer_cell_scale == p_value) {
+ return;
+ }
+ merge_rasterizer_cell_scale = p_value;
+ _update_merge_rasterizer_cell_dimensions();
regenerate_polygons = true;
}
@@ -103,9 +114,9 @@ void NavMap::set_link_connection_radius(real_t p_link_connection_radius) {
}
gd::PointKey NavMap::get_point_key(const Vector3 &p_pos) const {
- const int x = static_cast<int>(Math::floor(p_pos.x / cell_size));
- const int y = static_cast<int>(Math::floor(p_pos.y / cell_height));
- const int z = static_cast<int>(Math::floor(p_pos.z / cell_size));
+ const int x = static_cast<int>(Math::floor(p_pos.x / merge_rasterizer_cell_size));
+ const int y = static_cast<int>(Math::floor(p_pos.y / merge_rasterizer_cell_height));
+ const int z = static_cast<int>(Math::floor(p_pos.z / merge_rasterizer_cell_size));
gd::PointKey p;
p.key = 0;
@@ -116,7 +127,11 @@ gd::PointKey NavMap::get_point_key(const Vector3 &p_pos) const {
}
Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector<Vector3>(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector<Vector3>(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
+
// Clear metadata outputs.
if (r_path_types) {
r_path_types->clear();
@@ -576,7 +591,11 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
}
Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
+
bool use_collision = p_use_collision;
Vector3 closest_point;
real_t closest_point_d = FLT_MAX;
@@ -624,24 +643,35 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector
}
Vector3 NavMap::get_closest_point(const Vector3 &p_point) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
return cp.point;
}
Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(Vector3(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
return cp.normal;
}
RID NavMap::get_closest_point_owner(const Vector3 &p_point) const {
- ERR_FAIL_COND_V_MSG(map_update_id == 0, RID(), "NavigationServer map query failed because it was made before first map synchronization.");
+ RWLockRead read_lock(map_rwlock);
+ if (map_update_id == 0) {
+ ERR_FAIL_V_MSG(RID(), "NavigationServer map query failed because it was made before first map synchronization.");
+ }
gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
return cp.owner;
}
gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_point) const {
+ RWLockRead read_lock(map_rwlock);
+
gd::ClosestPointQueryResult result;
real_t closest_point_ds = FLT_MAX;
@@ -770,6 +800,8 @@ void NavMap::remove_agent_as_controlled(NavAgent *agent) {
}
Vector3 NavMap::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
+ RWLockRead read_lock(map_rwlock);
+
const LocalVector<NavRegion *> map_regions = get_regions();
if (map_regions.is_empty()) {
@@ -834,6 +866,8 @@ Vector3 NavMap::get_random_point(uint32_t p_navigation_layers, bool p_uniformly)
}
void NavMap::sync() {
+ RWLockWrite write_lock(map_rwlock);
+
// Performance Monitor
int _new_pm_region_count = regions.size();
int _new_pm_agent_count = agents.size();
@@ -923,7 +957,7 @@ void NavMap::sync() {
connections[ek].push_back(new_connection);
} else {
// The edge is already connected with another edge, skip.
- ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'.");
+ ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'. If you're certain none of above is the case, change 'navigation/3d/merge_rasterizer_cell_scale' to 0.001.");
}
}
}
@@ -1365,6 +1399,11 @@ void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys
}
}
+void NavMap::_update_merge_rasterizer_cell_dimensions() {
+ merge_rasterizer_cell_size = cell_size * merge_rasterizer_cell_scale;
+ merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale;
+}
+
NavMap::NavMap() {
avoidance_use_multiple_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_multiple_threads");
avoidance_use_high_priority_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_high_priority_threads");
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index e8cbe7e247..311a265e0c 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -48,6 +48,8 @@ class NavAgent;
class NavObstacle;
class NavMap : public NavRid {
+ RWLock map_rwlock;
+
/// Map Up
Vector3 up = Vector3(0, 1, 0);
@@ -56,6 +58,12 @@ class NavMap : public NavRid {
real_t cell_size = 0.25; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
real_t cell_height = 0.25; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+ // For the inter-region merging to work, internal rasterization is performed.
+ float merge_rasterizer_cell_size = 0.25;
+ float merge_rasterizer_cell_height = 0.25;
+ // This value is used to control sensitivity of internal rasterizer.
+ float merge_rasterizer_cell_scale = 1.0;
+
bool use_edge_connections = true;
/// This value is used to detect the near edges to connect.
real_t edge_connection_margin = 0.25;
@@ -133,6 +141,11 @@ public:
void set_cell_height(real_t p_cell_height);
real_t get_cell_height() const { return cell_height; }
+ void set_merge_rasterizer_cell_scale(float p_value);
+ float get_merge_rasterizer_cell_scale() const {
+ return merge_rasterizer_cell_scale;
+ }
+
void set_use_edge_connections(bool p_enabled);
bool get_use_edge_connections() const {
return use_edge_connections;
@@ -217,6 +230,8 @@ private:
void _update_rvo_obstacles_tree_2d();
void _update_rvo_agents_tree_2d();
void _update_rvo_agents_tree_3d();
+
+ void _update_merge_rasterizer_cell_dimensions();
};
#endif // NAV_MAP_H
diff --git a/modules/navigation/nav_mesh_generator_2d.cpp b/modules/navigation/nav_mesh_generator_2d.cpp
index 6dfafa4e91..0cb0648906 100644
--- a/modules/navigation/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/nav_mesh_generator_2d.cpp
@@ -157,11 +157,10 @@ void NavMeshGenerator2D::bake_from_source_geometry_data(Ref<NavigationPolygon> p
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -193,11 +192,10 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish.");
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -212,6 +210,13 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
generator_task_mutex.unlock();
}
+bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) {
+ baking_navmesh_mutex.lock();
+ bool baking = baking_navmeshes.has(p_navigation_polygon);
+ baking_navmesh_mutex.unlock();
+ return baking;
+}
+
void NavMeshGenerator2D::generator_thread_bake(void *p_arg) {
NavMeshGeneratorTask2D *generator_task = static_cast<NavMeshGeneratorTask2D *>(p_arg);
@@ -738,9 +743,13 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
Paths64 traversable_polygon_paths;
Paths64 obstruction_polygon_paths;
+ traversable_polygon_paths.reserve(outline_count + traversable_outlines.size());
+ obstruction_polygon_paths.reserve(obstruction_outlines.size());
+
for (int i = 0; i < outline_count; i++) {
const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i);
Path64 subject_path;
+ subject_path.reserve(traversable_outline.size());
for (const Vector2 &traversable_point : traversable_outline) {
const Point64 &point = Point64(traversable_point.x, traversable_point.y);
subject_path.push_back(point);
@@ -750,6 +759,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
Path64 subject_path;
+ subject_path.reserve(traversable_outline.size());
for (const Vector2 &traversable_point : traversable_outline) {
const Point64 &point = Point64(traversable_point.x, traversable_point.y);
subject_path.push_back(point);
@@ -759,6 +769,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
Path64 clip_path;
+ clip_path.reserve(obstruction_outline.size());
for (const Vector2 &obstruction_point : obstruction_outline) {
const Point64 &point = Point64(obstruction_point.x, obstruction_point.y);
clip_path.push_back(point);
@@ -766,6 +777,22 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
obstruction_polygon_paths.push_back(clip_path);
}
+ Rect2 baking_rect = p_navigation_mesh->get_baking_rect();
+ if (baking_rect.has_area()) {
+ Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset();
+
+ const int rect_begin_x = baking_rect.position[0] + baking_rect_offset.x;
+ const int rect_begin_y = baking_rect.position[1] + baking_rect_offset.y;
+ const int rect_end_x = baking_rect.position[0] + baking_rect.size[0] + baking_rect_offset.x;
+ const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y;
+
+ Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y);
+ RectClip rect_clip = RectClip(clipper_rect);
+
+ traversable_polygon_paths = rect_clip.Execute(traversable_polygon_paths);
+ obstruction_polygon_paths = rect_clip.Execute(obstruction_polygon_paths);
+ }
+
Paths64 path_solution;
// first merge all traversable polygons according to user specified fill rule
@@ -782,6 +809,21 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
}
//path_solution = RamerDouglasPeucker(path_solution, 0.025); //
+ real_t border_size = p_navigation_mesh->get_border_size();
+ if (baking_rect.has_area() && border_size > 0.0) {
+ Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset();
+
+ const int rect_begin_x = baking_rect.position[0] + baking_rect_offset.x + border_size;
+ const int rect_begin_y = baking_rect.position[1] + baking_rect_offset.y + border_size;
+ const int rect_end_x = baking_rect.position[0] + baking_rect.size[0] + baking_rect_offset.x - border_size;
+ const int rect_end_y = baking_rect.position[1] + baking_rect.size[1] + baking_rect_offset.y - border_size;
+
+ Rect64 clipper_rect = Rect64(rect_begin_x, rect_begin_y, rect_end_x, rect_end_y);
+ RectClip rect_clip = RectClip(clipper_rect);
+
+ path_solution = rect_clip.Execute(path_solution);
+ }
+
Vector<Vector<Vector2>> new_baked_outlines;
for (const Path64 &scaled_path : path_solution) {
@@ -799,6 +841,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
}
Paths64 polygon_paths;
+ polygon_paths.reserve(new_baked_outlines.size());
for (const Vector<Vector2> &baked_outline : new_baked_outlines) {
Path64 polygon_path;
diff --git a/modules/navigation/nav_mesh_generator_2d.h b/modules/navigation/nav_mesh_generator_2d.h
index 763ad24636..4ec582bd51 100644
--- a/modules/navigation/nav_mesh_generator_2d.h
+++ b/modules/navigation/nav_mesh_generator_2d.h
@@ -92,6 +92,7 @@ public:
static void parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static bool is_baking(Ref<NavigationPolygon> p_navigation_polygon);
NavMeshGenerator2D();
~NavMeshGenerator2D();
diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp
index 8719801c72..95854f29e7 100644
--- a/modules/navigation/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/nav_mesh_generator_3d.cpp
@@ -172,11 +172,10 @@ void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_na
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -208,12 +207,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
return;
}
- baking_navmesh_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- baking_navmesh_mutex.unlock();
+ if (is_baking(p_navigation_mesh)) {
ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
return;
}
+ baking_navmesh_mutex.lock();
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
@@ -228,6 +226,13 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
generator_task_mutex.unlock();
}
+bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) {
+ baking_navmesh_mutex.lock();
+ bool baking = baking_navmeshes.has(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+ return baking;
+}
+
void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
NavMeshGeneratorTask3D *generator_task = static_cast<NavMeshGeneratorTask3D *>(p_arg);
@@ -625,6 +630,9 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
cfg.cs = p_navigation_mesh->get_cell_size();
cfg.ch = p_navigation_mesh->get_cell_height();
+ if (p_navigation_mesh->get_border_size() > 0.0) {
+ cfg.borderSize = (int)Math::ceil(p_navigation_mesh->get_border_size() / cfg.cs);
+ }
cfg.walkableSlopeAngle = p_navigation_mesh->get_agent_max_slope();
cfg.walkableHeight = (int)Math::ceil(p_navigation_mesh->get_agent_height() / cfg.ch);
cfg.walkableClimb = (int)Math::floor(p_navigation_mesh->get_agent_max_climb() / cfg.ch);
@@ -637,6 +645,9 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
cfg.detailSampleDist = MAX(p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance(), 0.1f);
cfg.detailSampleMaxError = p_navigation_mesh->get_cell_height() * p_navigation_mesh->get_detail_sample_max_error();
+ if (p_navigation_mesh->get_border_size() > 0.0 && !Math::is_equal_approx(p_navigation_mesh->get_cell_size(), p_navigation_mesh->get_border_size())) {
+ WARN_PRINT("Property border_size is ceiled to cell_size voxel units and loses precision.");
+ }
if (!Math::is_equal_approx((float)cfg.walkableHeight * cfg.ch, p_navigation_mesh->get_agent_height())) {
WARN_PRINT("Property agent_height is ceiled to cell_height voxel units and loses precision.");
}
@@ -702,7 +713,7 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
Vector<unsigned char> tri_areas;
tri_areas.resize(ntris);
- ERR_FAIL_COND(tri_areas.size() == 0);
+ ERR_FAIL_COND(tri_areas.is_empty());
memset(tri_areas.ptrw(), 0, ntris * sizeof(unsigned char));
rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptrw());
@@ -738,11 +749,11 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation
if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) {
ERR_FAIL_COND(!rcBuildDistanceField(&ctx, *chf));
- ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
+ ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea));
} else if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
- ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
+ ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea));
} else {
- ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
+ ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea));
}
bake_state = "Creating contours..."; // step #8
diff --git a/modules/navigation/nav_mesh_generator_3d.h b/modules/navigation/nav_mesh_generator_3d.h
index 4220927641..0251b02235 100644
--- a/modules/navigation/nav_mesh_generator_3d.h
+++ b/modules/navigation/nav_mesh_generator_3d.h
@@ -99,6 +99,7 @@ public:
static void parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable());
+ static bool is_baking(Ref<NavigationMesh> p_navigation_mesh);
NavMeshGenerator3D();
~NavMeshGenerator3D();
diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
index 1bc3a1a3fc..2f1f472a7e 100644
--- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
@@ -78,7 +78,7 @@
<method name="is_environment_blend_mode_alpha_supported">
<return type="int" enum="OpenXRAPIExtension.OpenXRAlphaBlendModeSupport" />
<description>
- Returns [enum OpenXRAPIExtension.OpenXRAlphaBlendModeSupport] denoting if [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really support, emulated or not supported at all.
+ Returns [enum OpenXRAPIExtension.OpenXRAlphaBlendModeSupport] denoting if [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] is really supported, emulated or not supported at all.
</description>
</method>
<method name="is_initialized">
diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
index b923d9244d..ee2aa33108 100644
--- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
@@ -23,6 +23,12 @@
- If the [code]bool *[/code] points to a boolean, the boolean will be updated to [code]true[/code] if the extension is enabled.
</description>
</method>
+ <method name="_get_suggested_tracker_names" qualifiers="virtual">
+ <return type="PackedStringArray" />
+ <description>
+ Returns a [PackedStringArray] of positional tracker names that are used within the extension wrapper.
+ </description>
+ </method>
<method name="_on_before_instance_created" qualifiers="virtual">
<return type="void" />
<description>
diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h
index b9c9247bee..ad326472ab 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper.h
@@ -35,6 +35,7 @@
#include "core/math/projection.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid.h"
+#include "core/variant/variant.h"
#include <openxr/openxr.h>
@@ -62,6 +63,8 @@ public:
virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } // Add additional data structures when creating OpenXR swap chains.
virtual void *set_hand_joint_locations_and_get_next_pointer(int p_hand_index, void *p_next_pointer) { return p_next_pointer; }
+ virtual PackedStringArray get_suggested_tracker_names() { return PackedStringArray(); }
+
// `on_register_metadata` allows extensions to register additional controller metadata.
// This function is called even when OpenXRApi is not constructured as the metadata
// needs to be available to the editor.
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
index 5ad7a97eca..05f120b0d8 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -40,6 +40,7 @@ void OpenXRExtensionWrapperExtension::_bind_methods() {
GDVIRTUAL_BIND(_set_swapchain_create_info_and_get_next_pointer, "next_pointer");
GDVIRTUAL_BIND(_set_hand_joint_locations_and_get_next_pointer, "hand_index", "next_pointer");
GDVIRTUAL_BIND(_get_composition_layer);
+ GDVIRTUAL_BIND(_get_suggested_tracker_names);
GDVIRTUAL_BIND(_on_register_metadata);
GDVIRTUAL_BIND(_on_before_instance_created);
GDVIRTUAL_BIND(_on_instance_created, "instance");
@@ -129,6 +130,16 @@ void *OpenXRExtensionWrapperExtension::set_hand_joint_locations_and_get_next_poi
return nullptr;
}
+PackedStringArray OpenXRExtensionWrapperExtension::get_suggested_tracker_names() {
+ PackedStringArray ret;
+
+ if (GDVIRTUAL_CALL(_get_suggested_tracker_names, ret)) {
+ return ret;
+ }
+
+ return PackedStringArray();
+}
+
XrCompositionLayerBaseHeader *OpenXRExtensionWrapperExtension::get_composition_layer() {
uint64_t pointer;
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
index 4d8b19f4fd..d673a3bd8f 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
@@ -69,6 +69,10 @@ public:
GDVIRTUAL2R(uint64_t, _set_hand_joint_locations_and_get_next_pointer, int, GDExtensionPtr<void>);
GDVIRTUAL0R(uint64_t, _get_composition_layer);
+ virtual PackedStringArray get_suggested_tracker_names() override;
+
+ GDVIRTUAL0R(PackedStringArray, _get_suggested_tracker_names);
+
virtual void on_register_metadata() override;
virtual void on_before_instance_created() override;
virtual void on_instance_created(const XrInstance p_instance) override;
diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp
index 59bdec5c8e..e57491e7c6 100644
--- a/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp
+++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp
@@ -69,6 +69,11 @@ void *OpenXREyeGazeInteractionExtension::set_system_properties_and_get_next_poin
return &properties;
}
+PackedStringArray OpenXREyeGazeInteractionExtension::get_suggested_tracker_names() {
+ PackedStringArray arr = { "/user/eyes_ext" };
+ return arr;
+}
+
bool OpenXREyeGazeInteractionExtension::is_available() {
return available;
}
diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.h b/modules/openxr/extensions/openxr_eye_gaze_interaction.h
index 704940ad26..2b99f8edff 100644
--- a/modules/openxr/extensions/openxr_eye_gaze_interaction.h
+++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.h
@@ -43,6 +43,8 @@ public:
virtual HashMap<String, bool *> get_requested_extensions() override;
virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) override;
+ PackedStringArray get_suggested_tracker_names() override;
+
bool is_available();
bool supports_eye_gaze_interaction();
diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
index 8b8c6c5353..bb60f7adef 100644
--- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
+++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
@@ -42,6 +42,25 @@ HashMap<String, bool *> OpenXRHTCViveTrackerExtension::get_requested_extensions(
return request_extensions;
}
+PackedStringArray OpenXRHTCViveTrackerExtension::get_suggested_tracker_names() {
+ PackedStringArray arr = {
+ "/user/vive_tracker_htcx/role/handheld_object",
+ "/user/vive_tracker_htcx/role/left_foot",
+ "/user/vive_tracker_htcx/role/right_foot",
+ "/user/vive_tracker_htcx/role/left_shoulder",
+ "/user/vive_tracker_htcx/role/right_shoulder",
+ "/user/vive_tracker_htcx/role/left_elbow",
+ "/user/vive_tracker_htcx/role/right_elbow",
+ "/user/vive_tracker_htcx/role/left_knee",
+ "/user/vive_tracker_htcx/role/right_knee",
+ "/user/vive_tracker_htcx/role/waist",
+ "/user/vive_tracker_htcx/role/chest",
+ "/user/vive_tracker_htcx/role/camera",
+ "/user/vive_tracker_htcx/role/keyboard",
+ };
+ return arr;
+}
+
bool OpenXRHTCViveTrackerExtension::is_available() {
return available;
}
diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h
index b51398fd4e..e9c3d338ab 100644
--- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h
+++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.h
@@ -37,6 +37,8 @@ class OpenXRHTCViveTrackerExtension : public OpenXRExtensionWrapper {
public:
virtual HashMap<String, bool *> get_requested_extensions() override;
+ PackedStringArray get_suggested_tracker_names() override;
+
bool is_available();
virtual void on_register_metadata() override;
diff --git a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp
index a2f2577959..f5e7fc192c 100644
--- a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp
@@ -38,14 +38,6 @@
#include "servers/rendering/rendering_server_globals.h"
#include "servers/rendering_server.h"
-OpenXRVulkanExtension::OpenXRVulkanExtension() {
- VulkanContext::set_vulkan_hooks(this);
-}
-
-OpenXRVulkanExtension::~OpenXRVulkanExtension() {
- VulkanContext::set_vulkan_hooks(nullptr);
-}
-
HashMap<String, bool *> OpenXRVulkanExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
@@ -178,10 +170,6 @@ bool OpenXRVulkanExtension::get_physical_device(VkPhysicalDevice *r_device) {
bool OpenXRVulkanExtension::create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) {
ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
- // the first entry in our queue list should be the one we need to remember...
- vulkan_queue_family_index = p_device_create_info->pQueueCreateInfos[0].queueFamilyIndex;
- vulkan_queue_index = 0; // ??
-
XrVulkanDeviceCreateInfoKHR create_info = {
XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR, // type
nullptr, // next
@@ -209,9 +197,17 @@ bool OpenXRVulkanExtension::create_vulkan_device(const VkDeviceCreateInfo *p_dev
return true;
}
+void OpenXRVulkanExtension::set_direct_queue_family_and_index(uint32_t p_queue_family_index, uint32_t p_queue_index) {
+ vulkan_queue_family_index = p_queue_family_index;
+ vulkan_queue_index = p_queue_index;
+}
+
XrGraphicsBindingVulkanKHR OpenXRVulkanExtension::graphics_binding_vulkan;
void *OpenXRVulkanExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) {
+ DEV_ASSERT(vulkan_queue_family_index < UINT32_MAX && "Direct queue family index was not specified yet.");
+ DEV_ASSERT(vulkan_queue_index < UINT32_MAX && "Direct queue index was not specified yet.");
+
graphics_binding_vulkan.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR;
graphics_binding_vulkan.next = p_next_pointer;
graphics_binding_vulkan.instance = vulkan_instance;
diff --git a/modules/openxr/extensions/platform/openxr_vulkan_extension.h b/modules/openxr/extensions/platform/openxr_vulkan_extension.h
index 2d0973bb6b..a3f86a9968 100644
--- a/modules/openxr/extensions/platform/openxr_vulkan_extension.h
+++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.h
@@ -36,23 +36,25 @@
#include "../openxr_extension_wrapper.h"
#include "core/templates/vector.h"
+#include "drivers/vulkan/vulkan_hooks.h"
// Always include this as late as possible.
#include "../../openxr_platform_inc.h"
class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks {
public:
- OpenXRVulkanExtension();
- virtual ~OpenXRVulkanExtension() override;
+ OpenXRVulkanExtension() = default;
+ virtual ~OpenXRVulkanExtension() override = default;
virtual HashMap<String, bool *> get_requested_extensions() override;
virtual void on_instance_created(const XrInstance p_instance) override;
virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override;
- virtual bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) override;
- virtual bool get_physical_device(VkPhysicalDevice *r_device) override;
- virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override;
+ virtual bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) override final;
+ virtual bool get_physical_device(VkPhysicalDevice *r_device) override final;
+ virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) override final;
+ virtual void set_direct_queue_family_and_index(uint32_t p_queue_family_index, uint32_t p_queue_index) override final;
virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override;
virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override;
@@ -76,8 +78,8 @@ private:
VkInstance vulkan_instance = nullptr;
VkPhysicalDevice vulkan_physical_device = nullptr;
VkDevice vulkan_device = nullptr;
- uint32_t vulkan_queue_family_index = 0;
- uint32_t vulkan_queue_index = 0;
+ uint32_t vulkan_queue_family_index = UINT32_MAX;
+ uint32_t vulkan_queue_index = UINT32_MAX;
EXT_PROTO_XRRESULT_FUNC3(xrGetVulkanGraphicsRequirements2KHR, (XrInstance), p_instance, (XrSystemId), p_system_id, (XrGraphicsRequirementsVulkanKHR *), p_graphics_requirements)
EXT_PROTO_XRRESULT_FUNC4(xrCreateVulkanInstanceKHR, (XrInstance), p_instance, (const XrVulkanInstanceCreateInfoKHR *), p_create_info, (VkInstance *), r_vulkan_instance, (VkResult *), r_vulkan_result)
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 80ddfe703f..36c3faaf75 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -2857,7 +2857,7 @@ bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) {
}
}
- ERR_FAIL_COND_V(active_sets.size() == 0, false);
+ ERR_FAIL_COND_V(active_sets.is_empty(), false);
XrActionsSyncInfo sync_info = {
XR_TYPE_ACTIONS_SYNC_INFO, // type
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 05c53ad52f..956e5ed3f3 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -156,27 +156,12 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const {
"left_hand", // /user/hand/left is mapped to our defaults
"right_hand", // /user/hand/right is mapped to our defaults
"/user/treadmill",
-
- // Even though these are only available if you have the tracker extension,
- // we add these as we may be deploying on a different platform than our
- // editor is running on.
- "/user/vive_tracker_htcx/role/handheld_object",
- "/user/vive_tracker_htcx/role/left_foot",
- "/user/vive_tracker_htcx/role/right_foot",
- "/user/vive_tracker_htcx/role/left_shoulder",
- "/user/vive_tracker_htcx/role/right_shoulder",
- "/user/vive_tracker_htcx/role/left_elbow",
- "/user/vive_tracker_htcx/role/right_elbow",
- "/user/vive_tracker_htcx/role/left_knee",
- "/user/vive_tracker_htcx/role/right_knee",
- "/user/vive_tracker_htcx/role/waist",
- "/user/vive_tracker_htcx/role/chest",
- "/user/vive_tracker_htcx/role/camera",
- "/user/vive_tracker_htcx/role/keyboard",
-
- "/user/eyes_ext",
};
+ for (OpenXRExtensionWrapper *wrapper : OpenXRAPI::get_singleton()->get_registered_extension_wrappers()) {
+ arr.append_array(wrapper->get_suggested_tracker_names());
+ }
+
return arr;
}
diff --git a/modules/openxr/openxr_platform_inc.h b/modules/openxr/openxr_platform_inc.h
index 6288d1e380..957a87cbb2 100644
--- a/modules/openxr/openxr_platform_inc.h
+++ b/modules/openxr/openxr_platform_inc.h
@@ -36,7 +36,7 @@
#ifdef VULKAN_ENABLED
#define XR_USE_GRAPHICS_API_VULKAN
-#include "drivers/vulkan/vulkan_context.h"
+#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif // VULKAN_ENABLED
#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index a542bbc234..affe163aeb 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -72,7 +72,7 @@ Ref<Image> ImageLoaderSVG::load_mem_svg(const uint8_t *p_svg, int p_size, float
img.instantiate();
Error err = create_image_from_utf8_buffer(img, p_svg, p_size, p_scale, false);
- ERR_FAIL_COND_V(err, Ref<Image>());
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Image>(), vformat("ImageLoaderSVG: Failed to create SVG from buffer, error code %d.", err));
return img;
}
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index b4028492c7..4c460a002f 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -3274,7 +3274,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
- for (const KeyValue<Vector2i, FontForSizeAdvanced *> &E : fd->cache) {
+ for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
ret.push_back(E.key);
}
return ret;
diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp
index aef4f394b2..2812f37eb2 100644
--- a/modules/upnp/upnp.cpp
+++ b/modules/upnp/upnp.cpp
@@ -265,7 +265,7 @@ void UPNP::clear_devices() {
}
Ref<UPNPDevice> UPNP::get_gateway() const {
- ERR_FAIL_COND_V_MSG(devices.size() < 1, nullptr, "Couldn't find any UPNPDevices.");
+ ERR_FAIL_COND_V_MSG(devices.is_empty(), nullptr, "Couldn't find any UPNPDevices.");
for (int i = 0; i < devices.size(); i++) {
Ref<UPNPDevice> dev = get_device(i);
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index e6003f35df..b235b6f96c 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -177,13 +177,13 @@ int AudioStreamPlaybackOggVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p
if (info.channels > 1) {
for (int frame = 0; frame < frames; frame++) {
- p_buffer[frame].l = pcm[0][frame];
- p_buffer[frame].r = pcm[1][frame];
+ p_buffer[frame].left = pcm[0][frame];
+ p_buffer[frame].right = pcm[1][frame];
}
} else {
for (int frame = 0; frame < frames; frame++) {
- p_buffer[frame].l = pcm[0][frame];
- p_buffer[frame].r = pcm[0][frame];
+ p_buffer[frame].left = pcm[0][frame];
+ p_buffer[frame].right = pcm[0][frame];
}
}
vorbis_synthesis_read(&dsp_state, frames);
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp
index bf5d964d39..7b8d14741b 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.cpp
+++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp
@@ -212,11 +212,13 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect
granule_pos = packet.granulepos;
}
- PackedByteArray data;
- data.resize(packet.bytes);
- memcpy(data.ptrw(), packet.packet, packet.bytes);
- sorted_packets[granule_pos].push_back(data);
- packet_count++;
+ if (packet.bytes > 0) {
+ PackedByteArray data;
+ data.resize(packet.bytes);
+ memcpy(data.ptrw(), packet.packet, packet.bytes);
+ sorted_packets[granule_pos].push_back(data);
+ packet_count++;
+ }
}
Vector<Vector<uint8_t>> packet_data;
for (const KeyValue<uint64_t, Vector<Vector<uint8_t>>> &pair : sorted_packets) {
@@ -224,7 +226,7 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect
packet_data.push_back(packets);
}
}
- if (initialized_stream) {
+ if (initialized_stream && packet_data.size() > 0) {
ogg_packet_sequence->push_page(ogg_page_granulepos(&page), packet_data);
}
}
diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp
index 791b6d9ec9..dc6833e8c3 100644
--- a/modules/websocket/remote_debugger_peer_websocket.cpp
+++ b/modules/websocket/remote_debugger_peer_websocket.cpp
@@ -91,7 +91,7 @@ bool RemoteDebuggerPeerWebSocket::has_message() {
}
Array RemoteDebuggerPeerWebSocket::get_message() {
- ERR_FAIL_COND_V(in_queue.size() < 1, Array());
+ ERR_FAIL_COND_V(in_queue.is_empty(), Array());
Array msg = in_queue[0];
in_queue.pop_front();
return msg;
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index 9e706dbeef..332cf93d36 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -124,7 +124,7 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff
current_packet.data = nullptr;
}
- ERR_FAIL_COND_V(incoming_packets.size() == 0, ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), ERR_UNAVAILABLE);
current_packet = incoming_packets.front()->get();
incoming_packets.pop_front();
@@ -164,7 +164,7 @@ void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) {
}
int WebSocketMultiplayerPeer::get_packet_peer() const {
- ERR_FAIL_COND_V(incoming_packets.size() == 0, 1);
+ ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
return incoming_packets.front()->get().source;
}
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 828476edfb..5415423fb8 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -675,6 +675,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
event->set_index(touch_index);
event->set_position(position);
event->set_relative(delta);
+ event->set_relative_screen_position(delta);
Input::get_singleton()->parse_input_event(event);
}
}