summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/dds/image_loader_dds.cpp422
-rw-r--r--modules/dds/image_loader_dds.h (renamed from modules/mono/mono_gd/android_mono_config.h)22
-rw-r--r--modules/dds/register_types.cpp8
-rw-r--r--modules/dds/texture_loader_dds.cpp362
-rw-r--r--modules/enet/enet_connection.cpp2
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml3
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp44
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp145
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h9
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp107
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h10
-rw-r--r--modules/gdscript/gdscript.cpp46
-rw-r--r--modules/gdscript/gdscript.h68
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp19
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp14
-rw-r--r--modules/gdscript/gdscript_compiler.cpp336
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp3
-rw-r--r--modules/gdscript/gdscript_editor.cpp177
-rw-r--r--modules/gdscript/gdscript_function.h14
-rw-r--r--modules/gdscript/gdscript_parser.cpp172
-rw-r--r--modules/gdscript/gdscript_parser.h57
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp32
-rw-r--r--modules/gdscript/gdscript_vm.cpp94
-rw-r--r--modules/gdscript/gdscript_warning.cpp8
-rw-r--r--modules/gdscript/gdscript_warning.h4
-rw-r--r--modules/gdscript/language_server/godot_lsp.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.out7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out15
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out15
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd17
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out5
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml7
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml18
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml8
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp15
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h4
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_webp.cpp6
-rw-r--r--modules/gltf/extensions/gltf_document_extension_texture_webp.h1
-rw-r--r--modules/gltf/gltf_document.cpp297
-rw-r--r--modules/gltf/gltf_document.h25
-rw-r--r--modules/gltf/gltf_state.cpp22
-rw-r--r--modules/gltf/gltf_state.h9
-rw-r--r--modules/minimp3/doc_classes/ResourceImporterMP3.xml17
-rw-r--r--modules/mono/SCsub2
-rw-r--r--modules/mono/config.py2
-rw-r--r--modules/mono/csharp_script.cpp4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj1
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs66
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs54
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs13
-rw-r--r--modules/mono/editor/bindings_generator.cpp152
-rw-r--r--modules/mono/editor/bindings_generator.h12
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp24
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs584
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs71
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs26
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs106
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs6
-rw-r--r--modules/mono/glue/runtime_interop.cpp21
-rw-r--r--modules/mono/godotsharp_dirs.cpp82
-rw-r--r--modules/mono/icons/BuildCSharp.svg1
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp14
-rw-r--r--modules/mono/mono_gd/support/android_support.cpp720
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp20
-rw-r--r--modules/navigation/godot_navigation_server.cpp87
-rw-r--r--modules/navigation/godot_navigation_server.h10
-rw-r--r--modules/navigation/nav_link.cpp10
-rw-r--r--modules/navigation/nav_link.h4
-rw-r--r--modules/navigation/nav_map.cpp20
-rw-r--r--modules/navigation/nav_mesh_generator_3d.cpp728
-rw-r--r--modules/navigation/nav_mesh_generator_3d.h81
-rw-r--r--modules/navigation/nav_region.cpp10
-rw-r--r--modules/navigation/nav_region.h4
-rw-r--r--modules/navigation/navigation_mesh_generator.cpp720
-rw-r--r--modules/navigation/navigation_mesh_generator.h11
-rw-r--r--modules/navigation/register_types.cpp8
-rw-r--r--modules/noise/icons/NoiseTexture3D.svg1
-rw-r--r--modules/openxr/SCsub2
-rw-r--r--modules/openxr/extensions/openxr_android_extension.cpp1
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp207
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.h115
-rw-r--r--modules/openxr/openxr_api.cpp5
-rw-r--r--modules/openxr/openxr_api_extension.cpp130
-rw-r--r--modules/openxr/openxr_api_extension.h (renamed from modules/mono/mono_gd/support/android_support.h)56
-rw-r--r--modules/openxr/register_types.cpp8
-rw-r--r--modules/openxr/util.h11
-rw-r--r--modules/raycast/raycast_occlusion_cull.cpp33
-rw-r--r--modules/raycast/raycast_occlusion_cull.h10
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct1
-rw-r--r--modules/text_server_adv/text_server_adv.cpp2
-rw-r--r--modules/text_server_fb/gdextension_build/SConstruct1
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml17
112 files changed, 4092 insertions, 2996 deletions
diff --git a/modules/dds/image_loader_dds.cpp b/modules/dds/image_loader_dds.cpp
new file mode 100644
index 0000000000..42c8120595
--- /dev/null
+++ b/modules/dds/image_loader_dds.cpp
@@ -0,0 +1,422 @@
+/**************************************************************************/
+/* image_loader_dds.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 "image_loader_dds.h"
+
+#include "core/os/os.h"
+
+#include "core/io/file_access.h"
+#include "core/io/file_access_memory.h"
+
+#include <string.h>
+
+#define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0])))
+
+// Reference: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
+
+enum {
+ DDS_MAGIC = 0x20534444,
+ DDSD_PITCH = 0x00000008,
+ DDSD_LINEARSIZE = 0x00080000,
+ DDSD_MIPMAPCOUNT = 0x00020000,
+ DDPF_FOURCC = 0x00000004,
+ DDPF_ALPHAPIXELS = 0x00000001,
+ DDPF_INDEXED = 0x00000020,
+ DDPF_RGB = 0x00000040,
+};
+
+enum DDSFormat {
+ DDS_DXT1,
+ DDS_DXT3,
+ DDS_DXT5,
+ DDS_ATI1,
+ DDS_ATI2,
+ DDS_A2XY,
+ DDS_BGRA8,
+ DDS_BGR8,
+ DDS_RGBA8, //flipped in dds
+ DDS_RGB8, //flipped in dds
+ DDS_BGR5A1,
+ DDS_BGR565,
+ DDS_BGR10A2,
+ DDS_INDEXED,
+ DDS_LUMINANCE,
+ DDS_LUMINANCE_ALPHA,
+ DDS_MAX
+};
+
+struct DDSFormatInfo {
+ const char *name = nullptr;
+ bool compressed = false;
+ bool palette = false;
+ uint32_t divisor = 0;
+ uint32_t block_size = 0;
+ Image::Format format = Image::Format::FORMAT_BPTC_RGBA;
+};
+
+static const DDSFormatInfo dds_format_info[DDS_MAX] = {
+ { "DXT1/BC1", true, false, 4, 8, Image::FORMAT_DXT1 },
+ { "DXT3/BC2", true, false, 4, 16, Image::FORMAT_DXT3 },
+ { "DXT5/BC3", true, false, 4, 16, Image::FORMAT_DXT5 },
+ { "ATI1/BC4", true, false, 4, 8, Image::FORMAT_RGTC_R },
+ { "ATI2/3DC/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG },
+ { "A2XY/DXN/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG },
+ { "BGRA8", false, false, 1, 4, Image::FORMAT_RGBA8 },
+ { "BGR8", false, false, 1, 3, Image::FORMAT_RGB8 },
+ { "RGBA8", false, false, 1, 4, Image::FORMAT_RGBA8 },
+ { "RGB8", false, false, 1, 3, Image::FORMAT_RGB8 },
+ { "BGR5A1", false, false, 1, 2, Image::FORMAT_RGBA8 },
+ { "BGR565", false, false, 1, 2, Image::FORMAT_RGB8 },
+ { "BGR10A2", false, false, 1, 4, Image::FORMAT_RGBA8 },
+ { "GRAYSCALE", false, false, 1, 1, Image::FORMAT_L8 },
+ { "GRAYSCALE_ALPHA", false, false, 1, 2, Image::FORMAT_LA8 }
+};
+
+static Ref<Image> _dds_mem_loader_func(const uint8_t *p_buffer, int p_buffer_len) {
+ Ref<FileAccessMemory> memfile;
+ memfile.instantiate();
+ Error open_memfile_error = memfile->open_custom(p_buffer, p_buffer_len);
+ ERR_FAIL_COND_V_MSG(open_memfile_error, Ref<Image>(), "Could not create memfile for DDS image buffer.");
+
+ Ref<Image> img;
+ img.instantiate();
+ Error load_error = ImageLoaderDDS().load_image(img, memfile, false, 1.0f);
+ ERR_FAIL_COND_V_MSG(load_error, Ref<Image>(), "Failed to load DDS image.");
+ return img;
+}
+
+Error ImageLoaderDDS::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
+ uint32_t magic = f->get_32();
+ uint32_t hsize = f->get_32();
+ uint32_t flags = f->get_32();
+ uint32_t height = f->get_32();
+ uint32_t width = f->get_32();
+ uint32_t pitch = f->get_32();
+ /* uint32_t depth = */ f->get_32();
+ uint32_t mipmaps = f->get_32();
+
+ //skip 11
+ for (int i = 0; i < 11; i++) {
+ f->get_32();
+ }
+
+ //validate
+
+ // We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing,
+ // but non-mandatory when reading (as some writers don't set them)...
+ if (magic != DDS_MAGIC || hsize != 124) {
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid or unsupported DDS texture file '" + f->get_path() + "'.");
+ }
+
+ /* uint32_t format_size = */ f->get_32();
+ uint32_t format_flags = f->get_32();
+ uint32_t format_fourcc = f->get_32();
+ uint32_t format_rgb_bits = f->get_32();
+ uint32_t format_red_mask = f->get_32();
+ uint32_t format_green_mask = f->get_32();
+ uint32_t format_blue_mask = f->get_32();
+ uint32_t format_alpha_mask = f->get_32();
+
+ /* uint32_t caps_1 = */ f->get_32();
+ /* uint32_t caps_2 = */ f->get_32();
+ /* uint32_t caps_ddsx = */ f->get_32();
+
+ //reserved skip
+ f->get_32();
+ f->get_32();
+
+ /*
+ print_line("DDS width: "+itos(width));
+ print_line("DDS height: "+itos(height));
+ print_line("DDS mipmaps: "+itos(mipmaps));
+
+ printf("fourcc: %x fflags: %x, rgbbits: %x, fsize: %x\n",format_fourcc,format_flags,format_rgb_bits,format_size);
+ printf("rmask: %x gmask: %x, bmask: %x, amask: %x\n",format_red_mask,format_green_mask,format_blue_mask,format_alpha_mask);
+ */
+
+ //must avoid this later
+ while (f->get_position() < 128) {
+ f->get_8();
+ }
+
+ DDSFormat dds_format;
+
+ if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT1")) {
+ dds_format = DDS_DXT1;
+ } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT3")) {
+ dds_format = DDS_DXT3;
+
+ } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT5")) {
+ dds_format = DDS_DXT5;
+ } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI1")) {
+ dds_format = DDS_ATI1;
+ } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI2")) {
+ dds_format = DDS_ATI2;
+ } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("A2XY")) {
+ dds_format = DDS_A2XY;
+
+ } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
+ dds_format = DDS_BGRA8;
+ } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
+ dds_format = DDS_BGR8;
+ } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
+ dds_format = DDS_RGBA8;
+ } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
+ dds_format = DDS_RGB8;
+
+ } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
+ dds_format = DDS_BGR5A1;
+ } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
+ dds_format = DDS_BGR10A2;
+ } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
+ dds_format = DDS_BGR565;
+ } else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff && format_green_mask == 0xff && format_blue_mask == 0xff) {
+ dds_format = DDS_LUMINANCE;
+ } else if ((format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 16 && format_red_mask == 0xff && format_green_mask == 0xff && format_blue_mask == 0xff && format_alpha_mask == 0xff00) {
+ dds_format = DDS_LUMINANCE_ALPHA;
+ } else if (format_flags & DDPF_INDEXED && format_rgb_bits == 8) {
+ dds_format = DDS_BGR565;
+ } else {
+ //printf("unrecognized fourcc %x format_flags: %x - rgbbits %i - red_mask %x green mask %x blue mask %x alpha mask %x\n", format_fourcc, format_flags, format_rgb_bits, format_red_mask, format_green_mask, format_blue_mask, format_alpha_mask);
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Unrecognized or unsupported color layout in DDS '" + f->get_path() + "'.");
+ }
+
+ if (!(flags & DDSD_MIPMAPCOUNT)) {
+ mipmaps = 1;
+ }
+
+ Vector<uint8_t> src_data;
+
+ const DDSFormatInfo &info = dds_format_info[dds_format];
+ uint32_t w = width;
+ uint32_t h = height;
+
+ if (info.compressed) {
+ //compressed bc
+
+ uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
+ ERR_FAIL_COND_V(size != pitch, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(!(flags & DDSD_LINEARSIZE), ERR_FILE_CORRUPT);
+
+ for (uint32_t i = 1; i < mipmaps; i++) {
+ w = MAX(1u, w >> 1);
+ h = MAX(1u, h >> 1);
+ uint32_t bsize = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
+ //printf("%i x %i - block: %i\n",w,h,bsize);
+ size += bsize;
+ }
+
+ src_data.resize(size);
+ uint8_t *wb = src_data.ptrw();
+ f->get_buffer(wb, size);
+
+ } else if (info.palette) {
+ //indexed
+ ERR_FAIL_COND_V(!(flags & DDSD_PITCH), ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(format_rgb_bits != 8, ERR_FILE_CORRUPT);
+
+ uint32_t size = pitch * height;
+ ERR_FAIL_COND_V(size != width * height * info.block_size, ERR_FILE_CORRUPT);
+
+ uint8_t palette[256 * 4];
+ f->get_buffer(palette, 256 * 4);
+
+ int colsize = 3;
+ for (int i = 0; i < 256; i++) {
+ if (palette[i * 4 + 3] < 255) {
+ colsize = 4;
+ }
+ }
+
+ int w2 = width;
+ int h2 = height;
+
+ for (uint32_t i = 1; i < mipmaps; i++) {
+ w2 = (w2 + 1) >> 1;
+ h2 = (h2 + 1) >> 1;
+ size += w2 * h2 * info.block_size;
+ }
+
+ src_data.resize(size + 256 * colsize);
+ uint8_t *wb = src_data.ptrw();
+ f->get_buffer(wb, size);
+
+ for (int i = 0; i < 256; i++) {
+ int dst_ofs = size + i * colsize;
+ int src_ofs = i * 4;
+ wb[dst_ofs + 0] = palette[src_ofs + 2];
+ wb[dst_ofs + 1] = palette[src_ofs + 1];
+ wb[dst_ofs + 2] = palette[src_ofs + 0];
+ if (colsize == 4) {
+ wb[dst_ofs + 3] = palette[src_ofs + 3];
+ }
+ }
+ } else {
+ //uncompressed generic...
+
+ uint32_t size = width * height * info.block_size;
+
+ for (uint32_t i = 1; i < mipmaps; i++) {
+ w = (w + 1) >> 1;
+ h = (h + 1) >> 1;
+ size += w * h * info.block_size;
+ }
+
+ if (dds_format == DDS_BGR565) {
+ size = size * 3 / 2;
+ } else if (dds_format == DDS_BGR5A1) {
+ size = size * 2;
+ }
+
+ src_data.resize(size);
+ uint8_t *wb = src_data.ptrw();
+ f->get_buffer(wb, size);
+
+ switch (dds_format) {
+ case DDS_BGR5A1: {
+ // TO RGBA
+ int colcount = size / 4;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i * 2;
+ int dst_ofs = i * 4;
+
+ uint8_t a = wb[src_ofs + 1] & 0x80;
+ uint8_t b = wb[src_ofs] & 0x1F;
+ uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x3) << 3);
+ uint8_t r = (wb[src_ofs + 1] >> 2) & 0x1F;
+ wb[dst_ofs + 0] = r << 3;
+ wb[dst_ofs + 1] = g << 3;
+ wb[dst_ofs + 2] = b << 3;
+ wb[dst_ofs + 3] = a ? 255 : 0;
+ }
+ } break;
+ case DDS_BGR565: {
+ int colcount = size / 3;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i * 2;
+ int dst_ofs = i * 3;
+
+ uint8_t b = wb[src_ofs] & 0x1F;
+ uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x7) << 3);
+ uint8_t r = wb[src_ofs + 1] >> 3;
+ wb[dst_ofs + 0] = r << 3;
+ wb[dst_ofs + 1] = g << 2;
+ wb[dst_ofs + 2] = b << 3; //b<<3;
+ }
+
+ } break;
+ case DDS_BGR10A2: {
+ // TO RGBA
+ int colcount = size / 4;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int ofs = i * 4;
+
+ uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
+
+ uint8_t a = (w32 & 0xc0000000) >> 24;
+ uint8_t r = (w32 & 0x3ff00000) >> 22;
+ uint8_t g = (w32 & 0xffc00) >> 12;
+ uint8_t b = (w32 & 0x3ff) >> 2;
+
+ wb[ofs + 0] = r;
+ wb[ofs + 1] = g;
+ wb[ofs + 2] = b;
+ wb[ofs + 3] = a == 0xc0 ? 255 : a; //0xc0 should be opaque
+ }
+ } break;
+ case DDS_BGRA8: {
+ int colcount = size / 4;
+
+ for (int i = 0; i < colcount; i++) {
+ SWAP(wb[i * 4 + 0], wb[i * 4 + 2]);
+ }
+
+ } break;
+ case DDS_BGR8: {
+ int colcount = size / 3;
+
+ for (int i = 0; i < colcount; i++) {
+ SWAP(wb[i * 3 + 0], wb[i * 3 + 2]);
+ }
+ } break;
+ case DDS_RGBA8: {
+ /* do nothing either
+ int colcount = size/4;
+
+ for(int i=0;i<colcount;i++) {
+ uint8_t r = wb[i*4+1];
+ uint8_t g = wb[i*4+2];
+ uint8_t b = wb[i*4+3];
+ uint8_t a = wb[i*4+0];
+
+ wb[i*4+0]=r;
+ wb[i*4+1]=g;
+ wb[i*4+2]=b;
+ wb[i*4+3]=a;
+ }
+ */
+ } break;
+ case DDS_RGB8: {
+ // do nothing
+ /*
+ int colcount = size/3;
+
+ for(int i=0;i<colcount;i++) {
+ SWAP( wb[i*3+0],wb[i*3+2] );
+ }*/
+ } break;
+ case DDS_LUMINANCE: {
+ // do nothing i guess?
+
+ } break;
+ case DDS_LUMINANCE_ALPHA: {
+ // do nothing i guess?
+
+ } break;
+
+ default: {
+ }
+ }
+ }
+
+ p_image->set_data(width, height, mipmaps - 1, info.format, src_data);
+ return OK;
+}
+
+ImageLoaderDDS::ImageLoaderDDS() {
+ Image::_dds_mem_loader_func = _dds_mem_loader_func;
+}
+
+void ImageLoaderDDS::get_recognized_extensions(List<String> *p_extensions) const {
+ p_extensions->push_back("dds");
+}
diff --git a/modules/mono/mono_gd/android_mono_config.h b/modules/dds/image_loader_dds.h
index bb9c2a5d5b..81cfd43551 100644
--- a/modules/mono/mono_gd/android_mono_config.h
+++ b/modules/dds/image_loader_dds.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* android_mono_config.h */
+/* image_loader_dds.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,16 +28,16 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef ANDROID_MONO_CONFIG_H
-#define ANDROID_MONO_CONFIG_H
+#ifndef IMAGE_LOADER_DDS_H
+#define IMAGE_LOADER_DDS_H
-#ifdef ANDROID_ENABLED
+#include "core/io/image_loader.h"
-#include "core/string/ustring.h"
+class ImageLoaderDDS : public ImageFormatLoader {
+public:
+ virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale);
+ virtual void get_recognized_extensions(List<String> *p_extensions) const;
+ ImageLoaderDDS();
+};
-// This function is defined in an auto-generated source file
-String get_godot_android_mono_config();
-
-#endif // ANDROID_ENABLED
-
-#endif // ANDROID_MONO_CONFIG_H
+#endif // IMAGE_LOADER_DDS_H
diff --git a/modules/dds/register_types.cpp b/modules/dds/register_types.cpp
index d336269eb3..b4d406eda9 100644
--- a/modules/dds/register_types.cpp
+++ b/modules/dds/register_types.cpp
@@ -30,9 +30,11 @@
#include "register_types.h"
+#include "image_loader_dds.h"
#include "texture_loader_dds.h"
static Ref<ResourceFormatDDS> resource_loader_dds;
+static Ref<ImageLoaderDDS> image_loader_dds;
void initialize_dds_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
@@ -41,6 +43,9 @@ void initialize_dds_module(ModuleInitializationLevel p_level) {
resource_loader_dds.instantiate();
ResourceLoader::add_resource_format_loader(resource_loader_dds);
+
+ image_loader_dds.instantiate();
+ ImageLoader::add_image_format_loader(image_loader_dds);
}
void uninitialize_dds_module(ModuleInitializationLevel p_level) {
@@ -50,4 +55,7 @@ void uninitialize_dds_module(ModuleInitializationLevel p_level) {
ResourceLoader::remove_resource_format_loader(resource_loader_dds);
resource_loader_dds.unref();
+
+ ImageLoader::remove_image_format_loader(image_loader_dds);
+ image_loader_dds.unref();
}
diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp
index 8a3a36e84b..861cf20cc2 100644
--- a/modules/dds/texture_loader_dds.cpp
+++ b/modules/dds/texture_loader_dds.cpp
@@ -29,72 +29,11 @@
/**************************************************************************/
#include "texture_loader_dds.h"
+#include "image_loader_dds.h"
#include "core/io/file_access.h"
#include "scene/resources/image_texture.h"
-#define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0])))
-
-// Reference: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
-
-enum {
- DDS_MAGIC = 0x20534444,
- DDSD_PITCH = 0x00000008,
- DDSD_LINEARSIZE = 0x00080000,
- DDSD_MIPMAPCOUNT = 0x00020000,
- DDPF_FOURCC = 0x00000004,
- DDPF_ALPHAPIXELS = 0x00000001,
- DDPF_INDEXED = 0x00000020,
- DDPF_RGB = 0x00000040,
-};
-
-enum DDSFormat {
- DDS_DXT1,
- DDS_DXT3,
- DDS_DXT5,
- DDS_ATI1,
- DDS_ATI2,
- DDS_A2XY,
- DDS_BGRA8,
- DDS_BGR8,
- DDS_RGBA8, //flipped in dds
- DDS_RGB8, //flipped in dds
- DDS_BGR5A1,
- DDS_BGR565,
- DDS_BGR10A2,
- DDS_INDEXED,
- DDS_LUMINANCE,
- DDS_LUMINANCE_ALPHA,
- DDS_MAX
-};
-
-struct DDSFormatInfo {
- const char *name = nullptr;
- bool compressed = false;
- bool palette = false;
- uint32_t divisor = 0;
- uint32_t block_size = 0;
- Image::Format format = Image::Format::FORMAT_BPTC_RGBA;
-};
-
-static const DDSFormatInfo dds_format_info[DDS_MAX] = {
- { "DXT1/BC1", true, false, 4, 8, Image::FORMAT_DXT1 },
- { "DXT3/BC2", true, false, 4, 16, Image::FORMAT_DXT3 },
- { "DXT5/BC3", true, false, 4, 16, Image::FORMAT_DXT5 },
- { "ATI1/BC4", true, false, 4, 8, Image::FORMAT_RGTC_R },
- { "ATI2/3DC/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG },
- { "A2XY/DXN/BC5", true, false, 4, 16, Image::FORMAT_RGTC_RG },
- { "BGRA8", false, false, 1, 4, Image::FORMAT_RGBA8 },
- { "BGR8", false, false, 1, 3, Image::FORMAT_RGB8 },
- { "RGBA8", false, false, 1, 4, Image::FORMAT_RGBA8 },
- { "RGB8", false, false, 1, 3, Image::FORMAT_RGB8 },
- { "BGR5A1", false, false, 1, 2, Image::FORMAT_RGBA8 },
- { "BGR565", false, false, 1, 2, Image::FORMAT_RGB8 },
- { "BGR10A2", false, false, 1, 4, Image::FORMAT_RGBA8 },
- { "GRAYSCALE", false, false, 1, 1, Image::FORMAT_L8 },
- { "GRAYSCALE_ALPHA", false, false, 1, 2, Image::FORMAT_LA8 }
-};
-
Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
if (r_error) {
*r_error = ERR_CANT_OPEN;
@@ -113,303 +52,12 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Unable to open DDS texture file '" + p_path + "'.");
- uint32_t magic = f->get_32();
- uint32_t hsize = f->get_32();
- uint32_t flags = f->get_32();
- uint32_t height = f->get_32();
- uint32_t width = f->get_32();
- uint32_t pitch = f->get_32();
- /* uint32_t depth = */ f->get_32();
- uint32_t mipmaps = f->get_32();
-
- //skip 11
- for (int i = 0; i < 11; i++) {
- f->get_32();
- }
-
- //validate
-
- // We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing,
- // but non-mandatory when reading (as some writers don't set them)...
- if (magic != DDS_MAGIC || hsize != 124) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid or unsupported DDS texture file '" + p_path + "'.");
- }
-
- /* uint32_t format_size = */ f->get_32();
- uint32_t format_flags = f->get_32();
- uint32_t format_fourcc = f->get_32();
- uint32_t format_rgb_bits = f->get_32();
- uint32_t format_red_mask = f->get_32();
- uint32_t format_green_mask = f->get_32();
- uint32_t format_blue_mask = f->get_32();
- uint32_t format_alpha_mask = f->get_32();
-
- /* uint32_t caps_1 = */ f->get_32();
- /* uint32_t caps_2 = */ f->get_32();
- /* uint32_t caps_ddsx = */ f->get_32();
-
- //reserved skip
- f->get_32();
- f->get_32();
-
- /*
- print_line("DDS width: "+itos(width));
- print_line("DDS height: "+itos(height));
- print_line("DDS mipmaps: "+itos(mipmaps));
-
- printf("fourcc: %x fflags: %x, rgbbits: %x, fsize: %x\n",format_fourcc,format_flags,format_rgb_bits,format_size);
- printf("rmask: %x gmask: %x, bmask: %x, amask: %x\n",format_red_mask,format_green_mask,format_blue_mask,format_alpha_mask);
- */
-
- //must avoid this later
- while (f->get_position() < 128) {
- f->get_8();
- }
-
- DDSFormat dds_format;
-
- if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT1")) {
- dds_format = DDS_DXT1;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT3")) {
- dds_format = DDS_DXT3;
-
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("DXT5")) {
- dds_format = DDS_DXT5;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI1")) {
- dds_format = DDS_ATI1;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("ATI2")) {
- dds_format = DDS_ATI2;
- } else if (format_flags & DDPF_FOURCC && format_fourcc == PF_FOURCC("A2XY")) {
- dds_format = DDS_A2XY;
-
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
- dds_format = DDS_BGRA8;
- } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
- dds_format = DDS_BGR8;
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
- dds_format = DDS_RGBA8;
- } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
- dds_format = DDS_RGB8;
-
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
- dds_format = DDS_BGR5A1;
- } else if (format_flags & DDPF_RGB && format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
- dds_format = DDS_BGR10A2;
- } else if (format_flags & DDPF_RGB && !(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
- dds_format = DDS_BGR565;
- } else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff && format_green_mask == 0xff && format_blue_mask == 0xff) {
- dds_format = DDS_LUMINANCE;
- } else if ((format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 16 && format_red_mask == 0xff && format_green_mask == 0xff && format_blue_mask == 0xff && format_alpha_mask == 0xff00) {
- dds_format = DDS_LUMINANCE_ALPHA;
- } else if (format_flags & DDPF_INDEXED && format_rgb_bits == 8) {
- dds_format = DDS_BGR565;
- } else {
- //printf("unrecognized fourcc %x format_flags: %x - rgbbits %i - red_mask %x green mask %x blue mask %x alpha mask %x\n", format_fourcc, format_flags, format_rgb_bits, format_red_mask, format_green_mask, format_blue_mask, format_alpha_mask);
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'.");
- }
-
- if (!(flags & DDSD_MIPMAPCOUNT)) {
- mipmaps = 1;
- }
-
- Vector<uint8_t> src_data;
-
- const DDSFormatInfo &info = dds_format_info[dds_format];
- uint32_t w = width;
- uint32_t h = height;
-
- if (info.compressed) {
- //compressed bc
-
- uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
- ERR_FAIL_COND_V(size != pitch, Ref<Resource>());
- ERR_FAIL_COND_V(!(flags & DDSD_LINEARSIZE), Ref<Resource>());
-
- for (uint32_t i = 1; i < mipmaps; i++) {
- w = MAX(1u, w >> 1);
- h = MAX(1u, h >> 1);
- uint32_t bsize = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
- //printf("%i x %i - block: %i\n",w,h,bsize);
- size += bsize;
- }
-
- src_data.resize(size);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
-
- } else if (info.palette) {
- //indexed
- ERR_FAIL_COND_V(!(flags & DDSD_PITCH), Ref<Resource>());
- ERR_FAIL_COND_V(format_rgb_bits != 8, Ref<Resource>());
-
- uint32_t size = pitch * height;
- ERR_FAIL_COND_V(size != width * height * info.block_size, Ref<Resource>());
-
- uint8_t palette[256 * 4];
- f->get_buffer(palette, 256 * 4);
-
- int colsize = 3;
- for (int i = 0; i < 256; i++) {
- if (palette[i * 4 + 3] < 255) {
- colsize = 4;
- }
- }
-
- int w2 = width;
- int h2 = height;
-
- for (uint32_t i = 1; i < mipmaps; i++) {
- w2 = (w2 + 1) >> 1;
- h2 = (h2 + 1) >> 1;
- size += w2 * h2 * info.block_size;
- }
-
- src_data.resize(size + 256 * colsize);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
-
- for (int i = 0; i < 256; i++) {
- int dst_ofs = size + i * colsize;
- int src_ofs = i * 4;
- wb[dst_ofs + 0] = palette[src_ofs + 2];
- wb[dst_ofs + 1] = palette[src_ofs + 1];
- wb[dst_ofs + 2] = palette[src_ofs + 0];
- if (colsize == 4) {
- wb[dst_ofs + 3] = palette[src_ofs + 3];
- }
- }
- } else {
- //uncompressed generic...
-
- uint32_t size = width * height * info.block_size;
-
- for (uint32_t i = 1; i < mipmaps; i++) {
- w = (w + 1) >> 1;
- h = (h + 1) >> 1;
- size += w * h * info.block_size;
- }
-
- if (dds_format == DDS_BGR565) {
- size = size * 3 / 2;
- } else if (dds_format == DDS_BGR5A1) {
- size = size * 2;
- }
-
- src_data.resize(size);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
-
- switch (dds_format) {
- case DDS_BGR5A1: {
- // TO RGBA
- int colcount = size / 4;
-
- for (int i = colcount - 1; i >= 0; i--) {
- int src_ofs = i * 2;
- int dst_ofs = i * 4;
-
- uint8_t a = wb[src_ofs + 1] & 0x80;
- uint8_t b = wb[src_ofs] & 0x1F;
- uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x3) << 3);
- uint8_t r = (wb[src_ofs + 1] >> 2) & 0x1F;
- wb[dst_ofs + 0] = r << 3;
- wb[dst_ofs + 1] = g << 3;
- wb[dst_ofs + 2] = b << 3;
- wb[dst_ofs + 3] = a ? 255 : 0;
- }
- } break;
- case DDS_BGR565: {
- int colcount = size / 3;
-
- for (int i = colcount - 1; i >= 0; i--) {
- int src_ofs = i * 2;
- int dst_ofs = i * 3;
-
- uint8_t b = wb[src_ofs] & 0x1F;
- uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x7) << 3);
- uint8_t r = wb[src_ofs + 1] >> 3;
- wb[dst_ofs + 0] = r << 3;
- wb[dst_ofs + 1] = g << 2;
- wb[dst_ofs + 2] = b << 3; //b<<3;
- }
-
- } break;
- case DDS_BGR10A2: {
- // TO RGBA
- int colcount = size / 4;
-
- for (int i = colcount - 1; i >= 0; i--) {
- int ofs = i * 4;
-
- uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
-
- uint8_t a = (w32 & 0xc0000000) >> 24;
- uint8_t r = (w32 & 0x3ff00000) >> 22;
- uint8_t g = (w32 & 0xffc00) >> 12;
- uint8_t b = (w32 & 0x3ff) >> 2;
-
- wb[ofs + 0] = r;
- wb[ofs + 1] = g;
- wb[ofs + 2] = b;
- wb[ofs + 3] = a == 0xc0 ? 255 : a; //0xc0 should be opaque
- }
- } break;
- case DDS_BGRA8: {
- int colcount = size / 4;
-
- for (int i = 0; i < colcount; i++) {
- SWAP(wb[i * 4 + 0], wb[i * 4 + 2]);
- }
-
- } break;
- case DDS_BGR8: {
- int colcount = size / 3;
-
- for (int i = 0; i < colcount; i++) {
- SWAP(wb[i * 3 + 0], wb[i * 3 + 2]);
- }
- } break;
- case DDS_RGBA8: {
- /* do nothing either
- int colcount = size/4;
-
- for(int i=0;i<colcount;i++) {
- uint8_t r = wb[i*4+1];
- uint8_t g = wb[i*4+2];
- uint8_t b = wb[i*4+3];
- uint8_t a = wb[i*4+0];
-
- wb[i*4+0]=r;
- wb[i*4+1]=g;
- wb[i*4+2]=b;
- wb[i*4+3]=a;
- }
- */
- } break;
- case DDS_RGB8: {
- // do nothing
- /*
- int colcount = size/3;
-
- for(int i=0;i<colcount;i++) {
- SWAP( wb[i*3+0],wb[i*3+2] );
- }*/
- } break;
- case DDS_LUMINANCE: {
- // do nothing i guess?
-
- } break;
- case DDS_LUMINANCE_ALPHA: {
- // do nothing i guess?
-
- } break;
-
- default: {
- }
- }
+ Ref<Image> img = memnew(Image);
+ Error i_error = ImageLoaderDDS().load_image(img, f, false, 1.0);
+ if (r_error) {
+ *r_error = i_error;
}
- Ref<Image> img = memnew(Image(width, height, mipmaps - 1, info.format, src_data));
Ref<ImageTexture> texture = ImageTexture::create_from_image(img);
if (r_error) {
diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp
index 0ace89caa5..88aaa006b5 100644
--- a/modules/enet/enet_connection.cpp
+++ b/modules/enet/enet_connection.cpp
@@ -142,7 +142,7 @@ ENetConnection::EventType ENetConnection::_parse_event(const ENetEvent &p_event,
return EVENT_ERROR;
} break;
case ENET_EVENT_TYPE_RECEIVE: {
- // Packet reveived.
+ // Packet received.
if (p_event.peer->data != nullptr) {
Ref<ENetPacketPeer> pp = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
r_event.peer = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 0c300eade4..dd6b668c45 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -533,6 +533,7 @@
[codeblock]
@export_node_path("Button", "TouchScreenButton") var some_button
[/codeblock]
+ [b]Note:[/b] The type must be a native class or a globally registered script (using the [code]class_name[/code] keyword) that inherits [Node].
</description>
</annotation>
<annotation name="@export_placeholder">
@@ -621,7 +622,7 @@
<description>
Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url].
If [param mode] is set as [code]"any_peer"[/code], allows any peer to call this RPC function. Otherwise, only the authority peer is allowed to call it and [param mode] should be kept as [code]"authority"[/code]. When configuring functions as RPCs with [method Node.rpc_config], each of these modes respectively corresponds to the [constant MultiplayerAPI.RPC_MODE_AUTHORITY] and [constant MultiplayerAPI.RPC_MODE_ANY_PEER] RPC modes. See [enum MultiplayerAPI.RPCMode]. If a peer that is not the authority tries to call a function that is only allowed for the authority, the function will not be executed. If the error can be detected locally (when the RPC configuration is consistent between the local and the remote peer), an error message will be displayed on the sender peer. Otherwise, the remote peer will detect the error and print an error there.
- If [param sync] is set as [code]"call_remote"[/code], the function will only be executed on the remote peer, but not locally. To run this function locally too, set [param sync] to [code]"call_local"[/code]. When configuring functions as RPCs with [method Node.rpc_config], this is equivalent to setting `call_local` to `true`.
+ If [param sync] is set as [code]"call_remote"[/code], the function will only be executed on the remote peer, but not locally. To run this function locally too, set [param sync] to [code]"call_local"[/code]. When configuring functions as RPCs with [method Node.rpc_config], this is equivalent to setting [code]call_local[/code] to [code]true[/code].
The [param transfer_mode] accepted values are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code], or [code]"reliable"[/code]. It sets the transfer mode of the underlying [MultiplayerPeer]. See [member MultiplayerPeer.transfer_mode].
The [param transfer_channel] defines the channel of the underlying [MultiplayerPeer]. See [member MultiplayerPeer.transfer_channel].
The order of [param mode], [param sync] and [param transfer_mode] does not matter, but values related to the same argument must not be used more than once. [param transfer_channel] always has to be the 4th argument (you must specify 3 preceding arguments).
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 0d8453738d..26f326838c 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -101,14 +101,16 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
doc.inherits = p_script->native->get_name();
}
- doc.brief_description = p_class->doc_brief_description;
- doc.description = p_class->doc_description;
- for (const Pair<String, String> &p : p_class->doc_tutorials) {
+ doc.brief_description = p_class->doc_data.brief;
+ doc.description = p_class->doc_data.description;
+ for (const Pair<String, String> &p : p_class->doc_data.tutorials) {
DocData::TutorialDoc td;
td.title = p.first;
td.link = p.second;
doc.tutorials.append(td);
}
+ doc.is_deprecated = p_class->doc_data.is_deprecated;
+ doc.is_experimental = p_class->doc_data.is_experimental;
for (const GDP::ClassNode::Member &member : p_class->members) {
switch (member.type) {
@@ -130,7 +132,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[const_name] = m_const->start_line;
DocData::ConstantDoc const_doc;
- DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_description);
+ DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_data.description);
+ const_doc.is_deprecated = m_const->doc_data.is_deprecated;
+ const_doc.is_experimental = m_const->doc_data.is_experimental;
doc.constants.push_back(const_doc);
} break;
@@ -153,7 +157,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
}
DocData::MethodDoc method_doc;
- DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_description);
+ DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_data.description);
+ method_doc.is_deprecated = m_func->doc_data.is_deprecated;
+ method_doc.is_experimental = m_func->doc_data.is_experimental;
doc.methods.push_back(method_doc);
} break;
@@ -172,7 +178,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
}
DocData::MethodDoc signal_doc;
- DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_description);
+ DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_data.description);
+ signal_doc.is_deprecated = m_signal->doc_data.is_deprecated;
+ signal_doc.is_experimental = m_signal->doc_data.is_experimental;
doc.signals.push_back(signal_doc);
} break;
@@ -185,7 +193,9 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
DocData::PropertyDoc prop_doc;
prop_doc.name = var_name;
- prop_doc.description = m_var->doc_description;
+ prop_doc.description = m_var->doc_data.description;
+ prop_doc.is_deprecated = m_var->doc_data.is_deprecated;
+ prop_doc.is_experimental = m_var->doc_data.is_experimental;
GDType dt = m_var->get_datatype();
switch (dt.kind) {
@@ -236,15 +246,21 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[name] = m_enum->start_line;
- doc.enums[name] = m_enum->doc_description;
+ DocData::EnumDoc enum_doc;
+ enum_doc.description = m_enum->doc_data.description;
+ enum_doc.is_deprecated = m_enum->doc_data.is_deprecated;
+ enum_doc.is_experimental = m_enum->doc_data.is_experimental;
+ doc.enums[name] = enum_doc;
for (const GDP::EnumNode::Value &val : m_enum->values) {
DocData::ConstantDoc const_doc;
const_doc.name = val.identifier->name;
const_doc.value = String(Variant(val.value));
const_doc.is_value_valid = true;
- const_doc.description = val.doc_description;
const_doc.enumeration = name;
+ const_doc.description = val.doc_data.description;
+ const_doc.is_deprecated = val.doc_data.is_deprecated;
+ const_doc.is_experimental = val.doc_data.is_experimental;
doc.constants.push_back(const_doc);
}
@@ -257,10 +273,12 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[name] = m_enum_val.identifier->start_line;
- DocData::ConstantDoc constant_doc;
- constant_doc.enumeration = "@unnamed_enums";
- DocData::constant_doc_from_variant(constant_doc, name, m_enum_val.value, m_enum_val.doc_description);
- doc.constants.push_back(constant_doc);
+ DocData::ConstantDoc const_doc;
+ DocData::constant_doc_from_variant(const_doc, name, m_enum_val.value, m_enum_val.doc_data.description);
+ const_doc.enumeration = "@unnamed_enums";
+ const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
+ const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
+ doc.constants.push_back(const_doc);
} break;
case GDP::ClassNode::Member::GROUP:
case GDP::ClassNode::Member::UNDEFINED:
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index b54dc502ae..862799a6ec 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -145,6 +145,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Check if it's the whole line.
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
+ // Don't skip comments, for highlighting markers.
+ if (color_regions[in_region].start_key == "#") {
+ break;
+ }
if (from + end_key_length > line_length) {
// If it's key length and there is a '\', dont skip to highlight esc chars.
if (str.find("\\", from) >= 0) {
@@ -163,7 +167,8 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
break;
}
- if (j == line_length) {
+ // Don't skip comments, for highlighting markers.
+ if (j == line_length && color_regions[in_region].start_key != "#") {
continue;
}
}
@@ -185,56 +190,83 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
highlighter_info["color"] = region_color;
color_map[j] = highlighter_info;
- // Search the line.
- int region_end_index = -1;
- int end_key_length = color_regions[in_region].end_key.length();
- const char32_t *end_key = color_regions[in_region].end_key.get_data();
- for (; from < line_length; from++) {
- if (line_length - from < end_key_length) {
- // Don't break if '\' to highlight esc chars.
- if (str.find("\\", from) < 0) {
- break;
+ if (color_regions[in_region].start_key == "#") {
+ int marker_start_pos = from;
+ int marker_len = 0;
+ while (from <= line_length) {
+ if (from < line_length && is_unicode_identifier_continue(str[from])) {
+ marker_len++;
+ } else {
+ if (marker_len > 0) {
+ HashMap<String, CommentMarkerLevel>::ConstIterator E = comment_markers.find(str.substr(marker_start_pos, marker_len));
+ if (E) {
+ Dictionary marker_highlighter_info;
+ marker_highlighter_info["color"] = comment_marker_colors[E->value];
+ color_map[marker_start_pos] = marker_highlighter_info;
+
+ Dictionary marker_continue_highlighter_info;
+ marker_continue_highlighter_info["color"] = region_color;
+ color_map[from] = marker_continue_highlighter_info;
+ }
+ }
+ marker_start_pos = from + 1;
+ marker_len = 0;
}
+ from++;
}
+ from = line_length - 1;
+ j = from;
+ } else {
+ // Search the line.
+ int region_end_index = -1;
+ int end_key_length = color_regions[in_region].end_key.length();
+ const char32_t *end_key = color_regions[in_region].end_key.get_data();
+ for (; from < line_length; from++) {
+ if (line_length - from < end_key_length) {
+ // Don't break if '\' to highlight esc chars.
+ if (str.find("\\", from) < 0) {
+ break;
+ }
+ }
- if (!is_symbol(str[from])) {
- continue;
- }
+ if (!is_symbol(str[from])) {
+ continue;
+ }
- if (str[from] == '\\') {
- Dictionary escape_char_highlighter_info;
- escape_char_highlighter_info["color"] = symbol_color;
- color_map[from] = escape_char_highlighter_info;
+ if (str[from] == '\\') {
+ Dictionary escape_char_highlighter_info;
+ escape_char_highlighter_info["color"] = symbol_color;
+ color_map[from] = escape_char_highlighter_info;
- from++;
+ from++;
- Dictionary region_continue_highlighter_info;
- prev_color = region_color;
- region_continue_highlighter_info["color"] = region_color;
- color_map[from + 1] = region_continue_highlighter_info;
- continue;
- }
+ Dictionary region_continue_highlighter_info;
+ region_continue_highlighter_info["color"] = region_color;
+ color_map[from + 1] = region_continue_highlighter_info;
+ continue;
+ }
- region_end_index = from;
- for (int k = 0; k < end_key_length; k++) {
- if (end_key[k] != str[from + k]) {
- region_end_index = -1;
+ region_end_index = from;
+ for (int k = 0; k < end_key_length; k++) {
+ if (end_key[k] != str[from + k]) {
+ region_end_index = -1;
+ break;
+ }
+ }
+
+ if (region_end_index != -1) {
break;
}
}
-
- if (region_end_index != -1) {
- break;
+ j = from + (end_key_length - 1);
+ if (region_end_index == -1) {
+ color_region_cache[p_line] = in_region;
}
}
prev_type = REGION;
prev_text = "";
prev_column = j;
- j = from + (end_key_length - 1);
- if (region_end_index == -1) {
- color_region_cache[p_line] = in_region;
- }
in_region = -1;
prev_is_char = false;
@@ -706,6 +738,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
node_ref_color = Color(0.39, 0.76, 0.35);
annotation_color = Color(1.0, 0.7, 0.45);
string_name_color = Color(1.0, 0.76, 0.65);
+ comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.77, 0.35, 0.35);
+ comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.72, 0.61, 0.48);
+ comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.56, 0.67, 0.51);
} else {
function_definition_color = Color(0, 0.6, 0.6);
global_function_color = Color(0.36, 0.18, 0.72);
@@ -713,6 +748,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
node_ref_color = Color(0.0, 0.5, 0);
annotation_color = Color(0.8, 0.37, 0);
string_name_color = Color(0.8, 0.56, 0.45);
+ comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.8, 0.14, 0.14);
+ comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.75, 0.39, 0.03);
+ comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
}
EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
@@ -721,6 +759,14 @@ void GDScriptSyntaxHighlighter::_update_cache() {
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_reference_color", node_ref_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/annotation_color", annotation_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/string_name_color", string_name_color);
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_color", comment_marker_colors[COMMENT_MARKER_CRITICAL]);
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_color", comment_marker_colors[COMMENT_MARKER_WARNING]);
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_color", comment_marker_colors[COMMENT_MARKER_NOTICE]);
+ // The list is based on <https://github.com/KDE/syntax-highlighting/blob/master/data/syntax/alert.xml>.
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_list", "ALERT,ATTENTION,CAUTION,CRITICAL,DANGER,SECURITY");
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_list", "BUG,DEPRECATED,FIXME,HACK,TASK,TBD,TODO,WARNING");
+ EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_list", "INFO,NOTE,NOTICE,TEST,TESTING");
+
if (text_edit_color_theme == "Default" || godot_2_theme) {
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/function_definition_color",
@@ -746,6 +792,18 @@ void GDScriptSyntaxHighlighter::_update_cache() {
"text_editor/theme/highlighting/gdscript/string_name_color",
string_name_color,
true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/theme/highlighting/comment_markers/critical_color",
+ comment_marker_colors[COMMENT_MARKER_CRITICAL],
+ true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/theme/highlighting/comment_markers/warning_color",
+ comment_marker_colors[COMMENT_MARKER_WARNING],
+ true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/theme/highlighting/comment_markers/notice_color",
+ comment_marker_colors[COMMENT_MARKER_NOTICE],
+ true);
}
function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color");
@@ -755,6 +813,23 @@ void GDScriptSyntaxHighlighter::_update_cache() {
annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color");
string_name_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/string_name_color");
type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
+ comment_marker_colors[COMMENT_MARKER_CRITICAL] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_color");
+ comment_marker_colors[COMMENT_MARKER_WARNING] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_color");
+ comment_marker_colors[COMMENT_MARKER_NOTICE] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_color");
+
+ comment_markers.clear();
+ Vector<String> critical_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_list").operator String().split(",", false);
+ for (int i = 0; i < critical_list.size(); i++) {
+ comment_markers[critical_list[i]] = COMMENT_MARKER_CRITICAL;
+ }
+ Vector<String> warning_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_list").operator String().split(",", false);
+ for (int i = 0; i < warning_list.size(); i++) {
+ comment_markers[warning_list[i]] = COMMENT_MARKER_WARNING;
+ }
+ Vector<String> notice_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_list").operator String().split(",", false);
+ for (int i = 0; i < notice_list.size(); i++) {
+ comment_markers[notice_list[i]] = COMMENT_MARKER_NOTICE;
+ }
}
void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index aceb644658..fe3b63d713 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -84,6 +84,15 @@ private:
Color string_name_color;
Color type_color;
+ enum CommentMarkerLevel {
+ COMMENT_MARKER_CRITICAL,
+ COMMENT_MARKER_WARNING,
+ COMMENT_MARKER_NOTICE,
+ COMMENT_MARKER_MAX,
+ };
+ Color comment_marker_colors[COMMENT_MARKER_MAX];
+ HashMap<String, CommentMarkerLevel> comment_markers;
+
void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false);
public:
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index e17e748d7b..064143f400 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -31,6 +31,7 @@
#include "gdscript_translation_parser_plugin.h"
#include "../gdscript.h"
+#include "../gdscript_analyzer.h"
#include "core/io/resource_loader.h"
@@ -58,10 +59,11 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
GDScriptParser parser;
err = parser.parse(source_code, p_path, false);
- if (err != OK) {
- ERR_PRINT("Failed to parse with GDScript with GDScriptParser.");
- return err;
- }
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to parse GDScript with GDScriptParser.");
+
+ GDScriptAnalyzer analyzer(&parser);
+ err = analyzer.analyze();
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
// Traverse through the parsed tree from GDScriptParser.
GDScriptParser::ClassNode *c = parser.get_tree();
@@ -70,6 +72,11 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
return OK;
}
+bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
+ ERR_FAIL_NULL_V(p_expression, false);
+ return p_expression->is_constant && (p_expression->reduced_value.get_type() == Variant::STRING || p_expression->reduced_value.get_type() == Variant::STRING_NAME);
+}
+
void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &m = p_class->members[i];
@@ -105,15 +112,15 @@ void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser
const Vector<GDScriptParser::Node *> &statements = p_suite->statements;
for (int i = 0; i < statements.size(); i++) {
- GDScriptParser::Node *statement = statements[i];
+ const GDScriptParser::Node *statement = statements[i];
// Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings.
switch (statement->type) {
case GDScriptParser::Node::VARIABLE:
- _assess_expression(static_cast<GDScriptParser::VariableNode *>(statement)->initializer);
+ _assess_expression(static_cast<const GDScriptParser::VariableNode *>(statement)->initializer);
break;
case GDScriptParser::Node::IF: {
- GDScriptParser::IfNode *if_node = static_cast<GDScriptParser::IfNode *>(statement);
+ const GDScriptParser::IfNode *if_node = static_cast<const GDScriptParser::IfNode *>(statement);
_assess_expression(if_node->condition);
//FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if().
_traverse_block(if_node->true_block);
@@ -121,19 +128,19 @@ void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser
break;
}
case GDScriptParser::Node::FOR: {
- GDScriptParser::ForNode *for_node = static_cast<GDScriptParser::ForNode *>(statement);
+ const GDScriptParser::ForNode *for_node = static_cast<const GDScriptParser::ForNode *>(statement);
_assess_expression(for_node->list);
_traverse_block(for_node->loop);
break;
}
case GDScriptParser::Node::WHILE: {
- GDScriptParser::WhileNode *while_node = static_cast<GDScriptParser::WhileNode *>(statement);
+ const GDScriptParser::WhileNode *while_node = static_cast<const GDScriptParser::WhileNode *>(statement);
_assess_expression(while_node->condition);
_traverse_block(while_node->loop);
break;
}
case GDScriptParser::Node::MATCH: {
- GDScriptParser::MatchNode *match_node = static_cast<GDScriptParser::MatchNode *>(statement);
+ const GDScriptParser::MatchNode *match_node = static_cast<const GDScriptParser::MatchNode *>(statement);
_assess_expression(match_node->test);
for (int j = 0; j < match_node->branches.size(); j++) {
_traverse_block(match_node->branches[j]->block);
@@ -141,24 +148,24 @@ void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser
break;
}
case GDScriptParser::Node::RETURN:
- _assess_expression(static_cast<GDScriptParser::ReturnNode *>(statement)->return_value);
+ _assess_expression(static_cast<const GDScriptParser::ReturnNode *>(statement)->return_value);
break;
case GDScriptParser::Node::ASSERT:
- _assess_expression((static_cast<GDScriptParser::AssertNode *>(statement))->condition);
+ _assess_expression((static_cast<const GDScriptParser::AssertNode *>(statement))->condition);
break;
case GDScriptParser::Node::ASSIGNMENT:
- _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(statement));
+ _assess_assignment(static_cast<const GDScriptParser::AssignmentNode *>(statement));
break;
default:
if (statement->is_expression()) {
- _assess_expression(static_cast<GDScriptParser::ExpressionNode *>(statement));
+ _assess_expression(static_cast<const GDScriptParser::ExpressionNode *>(statement));
}
break;
}
}
}
-void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::ExpressionNode *p_expression) {
+void GDScriptEditorTranslationParserPlugin::_assess_expression(const GDScriptParser::ExpressionNode *p_expression) {
// Explore all ExpressionNodes to find CallNodes which contain translation strings, such as tr(), set_text() etc.
// tr() can be embedded quite deep within multiple ExpressionNodes so need to dig down to search through all ExpressionNodes.
if (!p_expression) {
@@ -169,30 +176,30 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::E
// containing translation strings.
switch (p_expression->type) {
case GDScriptParser::Node::ARRAY: {
- GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(p_expression);
+ const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
for (int i = 0; i < array_node->elements.size(); i++) {
_assess_expression(array_node->elements[i]);
}
break;
}
case GDScriptParser::Node::ASSIGNMENT:
- _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression));
+ _assess_assignment(static_cast<const GDScriptParser::AssignmentNode *>(p_expression));
break;
case GDScriptParser::Node::BINARY_OPERATOR: {
- GDScriptParser::BinaryOpNode *binary_op_node = static_cast<GDScriptParser::BinaryOpNode *>(p_expression);
+ const GDScriptParser::BinaryOpNode *binary_op_node = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
_assess_expression(binary_op_node->left_operand);
_assess_expression(binary_op_node->right_operand);
break;
}
case GDScriptParser::Node::CALL: {
- GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_expression);
+ const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_expression);
_extract_from_call(call_node);
for (int i = 0; i < call_node->arguments.size(); i++) {
_assess_expression(call_node->arguments[i]);
}
} break;
case GDScriptParser::Node::DICTIONARY: {
- GDScriptParser::DictionaryNode *dict_node = static_cast<GDScriptParser::DictionaryNode *>(p_expression);
+ const GDScriptParser::DictionaryNode *dict_node = static_cast<const GDScriptParser::DictionaryNode *>(p_expression);
for (int i = 0; i < dict_node->elements.size(); i++) {
_assess_expression(dict_node->elements[i].key);
_assess_expression(dict_node->elements[i].value);
@@ -200,7 +207,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::E
break;
}
case GDScriptParser::Node::TERNARY_OPERATOR: {
- GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<GDScriptParser::TernaryOpNode *>(p_expression);
+ const GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression);
_assess_expression(ternary_op_node->condition);
_assess_expression(ternary_op_node->true_expr);
_assess_expression(ternary_op_node->false_expr);
@@ -211,39 +218,39 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::E
}
}
-void GDScriptEditorTranslationParserPlugin::_assess_assignment(GDScriptParser::AssignmentNode *p_assignment) {
+void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptParser::AssignmentNode *p_assignment) {
// Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____"
StringName assignee_name;
if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
- assignee_name = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name;
+ assignee_name = static_cast<const GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name;
} else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
- assignee_name = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name;
+ assignee_name = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name;
}
- if (assignment_patterns.has(assignee_name) && p_assignment->assigned_value->type == GDScriptParser::Node::LITERAL) {
- // If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a string literal, we collect the string.
- ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_assignment->assigned_value)->value);
+ if (assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
+ // If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
+ ids->push_back(p_assignment->assigned_value->reduced_value);
} else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) {
// FileDialog.filters accepts assignment in the form of PackedStringArray. For example,
// get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]).
- GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value);
+ const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_assignment->assigned_value);
if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
- GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+ const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]);
// Extract the name in "extension ; name" of PackedStringArray.
for (int i = 0; i < array_node->elements.size(); i++) {
- _extract_fd_literals(array_node->elements[i]);
+ _extract_fd_constant_strings(array_node->elements[i]);
}
}
} else {
- // If the assignee is not in extract patterns or the assigned_value is not Literal type, try to see if the assigned_value contains tr().
+ // If the assignee is not in extract patterns or the assigned_value is not a constant string, try to see if the assigned_value contains tr().
_assess_expression(p_assignment->assigned_value);
}
}
-void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::CallNode *p_call) {
+void GDScriptEditorTranslationParserPlugin::_extract_from_call(const GDScriptParser::CallNode *p_call) {
// Extract the translatable strings coming from function calls. For example:
// tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____").
@@ -257,8 +264,8 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::C
if (function_name == tr_func) {
// Extract from tr(id, ctx).
for (int i = 0; i < p_call->arguments.size(); i++) {
- if (p_call->arguments[i]->type == GDScriptParser::Node::LITERAL) {
- id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[i])->value;
+ if (_is_constant_string(p_call->arguments[i])) {
+ id_ctx_plural.write[i] = p_call->arguments[i]->reduced_value;
} else {
// Avoid adding something like tr("Flying dragon", var_context_level_1). We want to extract both id and context together.
extract_id_ctx_plural = false;
@@ -278,8 +285,8 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::C
continue;
}
- if (p_call->arguments[indices[i]]->type == GDScriptParser::Node::LITERAL) {
- id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[indices[i]])->value;
+ if (_is_constant_string(p_call->arguments[indices[i]])) {
+ id_ctx_plural.write[i] = p_call->arguments[indices[i]]->reduced_value;
} else {
extract_id_ctx_plural = false;
}
@@ -288,45 +295,43 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::C
ids_ctx_plural->push_back(id_ctx_plural);
}
} else if (first_arg_patterns.has(function_name)) {
- // Extracting argument with only string literals. In other words, not extracting something like set_text("hello " + some_var).
- if (p_call->arguments[0]->type == GDScriptParser::Node::LITERAL) {
- ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[0])->value);
+ if (_is_constant_string(p_call->arguments[0])) {
+ ids->push_back(p_call->arguments[0]->reduced_value);
}
} else if (second_arg_patterns.has(function_name)) {
- if (p_call->arguments[1]->type == GDScriptParser::Node::LITERAL) {
- ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[1])->value);
+ if (_is_constant_string(p_call->arguments[1])) {
+ ids->push_back(p_call->arguments[1]->reduced_value);
}
} else if (function_name == fd_add_filter) {
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
- _extract_fd_literals(p_call->arguments[0]);
-
+ _extract_fd_constant_strings(p_call->arguments[0]);
} else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) {
// FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example,
// get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])).
- GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_call->arguments[0]);
+ const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_call->arguments[0]);
if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
- GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]);
+ const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]);
for (int i = 0; i < array_node->elements.size(); i++) {
- _extract_fd_literals(array_node->elements[i]);
+ _extract_fd_constant_strings(array_node->elements[i]);
}
}
}
if (p_call->callee && p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) {
- GDScriptParser::SubscriptNode *subscript_node = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee);
+ const GDScriptParser::SubscriptNode *subscript_node = static_cast<const GDScriptParser::SubscriptNode *>(p_call->callee);
if (subscript_node->base && subscript_node->base->type == GDScriptParser::Node::CALL) {
- GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(subscript_node->base);
+ const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(subscript_node->base);
_extract_from_call(call_node);
}
}
}
-void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) {
+void GDScriptEditorTranslationParserPlugin::_extract_fd_constant_strings(const GDScriptParser::ExpressionNode *p_expression) {
// Extract the name in "extension ; name".
- if (p_expression->type == GDScriptParser::Node::LITERAL) {
- String arg_val = String(static_cast<GDScriptParser::LiteralNode *>(p_expression)->value);
+ if (_is_constant_string(p_expression)) {
+ String arg_val = p_expression->reduced_value;
PackedStringArray arr = arg_val.split(";", true);
if (arr.size() != 2) {
ERR_PRINT("Argument for setting FileDialog has bad format.");
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 421030e49a..580c2a80cd 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -53,15 +53,17 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
StringName fd_set_filter = "set_filters";
StringName fd_filters = "filters";
+ static bool _is_constant_string(const GDScriptParser::ExpressionNode *p_expression);
+
void _traverse_class(const GDScriptParser::ClassNode *p_class);
void _traverse_function(const GDScriptParser::FunctionNode *p_func);
void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
void _read_variable(const GDScriptParser::VariableNode *p_var);
- void _assess_expression(GDScriptParser::ExpressionNode *p_expression);
- void _assess_assignment(GDScriptParser::AssignmentNode *p_assignment);
- void _extract_from_call(GDScriptParser::CallNode *p_call);
- void _extract_fd_literals(GDScriptParser::ExpressionNode *p_expression);
+ void _assess_expression(const GDScriptParser::ExpressionNode *p_expression);
+ void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment);
+ void _extract_from_call(const GDScriptParser::CallNode *p_call);
+ void _extract_fd_constant_strings(const GDScriptParser::ExpressionNode *p_expression);
public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 7117337827..42b08f8a68 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2094,10 +2094,7 @@ String GDScriptLanguage::get_extension() const {
}
void GDScriptLanguage::finish() {
- if (_call_stack) {
- memdelete_arr(_call_stack);
- _call_stack = nullptr;
- }
+ _call_stack.free();
// Clear the cache before parsing the script_list
GDScriptCache::clear();
@@ -2140,12 +2137,12 @@ void GDScriptLanguage::profiling_start() {
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
- elem->self()->profile.call_count = 0;
- elem->self()->profile.self_time = 0;
- elem->self()->profile.total_time = 0;
- elem->self()->profile.frame_call_count = 0;
- elem->self()->profile.frame_self_time = 0;
- elem->self()->profile.frame_total_time = 0;
+ elem->self()->profile.call_count.set(0);
+ elem->self()->profile.self_time.set(0);
+ elem->self()->profile.total_time.set(0);
+ elem->self()->profile.frame_call_count.set(0);
+ elem->self()->profile.frame_self_time.set(0);
+ elem->self()->profile.frame_total_time.set(0);
elem->self()->profile.last_frame_call_count = 0;
elem->self()->profile.last_frame_self_time = 0;
elem->self()->profile.last_frame_total_time = 0;
@@ -2175,9 +2172,9 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,
if (current >= p_info_max) {
break;
}
- p_info_arr[current].call_count = elem->self()->profile.call_count;
- p_info_arr[current].self_time = elem->self()->profile.self_time;
- p_info_arr[current].total_time = elem->self()->profile.total_time;
+ p_info_arr[current].call_count = elem->self()->profile.call_count.get();
+ p_info_arr[current].self_time = elem->self()->profile.self_time.get();
+ p_info_arr[current].total_time = elem->self()->profile.total_time.get();
p_info_arr[current].signature = elem->self()->profile.signature;
elem = elem->next();
current++;
@@ -2395,12 +2392,12 @@ void GDScriptLanguage::frame() {
SelfList<GDScriptFunction> *elem = function_list.first();
while (elem) {
- elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count;
- elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time;
- elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time;
- elem->self()->profile.frame_call_count = 0;
- elem->self()->profile.frame_self_time = 0;
- elem->self()->profile.frame_total_time = 0;
+ elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count.get();
+ elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time.get();
+ elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get();
+ elem->self()->profile.frame_call_count.set(0);
+ elem->self()->profile.frame_self_time.set(0);
+ elem->self()->profile.frame_total_time.set(0);
elem = elem->next();
}
}
@@ -2607,6 +2604,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
return c->identifier != nullptr ? String(c->identifier->name) : String();
}
+thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack;
+
GDScriptLanguage::GDScriptLanguage() {
calls = 0;
ERR_FAIL_COND(singleton);
@@ -2626,18 +2625,14 @@ GDScriptLanguage::GDScriptLanguage() {
profiling = false;
script_frame_time = 0;
- _debug_call_stack_pos = 0;
int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024);
if (EngineDebugger::is_active()) {
//debugging enabled!
_debug_max_call_stack = dmcs;
- _call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1);
-
} else {
_debug_max_call_stack = 0;
- _call_stack = nullptr;
}
#ifdef DEBUG_ENABLED
@@ -2700,6 +2695,11 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str
Error err;
Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE);
+ if (err && scr.is_valid()) {
+ // If !scr.is_valid(), the error was likely from scr->load_source_code(), which already generates an error.
+ ERR_PRINT_ED(vformat(R"(Failed to load script "%s" with error "%s".)", p_path, error_names[err]));
+ }
+
if (r_error) {
// Don't fail loading because of parsing error.
*r_error = scr.is_valid() ? OK : err;
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index d131ec6ab1..c41b1a0def 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -364,12 +364,26 @@ class GDScriptLanguage : public ScriptLanguage {
int *line = nullptr;
};
- int _debug_parse_err_line;
- String _debug_parse_err_file;
- String _debug_error;
- int _debug_call_stack_pos;
- int _debug_max_call_stack;
- CallLevel *_call_stack = nullptr;
+ static thread_local int _debug_parse_err_line;
+ static thread_local String _debug_parse_err_file;
+ static thread_local String _debug_error;
+ struct CallStack {
+ CallLevel *levels = nullptr;
+ int stack_pos = 0;
+
+ void free() {
+ if (levels) {
+ memdelete(levels);
+ levels = nullptr;
+ }
+ }
+ ~CallStack() {
+ free();
+ }
+ };
+
+ static thread_local CallStack _call_stack;
+ int _debug_max_call_stack = 0;
void _add_global(const StringName &p_name, const Variant &p_value);
@@ -395,59 +409,51 @@ public:
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
_FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) {
- if (Thread::get_main_id() != Thread::get_caller_id()) {
- return; //no support for other threads than main for now
+ if (unlikely(_call_stack.levels == nullptr)) {
+ _call_stack.levels = memnew_arr(CallLevel, _debug_max_call_stack + 1);
}
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1);
}
- if (_debug_call_stack_pos >= _debug_max_call_stack) {
+ if (_call_stack.stack_pos >= _debug_max_call_stack) {
//stack overflow
_debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack);
EngineDebugger::get_script_debugger()->debug(this);
return;
}
- _call_stack[_debug_call_stack_pos].stack = p_stack;
- _call_stack[_debug_call_stack_pos].instance = p_instance;
- _call_stack[_debug_call_stack_pos].function = p_function;
- _call_stack[_debug_call_stack_pos].ip = p_ip;
- _call_stack[_debug_call_stack_pos].line = p_line;
- _debug_call_stack_pos++;
+ _call_stack.levels[_call_stack.stack_pos].stack = p_stack;
+ _call_stack.levels[_call_stack.stack_pos].instance = p_instance;
+ _call_stack.levels[_call_stack.stack_pos].function = p_function;
+ _call_stack.levels[_call_stack.stack_pos].ip = p_ip;
+ _call_stack.levels[_call_stack.stack_pos].line = p_line;
+ _call_stack.stack_pos++;
}
_FORCE_INLINE_ void exit_function() {
- if (Thread::get_main_id() != Thread::get_caller_id()) {
- return; //no support for other threads than main for now
- }
-
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1);
}
- if (_debug_call_stack_pos == 0) {
+ if (_call_stack.stack_pos == 0) {
_debug_error = "Stack Underflow (Engine Bug)";
EngineDebugger::get_script_debugger()->debug(this);
return;
}
- _debug_call_stack_pos--;
+ _call_stack.stack_pos--;
}
virtual Vector<StackInfo> debug_get_current_stack_info() override {
- if (Thread::get_main_id() != Thread::get_caller_id()) {
- return Vector<StackInfo>();
- }
-
Vector<StackInfo> csi;
- csi.resize(_debug_call_stack_pos);
- for (int i = 0; i < _debug_call_stack_pos; i++) {
- csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
- if (_call_stack[i].function) {
- csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
- csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path();
+ csi.resize(_call_stack.stack_pos);
+ for (int i = 0; i < _call_stack.stack_pos; i++) {
+ csi.write[_call_stack.stack_pos - i - 1].line = _call_stack.levels[i].line ? *_call_stack.levels[i].line : 0;
+ if (_call_stack.levels[i].function) {
+ csi.write[_call_stack.stack_pos - i - 1].func = _call_stack.levels[i].function->get_name();
+ csi.write[_call_stack.stack_pos - i - 1].file = _call_stack.levels[i].function->get_script()->get_script_path();
}
}
return csi;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 01e94ebb22..cb04913620 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -1772,6 +1772,15 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
bool is_constant = p_assignable->type == GDScriptParser::Node::CONSTANT;
+#ifdef DEBUG_ENABLED
+ if (p_assignable->identifier != nullptr && p_assignable->identifier->suite != nullptr && p_assignable->identifier->suite->parent_block != nullptr) {
+ if (p_assignable->identifier->suite->parent_block->has_local(p_assignable->identifier->name)) {
+ const GDScriptParser::SuiteNode::Local &local = p_assignable->identifier->suite->parent_block->get_local(p_assignable->identifier->name);
+ parser->push_warning(p_assignable->identifier, GDScriptWarning::CONFUSABLE_LOCAL_DECLARATION, local.get_name(), p_assignable->identifier->name);
+ }
+ }
+#endif
+
GDScriptParser::DataType specified_type;
bool has_specified_type = p_assignable->datatype_specifier != nullptr;
if (has_specified_type) {
@@ -3518,12 +3527,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
case GDScriptParser::ClassNode::Member::FUNCTION: {
if (is_base && (!base.is_meta_type || member.function->is_static)) {
p_identifier->set_datatype(make_callable_type(member.function->info));
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_FUNCTION;
return;
}
} break;
case GDScriptParser::ClassNode::Member::CLASS: {
reduce_identifier_from_base_set_class(p_identifier, member.get_datatype());
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS;
return;
}
@@ -3662,9 +3673,17 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
found_source = true;
} break;
case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE:
+ case GDScriptParser::IdentifierNode::MEMBER_FUNCTION:
+ case GDScriptParser::IdentifierNode::MEMBER_CLASS:
break;
}
+#ifdef DEBUG_ENABLED
+ if (!found_source && p_identifier->suite != nullptr && p_identifier->suite->has_local(p_identifier->name)) {
+ parser->push_warning(p_identifier, GDScriptWarning::CONFUSABLE_LOCAL_USAGE, p_identifier->name);
+ }
+#endif
+
// Not a local, so check members.
if (!found_source) {
reduce_identifier_from_base(p_identifier);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 47cd3f768b..6057a00f9b 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -226,7 +226,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
if (opcodes.size()) {
function->code = opcodes;
- function->_code_ptr = &function->code[0];
+ function->_code_ptr = &function->code.write[0];
function->_code_size = opcodes.size();
} else {
@@ -577,6 +577,12 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
append(Address());
append(p_target);
append(p_operator);
+ append(0); // Signature storage.
+ append(0); // Return type storage.
+ constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*(opcodes.ptr()));
+ for (int i = 0; i < _pointer_size; i++) {
+ append(0); // Space for function pointer.
+ }
}
void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
@@ -610,6 +616,12 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V
append(p_right_operand);
append(p_target);
append(p_operator);
+ append(0); // Signature storage.
+ append(0); // Return type storage.
+ constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*(opcodes.ptr()));
+ for (int i = 0; i < _pointer_size; i++) {
+ append(0); // Space for function pointer.
+ }
}
void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) {
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 3f571602e8..3366fa2eec 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -225,104 +225,85 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
StringName identifier = in->name;
- // Try function parameters.
- if (codegen.parameters.has(identifier)) {
- return codegen.parameters[identifier];
- }
-
- // Try local variables and constants.
- if (!p_initializer && codegen.locals.has(identifier)) {
- return codegen.locals[identifier];
- }
+ switch (in->source) {
+ // LOCALS.
+ case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER:
+ case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
+ case GDScriptParser::IdentifierNode::LOCAL_CONSTANT:
+ case GDScriptParser::IdentifierNode::LOCAL_ITERATOR:
+ case GDScriptParser::IdentifierNode::LOCAL_BIND: {
+ // Try function parameters.
+ if (codegen.parameters.has(identifier)) {
+ return codegen.parameters[identifier];
+ }
- // Try class members.
- if (_is_class_member_property(codegen, identifier)) {
- // Get property.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script));
- gen->write_get_member(temp, identifier);
- return temp;
- }
+ // Try local variables and constants.
+ if (!p_initializer && codegen.locals.has(identifier)) {
+ return codegen.locals[identifier];
+ }
+ } break;
- // Try members.
- if (!codegen.function_node || !codegen.function_node->is_static) {
- // Try member variables.
- if (codegen.script->member_indices.has(identifier)) {
- if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) {
- // Perform getter.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->member_indices[identifier].data_type);
- Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
- gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
+ // MEMBERS.
+ case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
+ case GDScriptParser::IdentifierNode::MEMBER_FUNCTION:
+ case GDScriptParser::IdentifierNode::MEMBER_SIGNAL:
+ case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: {
+ // Try class members.
+ if (_is_class_member_property(codegen, identifier)) {
+ // Get property.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script));
+ gen->write_get_member(temp, identifier);
return temp;
- } else {
- // No getter or inside getter: direct member access.
- int idx = codegen.script->member_indices[identifier].index;
- return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier));
}
- }
- }
- // Try static variables.
- {
- GDScript *scr = codegen.script;
- while (scr) {
- if (scr->static_variables_indices.has(identifier)) {
- if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) {
- // Perform getter.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
- GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
- Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
- gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args);
- return temp;
- } else {
- // No getter or inside getter: direct variable access.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
- GDScriptCodeGenerator::Address _class = codegen.add_constant(scr);
- int index = scr->static_variables_indices[identifier].index;
- gen->write_get_static_variable(temp, _class, index);
- return temp;
+ // Try members.
+ if (!codegen.function_node || !codegen.function_node->is_static) {
+ // Try member variables.
+ if (codegen.script->member_indices.has(identifier)) {
+ if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) {
+ // Perform getter.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->member_indices[identifier].data_type);
+ Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
+ gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args);
+ return temp;
+ } else {
+ // No getter or inside getter: direct member access.
+ int idx = codegen.script->member_indices[identifier].index;
+ return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier));
+ }
}
}
- scr = scr->_base;
- }
- }
- // Try class constants.
- {
- GDScript *owner = codegen.script;
- while (owner) {
- GDScript *scr = owner;
- GDScriptNativeClass *nc = nullptr;
- while (scr) {
- if (scr->constants.has(identifier)) {
- return codegen.add_constant(scr->constants[identifier]); // TODO: Get type here.
- }
- if (scr->native.is_valid()) {
- nc = scr->native.ptr();
+ // Try methods and signals (can be Callable and Signal).
+ {
+ // Search upwards through parent classes:
+ const GDScriptParser::ClassNode *base_class = codegen.class_node;
+ while (base_class != nullptr) {
+ if (base_class->has_member(identifier)) {
+ const GDScriptParser::ClassNode::Member &member = base_class->get_member(identifier);
+ if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
+ // Get like it was a property.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here.
+ GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF);
+
+ gen->write_get_named(temp, identifier, self);
+ return temp;
+ }
+ }
+ base_class = base_class->base_type.class_type;
}
- scr = scr->_base;
- }
- // Class C++ integer constant.
- if (nc) {
- bool success = false;
- int64_t constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success);
- if (success) {
- return codegen.add_constant(constant);
+ // Try in native base.
+ GDScript *scr = codegen.script;
+ GDScriptNativeClass *nc = nullptr;
+ while (scr) {
+ if (scr->native.is_valid()) {
+ nc = scr->native.ptr();
+ }
+ scr = scr->_base;
}
- }
- owner = owner->_owner;
- }
- }
-
- // Try signals and methods (can be made callables).
- {
- // Search upwards through parent classes:
- const GDScriptParser::ClassNode *base_class = codegen.class_node;
- while (base_class != nullptr) {
- if (base_class->has_member(identifier)) {
- const GDScriptParser::ClassNode::Member &member = base_class->get_member(identifier);
- if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
+ if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) {
// Get like it was a property.
GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here.
GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF);
@@ -331,88 +312,126 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
return temp;
}
}
- base_class = base_class->base_type.class_type;
- }
+ } break;
+ case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
+ case GDScriptParser::IdentifierNode::MEMBER_CLASS: {
+ // Try class constants.
+ GDScript *owner = codegen.script;
+ while (owner) {
+ GDScript *scr = owner;
+ GDScriptNativeClass *nc = nullptr;
- // Try in native base.
- GDScript *scr = codegen.script;
- GDScriptNativeClass *nc = nullptr;
- while (scr) {
- if (scr->native.is_valid()) {
- nc = scr->native.ptr();
- }
- scr = scr->_base;
- }
+ while (scr) {
+ if (scr->constants.has(identifier)) {
+ return codegen.add_constant(scr->constants[identifier]); // TODO: Get type here.
+ }
+ if (scr->native.is_valid()) {
+ nc = scr->native.ptr();
+ }
+ scr = scr->_base;
+ }
- if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) {
- // Get like it was a property.
- GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here.
- GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF);
+ // Class C++ integer constant.
+ if (nc) {
+ bool success = false;
+ int64_t constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success);
+ if (success) {
+ return codegen.add_constant(constant);
+ }
+ }
- gen->write_get_named(temp, identifier, self);
- return temp;
- }
- }
+ owner = owner->_owner;
+ }
+ } break;
+ case GDScriptParser::IdentifierNode::STATIC_VARIABLE: {
+ // Try static variables.
+ GDScript *scr = codegen.script;
+ while (scr) {
+ if (scr->static_variables_indices.has(identifier)) {
+ if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) {
+ // Perform getter.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
+ GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS);
+ Vector<GDScriptCodeGenerator::Address> args; // No argument needed.
+ gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args);
+ return temp;
+ } else {
+ // No getter or inside getter: direct variable access.
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type);
+ GDScriptCodeGenerator::Address _class = codegen.add_constant(scr);
+ int index = scr->static_variables_indices[identifier].index;
+ gen->write_get_static_variable(temp, _class, index);
+ return temp;
+ }
+ }
+ scr = scr->_base;
+ }
+ } break;
- // Try globals.
- if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
- // If it's an autoload singleton, we postpone to load it at runtime.
- // This is so one autoload doesn't try to load another before it's compiled.
- HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- if (autoloads.has(identifier) && autoloads[identifier].is_singleton) {
- GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script));
- int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
- gen->write_store_global(global, idx);
- return global;
- } else {
- int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
- Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx];
- return codegen.add_constant(global);
- }
- }
+ // GLOBALS.
+ case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: {
+ // Try globals.
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
+ // If it's an autoload singleton, we postpone to load it at runtime.
+ // This is so one autoload doesn't try to load another before it's compiled.
+ HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ if (autoloads.has(identifier) && autoloads[identifier].is_singleton) {
+ GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script));
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
+ gen->write_store_global(global, idx);
+ return global;
+ } else {
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier];
+ Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+ return codegen.add_constant(global);
+ }
+ }
- // Try global classes.
- if (ScriptServer::is_global_class(identifier)) {
- const GDScriptParser::ClassNode *class_node = codegen.class_node;
- while (class_node->outer) {
- class_node = class_node->outer;
- }
+ // Try global classes.
+ if (ScriptServer::is_global_class(identifier)) {
+ const GDScriptParser::ClassNode *class_node = codegen.class_node;
+ while (class_node->outer) {
+ class_node = class_node->outer;
+ }
- Ref<Resource> res;
+ Ref<Resource> res;
- if (class_node->identifier && class_node->identifier->name == identifier) {
- res = Ref<GDScript>(main_script);
- } else {
- String global_class_path = ScriptServer::get_global_class_path(identifier);
- if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") {
- Error err = OK;
- res = GDScriptCache::get_full_script(global_class_path, err);
- if (err != OK) {
- _set_error("Can't load global class " + String(identifier), p_expression);
- r_error = ERR_COMPILATION_FAILED;
- return GDScriptCodeGenerator::Address();
- }
- } else {
- res = ResourceLoader::load(global_class_path);
- if (res.is_null()) {
- _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
- r_error = ERR_COMPILATION_FAILED;
- return GDScriptCodeGenerator::Address();
+ if (class_node->identifier && class_node->identifier->name == identifier) {
+ res = Ref<GDScript>(main_script);
+ } else {
+ String global_class_path = ScriptServer::get_global_class_path(identifier);
+ if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") {
+ Error err = OK;
+ res = GDScriptCache::get_full_script(global_class_path, err);
+ if (err != OK) {
+ _set_error("Can't load global class " + String(identifier), p_expression);
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
+ }
+ } else {
+ res = ResourceLoader::load(global_class_path);
+ if (res.is_null()) {
+ _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
+ }
+ }
}
- }
- }
- return codegen.add_constant(res);
- }
+ return codegen.add_constant(res);
+ }
#ifdef TOOLS_ENABLED
- if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
- GDScriptCodeGenerator::Address global = codegen.add_temporary(); // TODO: Get type.
- gen->write_store_named_global(global, identifier);
- return global;
- }
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
+ GDScriptCodeGenerator::Address global = codegen.add_temporary(); // TODO: Get type.
+ gen->write_store_named_global(global, identifier);
+ return global;
+ }
#endif
+ } break;
+ }
+
// Not found, error.
_set_error("Identifier not found: " + String(identifier), p_expression);
r_error = ERR_COMPILATION_FAILED;
@@ -2710,20 +2729,21 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
case GDScriptParser::ClassNode::Member::GROUP: {
const GDScriptParser::AnnotationNode *annotation = member.annotation;
- StringName name = annotation->export_info.name;
+ // Avoid name conflict. See GH-78252.
+ StringName name = vformat("@group_%d_%s", p_script->members.size(), annotation->export_info.name);
// This is not a normal member, but we need this to keep indices in order.
GDScript::MemberInfo minfo;
minfo.index = p_script->member_indices.size();
PropertyInfo prop_info;
- prop_info.name = name;
+ prop_info.name = annotation->export_info.name;
prop_info.usage = annotation->export_info.usage;
prop_info.hint_string = annotation->export_info.hint_string;
p_script->member_info[name] = prop_info;
p_script->member_indices[name] = minfo;
- p_script->members.insert(name);
+ p_script->members.insert(Variant());
} break;
default:
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index ec1d0af329..438ec02740 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -113,6 +113,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
switch (opcode) {
case OPCODE_OPERATOR: {
+ constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr);
int operation = _code_ptr[ip + 4];
text += "operator ";
@@ -125,7 +126,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += " ";
text += DADDR(2);
- incr += 5;
+ incr += 7 + _pointer_size;
} break;
case OPCODE_OPERATOR_VALIDATED: {
text += "validated operator ";
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 2a7346940b..d27ea974e3 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -233,6 +233,10 @@ Script *GDScriptLanguage::create_script() const {
/* DEBUGGER FUNCTIONS */
+thread_local int GDScriptLanguage::_debug_parse_err_line = -1;
+thread_local String GDScriptLanguage::_debug_parse_err_file;
+thread_local String GDScriptLanguage::_debug_error;
+
bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
// break because of parse error
@@ -241,6 +245,9 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
_debug_parse_err_file = p_file;
_debug_error = p_error;
EngineDebugger::get_script_debugger()->debug(this, false, true);
+ // Because this is thread local, clear the memory afterwards.
+ _debug_parse_err_file = String();
+ _debug_error = String();
return true;
} else {
return false;
@@ -248,12 +255,15 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
}
bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) {
- if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
+ if (EngineDebugger::is_active()) {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
bool is_error_breakpoint = p_error != "Breakpoint";
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint);
+ // Because this is thread local, clear the memory afterwards.
+ _debug_parse_err_file = String();
+ _debug_error = String();
return true;
} else {
return false;
@@ -269,7 +279,7 @@ int GDScriptLanguage::debug_get_stack_level_count() const {
return 1;
}
- return _debug_call_stack_pos;
+ return _call_stack.stack_pos;
}
int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
@@ -277,11 +287,11 @@ int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
return _debug_parse_err_line;
}
- ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, -1);
+ ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, -1);
- int l = _debug_call_stack_pos - p_level - 1;
+ int l = _call_stack.stack_pos - p_level - 1;
- return *(_call_stack[l].line);
+ return *(_call_stack.levels[l].line);
}
String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
@@ -289,9 +299,9 @@ String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
return "";
}
- ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
- int l = _debug_call_stack_pos - p_level - 1;
- return _call_stack[l].function->get_name();
+ ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
+ int l = _call_stack.stack_pos - p_level - 1;
+ return _call_stack.levels[l].function->get_name();
}
String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
@@ -299,9 +309,9 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
return _debug_parse_err_file;
}
- ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
- int l = _debug_call_stack_pos - p_level - 1;
- return _call_stack[l].function->get_source();
+ ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
+ int l = _call_stack.stack_pos - p_level - 1;
+ return _call_stack.levels[l].function->get_source();
}
void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
@@ -309,17 +319,17 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p
return;
}
- ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
- int l = _debug_call_stack_pos - p_level - 1;
+ ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
+ int l = _call_stack.stack_pos - p_level - 1;
- GDScriptFunction *f = _call_stack[l].function;
+ GDScriptFunction *f = _call_stack.levels[l].function;
List<Pair<StringName, int>> locals;
- f->debug_get_stack_member_state(*_call_stack[l].line, &locals);
+ f->debug_get_stack_member_state(*_call_stack.levels[l].line, &locals);
for (const Pair<StringName, int> &E : locals) {
p_locals->push_back(E.first);
- p_values->push_back(_call_stack[l].stack[E.second]);
+ p_values->push_back(_call_stack.levels[l].stack[E.second]);
}
}
@@ -328,10 +338,10 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *
return;
}
- ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
- int l = _debug_call_stack_pos - p_level - 1;
+ ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
+ int l = _call_stack.stack_pos - p_level - 1;
- GDScriptInstance *instance = _call_stack[l].instance;
+ GDScriptInstance *instance = _call_stack.levels[l].instance;
if (!instance) {
return;
@@ -353,10 +363,10 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) {
return nullptr;
}
- ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, nullptr);
+ ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, nullptr);
- int l = _debug_call_stack_pos - p_level - 1;
- ScriptInstance *instance = _call_stack[l].instance;
+ int l = _call_stack.stack_pos - p_level - 1;
+ ScriptInstance *instance = _call_stack.levels[l].instance;
return instance;
}
@@ -808,9 +818,10 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
node.insert_text = node.display.quote(p_quote_style);
r_result.insert(node.display, node);
- List<StringName> node_types;
- ClassDB::get_inheriters_from_class("Node", &node_types);
- for (const StringName &E : node_types) {
+
+ List<StringName> native_classes;
+ ClassDB::get_inheriters_from_class("Node", &native_classes);
+ for (const StringName &E : native_classes) {
if (!ClassDB::is_class_exposed(E)) {
continue;
}
@@ -818,6 +829,17 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
option.insert_text = option.display.quote(p_quote_style);
r_result.insert(option.display, option);
}
+
+ List<StringName> global_script_classes;
+ ScriptServer::get_global_class_list(&global_script_classes);
+ for (const StringName &E : global_script_classes) {
+ if (!ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(E), "Node")) {
+ continue;
+ }
+ ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+ option.insert_text = option.display.quote(p_quote_style);
+ r_result.insert(option.display, option);
+ }
} else if (p_annotation->name == SNAME("@warning_ignore")) {
for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
@@ -1393,7 +1415,7 @@ struct RecursionCheck {
}
};
-static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
+static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::IdentifierNode *p_identifier, GDScriptCompletionIdentifier &r_type);
static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type);
static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type);
@@ -1457,7 +1479,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
} break;
case GDScriptParser::Node::IDENTIFIER: {
const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression);
- found = _guess_identifier_type(p_context, id->name, r_type);
+ found = _guess_identifier_type(p_context, id, r_type);
} break;
case GDScriptParser::Node::DICTIONARY: {
// Try to recreate the dictionary.
@@ -1900,7 +1922,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
return found;
}
-static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type) {
+static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::IdentifierNode *p_identifier, GDScriptCompletionIdentifier &r_type) {
static int recursion_depth = 0;
RecursionCheck recursion(&recursion_depth);
if (unlikely(recursion.check())) {
@@ -1914,36 +1936,49 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
GDScriptParser::SuiteNode *suite = p_context.current_suite;
bool is_function_parameter = false;
- if (suite) {
- if (suite->has_local(p_identifier)) {
- const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier);
+ bool can_be_local = true;
+ switch (p_identifier->source) {
+ case GDScriptParser::IdentifierNode::MEMBER_VARIABLE:
+ case GDScriptParser::IdentifierNode::MEMBER_CONSTANT:
+ case GDScriptParser::IdentifierNode::MEMBER_FUNCTION:
+ case GDScriptParser::IdentifierNode::MEMBER_SIGNAL:
+ case GDScriptParser::IdentifierNode::MEMBER_CLASS:
+ case GDScriptParser::IdentifierNode::INHERITED_VARIABLE:
+ case GDScriptParser::IdentifierNode::STATIC_VARIABLE:
+ can_be_local = false;
+ break;
+ default:
+ break;
+ }
- id_type = local.get_datatype();
+ if (can_be_local && suite && suite->has_local(p_identifier->name)) {
+ const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier->name);
- // Check initializer as the first assignment.
- switch (local.type) {
- case GDScriptParser::SuiteNode::Local::VARIABLE:
- if (local.variable->initializer) {
- last_assign_line = local.variable->initializer->end_line;
- last_assigned_expression = local.variable->initializer;
- }
- break;
- case GDScriptParser::SuiteNode::Local::CONSTANT:
- if (local.constant->initializer) {
- last_assign_line = local.constant->initializer->end_line;
- last_assigned_expression = local.constant->initializer;
- }
- break;
- case GDScriptParser::SuiteNode::Local::PARAMETER:
- if (local.parameter->initializer) {
- last_assign_line = local.parameter->initializer->end_line;
- last_assigned_expression = local.parameter->initializer;
- }
- is_function_parameter = true;
- break;
- default:
- break;
- }
+ id_type = local.get_datatype();
+
+ // Check initializer as the first assignment.
+ switch (local.type) {
+ case GDScriptParser::SuiteNode::Local::VARIABLE:
+ if (local.variable->initializer) {
+ last_assign_line = local.variable->initializer->end_line;
+ last_assigned_expression = local.variable->initializer;
+ }
+ break;
+ case GDScriptParser::SuiteNode::Local::CONSTANT:
+ if (local.constant->initializer) {
+ last_assign_line = local.constant->initializer->end_line;
+ last_assigned_expression = local.constant->initializer;
+ }
+ break;
+ case GDScriptParser::SuiteNode::Local::PARAMETER:
+ if (local.parameter->initializer) {
+ last_assign_line = local.parameter->initializer->end_line;
+ last_assigned_expression = local.parameter->initializer;
+ }
+ is_function_parameter = true;
+ break;
+ default:
+ break;
}
}
@@ -1958,7 +1993,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
const GDScriptParser::AssignmentNode *assign = static_cast<const GDScriptParser::AssignmentNode *>(suite->statements[i]);
if (assign->end_line > last_assign_line && assign->assignee && assign->assigned_value && assign->assignee->type == GDScriptParser::Node::IDENTIFIER) {
const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(assign->assignee);
- if (id->name == p_identifier) {
+ if (id->name == p_identifier->name && id->source == p_identifier->source) {
last_assign_line = assign->assigned_value->end_line;
last_assigned_expression = assign->assigned_value;
}
@@ -1976,7 +2011,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
// Credit: Zylann.
// TODO: this could be hacked to detect ANDed conditions too...
const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition);
- if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier) {
+ if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier->name && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->source == p_identifier->source) {
// Bingo.
GDScriptParser::CompletionContext c = p_context;
c.current_line = type_test->operand->start_line;
@@ -2012,8 +2047,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
case GDScriptParser::DataType::CLASS:
if (base_type.class_type->has_function(p_context.current_function->identifier->name)) {
GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function;
- if (parent_function->parameters_indices.has(p_identifier)) {
- const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]];
+ if (parent_function->parameters_indices.has(p_identifier->name)) {
+ const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier->name]];
if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) {
id_type = parameter->get_datatype();
}
@@ -2038,7 +2073,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
MethodInfo info;
if (ClassDB::get_method_info(base_type.native_type, p_context.current_function->identifier->name, &info)) {
for (const PropertyInfo &E : info.arguments) {
- if (E.name == p_identifier) {
+ if (E.name == p_identifier->name) {
r_type = _type_from_property(E);
return true;
}
@@ -2066,14 +2101,14 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
base.type.class_type = p_context.current_class;
base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static;
- if (_guess_identifier_type_from_base(p_context, base, p_identifier, r_type)) {
+ if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) {
return true;
}
}
// Check global scripts.
- if (ScriptServer::is_global_class(p_identifier)) {
- String script = ScriptServer::get_global_class_path(p_identifier);
+ if (ScriptServer::is_global_class(p_identifier->name)) {
+ String script = ScriptServer::get_global_class_path(p_identifier->name);
if (script.to_lower().ends_with(".gd")) {
Error err = OK;
Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err);
@@ -2089,7 +2124,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
return true;
}
} else {
- Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier->name));
if (scr.is_valid()) {
r_type = _type_from_variant(scr);
r_type.type.is_meta_type = true;
@@ -2100,20 +2135,20 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
}
// Check global variables (including autoloads).
- if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
- r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]);
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier->name)) {
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name]);
return true;
}
// Check ClassDB.
- if (ClassDB::class_exists(p_identifier) && ClassDB::is_class_exposed(p_identifier)) {
+ if (ClassDB::class_exists(p_identifier->name) && ClassDB::is_class_exposed(p_identifier->name)) {
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
r_type.type.kind = GDScriptParser::DataType::NATIVE;
- r_type.type.native_type = p_identifier;
+ r_type.type.native_type = p_identifier->name;
r_type.type.is_constant = true;
- if (Engine::get_singleton()->has_singleton(p_identifier)) {
+ if (Engine::get_singleton()->has_singleton(p_identifier->name)) {
r_type.type.is_meta_type = false;
- r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier);
+ r_type.value = Engine::get_singleton()->get_singleton_object(p_identifier->name);
} else {
r_type.type.is_meta_type = true;
r_type.value = Variant();
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 9bbfb14f31..5230773c13 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -473,7 +473,7 @@ private:
MethodBind **_methods_ptr = nullptr;
int _lambdas_count = 0;
GDScriptFunction **_lambdas_ptr = nullptr;
- const int *_code_ptr = nullptr;
+ int *_code_ptr = nullptr;
int _code_size = 0;
int _argument_count = 0;
int _stack_size = 0;
@@ -539,12 +539,12 @@ private:
struct Profile {
StringName signature;
- uint64_t call_count = 0;
- uint64_t self_time = 0;
- uint64_t total_time = 0;
- uint64_t frame_call_count = 0;
- uint64_t frame_self_time = 0;
- uint64_t frame_total_time = 0;
+ SafeNumeric<uint64_t> call_count;
+ SafeNumeric<uint64_t> self_time;
+ SafeNumeric<uint64_t> total_time;
+ SafeNumeric<uint64_t> frame_call_count;
+ SafeNumeric<uint64_t> frame_self_time;
+ SafeNumeric<uint64_t> frame_total_time;
uint64_t last_frame_call_count = 0;
uint64_t last_frame_self_time = 0;
uint64_t last_frame_total_time = 0;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 2b91ba8f86..debc85ebbf 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -571,8 +571,8 @@ void GDScriptParser::parse_program() {
class_doc_line = MIN(class_doc_line, E.key);
}
}
- if (has_comment(class_doc_line)) {
- get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false);
+ if (has_comment(class_doc_line, true)) {
+ head->doc_data = parse_class_doc_comment(class_doc_line, false);
}
#endif // TOOLS_ENABLED
@@ -771,12 +771,16 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
// Check whether current line has a doc comment
if (has_comment(previous.start_line, true)) {
- member->doc_description = get_doc_comment(previous.start_line, true);
+ if constexpr (std::is_same_v<T, ClassNode>) {
+ member->doc_data = parse_class_doc_comment(previous.start_line, true, true);
+ } else {
+ member->doc_data = parse_doc_comment(previous.start_line, true);
+ }
} else if (has_comment(doc_comment_line, true)) {
if constexpr (std::is_same_v<T, ClassNode>) {
- get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true);
+ member->doc_data = parse_class_doc_comment(doc_comment_line, true);
} else {
- member->doc_description = get_doc_comment(doc_comment_line);
+ member->doc_data = parse_doc_comment(doc_comment_line);
}
}
#endif // TOOLS_ENABLED
@@ -1314,25 +1318,34 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
#ifdef TOOLS_ENABLED
// Enum values documentation.
for (int i = 0; i < enum_node->values.size(); i++) {
+ int doc_comment_line = enum_node->values[i].line;
+ bool single_line = false;
+
+ if (has_comment(doc_comment_line, true)) {
+ single_line = true;
+ } else if (has_comment(doc_comment_line - 1, true)) {
+ doc_comment_line--;
+ } else {
+ continue;
+ }
+
if (i == enum_node->values.size() - 1) {
// If close bracket is same line as last value.
- if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) {
- if (named) {
- enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true);
- } else {
- current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true));
- }
+ if (doc_comment_line == previous.start_line) {
+ break;
}
} else {
// If two values are same line.
- if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) {
- if (named) {
- enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true);
- } else {
- current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true));
- }
+ if (doc_comment_line == enum_node->values[i + 1].line) {
+ continue;
}
}
+
+ if (named) {
+ enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line);
+ } else {
+ current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line));
+ }
}
#endif // TOOLS_ENABLED
@@ -2267,6 +2280,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
+#ifdef DEBUG_ENABLED
+ identifier->suite = current_suite;
+#endif
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
@@ -3411,19 +3427,20 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
return tokenizer.get_comments()[p_line].comment.begins_with("##");
}
-String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
+GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {
+ MemberDocData result;
+
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), String());
+ ERR_FAIL_COND_V(!comments.has(p_line), result);
if (p_single_line) {
if (comments[p_line].comment.begins_with("##")) {
- return comments[p_line].comment.trim_prefix("##").strip_edges();
+ result.description = comments[p_line].comment.trim_prefix("##").strip_edges();
+ return result;
}
- return "";
+ return result;
}
- String doc;
-
int line = p_line;
DocLineState state = DOC_LINE_NORMAL;
@@ -3451,29 +3468,42 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
}
String doc_line = comments[line].comment.trim_prefix("##");
- doc += _process_doc_line(doc_line, doc, space_prefix, state);
line++;
+
+ if (state == DOC_LINE_NORMAL) {
+ String stripped_line = doc_line.strip_edges();
+ if (stripped_line.begins_with("@deprecated")) {
+ result.is_deprecated = true;
+ continue;
+ } else if (stripped_line.begins_with("@experimental")) {
+ result.is_experimental = true;
+ continue;
+ }
+ }
+
+ result.description += _process_doc_line(doc_line, result.description, space_prefix, state);
}
- return doc;
+ return result;
}
-void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) {
+GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) {
+ ClassDocData result;
+
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- if (!comments.has(p_line)) {
- return;
+ ERR_FAIL_COND_V(!comments.has(p_line), result);
+
+ if (p_single_line) {
+ if (comments[p_line].comment.begins_with("##")) {
+ result.brief = comments[p_line].comment.trim_prefix("##").strip_edges();
+ return result;
+ }
+ return result;
}
- ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0);
int line = p_line;
DocLineState state = DOC_LINE_NORMAL;
- enum Mode {
- BRIEF,
- DESC,
- TUTORIALS,
- DONE,
- };
- Mode mode = BRIEF;
+ bool is_in_brief = true;
if (p_inner_class) {
while (comments.has(line - 1)) {
@@ -3500,18 +3530,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
break;
}
- String doc_line = comments[line++].comment.trim_prefix("##");
- String title, link; // For tutorials.
+ String doc_line = comments[line].comment.trim_prefix("##");
+ line++;
if (state == DOC_LINE_NORMAL) {
- // Set the read mode.
String stripped_line = doc_line.strip_edges();
- if (stripped_line.is_empty()) {
- if (mode == BRIEF && !p_brief.is_empty()) {
- mode = DESC;
- }
+
+ // A blank line separates the description from the brief.
+ if (is_in_brief && !result.brief.is_empty() && stripped_line.is_empty()) {
+ is_in_brief = false;
continue;
- } else if (stripped_line.begins_with("@tutorial")) {
+ }
+
+ if (stripped_line.begins_with("@tutorial")) {
+ String title, link;
+
int begin_scan = String("@tutorial").length();
if (begin_scan >= stripped_line.length()) {
continue; // Invalid syntax.
@@ -3553,24 +3586,21 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
link = stripped_line.substr(colon_pos).strip_edges();
}
- mode = TUTORIALS;
- } else if (mode == TUTORIALS) { // Tutorial docs are single line, we need a @tag after it.
- mode = DONE;
+ result.tutorials.append(Pair<String, String>(title, link));
+ continue;
+ } else if (stripped_line.begins_with("@deprecated")) {
+ result.is_deprecated = true;
+ continue;
+ } else if (stripped_line.begins_with("@experimental")) {
+ result.is_experimental = true;
+ continue;
}
}
- switch (mode) {
- case BRIEF:
- p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state);
- break;
- case DESC:
- p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state);
- break;
- case TUTORIALS:
- p_tutorials.append(Pair<String, String>(title, link));
- break;
- case DONE:
- break;
+ if (is_in_brief) {
+ result.brief += _process_doc_line(doc_line, result.brief, space_prefix, state);
+ } else {
+ result.description += _process_doc_line(doc_line, result.description, space_prefix, state);
}
}
@@ -3578,11 +3608,11 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
const ClassNode::Member &m = current_class->members[0];
int first_member_line = m.get_line();
if (first_member_line == line) {
- p_brief = "";
- p_desc = "";
- p_tutorials.clear();
+ result = ClassDocData(); // Clear result.
}
}
+
+ return result;
}
#endif // TOOLS_ENABLED
@@ -3867,6 +3897,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
}
+ // WARNING: Do not merge with the previous `if` because there `!=`, not `==`!
if (p_annotation->name == SNAME("@export_flags")) {
const int64_t max_flags = 32;
Vector<String> t = arg_string.split(":", true, 1);
@@ -3892,6 +3923,18 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)", i + 1, max_flags + 1), p_annotation->arguments[i]);
return false;
}
+ } else if (p_annotation->name == SNAME("@export_node_path")) {
+ String native_class = arg_string;
+ if (ScriptServer::is_global_class(arg_string)) {
+ native_class = ScriptServer::get_global_class_native_base(arg_string);
+ }
+ if (!ClassDB::class_exists(native_class)) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" was not found in the global scope.)", i + 1, arg_string), p_annotation->arguments[i]);
+ return false;
+ } else if (!ClassDB::is_parent_class(native_class, SNAME("Node"))) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" does not inherit "Node".)", i + 1, arg_string), p_annotation->arguments[i]);
+ return false;
+ }
}
if (i > 0) {
@@ -3909,8 +3952,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
if (export_type.builtin_type == Variant::INT) {
variable->export_info.type = Variant::INT;
}
- }
- if (p_annotation->name == SNAME("@export_multiline")) {
+ } else if (p_annotation->name == SNAME("@export_multiline")) {
if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) {
DataType inner_type = export_type.get_container_element_type();
if (inner_type.builtin_type != Variant::STRING) {
@@ -3938,6 +3980,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
}
+ // WARNING: Do not merge with the previous `else if`! Otherwise `else` (default variable type check)
+ // will not work for the above annotations. `@export` and `@export_enum` validate the type separately.
if (p_annotation->name == SNAME("@export")) {
if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) {
push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 18757eb9fd..20f5dcf06d 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -257,6 +257,22 @@ public:
int line = 0, column = 0;
};
+#ifdef TOOLS_ENABLED
+ struct ClassDocData {
+ String brief;
+ String description;
+ Vector<Pair<String, String>> tutorials;
+ bool is_deprecated = false;
+ bool is_experimental = false;
+ };
+
+ struct MemberDocData {
+ String description;
+ bool is_deprecated = false;
+ bool is_experimental = false;
+ };
+#endif // TOOLS_ENABLED
+
struct Node {
enum Type {
NONE,
@@ -505,7 +521,7 @@ public:
int leftmost_column = 0;
int rightmost_column = 0;
#ifdef TOOLS_ENABLED
- String doc_description;
+ MemberDocData doc_data;
#endif // TOOLS_ENABLED
};
@@ -513,7 +529,7 @@ public:
Vector<Value> values;
Variant dictionary;
#ifdef TOOLS_ENABLED
- String doc_description;
+ MemberDocData doc_data;
#endif // TOOLS_ENABLED
EnumNode() {
@@ -720,14 +736,12 @@ public:
DataType base_type;
String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project.
#ifdef TOOLS_ENABLED
- String doc_description;
- String doc_brief_description;
- Vector<Pair<String, String>> doc_tutorials;
+ ClassDocData doc_data;
// EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later.
- void set_enum_value_doc(const StringName &p_name, const String &p_doc_description) {
+ void set_enum_value_doc_data(const StringName &p_name, const MemberDocData &p_doc_data) {
ERR_FAIL_INDEX(members_indices[p_name], members.size());
- members.write[members_indices[p_name]].enum_value.doc_description = p_doc_description;
+ members.write[members_indices[p_name]].enum_value.doc_data = p_doc_data;
}
#endif // TOOLS_ENABLED
@@ -753,7 +767,9 @@ public:
members.push_back(Member(p_enum_value));
}
void add_member_group(AnnotationNode *p_annotation_node) {
- members_indices[p_annotation_node->export_info.name] = members.size();
+ // Avoid name conflict. See GH-78252.
+ StringName name = vformat("@group_%d_%s", members.size(), p_annotation_node->export_info.name);
+ members_indices[name] = members.size();
members.push_back(Member(p_annotation_node));
}
@@ -764,7 +780,7 @@ public:
struct ConstantNode : public AssignableNode {
#ifdef TOOLS_ENABLED
- String doc_description;
+ MemberDocData doc_data;
#endif // TOOLS_ENABLED
ConstantNode() {
@@ -819,7 +835,7 @@ public:
LambdaNode *source_lambda = nullptr;
#ifdef TOOLS_ENABLED
Vector<Variant> default_arg_values;
- String doc_description;
+ MemberDocData doc_data;
#endif // TOOLS_ENABLED
bool resolved_signature = false;
@@ -843,19 +859,24 @@ public:
struct IdentifierNode : public ExpressionNode {
StringName name;
+#ifdef DEBUG_ENABLED
+ SuiteNode *suite = nullptr; // The block in which the identifier is used.
+#endif
enum Source {
UNDEFINED_SOURCE,
FUNCTION_PARAMETER,
- LOCAL_CONSTANT,
LOCAL_VARIABLE,
+ LOCAL_CONSTANT,
LOCAL_ITERATOR, // `for` loop iterator.
LOCAL_BIND, // Pattern bind.
- MEMBER_SIGNAL,
MEMBER_VARIABLE,
- STATIC_VARIABLE,
MEMBER_CONSTANT,
+ MEMBER_FUNCTION,
+ MEMBER_SIGNAL,
+ MEMBER_CLASS,
INHERITED_VARIABLE,
+ STATIC_VARIABLE,
};
Source source = UNDEFINED_SOURCE;
@@ -1006,7 +1027,7 @@ public:
Vector<ParameterNode *> parameters;
HashMap<StringName, int> parameters_indices;
#ifdef TOOLS_ENABLED
- String doc_description;
+ MemberDocData doc_data;
#endif // TOOLS_ENABLED
SignalNode() {
@@ -1211,7 +1232,7 @@ public:
int assignments = 0;
bool is_static = false;
#ifdef TOOLS_ENABLED
- String doc_description;
+ MemberDocData doc_data;
#endif // TOOLS_ENABLED
VariableNode() {
@@ -1486,12 +1507,12 @@ private:
ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign);
ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign);
TypeNode *parse_type(bool p_allow_void = false);
+
#ifdef TOOLS_ENABLED
- // Doc comments.
int class_doc_line = 0x7FFFFFFF;
bool has_comment(int p_line, bool p_must_be_doc = false);
- String get_doc_comment(int p_line, bool p_single_line = false);
- void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class);
+ MemberDocData parse_doc_comment(int p_line, bool p_single_line = false);
+ ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false);
#endif // TOOLS_ENABLED
public:
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 3927a4dd3e..4f374b63b0 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -579,6 +579,24 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
return make_identifier(name);
}
+ if (!only_ascii) {
+ // Kept here in case the order with push_error matters.
+ Token id = make_identifier(name);
+
+#ifdef DEBUG_ENABLED
+ // Additional checks for identifiers but only in debug and if it's available in TextServer.
+ if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
+ int64_t confusable = TS->is_confusable(name, keyword_list);
+ if (confusable >= 0) {
+ push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable]));
+ }
+ }
+#endif // DEBUG_ENABLED
+
+ // Cannot be a keyword, as keywords are ASCII only.
+ return id;
+ }
+
// Define some helper macros for the switch case.
#define KEYWORD_GROUP_CASE(char) \
break; \
@@ -614,19 +632,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
}
// Not a keyword, so must be an identifier.
- Token id = make_identifier(name);
-
-#ifdef DEBUG_ENABLED
- // Additional checks for identifiers but only in debug and if it's available in TextServer.
- if (!only_ascii && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
- int64_t confusable = TS->is_confusable(name, keyword_list);
- if (confusable >= 0) {
- push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable]));
- }
- }
-#endif // DEBUG_ENABLED
-
- return id;
+ return make_identifier(name);
#undef KEYWORD_GROUP_CASE
#undef KEYWORD
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index a8c9cfa579..1ddd54b323 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -663,8 +663,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (GDScriptLanguage::get_singleton()->profiling) {
function_start_time = OS::get_singleton()->get_ticks_usec();
function_call_time = 0;
- profile.call_count++;
- profile.frame_call_count++;
+ profile.call_count.increment();
+ profile.frame_call_count.increment();
}
bool exit_ok = false;
bool awaited = false;
@@ -685,7 +685,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_SWITCH(_code_ptr[ip]) {
OPCODE(OPCODE_OPERATOR) {
- CHECK_SPACE(5);
+ constexpr int _pointer_size = sizeof(Variant::ValidatedOperatorEvaluator) / sizeof(*_code_ptr);
+ CHECK_SPACE(7 + _pointer_size);
bool valid;
Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4];
@@ -694,28 +695,71 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_VARIANT_PTR(a, 0);
GET_VARIANT_PTR(b, 1);
GET_VARIANT_PTR(dst, 2);
+ // Compute signatures (types of operands) so it can be optimized when matching.
+ uint32_t op_signature = _code_ptr[ip + 5];
+ uint32_t actual_signature = (a->get_type() << 8) | (b->get_type());
+
+ // Check if this is the first run. If so, store the current signature for the optimized path.
+ if (unlikely(op_signature == 0)) {
+ static Mutex initializer_mutex;
+ initializer_mutex.lock();
+ Variant::Type a_type = (Variant::Type)((actual_signature >> 8) & 0xFF);
+ Variant::Type b_type = (Variant::Type)(actual_signature & 0xFF);
+ Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(op, a_type, b_type);
+
+ if (unlikely(!op_func)) {
+#ifdef DEBUG_ENABLED
+ err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
+#endif
+ initializer_mutex.unlock();
+ OPCODE_BREAK;
+ } else {
+ Variant::Type ret_type = Variant::get_operator_return_type(op, a_type, b_type);
+ VariantInternal::initialize(dst, ret_type);
+ op_func(a, b, dst);
+
+ // Check again in case another thread already set it.
+ if (_code_ptr[ip + 5] == 0) {
+ _code_ptr[ip + 5] = actual_signature;
+ _code_ptr[ip + 6] = static_cast<int>(ret_type);
+ Variant::ValidatedOperatorEvaluator *tmp = reinterpret_cast<Variant::ValidatedOperatorEvaluator *>(&_code_ptr[ip + 7]);
+ *tmp = op_func;
+ }
+ }
+ initializer_mutex.unlock();
+ } else if (likely(op_signature == actual_signature)) {
+ // If the signature matches, we can use the optimized path.
+ Variant::Type ret_type = static_cast<Variant::Type>(_code_ptr[ip + 6]);
+ Variant::ValidatedOperatorEvaluator op_func = *reinterpret_cast<Variant::ValidatedOperatorEvaluator *>(&_code_ptr[ip + 7]);
+
+ // Make sure the return value has the correct type.
+ VariantInternal::initialize(dst, ret_type);
+ op_func(a, b, dst);
+ } else {
+ // If the signature doesn't match, we have to use the slow path.
#ifdef DEBUG_ENABLED
- Variant ret;
- Variant::evaluate(op, *a, *b, ret, valid);
+ Variant ret;
+ Variant::evaluate(op, *a, *b, ret, valid);
#else
- Variant::evaluate(op, *a, *b, *dst, valid);
+ Variant::evaluate(op, *a, *b, *dst, valid);
#endif
#ifdef DEBUG_ENABLED
- if (!valid) {
- if (ret.get_type() == Variant::STRING) {
- //return a string when invalid with the error
- err_text = ret;
- err_text += " in operator '" + Variant::get_operator_name(op) + "'.";
- } else {
- err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
+ if (!valid) {
+ if (ret.get_type() == Variant::STRING) {
+ //return a string when invalid with the error
+ err_text = ret;
+ err_text += " in operator '" + Variant::get_operator_name(op) + "'.";
+ } else {
+ err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'.";
+ }
+ OPCODE_BREAK;
}
- OPCODE_BREAK;
- }
- *dst = ret;
+ *dst = ret;
#endif
- ip += 5;
+ }
+ ip += 7 + _pointer_size;
}
DISPATCH_OPCODE;
@@ -3550,7 +3594,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
// line
bool do_break = false;
- if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) {
+ if (unlikely(EngineDebugger::get_script_debugger()->get_lines_left() > 0)) {
if (EngineDebugger::get_script_debugger()->get_depth() <= 0) {
EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
}
@@ -3563,7 +3607,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
do_break = true;
}
- if (do_break) {
+ if (unlikely(do_break)) {
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
}
@@ -3630,11 +3674,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) {
uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
- profile.total_time += time_taken;
- profile.self_time += time_taken - function_call_time;
- profile.frame_total_time += time_taken;
- profile.frame_self_time += time_taken - function_call_time;
- GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
+ profile.total_time.add(time_taken);
+ profile.self_time.add(time_taken - function_call_time);
+ profile.frame_total_time.add(time_taken);
+ profile.frame_self_time.add(time_taken - function_call_time);
+ if (Thread::get_caller_id() == Thread::get_main_id()) {
+ GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
+ }
}
// Check if this is not the last time it was interrupted by `await` or if it's the first time executing.
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 8de78d2b9a..24aa793c47 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -136,6 +136,12 @@ String GDScriptWarning::get_message() const {
case CONFUSABLE_IDENTIFIER:
CHECK_SYMBOLS(1);
return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
+ case CONFUSABLE_LOCAL_DECLARATION:
+ CHECK_SYMBOLS(2);
+ return vformat(R"(The %s "%s" is declared below in the parent block.)", symbols[0], symbols[1]);
+ case CONFUSABLE_LOCAL_USAGE:
+ CHECK_SYMBOLS(1);
+ return vformat(R"(The identifier "%s" will be shadowed below in the block.)", symbols[0]);
case INFERENCE_ON_VARIANT:
CHECK_SYMBOLS(1);
return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]);
@@ -213,6 +219,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"DEPRECATED_KEYWORD",
"RENAMED_IN_GODOT_4_HINT",
"CONFUSABLE_IDENTIFIER",
+ "CONFUSABLE_LOCAL_DECLARATION",
+ "CONFUSABLE_LOCAL_USAGE",
"INFERENCE_ON_VARIANT",
"NATIVE_METHOD_OVERRIDE",
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index ae6207fcdc..8444d46a88 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -83,6 +83,8 @@ public:
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4.
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
+ CONFUSABLE_LOCAL_DECLARATION, // The parent block declares an identifier with the same name below.
+ CONFUSABLE_LOCAL_USAGE, // The identifier will be shadowed below in the block.
INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant.
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
@@ -128,6 +130,8 @@ public:
WARN, // DEPRECATED_KEYWORD
WARN, // RENAMED_IN_GODOT_4_HINT
WARN, // CONFUSABLE_IDENTIFIER
+ WARN, // CONFUSABLE_LOCAL_DECLARATION
+ WARN, // CONFUSABLE_LOCAL_USAGE
ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type.
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h
index b9a54cf818..3782945e07 100644
--- a/modules/gdscript/language_server/godot_lsp.h
+++ b/modules/gdscript/language_server/godot_lsp.h
@@ -1566,7 +1566,7 @@ struct SignatureHelp {
/**
* The active signature. If omitted or the value lies outside the
* range of `signatures` the value defaults to zero or is ignored if
- * `signatures.length === 0`. Whenever possible implementors should
+ * `signatures.length === 0`. Whenever possible implementers should
* make an active decision about the active signature and shouldn't
* rely on a default value.
* In future version of the protocol this property might become
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.gd
new file mode 100644
index 0000000000..57ae41922f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.gd
@@ -0,0 +1,4 @@
+var v1 = v1
+
+func test():
+ print(v1)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.out
new file mode 100644
index 0000000000..c337882d9c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var_self.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Could not resolve member "v1": Cyclic reference.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index c4108f50de..b000c82717 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -126,7 +126,7 @@ func test():
assert(a_objects.get_typed_builtin() == TYPE_OBJECT)
assert(a_objects.get_typed_script() == A)
- var a_passed = (func check_a_passing(a_objects: Array[A]): return a_objects.size()).call(a_objects)
+ var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects)
assert(a_passed == 4)
var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.gd
new file mode 100644
index 0000000000..3178f8d496
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.gd
@@ -0,0 +1,6 @@
+func test():
+ if true:
+ var a = 1
+ print(a)
+ var a = 2
+ print(a)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.out
new file mode 100644
index 0000000000..7365072ea7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_declaration.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> CONFUSABLE_LOCAL_DECLARATION
+>> The variable "a" is declared below in the parent block.
+1
+2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.gd
new file mode 100644
index 0000000000..4462c067bc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.gd
@@ -0,0 +1,6 @@
+var a = 1
+
+func test():
+ print(a)
+ var a = 2
+ print(a)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
new file mode 100644
index 0000000000..0e0d607831
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> CONFUSABLE_LOCAL_USAGE
+>> The identifier "a" will be shadowed below in the block.
+>> WARNING
+>> Line: 5
+>> SHADOWED_VARIABLE
+>> The local variable "a" is shadowing an already-declared variable at line 1.
+1
+2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.gd
new file mode 100644
index 0000000000..eef8eb66e6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.gd
@@ -0,0 +1,6 @@
+var a = 1
+
+func test():
+ print(a)
+ var a = a + 1
+ print(a)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
new file mode 100644
index 0000000000..228a510490
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
@@ -0,0 +1,15 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> CONFUSABLE_LOCAL_USAGE
+>> The identifier "a" will be shadowed below in the block.
+>> WARNING
+>> Line: 5
+>> CONFUSABLE_LOCAL_USAGE
+>> The identifier "a" will be shadowed below in the block.
+>> WARNING
+>> Line: 5
+>> SHADOWED_VARIABLE
+>> The local variable "a" is shadowing an already-declared variable at line 1.
+1
+2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.gd
new file mode 100644
index 0000000000..1f207f27ac
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.gd
@@ -0,0 +1,7 @@
+var a = 1
+
+func test():
+ for _i in 3:
+ print(a)
+ var a = 2
+ print(a)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
new file mode 100644
index 0000000000..0d20e9f7a0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
@@ -0,0 +1,15 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> CONFUSABLE_LOCAL_USAGE
+>> The identifier "a" will be shadowed below in the block.
+>> WARNING
+>> Line: 6
+>> SHADOWED_VARIABLE
+>> The local variable "a" is shadowing an already-declared variable at line 1.
+1
+2
+1
+2
+1
+2
diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
new file mode 100644
index 0000000000..e46f24cc5f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
@@ -0,0 +1,17 @@
+extends RefCounted # TODO: Fix standalone annotations parsing.
+
+# GH-73843
+@export_group("Resource")
+
+# GH-78252
+@export var prop_1: int
+@export_category("prop_1")
+@export var prop_2: int
+
+func test():
+ var resource := Resource.new()
+ prints("Not shadowed:", resource.get_class())
+
+ for property in get_property_list():
+ if property.name in ["prop_1", "prop_2"]:
+ print(property)
diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out
new file mode 100644
index 0000000000..96ae84e986
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Not shadowed: Resource
+{ "name": "prop_1", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "prop_1", "class_name": &"", "type": 0, "hint": 0, "hint_string": "", "usage": 128 }
+{ "name": "prop_2", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
diff --git a/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd
new file mode 100644
index 0000000000..f17fb9823d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.gd
@@ -0,0 +1,14 @@
+# GH-80157
+
+extends Node
+
+func f():
+ pass
+
+signal s()
+
+func test():
+ print(f)
+ print(s)
+ print(get_child)
+ print(ready)
diff --git a/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out
new file mode 100644
index 0000000000..e5e9ff7043
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/first_class_callable_and_signal.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Node::f
+Node::[signal]s
+Node::get_child
+Node::[signal]ready
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 5bc6081803..9b760a997a 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -1,11 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFDocument" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
+ Class for importing and exporting glTF files in and out of Godot.
</brief_description>
<description>
- Append a glTF2 3d format from a file, buffer or scene and then write to the filesystem, buffer or scene.
+ GLTFDocument supports reading data from a glTF file, buffer, or Godot scene. This data can then be written to the filesystem, buffer, or used to create a Godot scene.
+ All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself.
+ GLTFDocument can be extended with arbitrary functionality by extending the [GLTFDocumentExtension] class and registering it with GLTFDocument via [method register_gltf_document_extension]. This allows for custom data to be imported and exported.
</description>
<tutorials>
+ <link title="glTF 'What the duck?' guide">https://www.khronos.org/files/gltf20-reference-guide.pdf</link>
+ <link title="Khronos glTF specification">https://registry.khronos.org/glTF/</link>
</tutorials>
<methods>
<method name="append_from_buffer">
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index 927ffb6aae..bae980fb56 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -17,7 +17,7 @@
<param index="1" name="gltf_node" type="GLTFNode" />
<param index="2" name="scene_node" type="Node" />
<description>
- Part of the export process. This method is run after [method _export_preflight] and before [method _export_node].
+ Part of the export process. This method is run after [method _export_preflight] and before [method _export_preserialize].
Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node].
</description>
</method>
@@ -28,7 +28,7 @@
<param index="2" name="json" type="Dictionary" />
<param index="3" name="node" type="Node" />
<description>
- Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_post].
+ Part of the export process. This method is run after [method _export_preserialize] and before [method _export_post].
This method can be used to modify the final JSON of each node.
</description>
</method>
@@ -49,6 +49,14 @@
The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned.
</description>
</method>
+ <method name="_export_preserialize" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="state" type="GLTFState" />
+ <description>
+ Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_node].
+ This method can be used to alter the state before performing serialization. It runs every time when generating a buffer with [method GLTFDocument.generate_buffer] or writing to the file system with [method GLTFDocument.write_to_filesystem].
+ </description>
+ </method>
<method name="_generate_scene_node" qualifiers="virtual">
<return type="Node3D" />
<param index="0" name="state" type="GLTFState" />
@@ -59,6 +67,12 @@
Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node.
</description>
</method>
+ <method name="_get_image_file_extension" qualifiers="virtual">
+ <return type="String" />
+ <description>
+ Returns the file extension to use for saving image data into, for example, [code]".png"[/code]. If defined, when this extension is used to handle images, and the images are saved to a separate file, the image bytes will be copied to a file with this extension. If this is set, there should be a [ResourceImporter] class able to import the file. If not defined or empty, Godot will save the image into a PNG file.
+ </description>
+ </method>
<method name="_get_supported_extensions" qualifiers="virtual">
<return type="PackedStringArray" />
<description>
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index 32023de696..ba1c531283 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -8,6 +8,7 @@
GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a GLTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the GLTF file or for custom data to be converted to/from Godot nodes.
</description>
<tutorials>
+ <link title="GLTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link>
</tutorials>
<methods>
<method name="add_used_extension">
@@ -266,11 +267,18 @@
</methods>
<members>
<member name="base_path" type="String" setter="set_base_path" getter="get_base_path" default="&quot;&quot;">
+ The folder path associated with this GLTF data. This is used to find other files the GLTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file.
</member>
<member name="buffers" type="PackedByteArray[]" setter="set_buffers" getter="get_buffers" default="[]">
</member>
+ <member name="copyright" type="String" setter="set_copyright" getter="get_copyright" default="&quot;&quot;">
+ The copyright string in the asset header of the GLTF file. This is set during import if present and export if non-empty. See the GLTF asset header documentation for more information.
+ </member>
<member name="create_animations" type="bool" setter="set_create_animations" getter="get_create_animations" default="true">
</member>
+ <member name="filename" type="String" setter="set_filename" getter="get_filename" default="&quot;&quot;">
+ The file name associated with this GLTF data. If it ends with [code].gltf[/code], this is text-based GLTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string.
+ </member>
<member name="glb_data" type="PackedByteArray" setter="set_glb_data" getter="get_glb_data" default="PackedByteArray()">
</member>
<member name="json" type="Dictionary" setter="set_json" getter="get_json" default="{}">
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 2804a8b0a2..11718ba78a 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -36,6 +36,7 @@ void GLTFDocumentExtension::_bind_methods() {
GDVIRTUAL_BIND(_get_supported_extensions);
GDVIRTUAL_BIND(_parse_node_extensions, "state", "gltf_node", "extensions");
GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image");
+ GDVIRTUAL_BIND(_get_image_file_extension);
GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture");
GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent");
GDVIRTUAL_BIND(_import_post_parse, "state");
@@ -44,6 +45,7 @@ void GLTFDocumentExtension::_bind_methods() {
// Export process.
GDVIRTUAL_BIND(_export_preflight, "state", "root");
GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node");
+ GDVIRTUAL_BIND(_export_preserialize, "state");
GDVIRTUAL_BIND(_export_node, "state", "gltf_node", "json", "node");
GDVIRTUAL_BIND(_export_post, "state");
}
@@ -78,6 +80,12 @@ Error GLTFDocumentExtension::parse_image_data(Ref<GLTFState> p_state, const Pack
return err;
}
+String GLTFDocumentExtension::get_image_file_extension() {
+ String ret;
+ GDVIRTUAL_CALL(_get_image_file_extension, ret);
+ return ret;
+}
+
Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(r_gltf_texture, ERR_INVALID_PARAMETER);
@@ -134,6 +142,13 @@ void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFN
GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node);
}
+Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) {
+ ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ Error err = OK;
+ GDVIRTUAL_CALL(_export_preserialize, p_state, err);
+ return err;
+}
+
Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h
index d922588a29..0a631bb6c5 100644
--- a/modules/gltf/extensions/gltf_document_extension.h
+++ b/modules/gltf/extensions/gltf_document_extension.h
@@ -45,6 +45,7 @@ public:
virtual Vector<String> get_supported_extensions();
virtual Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions);
virtual Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image);
+ virtual String get_image_file_extension();
virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture);
virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
virtual Error import_post_parse(Ref<GLTFState> p_state);
@@ -53,6 +54,7 @@ public:
// Export process.
virtual Error export_preflight(Ref<GLTFState> p_state, Node *p_root);
virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node);
+ virtual Error export_preserialize(Ref<GLTFState> p_state);
virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
virtual Error export_post(Ref<GLTFState> p_state);
@@ -61,6 +63,7 @@ public:
GDVIRTUAL0R(Vector<String>, _get_supported_extensions);
GDVIRTUAL3R(Error, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary);
GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>);
+ GDVIRTUAL0R(String, _get_image_file_extension);
GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>);
GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>);
@@ -69,6 +72,7 @@ public:
// Export process.
GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *);
GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
+ GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>);
GDVIRTUAL4R(Error, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
GDVIRTUAL1R(Error, _export_post, Ref<GLTFState>);
};
diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp
index ded4970968..73c869be3b 100644
--- a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp
@@ -30,8 +30,6 @@
#include "gltf_document_extension_texture_webp.h"
-#include "scene/3d/area_3d.h"
-
// Import process.
Error GLTFDocumentExtensionTextureWebP::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
if (!p_extensions.has("EXT_texture_webp")) {
@@ -53,6 +51,10 @@ Error GLTFDocumentExtensionTextureWebP::parse_image_data(Ref<GLTFState> p_state,
return OK;
}
+String GLTFDocumentExtensionTextureWebP::get_image_file_extension() {
+ return ".webp";
+}
+
Error GLTFDocumentExtensionTextureWebP::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) {
if (!p_texture_json.has("extensions")) {
return OK;
diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.h b/modules/gltf/extensions/gltf_document_extension_texture_webp.h
index 9abf09a41f..d2654aae8c 100644
--- a/modules/gltf/extensions/gltf_document_extension_texture_webp.h
+++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.h
@@ -41,6 +41,7 @@ public:
Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override;
Vector<String> get_supported_extensions() override;
Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) override;
+ String get_image_file_extension() override;
Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) override;
};
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index d828363e03..572ef44876 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -110,11 +110,17 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
return importer_mesh;
}
-Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
+Error GLTFDocument::_serialize(Ref<GLTFState> p_state) {
if (!p_state->buffers.size()) {
p_state->buffers.push_back(Vector<uint8_t>());
}
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Error err = ext->export_preserialize(p_state);
+ ERR_CONTINUE(err != OK);
+ }
+
/* STEP CONVERT MESH INSTANCES */
_convert_mesh_instances(p_state);
@@ -161,7 +167,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
}
/* STEP SERIALIZE IMAGES */
- err = _serialize_images(p_state, p_path);
+ err = _serialize_images(p_state);
if (err != OK) {
return Error::FAILED;
}
@@ -207,7 +213,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) {
}
/* STEP SERIALIZE VERSION */
- err = _serialize_version(p_state);
+ err = _serialize_asset_header(p_state);
if (err != OK) {
return Error::FAILED;
}
@@ -243,23 +249,18 @@ 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.");
+ // Godot only supports one scene per glTF file.
Array scenes;
- const int loaded_scene = 0;
- p_state->json["scene"] = loaded_scene;
-
- if (p_state->nodes.size()) {
- Dictionary s;
- if (!p_state->scene_name.is_empty()) {
- s["name"] = p_state->scene_name;
- }
-
- Array nodes;
- nodes.push_back(0);
- s["nodes"] = nodes;
- scenes.push_back(s);
- }
+ Dictionary scene_dict;
+ scenes.append(scene_dict);
p_state->json["scenes"] = scenes;
-
+ p_state->json["scene"] = 0;
+ // Add nodes to the scene dict.
+ scene_dict["nodes"] = p_state->root_nodes;
+ if (!p_state->scene_name.is_empty()) {
+ scene_dict["name"] = p_state->scene_name;
+ }
return OK;
}
@@ -3000,17 +3001,32 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
return OK;
}
-Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_path) {
+Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
Array images;
for (int i = 0; i < p_state->images.size(); i++) {
- Dictionary d;
+ Dictionary image_dict;
ERR_CONTINUE(p_state->images[i].is_null());
Ref<Image> image = p_state->images[i]->get_image();
ERR_CONTINUE(image.is_null());
- if (p_path.to_lower().ends_with("glb") || p_path.is_empty()) {
+ if (p_state->filename.to_lower().ends_with("gltf")) {
+ String img_name = p_state->images[i]->get_name();
+ if (img_name.is_empty()) {
+ img_name = itos(i);
+ }
+ img_name = _gen_unique_name(p_state, img_name);
+ img_name = img_name.pad_zeros(3) + ".png";
+ String relative_texture_dir = "textures";
+ String full_texture_dir = p_state->base_path.path_join(relative_texture_dir);
+ Ref<DirAccess> da = DirAccess::open(p_state->base_path);
+ if (!da->dir_exists(full_texture_dir)) {
+ da->make_dir(full_texture_dir);
+ }
+ image->save_png(full_texture_dir.path_join(img_name));
+ image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode();
+ } else {
GLTFBufferViewIndex bvi;
Ref<GLTFBufferView> bv;
@@ -3036,27 +3052,10 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
p_state->buffer_views.push_back(bv);
bvi = p_state->buffer_views.size() - 1;
- d["bufferView"] = bvi;
- d["mimeType"] = "image/png";
- } else {
- ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
- String img_name = p_state->images[i]->get_name();
- if (img_name.is_empty()) {
- img_name = itos(i);
- }
- img_name = _gen_unique_name(p_state, img_name);
- img_name = img_name.pad_zeros(3) + ".png";
- String texture_dir = "textures";
- String path = p_path.get_base_dir();
- String new_texture_dir = path + "/" + texture_dir;
- Ref<DirAccess> da = DirAccess::open(path);
- if (!da->dir_exists(new_texture_dir)) {
- da->make_dir(new_texture_dir);
- }
- image->save_png(new_texture_dir.path_join(img_name));
- d["uri"] = texture_dir.path_join(img_name).uri_encode();
+ image_dict["bufferView"] = bvi;
+ image_dict["mimeType"] = "image/png";
}
- images.push_back(d);
+ images.push_back(image_dict);
}
print_verbose("Total images: " + itos(p_state->images.size()));
@@ -3069,7 +3068,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_pa
return OK;
}
-Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index) {
+Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension) {
Ref<Image> r_image;
r_image.instantiate();
// Check if any GLTFDocumentExtensions want to import this data as an image.
@@ -3078,6 +3077,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
Error err = ext->parse_image_data(p_state, p_bytes, p_mime_type, r_image);
ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing.");
if (!r_image->is_empty()) {
+ r_file_extension = ext->get_image_file_extension();
return r_image;
}
}
@@ -3085,8 +3085,10 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
// First we honor the mime types if they were defined.
if (p_mime_type == "image/png") { // Load buffer as PNG.
r_image->load_png_from_buffer(p_bytes);
+ r_file_extension = ".png";
} else if (p_mime_type == "image/jpeg") { // Loader buffer as JPEG.
r_image->load_jpg_from_buffer(p_bytes);
+ r_file_extension = ".jpg";
}
// If we didn't pass the above tests, we attempt loading as PNG and then JPEG directly.
// This covers URIs with base64-encoded data with application/* type but
@@ -3107,7 +3109,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
return r_image;
}
-void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String &p_mime_type, int p_index, Ref<Image> p_image) {
+void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image) {
GLTFState::GLTFHandleBinary handling = GLTFState::GLTFHandleBinary(p_state->handle_binary_image);
if (p_image->is_empty() || handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) {
p_state->images.push_back(Ref<Texture2D>());
@@ -3124,11 +3126,11 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String
p_state->images.push_back(Ref<Texture2D>());
p_state->source_images.push_back(Ref<Image>());
} else {
- Error err = OK;
bool must_import = true;
Vector<uint8_t> img_data = p_image->get_data();
Dictionary generator_parameters;
- String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + p_image->get_name() + ".png";
+ String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + p_image->get_name();
+ file_path += p_file_extension.is_empty() ? ".png" : p_file_extension;
if (FileAccess::exists(file_path + ".import")) {
Ref<ConfigFile> config;
config.instantiate();
@@ -3149,8 +3151,18 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const String
}
}
if (must_import) {
- err = p_image->save_png(file_path);
- ERR_FAIL_COND(err != OK);
+ Error err = OK;
+ if (p_file_extension.is_empty()) {
+ // If a file extension was not specified, save the image data to a PNG file.
+ err = p_image->save_png(file_path);
+ ERR_FAIL_COND(err != OK);
+ } else {
+ // If a file extension was specified, save the original bytes to a file with that extension.
+ Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err);
+ ERR_FAIL_COND(err != OK);
+ file->store_buffer(p_bytes);
+ file->close();
+ }
// ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed.
HashMap<StringName, Variant> custom_options;
custom_options[SNAME("mipmaps/generate")] = true;
@@ -3300,9 +3312,10 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
continue;
}
// Parse the image data from bytes into an Image resource and save if needed.
- Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i);
+ String file_extension;
+ Ref<Image> img = _parse_image_bytes_into_image(p_state, data, mime_type, i, file_extension);
img->set_name(image_name);
- _parse_image_save_image(p_state, mime_type, i, img);
+ _parse_image_save_image(p_state, data, file_extension, i, img);
}
print_verbose("glTF: Total images: " + itos(p_state->images.size()));
@@ -3317,16 +3330,16 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) {
Array textures;
for (int32_t i = 0; i < p_state->textures.size(); i++) {
- Dictionary d;
- Ref<GLTFTexture> t = p_state->textures[i];
- ERR_CONTINUE(t->get_src_image() == -1);
- d["source"] = t->get_src_image();
+ Dictionary texture_dict;
+ Ref<GLTFTexture> gltf_texture = p_state->textures[i];
+ ERR_CONTINUE(gltf_texture->get_src_image() == -1);
+ texture_dict["source"] = gltf_texture->get_src_image();
- GLTFTextureSamplerIndex sampler_index = t->get_sampler();
+ GLTFTextureSamplerIndex sampler_index = gltf_texture->get_sampler();
if (sampler_index != -1) {
- d["sampler"] = sampler_index;
+ texture_dict["sampler"] = sampler_index;
}
- textures.push_back(d);
+ textures.push_back(texture_dict);
}
p_state->json["textures"] = textures;
@@ -3340,28 +3353,28 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) {
const Array &textures = p_state->json["textures"];
for (GLTFTextureIndex i = 0; i < textures.size(); i++) {
- const Dictionary &dict = textures[i];
- Ref<GLTFTexture> texture;
- texture.instantiate();
+ const Dictionary &texture_dict = textures[i];
+ Ref<GLTFTexture> gltf_texture;
+ gltf_texture.instantiate();
// Check if any GLTFDocumentExtensions want to handle this texture JSON.
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- Error err = ext->parse_texture_json(p_state, dict, texture);
- ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(dict)) + " in file " + p_state->filename + ". Continuing.");
- if (texture->get_src_image() != -1) {
+ Error err = ext->parse_texture_json(p_state, texture_dict, gltf_texture);
+ ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing.");
+ if (gltf_texture->get_src_image() != -1) {
break;
}
}
- if (texture->get_src_image() == -1) {
+ if (gltf_texture->get_src_image() == -1) {
// No extensions handled it, so use the base GLTF source.
// This may be the fallback, or the only option anyway.
- ERR_FAIL_COND_V(!dict.has("source"), ERR_PARSE_ERROR);
- texture->set_src_image(dict["source"]);
+ ERR_FAIL_COND_V(!texture_dict.has("source"), ERR_PARSE_ERROR);
+ gltf_texture->set_src_image(texture_dict["source"]);
}
- if (texture->get_sampler() == -1 && dict.has("sampler")) {
- texture->set_sampler(dict["sampler"]);
+ if (gltf_texture->get_sampler() == -1 && texture_dict.has("sampler")) {
+ gltf_texture->set_sampler(texture_dict["sampler"]);
}
- p_state->textures.push_back(texture);
+ p_state->textures.push_back(gltf_texture);
}
return OK;
@@ -5254,26 +5267,26 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
return OK;
}
-void GLTFDocument::_assign_scene_names(Ref<GLTFState> p_state) {
+void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) {
for (int i = 0; i < p_state->nodes.size(); i++) {
- Ref<GLTFNode> n = p_state->nodes[i];
+ Ref<GLTFNode> gltf_node = p_state->nodes[i];
// Any joints get unique names generated when the skeleton is made, unique to the skeleton
- if (n->skeleton >= 0) {
+ if (gltf_node->skeleton >= 0) {
continue;
}
- if (n->get_name().is_empty()) {
- if (n->mesh >= 0) {
- n->set_name(_gen_unique_name(p_state, "Mesh"));
- } else if (n->camera >= 0) {
- n->set_name(_gen_unique_name(p_state, "Camera3D"));
+ if (gltf_node->get_name().is_empty()) {
+ if (gltf_node->mesh >= 0) {
+ gltf_node->set_name(_gen_unique_name(p_state, "Mesh"));
+ } else if (gltf_node->camera >= 0) {
+ gltf_node->set_name(_gen_unique_name(p_state, "Camera3D"));
} else {
- n->set_name(_gen_unique_name(p_state, "Node"));
+ gltf_node->set_name(_gen_unique_name(p_state, "Node"));
}
}
- n->set_name(_gen_unique_name(p_state, n->get_name()));
+ gltf_node->set_name(_gen_unique_name(p_state, gltf_node->get_name()));
}
}
@@ -5462,9 +5475,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
GLTFNodeIndex gltf_root = p_gltf_root;
if (gltf_root == -1) {
gltf_root = current_node_i;
- Array scenes;
- scenes.push_back(gltf_root);
- p_state->json["scene"] = scenes;
+ p_state->root_nodes.push_back(gltf_root);
}
_create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node);
for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) {
@@ -5751,40 +5762,40 @@ void GLTFDocument::_convert_mesh_instance_to_gltf(MeshInstance3D *p_scene_parent
}
}
-void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) {
- Ref<GLTFNode> gltf_node = p_state->nodes[node_index];
+void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root) {
+ Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index];
if (gltf_node->skeleton >= 0) {
- _generate_skeleton_bone_node(p_state, scene_parent, scene_root, node_index);
+ _generate_skeleton_bone_node(p_state, p_node_index, p_scene_parent, p_scene_root);
return;
}
Node3D *current_node = nullptr;
// Is our parent a skeleton
- Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(scene_parent);
+ Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(p_scene_parent);
const bool non_bone_parented_to_skeleton = active_skeleton;
// skinned meshes must not be placed in a bone attachment.
if (non_bone_parented_to_skeleton && gltf_node->skin < 0) {
// Bone Attachment - Parent Case
- BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, node_index, gltf_node->parent);
+ BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, gltf_node->parent);
- scene_parent->add_child(bone_attachment, true);
- bone_attachment->set_owner(scene_root);
+ p_scene_parent->add_child(bone_attachment, true);
+ bone_attachment->set_owner(p_scene_root);
// There is no gltf_node that represent this, so just directly create a unique name
bone_attachment->set_name(gltf_node->get_name());
// We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node
// and attach it to the bone_attachment
- scene_parent = bone_attachment;
+ p_scene_parent = bone_attachment;
}
// Check if any GLTFDocumentExtension classes want to generate a node for us.
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- current_node = ext->generate_scene_node(p_state, gltf_node, scene_parent);
+ current_node = ext->generate_scene_node(p_state, gltf_node, p_scene_parent);
if (current_node) {
break;
}
@@ -5792,38 +5803,38 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, Node *scene_pare
// If none of our GLTFDocumentExtension classes generated us a node, we generate one.
if (!current_node) {
if (gltf_node->skin >= 0 && gltf_node->mesh >= 0 && !gltf_node->children.is_empty()) {
- current_node = _generate_spatial(p_state, node_index);
- Node3D *mesh_inst = _generate_mesh_instance(p_state, node_index);
+ current_node = _generate_spatial(p_state, p_node_index);
+ Node3D *mesh_inst = _generate_mesh_instance(p_state, p_node_index);
mesh_inst->set_name(gltf_node->get_name());
current_node->add_child(mesh_inst, true);
} else if (gltf_node->mesh >= 0) {
- current_node = _generate_mesh_instance(p_state, node_index);
+ current_node = _generate_mesh_instance(p_state, p_node_index);
} else if (gltf_node->camera >= 0) {
- current_node = _generate_camera(p_state, node_index);
+ current_node = _generate_camera(p_state, p_node_index);
} else if (gltf_node->light >= 0) {
- current_node = _generate_light(p_state, node_index);
+ current_node = _generate_light(p_state, p_node_index);
} else {
- current_node = _generate_spatial(p_state, node_index);
+ current_node = _generate_spatial(p_state, p_node_index);
}
}
// Add the node we generated and set the owner to the scene root.
- scene_parent->add_child(current_node, true);
- if (current_node != scene_root) {
+ p_scene_parent->add_child(current_node, true);
+ if (current_node != p_scene_root) {
Array args;
- args.append(scene_root);
+ args.append(p_scene_root);
current_node->propagate_call(StringName("set_owner"), args);
}
current_node->set_transform(gltf_node->xform);
current_node->set_name(gltf_node->get_name());
- p_state->scene_nodes.insert(node_index, current_node);
+ p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
- _generate_scene_node(p_state, current_node, scene_root, gltf_node->children[i]);
+ _generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root);
}
}
-void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index) {
+void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root) {
Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index];
Node3D *current_node = nullptr;
@@ -5907,7 +5918,7 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_
p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
- _generate_scene_node(p_state, active_skeleton, p_scene_root, gltf_node->children[i]);
+ _generate_scene_node(p_state, gltf_node->children[i], active_skeleton, p_scene_root);
}
}
@@ -6999,20 +7010,8 @@ Error GLTFDocument::_parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess
p_state->json = json.get_data();
}
- if (!p_state->json.has("asset")) {
- return ERR_PARSE_ERROR;
- }
-
- Dictionary asset = p_state->json["asset"];
-
- if (!asset.has("version")) {
- return ERR_PARSE_ERROR;
- }
-
- String version = asset["version"];
-
- p_state->major_version = version.get_slice(".", 0).to_int();
- p_state->minor_version = version.get_slice(".", 1).to_int();
+ err = _parse_asset_header(p_state);
+ ERR_FAIL_COND_V(err != OK, err);
document_extensions.clear();
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
@@ -7069,13 +7068,15 @@ Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_
return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
}
-Error GLTFDocument::_serialize_version(Ref<GLTFState> p_state) {
+Error GLTFDocument::_serialize_asset_header(Ref<GLTFState> p_state) {
const String version = "2.0";
p_state->major_version = version.get_slice(".", 0).to_int();
p_state->minor_version = version.get_slice(".", 1).to_int();
Dictionary asset;
asset["version"] = version;
-
+ if (!p_state->copyright.is_empty()) {
+ asset["copyright"] = p_state->copyright;
+ }
String hash = String(VERSION_HASH);
asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash);
p_state->json["asset"] = asset;
@@ -7238,7 +7239,10 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Erro
PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, PackedByteArray());
- Error err = _serialize(p_state, "");
+ // For buffers, set the state filename to an empty string, but
+ // don't touch the base path, in case the user set it manually.
+ p_state->filename = "";
+ Error err = _serialize(p_state);
ERR_FAIL_COND_V(err != OK, PackedByteArray());
PackedByteArray bytes = _serialize_glb_buffer(p_state, &err);
return bytes;
@@ -7246,7 +7250,9 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- Error err = _serialize(p_state, p_path);
+ p_state->base_path = p_path.get_base_dir();
+ p_state->filename = p_path.get_file();
+ Error err = _serialize(p_state);
if (err != OK) {
return err;
}
@@ -7301,7 +7307,11 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
ERR_FAIL_COND_V(p_state.is_null(), FAILED);
p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
-
+ if (!p_state->buffers.size()) {
+ p_state->buffers.push_back(Vector<uint8_t>());
+ }
+ // Perform export preflight for document extensions. Only extensions that
+ // return OK will be used for the rest of the export steps.
document_extensions.clear();
for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
ERR_CONTINUE(ext.is_null());
@@ -7310,10 +7320,8 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint
document_extensions.push_back(ext);
}
}
+ // Add the root node(s) and their descendants to the state.
_convert_scene_node(p_state, p_node, -1, -1);
- if (!p_state->buffers.size()) {
- p_state->buffers.push_back(Vector<uint8_t>());
- }
return OK;
}
@@ -7338,6 +7346,23 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
return OK;
}
+Error GLTFDocument::_parse_asset_header(Ref<GLTFState> p_state) {
+ if (!p_state->json.has("asset")) {
+ return ERR_PARSE_ERROR;
+ }
+ Dictionary asset = p_state->json["asset"];
+ if (!asset.has("version")) {
+ return ERR_PARSE_ERROR;
+ }
+ String version = asset["version"];
+ p_state->major_version = version.get_slice(".", 0).to_int();
+ p_state->minor_version = version.get_slice(".", 1).to_int();
+ if (asset.has("copyright")) {
+ p_state->copyright = asset["copyright"];
+ }
+ return OK;
+}
+
Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path) {
Error err;
@@ -7424,24 +7449,24 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
/* ASSIGN SCENE NAMES */
- _assign_scene_names(p_state);
+ _assign_node_names(p_state);
Node3D *root = memnew(Node3D);
for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) {
- _generate_scene_node(p_state, root, root, p_state->root_nodes[root_i]);
+ _generate_scene_node(p_state, p_state->root_nodes[root_i], root, root);
}
return OK;
}
-Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, String p_base_path) {
+Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags, String p_base_path) {
// TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire
- if (r_state == Ref<GLTFState>()) {
- r_state.instantiate();
+ if (p_state == Ref<GLTFState>()) {
+ p_state.instantiate();
}
- r_state->filename = p_path.get_file().get_basename();
- r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
- r_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
+ p_state->filename = p_path.get_file().get_basename();
+ p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
+ p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
@@ -7450,12 +7475,12 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
}
- r_state->base_path = base_path;
- err = _parse(r_state, base_path, file);
+ p_state->base_path = base_path;
+ err = _parse(p_state, base_path, file);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
- err = ext->import_post_parse(r_state);
+ err = ext->import_post_parse(p_state);
ERR_FAIL_COND_V(err != OK, err);
}
return OK;
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 718b05b959..f2e36a0457 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -149,10 +149,10 @@ private:
Error _parse_meshes(Ref<GLTFState> p_state);
Error _serialize_textures(Ref<GLTFState> p_state);
Error _serialize_texture_samplers(Ref<GLTFState> p_state);
- Error _serialize_images(Ref<GLTFState> p_state, const String &p_path);
+ Error _serialize_images(Ref<GLTFState> p_state);
Error _serialize_lights(Ref<GLTFState> p_state);
- Ref<Image> _parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index);
- void _parse_image_save_image(Ref<GLTFState> p_state, const String &p_mime_type, int p_index, Ref<Image> p_image);
+ Ref<Image> _parse_image_bytes_into_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_mime_type, int p_index, String &r_file_extension);
+ void _parse_image_save_image(Ref<GLTFState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image);
Error _parse_images(Ref<GLTFState> p_state, const String &p_base_path);
Error _parse_textures(Ref<GLTFState> p_state);
Error _parse_texture_samplers(Ref<GLTFState> p_state);
@@ -199,7 +199,7 @@ private:
Camera3D *_generate_camera(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
Light3D *_generate_light(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
Node3D *_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
- void _assign_scene_names(Ref<GLTFState> p_state);
+ void _assign_node_names(Ref<GLTFState> p_state);
template <class T>
T _interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values,
const float p_time,
@@ -272,7 +272,7 @@ private:
PackedByteArray _serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err);
Dictionary _serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material);
Dictionary _serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material);
- Error _serialize_version(Ref<GLTFState> p_state);
+ Error _serialize_asset_header(Ref<GLTFState> p_state);
Error _serialize_file(Ref<GLTFState> p_state, const String p_path);
Error _serialize_gltf_extensions(Ref<GLTFState> p_state) const;
@@ -293,9 +293,9 @@ private:
static float get_max_component(const Color &p_color);
public:
- Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, String p_base_path = String());
- Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0);
- Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0);
+ Error append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags = 0, String p_base_path = String());
+ Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags = 0);
+ Error append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags = 0);
public:
Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true);
@@ -304,12 +304,11 @@ public:
public:
Error _parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path);
+ Error _parse_asset_header(Ref<GLTFState> p_state);
Error _parse_gltf_extensions(Ref<GLTFState> p_state);
void _process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root);
- void _generate_scene_node(Ref<GLTFState> p_state, Node *p_scene_parent,
- Node3D *p_scene_root,
- const GLTFNodeIndex p_node_index);
- void _generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index);
+ void _generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
+ void _generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
void _import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player,
const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks);
void _convert_mesh_instances(Ref<GLTFState> p_state);
@@ -367,7 +366,7 @@ public:
GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state,
MeshInstance3D *p_mesh_instance);
void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name);
- Error _serialize(Ref<GLTFState> p_state, const String &p_path);
+ Error _serialize(Ref<GLTFState> p_state);
Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file);
};
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index 372348d90d..c0ec004fd6 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -40,6 +40,8 @@ void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_major_version", "major_version"), &GLTFState::set_major_version);
ClassDB::bind_method(D_METHOD("get_minor_version"), &GLTFState::get_minor_version);
ClassDB::bind_method(D_METHOD("set_minor_version", "minor_version"), &GLTFState::set_minor_version);
+ ClassDB::bind_method(D_METHOD("get_copyright"), &GLTFState::get_copyright);
+ ClassDB::bind_method(D_METHOD("set_copyright", "copyright"), &GLTFState::set_copyright);
ClassDB::bind_method(D_METHOD("get_glb_data"), &GLTFState::get_glb_data);
ClassDB::bind_method(D_METHOD("set_glb_data", "glb_data"), &GLTFState::set_glb_data);
ClassDB::bind_method(D_METHOD("get_use_named_skin_binds"), &GLTFState::get_use_named_skin_binds);
@@ -62,6 +64,8 @@ void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_scene_name", "scene_name"), &GLTFState::set_scene_name);
ClassDB::bind_method(D_METHOD("get_base_path"), &GLTFState::get_base_path);
ClassDB::bind_method(D_METHOD("set_base_path", "base_path"), &GLTFState::set_base_path);
+ ClassDB::bind_method(D_METHOD("get_filename"), &GLTFState::get_filename);
+ ClassDB::bind_method(D_METHOD("set_filename", "filename"), &GLTFState::set_filename);
ClassDB::bind_method(D_METHOD("get_root_nodes"), &GLTFState::get_root_nodes);
ClassDB::bind_method(D_METHOD("set_root_nodes", "root_nodes"), &GLTFState::set_root_nodes);
ClassDB::bind_method(D_METHOD("get_textures"), &GLTFState::get_textures);
@@ -96,6 +100,7 @@ void GLTFState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "json"), "set_json", "get_json"); // Dictionary
ADD_PROPERTY(PropertyInfo(Variant::INT, "major_version"), "set_major_version", "get_major_version"); // int
ADD_PROPERTY(PropertyInfo(Variant::INT, "minor_version"), "set_minor_version", "get_minor_version"); // int
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "copyright"), "set_copyright", "get_copyright"); // String
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "glb_data"), "set_glb_data", "get_glb_data"); // Vector<uint8_t>
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_named_skin_binds"), "set_use_named_skin_binds", "get_use_named_skin_binds"); // bool
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_nodes", "get_nodes"); // Vector<Ref<GLTFNode>>
@@ -106,6 +111,7 @@ void GLTFState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "materials", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_materials", "get_materials"); // Vector<Ref<Material>
ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_name"), "set_scene_name", "get_scene_name"); // String
ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_path"), "set_base_path", "get_base_path"); // String
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename"), "set_filename", "get_filename"); // String
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "root_nodes"), "set_root_nodes", "get_root_nodes"); // Vector<int>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_textures", "get_textures"); // Vector<Ref<GLTFTexture>>
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "texture_samplers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_texture_samplers", "get_texture_samplers"); //Vector<Ref<GLTFTextureSampler>>
@@ -161,6 +167,14 @@ void GLTFState::set_minor_version(int p_minor_version) {
minor_version = p_minor_version;
}
+String GLTFState::get_copyright() const {
+ return copyright;
+}
+
+void GLTFState::set_copyright(const String &p_copyright) {
+ copyright = p_copyright;
+}
+
Vector<uint8_t> GLTFState::get_glb_data() {
return glb_data;
}
@@ -370,6 +384,14 @@ void GLTFState::set_base_path(String p_base_path) {
base_path = p_base_path;
}
+String GLTFState::get_filename() const {
+ return filename;
+}
+
+void GLTFState::set_filename(const String &p_filename) {
+ filename = p_filename;
+}
+
Variant GLTFState::get_additional_data(const StringName &p_extension_name) {
return additional_data[p_extension_name];
}
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index a2371b8040..91af8f91a4 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -47,11 +47,12 @@ class GLTFState : public Resource {
GDCLASS(GLTFState, Resource);
friend class GLTFDocument;
- String filename;
String base_path;
+ String filename;
Dictionary json;
int major_version = 0;
int minor_version = 0;
+ String copyright;
Vector<uint8_t> glb_data;
bool use_named_skin_binds = false;
@@ -125,6 +126,9 @@ public:
int get_minor_version();
void set_minor_version(int p_minor_version);
+ String get_copyright() const;
+ void set_copyright(const String &p_copyright);
+
Vector<uint8_t> get_glb_data();
void set_glb_data(Vector<uint8_t> p_glb_data);
@@ -167,6 +171,9 @@ public:
String get_base_path();
void set_base_path(String p_base_path);
+ String get_filename() const;
+ void set_filename(const String &p_filename);
+
PackedInt32Array get_root_nodes();
void set_root_nodes(PackedInt32Array p_root_nodes);
diff --git a/modules/minimp3/doc_classes/ResourceImporterMP3.xml b/modules/minimp3/doc_classes/ResourceImporterMP3.xml
index 7283577f36..a84c51cf68 100644
--- a/modules/minimp3/doc_classes/ResourceImporterMP3.xml
+++ b/modules/minimp3/doc_classes/ResourceImporterMP3.xml
@@ -1,22 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ResourceImporterMP3" inherits="ResourceImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
+ Imports a MP3 audio file for playback.
</brief_description>
<description>
+ MP3 is a lossy audio format, with worse audio quality compared to [ResourceImporterOggVorbis] at a given bitrate.
+ In most cases, it's recommended to use Ogg Vorbis over MP3. However, if you're using a MP3 sound source with no higher quality source available, then it's recommended to use the MP3 file directly to avoid double lossy compression.
+ MP3 requires more CPU to decode than [ResourceImporterWAV]. If you need to play a lot of simultaneous sounds, it's recommended to use WAV for those sounds instead, especially if targeting low-end devices.
</description>
<tutorials>
+ <link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
</tutorials>
<members>
<member name="bar_beats" type="int" setter="" getter="" default="4">
+ The number of bars within a single beat in the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ A more convenient editor for [member bar_beats] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="beat_count" type="int" setter="" getter="" default="0">
+ The beat count of the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ A more convenient editor for [member beat_count] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="bpm" type="float" setter="" getter="" default="0">
+ The Beats Per Minute of the audio track. This should match the BPM measure that was used to compose the track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ A more convenient editor for [member bpm] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="loop" type="bool" setter="" getter="" default="false">
- If [code]true[/code], the audio will play again from the specified [member loop_offset] once it is done playing. Useful for ambient sounds and background music.
+ If enabled, the audio will begin playing at the beginning after playback ends by reaching the end of the audio.
+ [b]Note:[/b] In [AudioStreamPlayer], the [signal AudioStreamPlayer.finished] signal won't be emitted for looping audio when it reaches the end of the audio file, as the audio will keep playing indefinitely.
</member>
<member name="loop_offset" type="float" setter="" getter="" default="0">
+ Determines where audio will start to loop after playback reaches the end of the audio. This can be used to only loop a part of the audio file, which is useful for some ambient sounds or music. The value is determined in seconds relative to the beginning of the audio. A value of [code]0.0[/code] will loop the entire audio file.
+ Only has an effect if [member loop] is [code]true[/code].
+ A more convenient editor for [member loop_offset] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
</members>
</class>
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index a4667f784d..564c5929c7 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -23,8 +23,6 @@ env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.cpp")
if env["platform"] in ["macos", "ios"]:
env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.mm")
env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.m")
-elif env["platform"] == "android":
- env_mono.add_source_files(env.modules_sources, "mono_gd/android_mono_config.gen.cpp")
if env.editor_build:
env_mono.add_source_files(env.modules_sources, "editor/*.cpp")
diff --git a/modules/mono/config.py b/modules/mono/config.py
index a36083b64b..2b2a8d6235 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -1,6 +1,6 @@
# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "haiku", "web", "ios"]
# Eventually support for each them should be added back (except Haiku if not supported by .NET Core)
-supported_platforms = ["windows", "macos", "linuxbsd"]
+supported_platforms = ["windows", "macos", "linuxbsd", "android"]
def can_build(env, platform):
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 1ed495943f..2971706c75 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -72,7 +72,7 @@
// Types that will be skipped over (in favor of their base types) when setting up instance bindings.
// This must be a superset of `ignored_types` in bindings_generator.cpp.
-const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" };
+const Vector<String> ignored_types = {};
#ifdef TOOLS_ENABLED
static bool _create_project_solution_if_needed() {
@@ -1197,8 +1197,6 @@ void CSharpLanguage::_editor_init_callback() {
// Add plugin to EditorNode and enable it
EditorNode::add_editor_plugin(godotsharp_editor);
- ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B);
- ED_SHORTCUT_OVERRIDE("mono/build_solution", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::B);
godotsharp_editor->enable_plugin();
get_singleton()->godotsharp_editor = godotsharp_editor;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
index a03c9bc06c..23879e0e53 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
@@ -18,6 +18,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<!-- Do not include the generator as a lib dependency -->
<IncludeBuildOutput>false</IncludeBuildOutput>
+ <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
index c083b9cc60..54f7ed02f5 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
@@ -138,7 +138,8 @@ namespace GodotTools.Build
{
var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
- if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
+ // Godot's ScriptEditor.Edit is 0-based but the issue lines are 1-based.
+ if (script != null && Internal.ScriptEditorEdit(script, issue.Line - 1, issue.Column - 1))
Internal.EditorNodeShowScriptScreen();
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index 1bb1b3227e..cc11132a55 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -114,7 +114,7 @@ namespace GodotTools.Build
var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
AddChild(toolBarHBox);
- _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons") };
+ _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("BuildCSharp", "EditorIcons") };
toolBarHBox.AddChild(_buildMenuBtn);
var buildMenu = _buildMenuBtn.GetPopup();
@@ -184,7 +184,7 @@ namespace GodotTools.Build
if (what == NotificationThemeChanged)
{
if (_buildMenuBtn != null)
- _buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons");
+ _buildMenuBtn.Icon = GetThemeIcon("BuildCSharp", "EditorIcons");
if (_errorsBtn != null)
_errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons");
if (_warningsBtn != null)
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index ea2d14958b..b98df190ca 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -3,6 +3,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
using GodotTools.Build;
using GodotTools.Core;
using GodotTools.Internals;
@@ -45,6 +47,17 @@ namespace GodotTools.Export
}
},
{ "default_value", true }
+ },
+ new Godot.Collections.Dictionary()
+ {
+ {
+ "option", new Godot.Collections.Dictionary()
+ {
+ { "name", "dotnet/embed_build_outputs" },
+ { "type", (int)Variant.Type.Bool }
+ }
+ },
+ { "default_value", false }
}
};
}
@@ -114,7 +127,7 @@ namespace GodotTools.Export
if (!DeterminePlatformFromFeatures(features, out string platform))
throw new NotSupportedException("Target platform not supported.");
- if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS }
+ if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android }
.Contains(platform))
{
throw new NotImplementedException("Target platform not yet implemented.");
@@ -129,15 +142,19 @@ namespace GodotTools.Export
{
archs.Add("x86_64");
}
- else if (features.Contains("x86_32"))
+ if (features.Contains("x86_32"))
{
archs.Add("x86_32");
}
- else if (features.Contains("arm64"))
+ if (features.Contains("arm64"))
{
archs.Add("arm64");
}
- else if (features.Contains("universal"))
+ if (features.Contains("arm32"))
+ {
+ archs.Add("arm32");
+ }
+ if (features.Contains("universal"))
{
if (platform == OS.Platforms.MacOS)
{
@@ -146,12 +163,14 @@ namespace GodotTools.Export
}
}
+ bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || features.Contains("android");
+
foreach (var arch in archs)
{
string ridOS = DetermineRuntimeIdentifierOS(platform);
string ridArch = DetermineRuntimeIdentifierArch(arch);
string runtimeIdentifier = $"{ridOS}-{ridArch}";
- string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{arch}";
+ string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
if (platform == OS.Platforms.MacOS)
{
projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
@@ -190,17 +209,44 @@ namespace GodotTools.Export
"Publish succeeded but project assembly not found in the output directory");
}
- // Add to the exported project shared object list.
+ var manifest = new StringBuilder();
+ // Add to the exported project shared object list or packed resources.
foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
- AddSharedObject(file, tags: null,
- Path.Join(projectDataDirName,
- Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file))));
+ if (embedBuildResults)
+ {
+ var filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputTempDir, file));
+ var fileData = File.ReadAllBytes(file);
+ var hash = Convert.ToBase64String(SHA512.HashData(fileData));
+
+ manifest.Append($"{filePath}\t{hash}\n");
+
+ AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false);
+ }
+ else
+ {
+ AddSharedObject(file, tags: null,
+ Path.Join(projectDataDirName,
+ Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file))));
+ }
+ }
+
+ if (embedBuildResults)
+ {
+ var fileData = Encoding.Default.GetBytes(manifest.ToString());
+ AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false);
}
}
}
+ private string SanitizeSlashes(string path)
+ {
+ if (Path.DirectorySeparatorChar == '\\')
+ return path.Replace('\\', '/');
+ return path;
+ }
+
private string DetermineRuntimeIdentifierOS(string platform)
=> OS.DotNetOSPlatformMap[platform];
@@ -214,7 +260,7 @@ namespace GodotTools.Export
"x86_64" => "x64",
"armeabi-v7a" => "arm",
"arm64-v8a" => "arm64",
- "armv7" => "arm",
+ "arm32" => "arm",
"arm64" => "arm64",
_ => throw new ArgumentOutOfRangeException(nameof(arch), arch, "Unexpected architecture")
};
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 622a155d37..618d255938 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -280,7 +280,7 @@ namespace GodotTools
case ExternalEditorId.Rider:
{
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
- RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line);
+ RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line + 1, col);
return Error.Ok;
}
case ExternalEditorId.MonoDevelop:
@@ -497,18 +497,20 @@ namespace GodotTools
AddToolSubmenuItem("C#", _menuPopup);
- var buildSolutionShortcut = (Shortcut)EditorShortcut("mono/build_solution");
-
_toolBarBuildButton = new Button
{
- Text = "Build",
- TooltipText = "Build Solution".TTR(),
+ Flat = true,
+ Icon = editorBaseControl.GetThemeIcon("BuildCSharp", "EditorIcons"),
FocusMode = Control.FocusModeEnum.None,
- Shortcut = buildSolutionShortcut,
- ShortcutInTooltip = true
+ Shortcut = EditorDefShortcut("mono/build_solution", "Build Project".TTR(), (Key)KeyModifierMask.MaskAlt | Key.B),
+ ShortcutInTooltip = true,
};
+ EditorShortcutOverride("mono/build_solution", "macos", (Key)KeyModifierMask.MaskMeta | (Key)KeyModifierMask.MaskCtrl | Key.B);
+
_toolBarBuildButton.Pressed += BuildProjectPressed;
- AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton);
+ Internal.EditorPlugin_AddControlToEditorRunBar(_toolBarBuildButton);
+ // Move Build button so it appears to the left of the Play button.
+ _toolBarBuildButton.GetParent().MoveChild(_toolBarBuildButton, 0);
if (File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
@@ -538,7 +540,7 @@ namespace GodotTools
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsMacOS)
@@ -546,14 +548,14 @@ namespace GodotTools
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 4a0b7f9bed..56ae37b4dd 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -28,7 +28,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
- <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.1" />
+ <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.4" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<Reference Include="GodotSharp">
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs
index 7e08d8c01d..61c1581281 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs
@@ -48,4 +48,9 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment
else
GD.PushError(message, e);
}
+
+ public void Verbose(string message, Exception e = null)
+ {
+ // do nothing, since IDK how to write only to the log, without spamming the output
+ }
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
index 5c09f1f83a..0d77b8999a 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using Godot;
@@ -11,10 +10,13 @@ namespace GodotTools.Ides.Rider
public static class RiderPathManager
{
private static readonly RiderPathLocator RiderPathLocator;
+ private static readonly RiderFileOpener RiderFileOpener;
static RiderPathManager()
{
- RiderPathLocator = new RiderPathLocator(new RiderLocatorEnvironment());
+ var riderLocatorEnvironment = new RiderLocatorEnvironment();
+ RiderPathLocator = new RiderPathLocator(riderLocatorEnvironment);
+ RiderFileOpener = new RiderFileOpener(riderLocatorEnvironment);
}
public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional";
@@ -46,7 +48,7 @@ namespace GodotTools.Ides.Rider
}
var riderPath = (string)editorSettings.GetSetting(EditorPathSettingName);
- if (IsRiderAndExists(riderPath))
+ if (File.Exists(riderPath))
{
Globals.EditorDef(EditorPathSettingName, riderPath);
return;
@@ -63,12 +65,6 @@ namespace GodotTools.Ides.Rider
}
}
- public static bool IsExternalEditorSetToRider(EditorSettings editorSettings)
- {
- return editorSettings.HasSetting(EditorPathSettingName) &&
- IsRider((string)editorSettings.GetSetting(EditorPathSettingName));
- }
-
public static bool IsRider(string path)
{
if (string.IsNullOrEmpty(path))
@@ -84,49 +80,29 @@ namespace GodotTools.Ides.Rider
private static string CheckAndUpdatePath(string riderPath)
{
- if (IsRiderAndExists(riderPath))
+ if (File.Exists(riderPath))
{
return riderPath;
}
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var paths = RiderPathLocator.GetAllRiderPaths();
-
- if (!paths.Any())
+ var allInfos = RiderPathLocator.GetAllRiderPaths();
+ if (allInfos.Length == 0)
return null;
-
- string newPath = paths.Last().Path;
+ var riderInfos = allInfos.Where(info => IsRider(info.Path)).ToArray();
+ string newPath = riderInfos.Length > 0
+ ? riderInfos[riderInfos.Length - 1].Path
+ : allInfos[allInfos.Length - 1].Path;
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
editorSettings.SetSetting(EditorPathSettingName, newPath);
Globals.EditorDef(EditorPathSettingName, newPath);
return newPath;
}
- private static bool IsRiderAndExists(string riderPath)
- {
- return !string.IsNullOrEmpty(riderPath) && IsRider(riderPath) && new FileInfo(riderPath).Exists;
- }
-
- public static void OpenFile(string slnPath, string scriptPath, int line)
+ public static void OpenFile(string slnPath, string scriptPath, int line, int column)
{
string pathFromSettings = GetRiderPathFromSettings();
string path = CheckAndUpdatePath(pathFromSettings);
-
- var args = new List<string>();
- args.Add(slnPath);
- if (line >= 0)
- {
- args.Add("--line");
- args.Add((line + 1).ToString()); // https://github.com/JetBrains/godot-support/issues/61
- }
- args.Add(scriptPath);
- try
- {
- Utils.OS.RunProcess(path, args);
- }
- catch (Exception e)
- {
- GD.PushError($"Error when trying to run code editor: JetBrains Rider. Exception message: '{e.Message}'");
- }
+ RiderFileOpener.OpenFile(path, slnPath, scriptPath, line, column);
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
index 45ae7eb86b..a6718e8fd5 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs
@@ -29,11 +29,26 @@ namespace GodotTools.Internals
return Variant.CreateTakingOwnershipOfDisposableValue(result);
}
- public static Variant EditorShortcut(string setting)
+ public static Shortcut EditorDefShortcut(string setting, string name, Key keycode = Key.None, bool physical = false)
{
using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
- Internal.godot_icall_Globals_EditorShortcut(settingIn, out godot_variant result);
- return Variant.CreateTakingOwnershipOfDisposableValue(result);
+ using godot_string nameIn = Marshaling.ConvertStringToNative(name);
+ Internal.godot_icall_Globals_EditorDefShortcut(settingIn, nameIn, keycode, physical.ToGodotBool(), out godot_variant result);
+ return (Shortcut)Variant.CreateTakingOwnershipOfDisposableValue(result);
+ }
+
+ public static Shortcut EditorGetShortcut(string setting)
+ {
+ using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
+ Internal.godot_icall_Globals_EditorGetShortcut(settingIn, out godot_variant result);
+ return (Shortcut)Variant.CreateTakingOwnershipOfDisposableValue(result);
+ }
+
+ public static void EditorShortcutOverride(string setting, string feature, Key keycode = Key.None, bool physical = false)
+ {
+ using godot_string settingIn = Marshaling.ConvertStringToNative(setting);
+ using godot_string featureIn = Marshaling.ConvertStringToNative(feature);
+ Internal.godot_icall_Globals_EditorShortcutOverride(settingIn, featureIn, keycode, physical.ToGodotBool());
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 3ea11750b7..90c443ebb8 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -54,6 +54,9 @@ namespace GodotTools.Internals
public static void EditorRunStop() => godot_icall_Internal_EditorRunStop();
+ public static void EditorPlugin_AddControlToEditorRunBar(Control control) =>
+ godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(control.NativeInstance);
+
public static void ScriptEditorDebugger_ReloadScripts() =>
godot_icall_Internal_ScriptEditorDebugger_ReloadScripts();
@@ -137,6 +140,8 @@ namespace GodotTools.Internals
private static partial void godot_icall_Internal_EditorRunStop();
+ private static partial void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(IntPtr p_control);
+
private static partial void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts();
private static partial void godot_icall_Internal_CodeCompletionRequest(int kind, in godot_string scriptFile,
@@ -151,7 +156,13 @@ namespace GodotTools.Internals
bool restartIfChanged, out godot_variant result);
public static partial void
- godot_icall_Globals_EditorShortcut(in godot_string setting, out godot_variant result);
+ godot_icall_Globals_EditorDefShortcut(in godot_string setting, in godot_string name, Key keycode, godot_bool physical, out godot_variant result);
+
+ public static partial void
+ godot_icall_Globals_EditorGetShortcut(in godot_string setting, out godot_variant result);
+
+ public static partial void
+ godot_icall_Globals_EditorShortcutOverride(in godot_string setting, in godot_string feature, Key keycode, godot_bool physical);
public static partial void godot_icall_Globals_TTR(in godot_string text, out godot_string dest);
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index cff41a57f3..9700bb18bb 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -82,6 +82,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define CS_STATIC_METHOD_GETINSTANCE "GetPtr"
#define CS_METHOD_CALL "Call"
#define CS_PROPERTY_SINGLETON "Singleton"
+#define CS_SINGLETON_INSTANCE_SUFFIX "Instance"
#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod"
#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod"
#define CS_METHOD_HAS_GODOT_CLASS_SIGNAL "HasGodotClassSignal"
@@ -116,7 +117,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
// Types that will be ignored by the generator and won't be available in C#.
// This must be kept in sync with `ignored_types` in csharp_script.cpp
-const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" };
+const Vector<String> ignored_types = {};
void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) {
// C interface for enums is the same as that of 'uint32_t'. Remember to apply
@@ -653,6 +654,11 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const
_append_xml_undeclared(p_xml_output, p_link_target);
} else {
// Try to find the constant in the current class
+ if (p_target_itype->is_singleton_instance) {
+ // Constants and enums are declared in the static singleton class.
+ p_target_itype = &obj_types[p_target_itype->cname];
+ }
+
const ConstantInterface *target_iconst = find_constant_by_name(p_target_cname, p_target_itype->constants);
if (target_iconst) {
@@ -1403,8 +1409,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
if (is_derived_type && !itype.is_singleton) {
if (obj_types.has(itype.base_name)) {
+ TypeInterface base_type = obj_types[itype.base_name];
output.append(" : ");
- output.append(obj_types[itype.base_name].proxy_name);
+ output.append(base_type.proxy_name);
+ if (base_type.is_singleton) {
+ // If the type is a singleton, use the instance type.
+ output.append(CS_SINGLETON_INSTANCE_SUFFIX);
+ }
} else {
ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'.");
return ERR_INVALID_DATA;
@@ -1504,37 +1515,44 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
"' for class '" + itype.name + "'.");
}
- if (itype.is_singleton) {
- // Add the type name and the singleton pointer as static fields
+ // Add native name static field and cached type.
- output.append(MEMBER_BEGIN "private static GodotObject singleton;\n");
+ if (is_derived_type && !itype.is_singleton) {
+ output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";
+ }
- output << MEMBER_BEGIN "public static GodotObject " CS_PROPERTY_SINGLETON "\n" INDENT1 "{\n"
- << INDENT2 "get\n" INDENT2 "{\n" INDENT3 "if (singleton == null)\n"
- << INDENT4 "singleton = " C_METHOD_ENGINE_GET_SINGLETON "(\""
- << itype.name
- << "\");\n" INDENT3 "return singleton;\n" INDENT2 "}\n" INDENT1 "}\n";
+ output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
+ output.append(itype.name);
+ output.append("\";\n");
- output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
- output.append(itype.name);
- output.append("\";\n");
- } else {
+ if (itype.is_singleton || itype.is_compat_singleton) {
+ // Add the Singleton static property.
+
+ String instance_type_name;
+
+ if (itype.is_singleton) {
+ StringName instance_name = itype.name + CS_SINGLETON_INSTANCE_SUFFIX;
+ instance_type_name = obj_types.has(instance_name)
+ ? obj_types[instance_name].proxy_name
+ : "GodotObject";
+ } else {
+ instance_type_name = itype.proxy_name;
+ }
+
+ output.append(MEMBER_BEGIN "private static " + instance_type_name + " singleton;\n");
+
+ output << MEMBER_BEGIN "public static " + instance_type_name + " " CS_PROPERTY_SINGLETON " =>\n"
+ << INDENT2 "singleton \?\?= (" + instance_type_name + ")"
+ << C_METHOD_ENGINE_GET_SINGLETON "(\"" << itype.name << "\");\n";
+ }
+
+ if (!itype.is_singleton) {
// IMPORTANT: We also generate the static fields for GodotObject instead of declaring
// them manually in the `GodotObject.base.cs` partial class declaration, because they're
// required by other static fields in this generated partial class declaration.
// Static fields are initialized in order of declaration, but when they're in different
// partial class declarations then it becomes harder to tell (Rider warns about this).
- // Add native name static field
-
- if (is_derived_type) {
- output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n";
- }
-
- output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
- output.append(itype.name);
- output.append("\";\n");
-
if (itype.is_instantiable) {
// Add native constructor static field
@@ -1894,7 +1912,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type;
- const TypeInterface *prop_itype = _get_type_or_null(proptype_name);
+ const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name);
ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found
ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG,
@@ -1983,7 +2001,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
}
Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) {
- const TypeInterface *return_type = _get_type_or_null(p_imethod.return_type);
+ const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);
ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found
ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG,
@@ -2004,12 +2022,17 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
String icall_params = method_bind_field;
if (!p_imethod.is_static) {
+ String self_reference = "this";
+ if (p_itype.is_singleton) {
+ self_reference = CS_PROPERTY_SINGLETON;
+ }
+
if (p_itype.cs_in.size()) {
- cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, "this",
+ cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, self_reference,
String(), String(), String(), INDENT2);
}
- icall_params += ", " + sformat(p_itype.cs_in_expr, "this");
+ icall_params += ", " + sformat(p_itype.cs_in_expr, self_reference);
}
StringBuilder default_args_doc;
@@ -2017,7 +2040,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
// Retrieve information from the arguments
const ArgumentInterface &first = p_imethod.arguments.front()->get();
for (const ArgumentInterface &iarg : p_imethod.arguments) {
- const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+ const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found
ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
@@ -2128,6 +2151,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;
}
+ // Collect caller name for MethodBind
+ if (p_imethod.is_vararg) {
+ icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";
+ }
+
// Generate method
{
if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
@@ -2270,7 +2298,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
// Retrieve information from the arguments
const ArgumentInterface &first = p_isignal.arguments.front()->get();
for (const ArgumentInterface &iarg : p_isignal.arguments) {
- const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+ const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found
ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
@@ -2501,6 +2529,11 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
i++;
}
+ // Collect caller name for MethodBind
+ if (p_icall.is_vararg) {
+ c_func_sig << ", godot_string_name caller";
+ }
+
String icall_method = p_icall.name;
// Generate icall function
@@ -2566,7 +2599,12 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall,
r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call("
<< CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
<< ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
- << ", total_length, out _);\n";
+ << ", total_length, out godot_variant_call_error vcall_error);\n";
+
+ r_output << base_indent << "ExceptionUtils.DebugCheckCallError(caller"
+ << ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE)
+ << ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null")
+ << ", total_length, vcall_error);\n";
if (!ret_void) {
if (return_type->cname != name_cache.type_Variant) {
@@ -2687,6 +2725,20 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con
return nullptr;
}
+const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_singleton_or_null(const TypeReference &p_typeref) {
+ const TypeInterface *itype = _get_type_or_null(p_typeref);
+ if (itype == nullptr) {
+ return nullptr;
+ }
+
+ if (itype->is_singleton) {
+ StringName instance_type_name = itype->name + CS_SINGLETON_INSTANCE_SUFFIX;
+ itype = &obj_types.find(instance_type_name)->value;
+ }
+
+ return itype;
+}
+
const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) {
if (p_generic_type_parameters.is_empty()) {
return "";
@@ -2700,8 +2752,8 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface
int i = 0;
String params = "<";
for (const TypeReference &param_type : p_generic_type_parameters) {
- const TypeInterface *param_itype = _get_type_or_null(param_type);
- ERR_FAIL_NULL_V(param_itype, "");
+ const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type);
+ ERR_FAIL_NULL_V(param_itype, ""); // Parameter type not found
ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "",
"Generic type parameter is a singleton: '" + param_itype->name + "'.");
@@ -2917,17 +2969,18 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted);
itype.memory_own = itype.is_ref_counted;
+ if (itype.is_singleton && compat_singletons.has(itype.cname)) {
+ itype.is_singleton = false;
+ itype.is_compat_singleton = true;
+ }
+
itype.c_out = "%5return ";
itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED;
itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n";
itype.cs_type = itype.proxy_name;
- if (itype.is_singleton) {
- itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(" CS_PROPERTY_SINGLETON ")";
- } else {
- itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";
- }
+ itype.cs_in_expr = "GodotObject." CS_STATIC_METHOD_GETINSTANCE "(%0)";
itype.cs_out = "%5return (%2)%0(%1);";
@@ -3331,6 +3384,19 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
obj_types.insert(itype.cname, itype);
+ if (itype.is_singleton) {
+ // Add singleton instance type.
+ itype.proxy_name += CS_SINGLETON_INSTANCE_SUFFIX;
+ itype.is_singleton = false;
+ itype.is_singleton_instance = true;
+
+ // Remove constants and enums, those will remain in the static class.
+ itype.constants.clear();
+ itype.enums.clear();
+
+ obj_types.insert(itype.name + CS_SINGLETON_INSTANCE_SUFFIX, itype);
+ }
+
class_list.pop_front();
}
@@ -3806,7 +3872,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
builtin_types.insert(itype.cname, itype);
// Array_@generic
- // Re-use Array's itype
+ // Reuse Array's itype
itype.name = "Array_@generic";
itype.cname = itype.name;
itype.cs_out = "%5return new %2(%0(%1));";
@@ -3833,7 +3899,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
builtin_types.insert(itype.cname, itype);
// Dictionary_@generic
- // Re-use Dictionary's itype
+ // Reuse Dictionary's itype
itype.name = "Dictionary_@generic";
itype.cname = itype.name;
itype.cs_out = "%5return new %2(%0(%1));";
@@ -3947,6 +4013,10 @@ void BindingsGenerator::_initialize_blacklisted_methods() {
blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it)
}
+void BindingsGenerator::_initialize_compat_singletons() {
+ // No compat singletons yet.
+}
+
void BindingsGenerator::_log(const char *p_format, ...) {
if (log_print_enabled) {
va_list list;
@@ -3966,6 +4036,8 @@ void BindingsGenerator::_initialize() {
_initialize_blacklisted_methods();
+ _initialize_compat_singletons();
+
bool obj_type_ok = _populate_object_type_interfaces();
ERR_FAIL_COND_MSG(!obj_type_ok, "Failed to generate object type interfaces");
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 38347a5181..d4c7a59e74 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -227,9 +227,18 @@ class BindingsGenerator {
bool is_enum = false;
bool is_object_type = false;
bool is_singleton = false;
+ bool is_singleton_instance = false;
bool is_ref_counted = false;
/**
+ * Class is a singleton, but can't be declared as a static class as that would
+ * break backwards compatibility. As such, instead of going with a static class,
+ * we use the actual singleton pattern (private constructor with instance property),
+ * which doesn't break compatibility.
+ */
+ bool is_compat_singleton = false;
+
+ /**
* Determines whether the native return value of this type must be disposed
* by the generated internal call (think of `godot_string`, whose destructor
* must be called). Some structs that are disposable may still disable this
@@ -614,8 +623,10 @@ class BindingsGenerator {
HashMap<const MethodInterface *, const InternalCall *> method_icalls_map;
HashMap<StringName, List<StringName>> blacklisted_methods;
+ HashSet<StringName> compat_singletons;
void _initialize_blacklisted_methods();
+ void _initialize_compat_singletons();
struct NameCache {
StringName type_void = StaticCString::create("void");
@@ -756,6 +767,7 @@ class BindingsGenerator {
Error _populate_method_icalls_table(const TypeInterface &p_itype);
const TypeInterface *_get_type_or_null(const TypeReference &p_typeref);
+ const TypeInterface *_get_type_or_singleton_or_null(const TypeReference &p_typeref);
const String _get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters);
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index ba6b91b704..fc99f3ceda 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -168,6 +168,10 @@ void godot_icall_Internal_EditorRunStop() {
EditorRunBar::get_singleton()->stop_playing();
}
+void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(Control *p_control) {
+ EditorRunBar::get_singleton()->get_buttons_container()->add_child(p_control);
+}
+
void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
if (ed) {
@@ -199,12 +203,25 @@ void godot_icall_Globals_EditorDef(const godot_string *p_setting, const godot_va
memnew_placement(r_result, Variant(result));
}
-void godot_icall_Globals_EditorShortcut(const godot_string *p_setting, godot_variant *r_result) {
+void godot_icall_Globals_EditorDefShortcut(const godot_string *p_setting, const godot_string *p_name, Key p_keycode, bool p_physical, godot_variant *r_result) {
+ String setting = *reinterpret_cast<const String *>(p_setting);
+ String name = *reinterpret_cast<const String *>(p_name);
+ Ref<Shortcut> result = ED_SHORTCUT(setting, name, p_keycode, p_physical);
+ memnew_placement(r_result, Variant(result));
+}
+
+void godot_icall_Globals_EditorGetShortcut(const godot_string *p_setting, Ref<Shortcut> *r_result) {
String setting = *reinterpret_cast<const String *>(p_setting);
Ref<Shortcut> result = ED_GET_SHORTCUT(setting);
memnew_placement(r_result, Variant(result));
}
+void godot_icall_Globals_EditorShortcutOverride(const godot_string *p_setting, const godot_string *p_feature, Key p_keycode, bool p_physical) {
+ String setting = *reinterpret_cast<const String *>(p_setting);
+ String feature = *reinterpret_cast<const String *>(p_feature);
+ ED_SHORTCUT_OVERRIDE(setting, feature, p_keycode, p_physical);
+}
+
void godot_icall_Globals_TTR(const godot_string *p_text, godot_string *r_dest) {
String text = *reinterpret_cast<const String *>(p_text);
memnew_placement(r_dest, String(TTR(text)));
@@ -251,12 +268,15 @@ static const void *unmanaged_callbacks[]{
(void *)godot_icall_Internal_EditorNodeShowScriptScreen,
(void *)godot_icall_Internal_EditorRunPlay,
(void *)godot_icall_Internal_EditorRunStop,
+ (void *)godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar,
(void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts,
(void *)godot_icall_Internal_CodeCompletionRequest,
(void *)godot_icall_Globals_EditorScale,
(void *)godot_icall_Globals_GlobalDef,
(void *)godot_icall_Globals_EditorDef,
- (void *)godot_icall_Globals_EditorShortcut,
+ (void *)godot_icall_Globals_EditorDefShortcut,
+ (void *)godot_icall_Globals_EditorGetShortcut,
+ (void *)godot_icall_Globals_EditorShortcutOverride,
(void *)godot_icall_Globals_TTR,
(void *)godot_icall_Utils_OS_GetPlatformName,
(void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index dfae85b667..0fe4bcdfce 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -276,8 +276,13 @@ namespace Godot.Bridge
if (wrapperType != null && IsStatic(wrapperType))
{
- // A static class means this is a Godot singleton class. If an instance is needed we use GodotObject.
- return typeof(GodotObject);
+ // A static class means this is a Godot singleton class. Try to get the Instance proxy type.
+ wrapperType = TypeGetProxyClass($"{nativeTypeNameStr}Instance");
+ if (wrapperType == null)
+ {
+ // Otherwise, fallback to GodotObject.
+ return typeof(GodotObject);
+ }
}
return wrapperType;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
index 219a9a8c15..1239533a01 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
@@ -109,7 +109,8 @@ namespace Godot
}
godot_variant ret = NativeFuncs.godotsharp_callable_call(callable,
- (godot_variant**)argsPtr, argc, out _);
+ (godot_variant**)argsPtr, argc, out godot_variant_call_error vcall_error);
+ ExceptionUtils.DebugCheckCallError(callable, (godot_variant**)argsPtr, argc, vcall_error);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
index 5bce66ea87..44b1c2554c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
@@ -10,301 +10,301 @@ namespace Godot
{
// Color names and values are derived from core/math/color_names.inc
internal static readonly Dictionary<string, Color> namedColors = new Dictionary<string, Color> {
- { "ALICEBLUE", new Color(0xF0F8FFFF) },
- { "ANTIQUEWHITE", new Color(0xFAEBD7FF) },
- { "AQUA", new Color(0x00FFFFFF) },
- { "AQUAMARINE", new Color(0x7FFFD4FF) },
- { "AZURE", new Color(0xF0FFFFFF) },
- { "BEIGE", new Color(0xF5F5DCFF) },
- { "BISQUE", new Color(0xFFE4C4FF) },
- { "BLACK", new Color(0x000000FF) },
- { "BLANCHEDALMOND", new Color(0xFFEBCDFF) },
- { "BLUE", new Color(0x0000FFFF) },
- { "BLUEVIOLET", new Color(0x8A2BE2FF) },
- { "BROWN", new Color(0xA52A2AFF) },
- { "BURLYWOOD", new Color(0xDEB887FF) },
- { "CADETBLUE", new Color(0x5F9EA0FF) },
- { "CHARTREUSE", new Color(0x7FFF00FF) },
- { "CHOCOLATE", new Color(0xD2691EFF) },
- { "CORAL", new Color(0xFF7F50FF) },
- { "CORNFLOWERBLUE", new Color(0x6495EDFF) },
- { "CORNSILK", new Color(0xFFF8DCFF) },
- { "CRIMSON", new Color(0xDC143CFF) },
- { "CYAN", new Color(0x00FFFFFF) },
- { "DARKBLUE", new Color(0x00008BFF) },
- { "DARKCYAN", new Color(0x008B8BFF) },
- { "DARKGOLDENROD", new Color(0xB8860BFF) },
- { "DARKGRAY", new Color(0xA9A9A9FF) },
- { "DARKGREEN", new Color(0x006400FF) },
- { "DARKKHAKI", new Color(0xBDB76BFF) },
- { "DARKMAGENTA", new Color(0x8B008BFF) },
- { "DARKOLIVEGREEN", new Color(0x556B2FFF) },
- { "DARKORANGE", new Color(0xFF8C00FF) },
- { "DARKORCHID", new Color(0x9932CCFF) },
- { "DARKRED", new Color(0x8B0000FF) },
- { "DARKSALMON", new Color(0xE9967AFF) },
- { "DARKSEAGREEN", new Color(0x8FBC8FFF) },
- { "DARKSLATEBLUE", new Color(0x483D8BFF) },
- { "DARKSLATEGRAY", new Color(0x2F4F4FFF) },
- { "DARKTURQUOISE", new Color(0x00CED1FF) },
- { "DARKVIOLET", new Color(0x9400D3FF) },
- { "DEEPPINK", new Color(0xFF1493FF) },
- { "DEEPSKYBLUE", new Color(0x00BFFFFF) },
- { "DIMGRAY", new Color(0x696969FF) },
- { "DODGERBLUE", new Color(0x1E90FFFF) },
- { "FIREBRICK", new Color(0xB22222FF) },
- { "FLORALWHITE", new Color(0xFFFAF0FF) },
- { "FORESTGREEN", new Color(0x228B22FF) },
- { "FUCHSIA", new Color(0xFF00FFFF) },
- { "GAINSBORO", new Color(0xDCDCDCFF) },
- { "GHOSTWHITE", new Color(0xF8F8FFFF) },
- { "GOLD", new Color(0xFFD700FF) },
- { "GOLDENROD", new Color(0xDAA520FF) },
- { "GRAY", new Color(0xBEBEBEFF) },
- { "GREEN", new Color(0x00FF00FF) },
- { "GREENYELLOW", new Color(0xADFF2FFF) },
- { "HONEYDEW", new Color(0xF0FFF0FF) },
- { "HOTPINK", new Color(0xFF69B4FF) },
- { "INDIANRED", new Color(0xCD5C5CFF) },
- { "INDIGO", new Color(0x4B0082FF) },
- { "IVORY", new Color(0xFFFFF0FF) },
- { "KHAKI", new Color(0xF0E68CFF) },
- { "LAVENDER", new Color(0xE6E6FAFF) },
- { "LAVENDERBLUSH", new Color(0xFFF0F5FF) },
- { "LAWNGREEN", new Color(0x7CFC00FF) },
- { "LEMONCHIFFON", new Color(0xFFFACDFF) },
- { "LIGHTBLUE", new Color(0xADD8E6FF) },
- { "LIGHTCORAL", new Color(0xF08080FF) },
- { "LIGHTCYAN", new Color(0xE0FFFFFF) },
- { "LIGHTGOLDENROD", new Color(0xFAFAD2FF) },
- { "LIGHTGRAY", new Color(0xD3D3D3FF) },
- { "LIGHTGREEN", new Color(0x90EE90FF) },
- { "LIGHTPINK", new Color(0xFFB6C1FF) },
- { "LIGHTSALMON", new Color(0xFFA07AFF) },
- { "LIGHTSEAGREEN", new Color(0x20B2AAFF) },
- { "LIGHTSKYBLUE", new Color(0x87CEFAFF) },
- { "LIGHTSLATEGRAY", new Color(0x778899FF) },
- { "LIGHTSTEELBLUE", new Color(0xB0C4DEFF) },
- { "LIGHTYELLOW", new Color(0xFFFFE0FF) },
- { "LIME", new Color(0x00FF00FF) },
- { "LIMEGREEN", new Color(0x32CD32FF) },
- { "LINEN", new Color(0xFAF0E6FF) },
- { "MAGENTA", new Color(0xFF00FFFF) },
- { "MAROON", new Color(0xB03060FF) },
- { "MEDIUMAQUAMARINE", new Color(0x66CDAAFF) },
- { "MEDIUMBLUE", new Color(0x0000CDFF) },
- { "MEDIUMORCHID", new Color(0xBA55D3FF) },
- { "MEDIUMPURPLE", new Color(0x9370DBFF) },
- { "MEDIUMSEAGREEN", new Color(0x3CB371FF) },
- { "MEDIUMSLATEBLUE", new Color(0x7B68EEFF) },
- { "MEDIUMSPRINGGREEN", new Color(0x00FA9AFF) },
- { "MEDIUMTURQUOISE", new Color(0x48D1CCFF) },
- { "MEDIUMVIOLETRED", new Color(0xC71585FF) },
- { "MIDNIGHTBLUE", new Color(0x191970FF) },
- { "MINTCREAM", new Color(0xF5FFFAFF) },
- { "MISTYROSE", new Color(0xFFE4E1FF) },
- { "MOCCASIN", new Color(0xFFE4B5FF) },
- { "NAVAJOWHITE", new Color(0xFFDEADFF) },
- { "NAVYBLUE", new Color(0x000080FF) },
- { "OLDLACE", new Color(0xFDF5E6FF) },
- { "OLIVE", new Color(0x808000FF) },
- { "OLIVEDRAB", new Color(0x6B8E23FF) },
- { "ORANGE", new Color(0xFFA500FF) },
- { "ORANGERED", new Color(0xFF4500FF) },
- { "ORCHID", new Color(0xDA70D6FF) },
- { "PALEGOLDENROD", new Color(0xEEE8AAFF) },
- { "PALEGREEN", new Color(0x98FB98FF) },
- { "PALETURQUOISE", new Color(0xAFEEEEFF) },
- { "PALEVIOLETRED", new Color(0xDB7093FF) },
- { "PAPAYAWHIP", new Color(0xFFEFD5FF) },
- { "PEACHPUFF", new Color(0xFFDAB9FF) },
- { "PERU", new Color(0xCD853FFF) },
- { "PINK", new Color(0xFFC0CBFF) },
- { "PLUM", new Color(0xDDA0DDFF) },
- { "POWDERBLUE", new Color(0xB0E0E6FF) },
- { "PURPLE", new Color(0xA020F0FF) },
- { "REBECCAPURPLE", new Color(0x663399FF) },
- { "RED", new Color(0xFF0000FF) },
- { "ROSYBROWN", new Color(0xBC8F8FFF) },
- { "ROYALBLUE", new Color(0x4169E1FF) },
- { "SADDLEBROWN", new Color(0x8B4513FF) },
- { "SALMON", new Color(0xFA8072FF) },
- { "SANDYBROWN", new Color(0xF4A460FF) },
- { "SEAGREEN", new Color(0x2E8B57FF) },
- { "SEASHELL", new Color(0xFFF5EEFF) },
- { "SIENNA", new Color(0xA0522DFF) },
- { "SILVER", new Color(0xC0C0C0FF) },
- { "SKYBLUE", new Color(0x87CEEBFF) },
- { "SLATEBLUE", new Color(0x6A5ACDFF) },
- { "SLATEGRAY", new Color(0x708090FF) },
- { "SNOW", new Color(0xFFFAFAFF) },
- { "SPRINGGREEN", new Color(0x00FF7FFF) },
- { "STEELBLUE", new Color(0x4682B4FF) },
- { "TAN", new Color(0xD2B48CFF) },
- { "TEAL", new Color(0x008080FF) },
- { "THISTLE", new Color(0xD8BFD8FF) },
- { "TOMATO", new Color(0xFF6347FF) },
- { "TRANSPARENT", new Color(0xFFFFFF00) },
- { "TURQUOISE", new Color(0x40E0D0FF) },
- { "VIOLET", new Color(0xEE82EEFF) },
- { "WEBGRAY", new Color(0x808080FF) },
- { "WEBGREEN", new Color(0x008000FF) },
- { "WEBMAROON", new Color(0x800000FF) },
- { "WEBPURPLE", new Color(0x800080FF) },
- { "WHEAT", new Color(0xF5DEB3FF) },
- { "WHITE", new Color(0xFFFFFFFF) },
- { "WHITESMOKE", new Color(0xF5F5F5FF) },
- { "YELLOW", new Color(0xFFFF00FF) },
- { "YELLOWGREEN", new Color(0x9ACD32FF) },
+ { "ALICEBLUE", Colors.AliceBlue },
+ { "ANTIQUEWHITE", Colors.AntiqueWhite },
+ { "AQUA", Colors.Aqua },
+ { "AQUAMARINE", Colors.Aquamarine },
+ { "AZURE", Colors.Azure },
+ { "BEIGE", Colors.Beige },
+ { "BISQUE", Colors.Bisque },
+ { "BLACK", Colors.Black },
+ { "BLANCHEDALMOND", Colors.BlanchedAlmond },
+ { "BLUE", Colors.Blue },
+ { "BLUEVIOLET", Colors.BlueViolet },
+ { "BROWN", Colors.Brown },
+ { "BURLYWOOD", Colors.Burlywood },
+ { "CADETBLUE", Colors.CadetBlue },
+ { "CHARTREUSE", Colors.Chartreuse },
+ { "CHOCOLATE", Colors.Chocolate },
+ { "CORAL", Colors.Coral },
+ { "CORNFLOWERBLUE", Colors.CornflowerBlue },
+ { "CORNSILK", Colors.Cornsilk },
+ { "CRIMSON", Colors.Crimson },
+ { "CYAN", Colors.Cyan },
+ { "DARKBLUE", Colors.DarkBlue },
+ { "DARKCYAN", Colors.DarkCyan },
+ { "DARKGOLDENROD", Colors.DarkGoldenrod },
+ { "DARKGRAY", Colors.DarkGray },
+ { "DARKGREEN", Colors.DarkGreen },
+ { "DARKKHAKI", Colors.DarkKhaki },
+ { "DARKMAGENTA", Colors.DarkMagenta },
+ { "DARKOLIVEGREEN", Colors.DarkOliveGreen },
+ { "DARKORANGE", Colors.DarkOrange },
+ { "DARKORCHID", Colors.DarkOrchid },
+ { "DARKRED", Colors.DarkRed },
+ { "DARKSALMON", Colors.DarkSalmon },
+ { "DARKSEAGREEN", Colors.DarkSeaGreen },
+ { "DARKSLATEBLUE", Colors.DarkSlateBlue },
+ { "DARKSLATEGRAY", Colors.DarkSlateGray },
+ { "DARKTURQUOISE", Colors.DarkTurquoise },
+ { "DARKVIOLET", Colors.DarkViolet },
+ { "DEEPPINK", Colors.DeepPink },
+ { "DEEPSKYBLUE", Colors.DeepSkyBlue },
+ { "DIMGRAY", Colors.DimGray },
+ { "DODGERBLUE", Colors.DodgerBlue },
+ { "FIREBRICK", Colors.Firebrick },
+ { "FLORALWHITE", Colors.FloralWhite },
+ { "FORESTGREEN", Colors.ForestGreen },
+ { "FUCHSIA", Colors.Fuchsia },
+ { "GAINSBORO", Colors.Gainsboro },
+ { "GHOSTWHITE", Colors.GhostWhite },
+ { "GOLD", Colors.Gold },
+ { "GOLDENROD", Colors.Goldenrod },
+ { "GRAY", Colors.Gray },
+ { "GREEN", Colors.Green },
+ { "GREENYELLOW", Colors.GreenYellow },
+ { "HONEYDEW", Colors.Honeydew },
+ { "HOTPINK", Colors.HotPink },
+ { "INDIANRED", Colors.IndianRed },
+ { "INDIGO", Colors.Indigo },
+ { "IVORY", Colors.Ivory },
+ { "KHAKI", Colors.Khaki },
+ { "LAVENDER", Colors.Lavender },
+ { "LAVENDERBLUSH", Colors.LavenderBlush },
+ { "LAWNGREEN", Colors.LawnGreen },
+ { "LEMONCHIFFON", Colors.LemonChiffon },
+ { "LIGHTBLUE", Colors.LightBlue },
+ { "LIGHTCORAL", Colors.LightCoral },
+ { "LIGHTCYAN", Colors.LightCyan },
+ { "LIGHTGOLDENROD", Colors.LightGoldenrod },
+ { "LIGHTGRAY", Colors.LightGray },
+ { "LIGHTGREEN", Colors.LightGreen },
+ { "LIGHTPINK", Colors.LightPink },
+ { "LIGHTSALMON", Colors.LightSalmon },
+ { "LIGHTSEAGREEN", Colors.LightSeaGreen },
+ { "LIGHTSKYBLUE", Colors.LightSkyBlue },
+ { "LIGHTSLATEGRAY", Colors.LightSlateGray },
+ { "LIGHTSTEELBLUE", Colors.LightSteelBlue },
+ { "LIGHTYELLOW", Colors.LightYellow },
+ { "LIME", Colors.Lime },
+ { "LIMEGREEN", Colors.LimeGreen },
+ { "LINEN", Colors.Linen },
+ { "MAGENTA", Colors.Magenta },
+ { "MAROON", Colors.Maroon },
+ { "MEDIUMAQUAMARINE", Colors.MediumAquamarine },
+ { "MEDIUMBLUE", Colors.MediumBlue },
+ { "MEDIUMORCHID", Colors.MediumOrchid },
+ { "MEDIUMPURPLE", Colors.MediumPurple },
+ { "MEDIUMSEAGREEN", Colors.MediumSeaGreen },
+ { "MEDIUMSLATEBLUE", Colors.MediumSlateBlue },
+ { "MEDIUMSPRINGGREEN", Colors.MediumSpringGreen },
+ { "MEDIUMTURQUOISE", Colors.MediumTurquoise },
+ { "MEDIUMVIOLETRED", Colors.MediumVioletRed },
+ { "MIDNIGHTBLUE", Colors.MidnightBlue },
+ { "MINTCREAM", Colors.MintCream },
+ { "MISTYROSE", Colors.MistyRose },
+ { "MOCCASIN", Colors.Moccasin },
+ { "NAVAJOWHITE", Colors.NavajoWhite },
+ { "NAVYBLUE", Colors.NavyBlue },
+ { "OLDLACE", Colors.OldLace },
+ { "OLIVE", Colors.Olive },
+ { "OLIVEDRAB", Colors.OliveDrab },
+ { "ORANGE", Colors.Orange },
+ { "ORANGERED", Colors.OrangeRed },
+ { "ORCHID", Colors.Orchid },
+ { "PALEGOLDENROD", Colors.PaleGoldenrod },
+ { "PALEGREEN", Colors.PaleGreen },
+ { "PALETURQUOISE", Colors.PaleTurquoise },
+ { "PALEVIOLETRED", Colors.PaleVioletRed },
+ { "PAPAYAWHIP", Colors.PapayaWhip },
+ { "PEACHPUFF", Colors.PeachPuff },
+ { "PERU", Colors.Peru },
+ { "PINK", Colors.Pink },
+ { "PLUM", Colors.Plum },
+ { "POWDERBLUE", Colors.PowderBlue },
+ { "PURPLE", Colors.Purple },
+ { "REBECCAPURPLE", Colors.RebeccaPurple },
+ { "RED", Colors.Red },
+ { "ROSYBROWN", Colors.RosyBrown },
+ { "ROYALBLUE", Colors.RoyalBlue },
+ { "SADDLEBROWN", Colors.SaddleBrown },
+ { "SALMON", Colors.Salmon },
+ { "SANDYBROWN", Colors.SandyBrown },
+ { "SEAGREEN", Colors.SeaGreen },
+ { "SEASHELL", Colors.Seashell },
+ { "SIENNA", Colors.Sienna },
+ { "SILVER", Colors.Silver },
+ { "SKYBLUE", Colors.SkyBlue },
+ { "SLATEBLUE", Colors.SlateBlue },
+ { "SLATEGRAY", Colors.SlateGray },
+ { "SNOW", Colors.Snow },
+ { "SPRINGGREEN", Colors.SpringGreen },
+ { "STEELBLUE", Colors.SteelBlue },
+ { "TAN", Colors.Tan },
+ { "TEAL", Colors.Teal },
+ { "THISTLE", Colors.Thistle },
+ { "TOMATO", Colors.Tomato },
+ { "TRANSPARENT", Colors.Transparent },
+ { "TURQUOISE", Colors.Turquoise },
+ { "VIOLET", Colors.Violet },
+ { "WEBGRAY", Colors.WebGray },
+ { "WEBGREEN", Colors.WebGreen },
+ { "WEBMAROON", Colors.WebMaroon },
+ { "WEBPURPLE", Colors.WebPurple },
+ { "WHEAT", Colors.Wheat },
+ { "WHITE", Colors.White },
+ { "WHITESMOKE", Colors.WhiteSmoke },
+ { "YELLOW", Colors.Yellow },
+ { "YELLOWGREEN", Colors.YellowGreen },
};
#pragma warning disable CS1591 // Disable warning: "Missing XML comment for publicly visible type or member"
- public static Color AliceBlue { get { return namedColors["ALICEBLUE"]; } }
- public static Color AntiqueWhite { get { return namedColors["ANTIQUEWHITE"]; } }
- public static Color Aqua { get { return namedColors["AQUA"]; } }
- public static Color Aquamarine { get { return namedColors["AQUAMARINE"]; } }
- public static Color Azure { get { return namedColors["AZURE"]; } }
- public static Color Beige { get { return namedColors["BEIGE"]; } }
- public static Color Bisque { get { return namedColors["BISQUE"]; } }
- public static Color Black { get { return namedColors["BLACK"]; } }
- public static Color BlanchedAlmond { get { return namedColors["BLANCHEDALMOND"]; } }
- public static Color Blue { get { return namedColors["BLUE"]; } }
- public static Color BlueViolet { get { return namedColors["BLUEVIOLET"]; } }
- public static Color Brown { get { return namedColors["BROWN"]; } }
- public static Color Burlywood { get { return namedColors["BURLYWOOD"]; } }
- public static Color CadetBlue { get { return namedColors["CADETBLUE"]; } }
- public static Color Chartreuse { get { return namedColors["CHARTREUSE"]; } }
- public static Color Chocolate { get { return namedColors["CHOCOLATE"]; } }
- public static Color Coral { get { return namedColors["CORAL"]; } }
- public static Color CornflowerBlue { get { return namedColors["CORNFLOWERBLUE"]; } }
- public static Color Cornsilk { get { return namedColors["CORNSILK"]; } }
- public static Color Crimson { get { return namedColors["CRIMSON"]; } }
- public static Color Cyan { get { return namedColors["CYAN"]; } }
- public static Color DarkBlue { get { return namedColors["DARKBLUE"]; } }
- public static Color DarkCyan { get { return namedColors["DARKCYAN"]; } }
- public static Color DarkGoldenrod { get { return namedColors["DARKGOLDENROD"]; } }
- public static Color DarkGray { get { return namedColors["DARKGRAY"]; } }
- public static Color DarkGreen { get { return namedColors["DARKGREEN"]; } }
- public static Color DarkKhaki { get { return namedColors["DARKKHAKI"]; } }
- public static Color DarkMagenta { get { return namedColors["DARKMAGENTA"]; } }
- public static Color DarkOliveGreen { get { return namedColors["DARKOLIVEGREEN"]; } }
- public static Color DarkOrange { get { return namedColors["DARKORANGE"]; } }
- public static Color DarkOrchid { get { return namedColors["DARKORCHID"]; } }
- public static Color DarkRed { get { return namedColors["DARKRED"]; } }
- public static Color DarkSalmon { get { return namedColors["DARKSALMON"]; } }
- public static Color DarkSeaGreen { get { return namedColors["DARKSEAGREEN"]; } }
- public static Color DarkSlateBlue { get { return namedColors["DARKSLATEBLUE"]; } }
- public static Color DarkSlateGray { get { return namedColors["DARKSLATEGRAY"]; } }
- public static Color DarkTurquoise { get { return namedColors["DARKTURQUOISE"]; } }
- public static Color DarkViolet { get { return namedColors["DARKVIOLET"]; } }
- public static Color DeepPink { get { return namedColors["DEEPPINK"]; } }
- public static Color DeepSkyBlue { get { return namedColors["DEEPSKYBLUE"]; } }
- public static Color DimGray { get { return namedColors["DIMGRAY"]; } }
- public static Color DodgerBlue { get { return namedColors["DODGERBLUE"]; } }
- public static Color Firebrick { get { return namedColors["FIREBRICK"]; } }
- public static Color FloralWhite { get { return namedColors["FLORALWHITE"]; } }
- public static Color ForestGreen { get { return namedColors["FORESTGREEN"]; } }
- public static Color Fuchsia { get { return namedColors["FUCHSIA"]; } }
- public static Color Gainsboro { get { return namedColors["GAINSBORO"]; } }
- public static Color GhostWhite { get { return namedColors["GHOSTWHITE"]; } }
- public static Color Gold { get { return namedColors["GOLD"]; } }
- public static Color Goldenrod { get { return namedColors["GOLDENROD"]; } }
- public static Color Gray { get { return namedColors["GRAY"]; } }
- public static Color Green { get { return namedColors["GREEN"]; } }
- public static Color GreenYellow { get { return namedColors["GREENYELLOW"]; } }
- public static Color Honeydew { get { return namedColors["HONEYDEW"]; } }
- public static Color HotPink { get { return namedColors["HOTPINK"]; } }
- public static Color IndianRed { get { return namedColors["INDIANRED"]; } }
- public static Color Indigo { get { return namedColors["INDIGO"]; } }
- public static Color Ivory { get { return namedColors["IVORY"]; } }
- public static Color Khaki { get { return namedColors["KHAKI"]; } }
- public static Color Lavender { get { return namedColors["LAVENDER"]; } }
- public static Color LavenderBlush { get { return namedColors["LAVENDERBLUSH"]; } }
- public static Color LawnGreen { get { return namedColors["LAWNGREEN"]; } }
- public static Color LemonChiffon { get { return namedColors["LEMONCHIFFON"]; } }
- public static Color LightBlue { get { return namedColors["LIGHTBLUE"]; } }
- public static Color LightCoral { get { return namedColors["LIGHTCORAL"]; } }
- public static Color LightCyan { get { return namedColors["LIGHTCYAN"]; } }
- public static Color LightGoldenrod { get { return namedColors["LIGHTGOLDENROD"]; } }
- public static Color LightGray { get { return namedColors["LIGHTGRAY"]; } }
- public static Color LightGreen { get { return namedColors["LIGHTGREEN"]; } }
- public static Color LightPink { get { return namedColors["LIGHTPINK"]; } }
- public static Color LightSalmon { get { return namedColors["LIGHTSALMON"]; } }
- public static Color LightSeaGreen { get { return namedColors["LIGHTSEAGREEN"]; } }
- public static Color LightSkyBlue { get { return namedColors["LIGHTSKYBLUE"]; } }
- public static Color LightSlateGray { get { return namedColors["LIGHTSLATEGRAY"]; } }
- public static Color LightSteelBlue { get { return namedColors["LIGHTSTEELBLUE"]; } }
- public static Color LightYellow { get { return namedColors["LIGHTYELLOW"]; } }
- public static Color Lime { get { return namedColors["LIME"]; } }
- public static Color LimeGreen { get { return namedColors["LIMEGREEN"]; } }
- public static Color Linen { get { return namedColors["LINEN"]; } }
- public static Color Magenta { get { return namedColors["MAGENTA"]; } }
- public static Color Maroon { get { return namedColors["MAROON"]; } }
- public static Color MediumAquamarine { get { return namedColors["MEDIUMAQUAMARINE"]; } }
- public static Color MediumBlue { get { return namedColors["MEDIUMBLUE"]; } }
- public static Color MediumOrchid { get { return namedColors["MEDIUMORCHID"]; } }
- public static Color MediumPurple { get { return namedColors["MEDIUMPURPLE"]; } }
- public static Color MediumSeaGreen { get { return namedColors["MEDIUMSEAGREEN"]; } }
- public static Color MediumSlateBlue { get { return namedColors["MEDIUMSLATEBLUE"]; } }
- public static Color MediumSpringGreen { get { return namedColors["MEDIUMSPRINGGREEN"]; } }
- public static Color MediumTurquoise { get { return namedColors["MEDIUMTURQUOISE"]; } }
- public static Color MediumVioletRed { get { return namedColors["MEDIUMVIOLETRED"]; } }
- public static Color MidnightBlue { get { return namedColors["MIDNIGHTBLUE"]; } }
- public static Color MintCream { get { return namedColors["MINTCREAM"]; } }
- public static Color MistyRose { get { return namedColors["MISTYROSE"]; } }
- public static Color Moccasin { get { return namedColors["MOCCASIN"]; } }
- public static Color NavajoWhite { get { return namedColors["NAVAJOWHITE"]; } }
- public static Color NavyBlue { get { return namedColors["NAVYBLUE"]; } }
- public static Color OldLace { get { return namedColors["OLDLACE"]; } }
- public static Color Olive { get { return namedColors["OLIVE"]; } }
- public static Color OliveDrab { get { return namedColors["OLIVEDRAB"]; } }
- public static Color Orange { get { return namedColors["ORANGE"]; } }
- public static Color OrangeRed { get { return namedColors["ORANGERED"]; } }
- public static Color Orchid { get { return namedColors["ORCHID"]; } }
- public static Color PaleGoldenrod { get { return namedColors["PALEGOLDENROD"]; } }
- public static Color PaleGreen { get { return namedColors["PALEGREEN"]; } }
- public static Color PaleTurquoise { get { return namedColors["PALETURQUOISE"]; } }
- public static Color PaleVioletRed { get { return namedColors["PALEVIOLETRED"]; } }
- public static Color PapayaWhip { get { return namedColors["PAPAYAWHIP"]; } }
- public static Color PeachPuff { get { return namedColors["PEACHPUFF"]; } }
- public static Color Peru { get { return namedColors["PERU"]; } }
- public static Color Pink { get { return namedColors["PINK"]; } }
- public static Color Plum { get { return namedColors["PLUM"]; } }
- public static Color PowderBlue { get { return namedColors["POWDERBLUE"]; } }
- public static Color Purple { get { return namedColors["PURPLE"]; } }
- public static Color RebeccaPurple { get { return namedColors["REBECCAPURPLE"]; } }
- public static Color Red { get { return namedColors["RED"]; } }
- public static Color RosyBrown { get { return namedColors["ROSYBROWN"]; } }
- public static Color RoyalBlue { get { return namedColors["ROYALBLUE"]; } }
- public static Color SaddleBrown { get { return namedColors["SADDLEBROWN"]; } }
- public static Color Salmon { get { return namedColors["SALMON"]; } }
- public static Color SandyBrown { get { return namedColors["SANDYBROWN"]; } }
- public static Color SeaGreen { get { return namedColors["SEAGREEN"]; } }
- public static Color Seashell { get { return namedColors["SEASHELL"]; } }
- public static Color Sienna { get { return namedColors["SIENNA"]; } }
- public static Color Silver { get { return namedColors["SILVER"]; } }
- public static Color SkyBlue { get { return namedColors["SKYBLUE"]; } }
- public static Color SlateBlue { get { return namedColors["SLATEBLUE"]; } }
- public static Color SlateGray { get { return namedColors["SLATEGRAY"]; } }
- public static Color Snow { get { return namedColors["SNOW"]; } }
- public static Color SpringGreen { get { return namedColors["SPRINGGREEN"]; } }
- public static Color SteelBlue { get { return namedColors["STEELBLUE"]; } }
- public static Color Tan { get { return namedColors["TAN"]; } }
- public static Color Teal { get { return namedColors["TEAL"]; } }
- public static Color Thistle { get { return namedColors["THISTLE"]; } }
- public static Color Tomato { get { return namedColors["TOMATO"]; } }
- public static Color Transparent { get { return namedColors["TRANSPARENT"]; } }
- public static Color Turquoise { get { return namedColors["TURQUOISE"]; } }
- public static Color Violet { get { return namedColors["VIOLET"]; } }
- public static Color WebGray { get { return namedColors["WEBGRAY"]; } }
- public static Color WebGreen { get { return namedColors["WEBGREEN"]; } }
- public static Color WebMaroon { get { return namedColors["WEBMAROON"]; } }
- public static Color WebPurple { get { return namedColors["WEBPURPLE"]; } }
- public static Color Wheat { get { return namedColors["WHEAT"]; } }
- public static Color White { get { return namedColors["WHITE"]; } }
- public static Color WhiteSmoke { get { return namedColors["WHITESMOKE"]; } }
- public static Color Yellow { get { return namedColors["YELLOW"]; } }
- public static Color YellowGreen { get { return namedColors["YELLOWGREEN"]; } }
+ public static Color AliceBlue => new Color(0xF0F8FFFF);
+ public static Color AntiqueWhite => new Color(0xFAEBD7FF);
+ public static Color Aqua => new Color(0x00FFFFFF);
+ public static Color Aquamarine => new Color(0x7FFFD4FF);
+ public static Color Azure => new Color(0xF0FFFFFF);
+ public static Color Beige => new Color(0xF5F5DCFF);
+ public static Color Bisque => new Color(0xFFE4C4FF);
+ public static Color Black => new Color(0x000000FF);
+ public static Color BlanchedAlmond => new Color(0xFFEBCDFF);
+ public static Color Blue => new Color(0x0000FFFF);
+ public static Color BlueViolet => new Color(0x8A2BE2FF);
+ public static Color Brown => new Color(0xA52A2AFF);
+ public static Color Burlywood => new Color(0xDEB887FF);
+ public static Color CadetBlue => new Color(0x5F9EA0FF);
+ public static Color Chartreuse => new Color(0x7FFF00FF);
+ public static Color Chocolate => new Color(0xD2691EFF);
+ public static Color Coral => new Color(0xFF7F50FF);
+ public static Color CornflowerBlue => new Color(0x6495EDFF);
+ public static Color Cornsilk => new Color(0xFFF8DCFF);
+ public static Color Crimson => new Color(0xDC143CFF);
+ public static Color Cyan => new Color(0x00FFFFFF);
+ public static Color DarkBlue => new Color(0x00008BFF);
+ public static Color DarkCyan => new Color(0x008B8BFF);
+ public static Color DarkGoldenrod => new Color(0xB8860BFF);
+ public static Color DarkGray => new Color(0xA9A9A9FF);
+ public static Color DarkGreen => new Color(0x006400FF);
+ public static Color DarkKhaki => new Color(0xBDB76BFF);
+ public static Color DarkMagenta => new Color(0x8B008BFF);
+ public static Color DarkOliveGreen => new Color(0x556B2FFF);
+ public static Color DarkOrange => new Color(0xFF8C00FF);
+ public static Color DarkOrchid => new Color(0x9932CCFF);
+ public static Color DarkRed => new Color(0x8B0000FF);
+ public static Color DarkSalmon => new Color(0xE9967AFF);
+ public static Color DarkSeaGreen => new Color(0x8FBC8FFF);
+ public static Color DarkSlateBlue => new Color(0x483D8BFF);
+ public static Color DarkSlateGray => new Color(0x2F4F4FFF);
+ public static Color DarkTurquoise => new Color(0x00CED1FF);
+ public static Color DarkViolet => new Color(0x9400D3FF);
+ public static Color DeepPink => new Color(0xFF1493FF);
+ public static Color DeepSkyBlue => new Color(0x00BFFFFF);
+ public static Color DimGray => new Color(0x696969FF);
+ public static Color DodgerBlue => new Color(0x1E90FFFF);
+ public static Color Firebrick => new Color(0xB22222FF);
+ public static Color FloralWhite => new Color(0xFFFAF0FF);
+ public static Color ForestGreen => new Color(0x228B22FF);
+ public static Color Fuchsia => new Color(0xFF00FFFF);
+ public static Color Gainsboro => new Color(0xDCDCDCFF);
+ public static Color GhostWhite => new Color(0xF8F8FFFF);
+ public static Color Gold => new Color(0xFFD700FF);
+ public static Color Goldenrod => new Color(0xDAA520FF);
+ public static Color Gray => new Color(0xBEBEBEFF);
+ public static Color Green => new Color(0x00FF00FF);
+ public static Color GreenYellow => new Color(0xADFF2FFF);
+ public static Color Honeydew => new Color(0xF0FFF0FF);
+ public static Color HotPink => new Color(0xFF69B4FF);
+ public static Color IndianRed => new Color(0xCD5C5CFF);
+ public static Color Indigo => new Color(0x4B0082FF);
+ public static Color Ivory => new Color(0xFFFFF0FF);
+ public static Color Khaki => new Color(0xF0E68CFF);
+ public static Color Lavender => new Color(0xE6E6FAFF);
+ public static Color LavenderBlush => new Color(0xFFF0F5FF);
+ public static Color LawnGreen => new Color(0x7CFC00FF);
+ public static Color LemonChiffon => new Color(0xFFFACDFF);
+ public static Color LightBlue => new Color(0xADD8E6FF);
+ public static Color LightCoral => new Color(0xF08080FF);
+ public static Color LightCyan => new Color(0xE0FFFFFF);
+ public static Color LightGoldenrod => new Color(0xFAFAD2FF);
+ public static Color LightGray => new Color(0xD3D3D3FF);
+ public static Color LightGreen => new Color(0x90EE90FF);
+ public static Color LightPink => new Color(0xFFB6C1FF);
+ public static Color LightSalmon => new Color(0xFFA07AFF);
+ public static Color LightSeaGreen => new Color(0x20B2AAFF);
+ public static Color LightSkyBlue => new Color(0x87CEFAFF);
+ public static Color LightSlateGray => new Color(0x778899FF);
+ public static Color LightSteelBlue => new Color(0xB0C4DEFF);
+ public static Color LightYellow => new Color(0xFFFFE0FF);
+ public static Color Lime => new Color(0x00FF00FF);
+ public static Color LimeGreen => new Color(0x32CD32FF);
+ public static Color Linen => new Color(0xFAF0E6FF);
+ public static Color Magenta => new Color(0xFF00FFFF);
+ public static Color Maroon => new Color(0xB03060FF);
+ public static Color MediumAquamarine => new Color(0x66CDAAFF);
+ public static Color MediumBlue => new Color(0x0000CDFF);
+ public static Color MediumOrchid => new Color(0xBA55D3FF);
+ public static Color MediumPurple => new Color(0x9370DBFF);
+ public static Color MediumSeaGreen => new Color(0x3CB371FF);
+ public static Color MediumSlateBlue => new Color(0x7B68EEFF);
+ public static Color MediumSpringGreen => new Color(0x00FA9AFF);
+ public static Color MediumTurquoise => new Color(0x48D1CCFF);
+ public static Color MediumVioletRed => new Color(0xC71585FF);
+ public static Color MidnightBlue => new Color(0x191970FF);
+ public static Color MintCream => new Color(0xF5FFFAFF);
+ public static Color MistyRose => new Color(0xFFE4E1FF);
+ public static Color Moccasin => new Color(0xFFE4B5FF);
+ public static Color NavajoWhite => new Color(0xFFDEADFF);
+ public static Color NavyBlue => new Color(0x000080FF);
+ public static Color OldLace => new Color(0xFDF5E6FF);
+ public static Color Olive => new Color(0x808000FF);
+ public static Color OliveDrab => new Color(0x6B8E23FF);
+ public static Color Orange => new Color(0xFFA500FF);
+ public static Color OrangeRed => new Color(0xFF4500FF);
+ public static Color Orchid => new Color(0xDA70D6FF);
+ public static Color PaleGoldenrod => new Color(0xEEE8AAFF);
+ public static Color PaleGreen => new Color(0x98FB98FF);
+ public static Color PaleTurquoise => new Color(0xAFEEEEFF);
+ public static Color PaleVioletRed => new Color(0xDB7093FF);
+ public static Color PapayaWhip => new Color(0xFFEFD5FF);
+ public static Color PeachPuff => new Color(0xFFDAB9FF);
+ public static Color Peru => new Color(0xCD853FFF);
+ public static Color Pink => new Color(0xFFC0CBFF);
+ public static Color Plum => new Color(0xDDA0DDFF);
+ public static Color PowderBlue => new Color(0xB0E0E6FF);
+ public static Color Purple => new Color(0xA020F0FF);
+ public static Color RebeccaPurple => new Color(0x663399FF);
+ public static Color Red => new Color(0xFF0000FF);
+ public static Color RosyBrown => new Color(0xBC8F8FFF);
+ public static Color RoyalBlue => new Color(0x4169E1FF);
+ public static Color SaddleBrown => new Color(0x8B4513FF);
+ public static Color Salmon => new Color(0xFA8072FF);
+ public static Color SandyBrown => new Color(0xF4A460FF);
+ public static Color SeaGreen => new Color(0x2E8B57FF);
+ public static Color Seashell => new Color(0xFFF5EEFF);
+ public static Color Sienna => new Color(0xA0522DFF);
+ public static Color Silver => new Color(0xC0C0C0FF);
+ public static Color SkyBlue => new Color(0x87CEEBFF);
+ public static Color SlateBlue => new Color(0x6A5ACDFF);
+ public static Color SlateGray => new Color(0x708090FF);
+ public static Color Snow => new Color(0xFFFAFAFF);
+ public static Color SpringGreen => new Color(0x00FF7FFF);
+ public static Color SteelBlue => new Color(0x4682B4FF);
+ public static Color Tan => new Color(0xD2B48CFF);
+ public static Color Teal => new Color(0x008080FF);
+ public static Color Thistle => new Color(0xD8BFD8FF);
+ public static Color Tomato => new Color(0xFF6347FF);
+ public static Color Transparent => new Color(0xFFFFFF00);
+ public static Color Turquoise => new Color(0x40E0D0FF);
+ public static Color Violet => new Color(0xEE82EEFF);
+ public static Color WebGray => new Color(0x808080FF);
+ public static Color WebGreen => new Color(0x008000FF);
+ public static Color WebMaroon => new Color(0x800000FF);
+ public static Color WebPurple => new Color(0x800080FF);
+ public static Color Wheat => new Color(0xF5DEB3FF);
+ public static Color White => new Color(0xFFFFFFFF);
+ public static Color WhiteSmoke => new Color(0xF5F5F5FF);
+ public static Color Yellow => new Color(0xFFFF00FF);
+ public static Color YellowGreen => new Color(0x9ACD32FF);
#pragma warning restore CS1591
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs
index c4161d2ded..57b292793a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs
@@ -14,14 +14,46 @@ namespace Godot
{
private static void AppendTypeName(this StringBuilder sb, Type type)
{
- if (type.IsPrimitive)
- sb.Append(type.Name);
- else if (type == typeof(void))
+ // Use the C# type keyword for built-in types.
+ // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
+ if (type == typeof(void))
sb.Append("void");
+ else if (type == typeof(bool))
+ sb.Append("bool");
+ else if (type == typeof(byte))
+ sb.Append("byte");
+ else if (type == typeof(sbyte))
+ sb.Append("sbyte");
+ else if (type == typeof(char))
+ sb.Append("char");
+ else if (type == typeof(decimal))
+ sb.Append("decimal");
+ else if (type == typeof(double))
+ sb.Append("double");
+ else if (type == typeof(float))
+ sb.Append("float");
+ else if (type == typeof(int))
+ sb.Append("int");
+ else if (type == typeof(uint))
+ sb.Append("uint");
+ else if (type == typeof(nint))
+ sb.Append("nint");
+ else if (type == typeof(nuint))
+ sb.Append("nuint");
+ else if (type == typeof(long))
+ sb.Append("long");
+ else if (type == typeof(ulong))
+ sb.Append("ulong");
+ else if (type == typeof(short))
+ sb.Append("short");
+ else if (type == typeof(ushort))
+ sb.Append("ushort");
+ else if (type == typeof(object))
+ sb.Append("object");
+ else if (type == typeof(string))
+ sb.Append("string");
else
sb.Append(type);
-
- sb.Append(' ');
}
internal static void InstallTraceListener()
@@ -70,13 +102,26 @@ namespace Godot
}
}
+ internal static unsafe StackFrame? GetCurrentStackFrame(int skipFrames = 0)
+ {
+ // We skip 2 frames:
+ // The first skipped frame is the current method.
+ // The second skipped frame is a method in NativeInterop.NativeFuncs.
+ var stackTrace = new StackTrace(skipFrames: 2 + skipFrames, fNeedFileInfo: true);
+ return stackTrace.GetFrame(0);
+ }
+
[UnmanagedCallersOnly]
internal static unsafe void GetCurrentStackInfo(void* destVector)
{
try
{
var vector = (godot_stack_info_vector*)destVector;
- var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true);
+
+ // We skip 2 frames:
+ // The first skipped frame is the current method.
+ // The second skipped frame is a method in NativeInterop.NativeFuncs.
+ var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
int frameCount = stackTrace.FrameCount;
if (frameCount == 0)
@@ -87,6 +132,14 @@ namespace Godot
int i = 0;
foreach (StackFrame frame in stackTrace.GetFrames())
{
+ var method = frame.GetMethod();
+
+ if (method is MethodInfo methodInfo && methodInfo.IsDefined(typeof(StackTraceHiddenAttribute)))
+ {
+ // Skip methods marked hidden from the stack trace.
+ continue;
+ }
+
string? fileName = frame.GetFileName();
int fileLineNumber = frame.GetFileLineNumber();
@@ -102,6 +155,9 @@ namespace Godot
i++;
}
+
+ // Resize the vector again in case we skipped some frames.
+ vector->Resize(i);
}
catch (Exception e)
{
@@ -122,7 +178,10 @@ namespace Godot
var sb = new StringBuilder();
if (methodBase is MethodInfo methodInfo)
+ {
sb.AppendTypeName(methodInfo.ReturnType);
+ sb.Append(' ');
+ }
sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>");
sb.Append('.');
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
index 9425b7424c..33ebb8171e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Text;
using Godot.NativeInterop;
@@ -334,6 +335,21 @@ namespace Godot
NativeFuncs.godotsharp_printt(godotStr);
}
+ [StackTraceHidden]
+ private static void ErrPrintError(string message, godot_error_handler_type type = godot_error_handler_type.ERR_HANDLER_ERROR)
+ {
+ // Skip 1 frame to avoid current method.
+ var stackFrame = DebuggingUtils.GetCurrentStackFrame(skipFrames: 1);
+ string callerFilePath = ProjectSettings.LocalizePath(stackFrame.GetFileName());
+ DebuggingUtils.GetStackFrameMethodDecl(stackFrame, out string callerName);
+ int callerLineNumber = stackFrame.GetFileLineNumber();
+
+ using godot_string messageStr = Marshaling.ConvertStringToNative(message);
+ using godot_string callerNameStr = Marshaling.ConvertStringToNative(callerName);
+ using godot_string callerFilePathStr = Marshaling.ConvertStringToNative(callerFilePath);
+ NativeFuncs.godotsharp_err_print_error(callerNameStr, callerFilePathStr, callerLineNumber, messageStr, p_type: type);
+ }
+
/// <summary>
/// Pushes an error message to Godot's built-in debugger and to the OS terminal.
///
@@ -347,8 +363,7 @@ namespace Godot
/// <param name="message">Error message.</param>
public static void PushError(string message)
{
- using var godotStr = Marshaling.ConvertStringToNative(message);
- NativeFuncs.godotsharp_pusherror(godotStr);
+ ErrPrintError(message);
}
/// <summary>
@@ -364,7 +379,7 @@ namespace Godot
/// <param name="what">Arguments that form the error message.</param>
public static void PushError(params object[] what)
{
- PushError(AppendPrintParams(what));
+ ErrPrintError(AppendPrintParams(what));
}
/// <summary>
@@ -378,8 +393,7 @@ namespace Godot
/// <param name="message">Warning message.</param>
public static void PushWarning(string message)
{
- using var godotStr = Marshaling.ConvertStringToNative(message);
- NativeFuncs.godotsharp_pushwarning(godotStr);
+ ErrPrintError(message, type: godot_error_handler_type.ERR_HANDLER_WARNING);
}
/// <summary>
@@ -393,7 +407,7 @@ namespace Godot
/// <param name="what">Arguments that form the warning message.</param>
public static void PushWarning(params object[] what)
{
- PushWarning(AppendPrintParams(what));
+ ErrPrintError(AppendPrintParams(what), type: godot_error_handler_type.ERR_HANDLER_WARNING);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
index 2d8067d300..a656c5de90 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
@@ -95,7 +95,7 @@ namespace Godot.NativeInterop
}
NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line,
- nErrorMsg, nExcMsg, p_warning: godot_bool.False, stackInfoVector);
+ nErrorMsg, nExcMsg, godot_error_handler_type.ERR_HANDLER_ERROR, stackInfoVector);
}
}
@@ -135,5 +135,109 @@ namespace Godot.NativeInterop
OnExceptionLoggerException(unexpected, e);
}
}
+
+ [Conditional("DEBUG")]
+ public unsafe static void DebugCheckCallError(godot_string_name method, IntPtr instance, godot_variant** args, int argCount, godot_variant_call_error error)
+ {
+ if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
+ {
+ using godot_variant instanceVariant = VariantUtils.CreateFromGodotObjectPtr(instance);
+ string where = GetCallErrorWhere(method, &instanceVariant, args, argCount);
+ string errorText = GetCallErrorMessage(error, where, args);
+ GD.PushError(errorText);
+ }
+ }
+
+ [Conditional("DEBUG")]
+ public unsafe static void DebugCheckCallError(in godot_callable callable, godot_variant** args, int argCount, godot_variant_call_error error)
+ {
+ if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
+ {
+ using godot_variant callableVariant = VariantUtils.CreateFromCallableTakingOwnershipOfDisposableValue(callable);
+ string where = $"callable '{VariantUtils.ConvertToString(callableVariant)}'";
+ string errorText = GetCallErrorMessage(error, where, args);
+ GD.PushError(errorText);
+ }
+ }
+
+ private unsafe static string GetCallErrorWhere(godot_string_name method, godot_variant* instance, godot_variant** args, int argCount)
+ {
+ string? methodstr = null;
+ string basestr = GetVariantTypeName(instance);
+
+ if (method == GodotObject.MethodName.Call || (basestr == "Godot.TreeItem" && method == TreeItem.MethodName.CallRecursive))
+ {
+ if (argCount >= 1)
+ {
+ methodstr = VariantUtils.ConvertToString(*args[0]);
+ }
+ }
+
+ if (string.IsNullOrEmpty(methodstr))
+ {
+ methodstr = StringName.CreateTakingOwnershipOfDisposableValue(method);
+ }
+
+ return $"function '{methodstr}' in base '{basestr}'";
+ }
+
+ private unsafe static string GetCallErrorMessage(godot_variant_call_error error, string where, godot_variant** args)
+ {
+ switch (error.Error)
+ {
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT:
+ {
+ int errorarg = error.Argument;
+ // Handle the Object to Object case separately as we don't have further class details.
+#if DEBUG
+ if (error.Expected == Variant.Type.Object && args[errorarg]->Type == error.Expected)
+ {
+ return $"Invalid type in {where}. The Object-derived class of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") is not a subclass of the expected argument class.";
+ }
+ else if (error.Expected == Variant.Type.Array && args[errorarg]->Type == error.Expected)
+ {
+ return $"Invalid type in {where}. The array of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") does not have the same element type as the expected typed array argument.";
+ }
+ else
+#endif
+ {
+ return $"Invalid type in {where}. Cannot convert argument {errorarg + 1} from {args[errorarg]->Type} to {error.Expected}.";
+ }
+ }
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS:
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS:
+ return $"Invalid call to {where}. Expected {error.Argument} arguments.";
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD:
+ return $"Invalid call. Nonexistent {where}.";
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL:
+ return $"Attempt to call {where} on a null instance.";
+ case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST:
+ return $"Attempt to call {where} on a const instance.";
+ default:
+ return $"Bug, call error: #{error.Error}";
+ }
+ }
+
+ private unsafe static string GetVariantTypeName(godot_variant* variant)
+ {
+ if (variant->Type == Variant.Type.Object)
+ {
+ GodotObject obj = VariantUtils.ConvertToGodotObject(*variant);
+ if (obj == null)
+ {
+ return "null instance";
+ }
+ else if (!GodotObject.IsInstanceValid(obj))
+ {
+ return "previously freed";
+ }
+ else
+ {
+ return obj.GetType().ToString();
+ }
+ }
+
+ return variant->Type.ToString();
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
index 43e7c7eb9a..d5d9404ed1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs
@@ -71,6 +71,7 @@ namespace Godot.NativeInterop
GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS,
GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS,
GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL,
+ GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST,
}
[StructLayout(LayoutKind.Sequential)]
@@ -1133,4 +1134,13 @@ namespace Godot.NativeInterop
get => _ptr != null ? *((int*)_ptr - 1) : 0;
}
}
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public enum godot_error_handler_type
+ {
+ ERR_HANDLER_ERROR = 0,
+ ERR_HANDLER_WARNING,
+ ERR_HANDLER_SCRIPT,
+ ERR_HANDLER_SHADER,
+ }
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index 3ec3d1e530..d42ee15657 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -58,7 +58,7 @@ namespace Godot.NativeInterop
internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func,
in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr,
- godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
+ godot_error_handler_type p_type, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
internal static partial godot_bool godotsharp_internal_script_debugger_is_active();
@@ -540,9 +540,7 @@ namespace Godot.NativeInterop
internal static partial void godotsharp_var_to_str(in godot_variant p_var, out godot_string r_ret);
- internal static partial void godotsharp_pusherror(in godot_string p_str);
-
- internal static partial void godotsharp_pushwarning(in godot_string p_str);
+ internal static partial void godotsharp_err_print_error(in godot_string p_function, in godot_string p_file, int p_line, in godot_string p_error, in godot_string p_message = default, godot_bool p_editor_notify = godot_bool.False, godot_error_handler_type p_type = godot_error_handler_type.ERR_HANDLER_ERROR);
// Object
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index ee4de4e9f5..24a9d4030a 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -92,10 +92,10 @@ void godotsharp_stack_info_vector_destroy(
void godotsharp_internal_script_debugger_send_error(const String *p_func,
const String *p_file, int32_t p_line, const String *p_err, const String *p_descr,
- bool p_warning, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) {
+ ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) {
const String file = ProjectSettings::get_singleton()->localize_path(p_file->simplify_path());
EngineDebugger::get_script_debugger()->send_error(*p_func, file, p_line, *p_err, *p_descr,
- true, p_warning ? ERR_HANDLER_WARNING : ERR_HANDLER_ERROR, *p_stack_info_vector);
+ true, p_type, *p_stack_info_vector);
}
bool godotsharp_internal_script_debugger_is_active() {
@@ -1320,12 +1320,14 @@ void godotsharp_printraw(const godot_string *p_what) {
OS::get_singleton()->print("%s", reinterpret_cast<const String *>(p_what)->utf8().get_data());
}
-void godotsharp_pusherror(const godot_string *p_str) {
- ERR_PRINT(*reinterpret_cast<const String *>(p_str));
-}
-
-void godotsharp_pushwarning(const godot_string *p_str) {
- WARN_PRINT(*reinterpret_cast<const String *>(p_str));
+void godotsharp_err_print_error(const godot_string *p_function, const godot_string *p_file, int32_t p_line, const godot_string *p_error, const godot_string *p_message, bool p_editor_notify, ErrorHandlerType p_type) {
+ _err_print_error(
+ reinterpret_cast<const String *>(p_function)->utf8().get_data(),
+ reinterpret_cast<const String *>(p_file)->utf8().get_data(),
+ p_line,
+ reinterpret_cast<const String *>(p_error)->utf8().get_data(),
+ reinterpret_cast<const String *>(p_message)->utf8().get_data(),
+ p_editor_notify, p_type);
}
void godotsharp_var_to_str(const godot_variant *p_var, godot_string *r_ret) {
@@ -1611,8 +1613,7 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_str_to_var,
(void *)godotsharp_var_to_bytes,
(void *)godotsharp_var_to_str,
- (void *)godotsharp_pusherror,
- (void *)godotsharp_pushwarning,
+ (void *)godotsharp_err_print_error,
(void *)godotsharp_object_to_string,
};
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 159cb91d1b..00ef4ccdde 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -95,6 +95,38 @@ String _get_mono_user_dir() {
#endif
}
+#if !TOOLS_ENABLED
+// This should be the equivalent of GodotTools.Utils.OS.PlatformNameMap.
+static const char *platform_name_map[][2] = {
+ { "Windows", "windows" },
+ { "macOS", "macos" },
+ { "Linux", "linuxbsd" },
+ { "FreeBSD", "linuxbsd" },
+ { "NetBSD", "linuxbsd" },
+ { "BSD", "linuxbsd" },
+ { "UWP", "uwp" },
+ { "Haiku", "haiku" },
+ { "Android", "android" },
+ { "iOS", "ios" },
+ { "Web", "web" },
+ { nullptr, nullptr }
+};
+
+String _get_platform_name() {
+ String platform_name = OS::get_singleton()->get_name();
+
+ int idx = 0;
+ while (platform_name_map[idx][0] != nullptr) {
+ if (platform_name_map[idx][0] == platform_name) {
+ return platform_name_map[idx][1];
+ }
+ idx++;
+ }
+
+ return "";
+}
+#endif
+
class _GodotSharpDirs {
public:
String res_metadata_dir;
@@ -139,21 +171,49 @@ private:
#endif
api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config());
#else // TOOLS_ENABLED
+ String platform = _get_platform_name();
String arch = Engine::get_singleton()->get_architecture_name();
String appname_safe = path::get_csharp_project_name();
- String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + arch);
- if (!DirAccess::exists(data_dir_root)) {
- data_dir_root = exe_dir.path_join("data_Godot_" + arch);
- }
+ String packed_path = "res://.godot/mono/publish/" + arch;
+ if (DirAccess::exists(packed_path)) {
+ // The dotnet publish data is packed in the pck/zip.
+ String data_dir_root = OS::get_singleton()->get_cache_path().path_join("data_" + appname_safe + "_" + platform + "_" + arch);
+ bool has_data = false;
+ if (!has_data) {
+ // 1. Try to access the data directly.
+ String global_packed = ProjectSettings::get_singleton()->globalize_path(packed_path);
+ if (global_packed.is_absolute_path() && FileAccess::exists(global_packed.path_join(".dotnet-publish-manifest"))) {
+ data_dir_root = global_packed;
+ has_data = true;
+ }
+ }
+ if (!has_data) {
+ // 2. Check if the data was extracted before and is up-to-date.
+ String packed_manifest = packed_path.path_join(".dotnet-publish-manifest");
+ String extracted_manifest = data_dir_root.path_join(".dotnet-publish-manifest");
+ if (FileAccess::exists(packed_manifest) && FileAccess::exists(extracted_manifest)) {
+ if (FileAccess::get_file_as_bytes(packed_manifest) == FileAccess::get_file_as_bytes(extracted_manifest)) {
+ has_data = true;
+ }
+ }
+ }
+ if (!has_data) {
+ // 3. Extract the data to a temporary location to load from there.
+ Ref<DirAccess> da = DirAccess::create_for_path(packed_path);
+ ERR_FAIL_NULL(da);
+ ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK);
+ }
+ api_assemblies_dir = data_dir_root;
+ } else {
+ // The dotnet publish data is in a directory next to the executable.
+ String data_dir_root = exe_dir.path_join("data_" + appname_safe + "_" + platform + "_" + arch);
#ifdef MACOS_ENABLED
- if (!DirAccess::exists(data_dir_root)) {
- data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + arch);
- }
- if (!DirAccess::exists(data_dir_root)) {
- data_dir_root = res_dir.path_join("data_Godot_" + arch);
- }
+ if (!DirAccess::exists(data_dir_root)) {
+ data_dir_root = res_dir.path_join("data_" + appname_safe + "_" + platform + "_" + arch);
+ }
#endif
- api_assemblies_dir = data_dir_root;
+ api_assemblies_dir = data_dir_root;
+ }
#endif
}
diff --git a/modules/mono/icons/BuildCSharp.svg b/modules/mono/icons/BuildCSharp.svg
new file mode 100644
index 0000000000..9d0102c35d
--- /dev/null
+++ b/modules/mono/icons/BuildCSharp.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M 9.6060193,0.78346667 C 8.6741914,0.96303367 7.6708299,1.5334576 6.9028943,1.9768256 l -2.1523438,1.244141 0.082031,0.138672 -0.3105469,-0.00781 -2.5839844,1.492188 1.9101563,3.308593 2.5820312,-1.490234 0.1425781,-0.255859 4.1875002,7.2539054 0.5,0.867187 c 0.415803,0.720194 1.331398,0.964165 2.050782,0.548829 0.719286,-0.415279 0.963839,-1.33001 0.548828,-2.048829 l -2,-3.4648424 -2.8808602,-4.990235 3.7070322,-2.101562 -0.265626,-0.439453 C 11.697382,0.83561667 10.650124,0.58226267 9.6060193,0.78346667 Z" fill="#e0e0e0"/></svg>
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 4337478370..247968e251 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -55,9 +55,7 @@
// TODO mobile
#if 0
-#ifdef ANDROID_ENABLED
-#include "support/android_support.h"
-#elif defined(IOS_ENABLED)
+#ifdef IOS_ENABLED
#include "support/ios_support.h"
#endif
#endif
@@ -378,6 +376,12 @@ void GDMono::initialize() {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+ // Check that the .NET assemblies directory exists before trying to use it.
+ if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) {
+ OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found"));
+ ERR_FAIL_MSG(".NET: Assemblies not found");
+ }
+
if (!load_hostfxr(hostfxr_dll_handle)) {
#if !defined(TOOLS_ENABLED)
godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);
@@ -554,10 +558,6 @@ GDMono::~GDMono() {
finalizing_scripts_domain = false;
runtime_initialized = false;
-#if defined(ANDROID_ENABLED)
- gdmono::android::support::cleanup();
-#endif
-
singleton = nullptr;
}
diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp
deleted file mode 100644
index 14b442516e..0000000000
--- a/modules/mono/mono_gd/support/android_support.cpp
+++ /dev/null
@@ -1,720 +0,0 @@
-/**************************************************************************/
-/* android_support.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 "android_support.h"
-
-#if defined(ANDROID_ENABLED)
-
-#include "../../utils/path_utils.h"
-#include "../../utils/string_utils.h"
-#include "../gd_mono_cache.h"
-#include "../gd_mono_marshal.h"
-
-#include "core/os/os.h"
-#include "core/string/ustring.h"
-
-#include "java_godot_wrapper.h"
-#include "os_android.h"
-#include "thread_jandroid.h"
-
-#include <mono/utils/mono-dl-fallback.h>
-
-#include <dlfcn.h> // dlopen, dlsym
-#include <sys/system_properties.h>
-#include <cstddef>
-
-#if __ANDROID_API__ < 24
-#include "thirdparty/misc/ifaddrs-android.h"
-#else
-#include <ifaddrs.h>
-#endif
-
-// Warning: JNI boilerplate ahead... continue at your own risk
-
-namespace gdmono {
-namespace android {
-namespace support {
-
-template <typename T>
-struct ScopedLocalRef {
- JNIEnv *env;
- T local_ref;
-
- _FORCE_INLINE_ T get() const { return local_ref; }
- _FORCE_INLINE_ operator T() const { return local_ref; }
- _FORCE_INLINE_ operator jvalue() const { return (jvalue)local_ref; }
-
- _FORCE_INLINE_ operator bool() const { return local_ref != nullptr; }
-
- _FORCE_INLINE_ bool operator==(std::nullptr_t) const {
- return local_ref == nullptr;
- }
-
- _FORCE_INLINE_ bool operator!=(std::nullptr_t) const {
- return local_ref != nullptr;
- }
-
- ScopedLocalRef(const ScopedLocalRef &) = delete;
- ScopedLocalRef &operator=(const ScopedLocalRef &) = delete;
-
- ScopedLocalRef(JNIEnv *p_env, T p_local_ref) :
- env(p_env),
- local_ref(p_local_ref) {
- }
-
- ~ScopedLocalRef() {
- if (local_ref) {
- env->DeleteLocalRef(local_ref);
- }
- }
-};
-
-bool jni_exception_check(JNIEnv *p_env) {
- if (p_env->ExceptionCheck()) {
- // Print the exception to logcat
- p_env->ExceptionDescribe();
-
- p_env->ExceptionClear();
- return true;
- }
-
- return false;
-}
-
-String app_native_lib_dir_cache;
-
-String determine_app_native_lib_dir() {
- JNIEnv *env = get_jni_env();
-
- ScopedLocalRef<jclass> activityThreadClass(env, env->FindClass("android/app/ActivityThread"));
- jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
- ScopedLocalRef<jobject> activityThread(env, env->CallStaticObjectMethod(activityThreadClass, currentActivityThread));
- jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;");
- ScopedLocalRef<jobject> ctx(env, env->CallObjectMethod(activityThread, getApplication));
-
- jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
- ScopedLocalRef<jobject> applicationInfo(env, env->CallObjectMethod(ctx, getApplicationInfo));
- jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;");
- ScopedLocalRef<jstring> nativeLibraryDir(env, (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField));
-
- String result;
-
- const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, nullptr);
- if (nativeLibraryDirUtf8) {
- result.parse_utf8(nativeLibraryDirUtf8);
- env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDirUtf8);
- }
-
- return result;
-}
-
-String get_app_native_lib_dir() {
- if (app_native_lib_dir_cache.is_empty()) {
- app_native_lib_dir_cache = determine_app_native_lib_dir();
- }
- return app_native_lib_dir_cache;
-}
-
-int gd_mono_convert_dl_flags(int flags) {
- // from mono's runtime-bootstrap.c
-
- int lflags = flags & MONO_DL_LOCAL ? 0 : RTLD_GLOBAL;
-
- if (flags & MONO_DL_LAZY) {
- lflags |= RTLD_LAZY;
- } else {
- lflags |= RTLD_NOW;
- }
-
- return lflags;
-}
-
-#ifndef GD_MONO_SO_NAME
-#define GD_MONO_SO_NAME "libmonosgen-2.0.so"
-#endif
-
-const char *mono_so_name = GD_MONO_SO_NAME;
-const char *godot_so_name = "libgodot_android.so";
-
-void *mono_dl_handle = nullptr;
-void *godot_dl_handle = nullptr;
-
-void *try_dlopen(const String &p_so_path, int p_flags) {
- if (!FileAccess::exists(p_so_path)) {
- if (OS::get_singleton()->is_stdout_verbose()) {
- OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data());
- }
- return nullptr;
- }
-
- int lflags = gd_mono_convert_dl_flags(p_flags);
-
- void *handle = dlopen(p_so_path.utf8().get_data(), lflags);
-
- if (!handle) {
- if (OS::get_singleton()->is_stdout_verbose()) {
- OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror());
- }
- return nullptr;
- }
-
- if (OS::get_singleton()->is_stdout_verbose()) {
- OS::get_singleton()->print("Successfully loaded shared library: '%s'\n", p_so_path.utf8().get_data());
- }
-
- return handle;
-}
-
-void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) {
- if (p_name == nullptr) {
- // __Internal
-
- if (!mono_dl_handle) {
- String app_native_lib_dir = get_app_native_lib_dir();
- String so_path = path::join(app_native_lib_dir, mono_so_name);
-
- mono_dl_handle = try_dlopen(so_path, p_flags);
- }
-
- return mono_dl_handle;
- }
-
- String name = String::utf8(p_name);
-
- if (name.ends_with(".dll.so") || name.ends_with(".exe.so")) {
- String app_native_lib_dir = get_app_native_lib_dir();
-
- String orig_so_name = name.get_file();
- String so_name = "lib-aot-" + orig_so_name;
- String so_path = path::join(app_native_lib_dir, so_name);
-
- return try_dlopen(so_path, p_flags);
- }
-
- return nullptr;
-}
-
-void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) {
- void *sym_addr = dlsym(p_handle, p_name);
-
- if (sym_addr) {
- return sym_addr;
- }
-
- if (p_handle == mono_dl_handle && godot_dl_handle) {
- // Looking up for '__Internal' P/Invoke. We want to search in both the Mono and Godot shared libraries.
- // This is needed to resolve the monodroid P/Invoke functions that are defined at the bottom of the file.
- sym_addr = dlsym(godot_dl_handle, p_name);
-
- if (sym_addr) {
- return sym_addr;
- }
- }
-
- if (r_err) {
- *r_err = str_format_new("%s\n", dlerror());
- }
-
- return nullptr;
-}
-
-void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) {
- dlclose(p_handle);
-
- // Not sure if this ever happens. Does Mono close the handle for the main module?
- if (p_handle == mono_dl_handle) {
- mono_dl_handle = nullptr;
- }
-
- return nullptr;
-}
-
-int32_t build_version_sdk_int = 0;
-
-int32_t get_build_version_sdk_int() {
- // The JNI code is the equivalent of:
- //
- // android.os.Build.VERSION.SDK_INT
-
- if (build_version_sdk_int == 0) {
- JNIEnv *env = get_jni_env();
-
- jclass versionClass = env->FindClass("android/os/Build$VERSION");
- ERR_FAIL_NULL_V(versionClass, 0);
-
- jfieldID sdkIntField = env->GetStaticFieldID(versionClass, "SDK_INT", "I");
- ERR_FAIL_NULL_V(sdkIntField, 0);
-
- build_version_sdk_int = (int32_t)env->GetStaticIntField(versionClass, sdkIntField);
- }
-
- return build_version_sdk_int;
-}
-
-jobject certStore = nullptr; // KeyStore
-
-MonoBoolean _gd_mono_init_cert_store() {
- // The JNI code is the equivalent of:
- //
- // try {
- // certStoreLocal = KeyStore.getInstance("AndroidCAStore");
- // certStoreLocal.load(null);
- // certStore = certStoreLocal;
- // return true;
- // } catch (Exception e) {
- // return false;
- // }
-
- JNIEnv *env = get_jni_env();
-
- ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore"));
-
- jmethodID getInstance = env->GetStaticMethodID(keyStoreClass, "getInstance", "(Ljava/lang/String;)Ljava/security/KeyStore;");
- jmethodID load = env->GetMethodID(keyStoreClass, "load", "(Ljava/security/KeyStore$LoadStoreParameter;)V");
-
- ScopedLocalRef<jstring> androidCAStoreString(env, env->NewStringUTF("AndroidCAStore"));
-
- ScopedLocalRef<jobject> certStoreLocal(env, env->CallStaticObjectMethod(keyStoreClass, getInstance, androidCAStoreString.get()));
-
- if (jni_exception_check(env)) {
- return 0;
- }
-
- env->CallVoidMethod(certStoreLocal, load, nullptr);
-
- if (jni_exception_check(env)) {
- return 0;
- }
-
- certStore = env->NewGlobalRef(certStoreLocal);
-
- return 1;
-}
-
-MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) {
- // The JNI code is the equivalent of:
- //
- // Certificate certificate = certStore.getCertificate(alias);
- // if (certificate == null) {
- // return null;
- // }
- // return certificate.getEncoded();
-
- MonoError mono_error;
- char *alias_utf8 = mono_string_to_utf8_checked(p_alias, &mono_error);
-
- if (!mono_error_ok(&mono_error)) {
- ERR_PRINT(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'.");
- mono_error_cleanup(&mono_error);
- return nullptr;
- }
-
- JNIEnv *env = get_jni_env();
-
- ScopedLocalRef<jstring> js_alias(env, env->NewStringUTF(alias_utf8));
- mono_free(alias_utf8);
-
- ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore"));
- ERR_FAIL_NULL_V(keyStoreClass, nullptr);
- ScopedLocalRef<jclass> certificateClass(env, env->FindClass("java/security/cert/Certificate"));
- ERR_FAIL_NULL_V(certificateClass, nullptr);
-
- jmethodID getCertificate = env->GetMethodID(keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;");
- ERR_FAIL_NULL_V(getCertificate, nullptr);
-
- jmethodID getEncoded = env->GetMethodID(certificateClass, "getEncoded", "()[B");
- ERR_FAIL_NULL_V(getEncoded, nullptr);
-
- ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get()));
-
- if (!certificate) {
- return nullptr;
- }
-
- ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded));
- jsize encodedLength = env->GetArrayLength(encoded);
-
- MonoArray *encoded_ret = mono_array_new(mono_domain_get(), mono_get_byte_class(), encodedLength);
- uint8_t *dest = (uint8_t *)mono_array_addr(encoded_ret, uint8_t, 0);
-
- env->GetByteArrayRegion(encoded, 0, encodedLength, reinterpret_cast<jbyte *>(dest));
-
- return encoded_ret;
-}
-
-void register_internal_calls() {
- GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", _gd_mono_init_cert_store);
- GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", _gd_mono_android_cert_store_lookup);
-}
-
-void initialize() {
- // We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider
- OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls");
-
- mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, nullptr);
-
- String app_native_lib_dir = get_app_native_lib_dir();
- String so_path = path::join(app_native_lib_dir, godot_so_name);
-
- godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY));
-}
-
-void cleanup() {
- // This is called after shutting down the Mono runtime
-
- if (mono_dl_handle) {
- gd_mono_android_dlclose(mono_dl_handle, nullptr);
- }
-
- if (godot_dl_handle) {
- gd_mono_android_dlclose(godot_dl_handle, nullptr);
- }
-
- JNIEnv *env = get_jni_env();
-
- if (certStore) {
- env->DeleteGlobalRef(certStore);
- certStore = nullptr;
- }
-}
-} // namespace support
-} // namespace android
-} // namespace gdmono
-
-using namespace gdmono::android::support;
-
-// The following are P/Invoke functions required by the monodroid profile of the BCL.
-// These are P/Invoke functions and not internal calls, hence why they use
-// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'.
-
-#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default")))
-
-GD_PINVOKE_EXPORT int32_t _monodroid_get_android_api_level() {
- return get_build_version_sdk_int();
-}
-
-GD_PINVOKE_EXPORT void monodroid_free(void *ptr) {
- free(ptr);
-}
-
-GD_PINVOKE_EXPORT int32_t monodroid_get_system_property(const char *p_name, char **r_value) {
- char prop_value_str[PROP_VALUE_MAX + 1] = { 0 };
-
- int len = __system_property_get(p_name, prop_value_str);
-
- if (r_value) {
- if (len >= 0) {
- *r_value = (char *)malloc(len + 1);
- ERR_FAIL_NULL_V_MSG(*r_value, -1, "Out of memory.");
- memcpy(*r_value, prop_value_str, len);
- (*r_value)[len] = '\0';
- } else {
- *r_value = nullptr;
- }
- }
-
- return len;
-}
-
-GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char *p_ifname, mono_bool *r_is_up) {
- // The JNI code is the equivalent of:
- //
- // NetworkInterface.getByName(p_ifname).isUp()
-
- if (!r_is_up || !p_ifname || strlen(p_ifname) == 0) {
- return 0;
- }
-
- *r_is_up = 0;
-
- JNIEnv *env = get_jni_env();
-
- jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
- ERR_FAIL_NULL_V(networkInterfaceClass, 0);
-
- jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;");
- ERR_FAIL_NULL_V(getByName, 0);
-
- jmethodID isUp = env->GetMethodID(networkInterfaceClass, "isUp", "()Z");
- ERR_FAIL_NULL_V(isUp, 0);
-
- ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname));
- ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get()));
-
- if (!networkInterface) {
- return 0;
- }
-
- *r_is_up = (mono_bool)env->CallBooleanMethod(networkInterface, isUp);
-
- return 1;
-}
-
-GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast(const char *p_ifname, mono_bool *r_supports_multicast) {
- // The JNI code is the equivalent of:
- //
- // NetworkInterface.getByName(p_ifname).supportsMulticast()
-
- if (!r_supports_multicast || !p_ifname || strlen(p_ifname) == 0) {
- return 0;
- }
-
- *r_supports_multicast = 0;
-
- JNIEnv *env = get_jni_env();
-
- jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
- ERR_FAIL_NULL_V(networkInterfaceClass, 0);
-
- jmethodID getByName = env->GetStaticMethodID(networkInterfaceClass, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;");
- ERR_FAIL_NULL_V(getByName, 0);
-
- jmethodID supportsMulticast = env->GetMethodID(networkInterfaceClass, "supportsMulticast", "()Z");
- ERR_FAIL_NULL_V(supportsMulticast, 0);
-
- ScopedLocalRef<jstring> js_ifname(env, env->NewStringUTF(p_ifname));
- ScopedLocalRef<jobject> networkInterface(env, env->CallStaticObjectMethod(networkInterfaceClass, getByName, js_ifname.get()));
-
- if (!networkInterface) {
- return 0;
- }
-
- *r_supports_multicast = (mono_bool)env->CallBooleanMethod(networkInterface, supportsMulticast);
-
- return 1;
-}
-
-static const int dns_servers_len = 8;
-
-static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dns_servers_count) {
- // The JNI code is the equivalent of:
- //
- // ConnectivityManager connectivityManager = (ConnectivityManager)getApplicationContext()
- // .getSystemService(Context.CONNECTIVITY_SERVICE);
- // Network activeNerwork = connectivityManager.getActiveNetwork();
- // LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNerwork);
- // List<String> dnsServers = linkProperties.getDnsServers().stream()
- // .map(inetAddress -> inetAddress.getHostAddress()).collect(Collectors.toList());
-
-#ifdef DEBUG_ENABLED
- CRASH_COND(get_build_version_sdk_int() < 23);
-#endif
-
- JNIEnv *env = get_jni_env();
-
- GodotJavaWrapper *godot_java = ((OS_Android *)OS::get_singleton())->get_godot_java();
- jobject activity = godot_java->get_activity();
-
- ScopedLocalRef<jclass> activityClass(env, env->GetObjectClass(activity));
- ERR_FAIL_NULL(activityClass);
-
- jmethodID getApplicationContext = env->GetMethodID(activityClass, "getApplicationContext", "()Landroid/content/Context;");
-
- ScopedLocalRef<jobject> applicationContext(env, env->CallObjectMethod(activity, getApplicationContext));
-
- ScopedLocalRef<jclass> contextClass(env, env->FindClass("android/content/Context"));
- ERR_FAIL_NULL(contextClass);
-
- jfieldID connectivityServiceField = env->GetStaticFieldID(contextClass, "CONNECTIVITY_SERVICE", "Ljava/lang/String;");
- ScopedLocalRef<jstring> connectivityServiceString(env, (jstring)env->GetStaticObjectField(contextClass, connectivityServiceField));
-
- jmethodID getSystemService = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
-
- ScopedLocalRef<jobject> connectivityManager(env, env->CallObjectMethod(applicationContext, getSystemService, connectivityServiceString.get()));
-
- if (!connectivityManager) {
- return;
- }
-
- ScopedLocalRef<jclass> connectivityManagerClass(env, env->FindClass("android/net/ConnectivityManager"));
- ERR_FAIL_NULL(connectivityManagerClass);
-
- jmethodID getActiveNetwork = env->GetMethodID(connectivityManagerClass, "getActiveNetwork", "()Landroid/net/Network;");
- ERR_FAIL_NULL(getActiveNetwork);
-
- ScopedLocalRef<jobject> activeNetwork(env, env->CallObjectMethod(connectivityManager, getActiveNetwork));
-
- if (!activeNetwork) {
- return;
- }
-
- jmethodID getLinkProperties = env->GetMethodID(connectivityManagerClass,
- "getLinkProperties", "(Landroid/net/Network;)Landroid/net/LinkProperties;");
- ERR_FAIL_NULL(getLinkProperties);
-
- ScopedLocalRef<jobject> linkProperties(env, env->CallObjectMethod(connectivityManager, getLinkProperties, activeNetwork.get()));
-
- if (!linkProperties) {
- return;
- }
-
- ScopedLocalRef<jclass> linkPropertiesClass(env, env->FindClass("android/net/LinkProperties"));
- ERR_FAIL_NULL(linkPropertiesClass);
-
- jmethodID getDnsServers = env->GetMethodID(linkPropertiesClass, "getDnsServers", "()Ljava/util/List;");
- ERR_FAIL_NULL(getDnsServers);
-
- ScopedLocalRef<jobject> dnsServers(env, env->CallObjectMethod(linkProperties, getDnsServers));
-
- if (!dnsServers) {
- return;
- }
-
- ScopedLocalRef<jclass> listClass(env, env->FindClass("java/util/List"));
- ERR_FAIL_NULL(listClass);
-
- jmethodID listSize = env->GetMethodID(listClass, "size", "()I");
- ERR_FAIL_NULL(listSize);
-
- int dnsServersCount = env->CallIntMethod(dnsServers, listSize);
-
- if (dnsServersCount > dns_servers_len) {
- dnsServersCount = dns_servers_len;
- }
-
- if (dnsServersCount <= 0) {
- return;
- }
-
- jmethodID listGet = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");
- ERR_FAIL_NULL(listGet);
-
- ScopedLocalRef<jclass> inetAddressClass(env, env->FindClass("java/net/InetAddress"));
- ERR_FAIL_NULL(inetAddressClass);
-
- jmethodID getHostAddress = env->GetMethodID(inetAddressClass, "getHostAddress", "()Ljava/lang/String;");
- ERR_FAIL_NULL(getHostAddress);
-
- for (int i = 0; i < dnsServersCount; i++) {
- ScopedLocalRef<jobject> dnsServer(env, env->CallObjectMethod(dnsServers, listGet, (jint)i));
- if (!dnsServer) {
- continue;
- }
-
- ScopedLocalRef<jstring> hostAddress(env, (jstring)env->CallObjectMethod(dnsServer, getHostAddress));
- const char *host_address = env->GetStringUTFChars(hostAddress, 0);
-
- r_dns_servers[i] = strdup(host_address); // freed by the BCL
- (*dns_servers_count)++;
-
- env->ReleaseStringUTFChars(hostAddress, host_address);
- }
-
- // jesus...
-}
-
-GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array) {
- if (!r_dns_servers_array) {
- return -1;
- }
-
- *r_dns_servers_array = nullptr;
-
- char *dns_servers[dns_servers_len];
- int dns_servers_count = 0;
-
- if (_monodroid_get_android_api_level() < 26) {
- // The 'net.dns*' system properties are no longer available in Android 8.0 (API level 26) and greater:
- // https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri
-
- char prop_name[] = "net.dns*";
-
- for (int i = 0; i < dns_servers_len; i++) {
- prop_name[7] = (char)(i + 0x31);
- char *prop_value;
- int32_t len = monodroid_get_system_property(prop_name, &prop_value);
-
- if (len > 0) {
- dns_servers[dns_servers_count] = strndup(prop_value, (size_t)len); // freed by the BCL
- dns_servers_count++;
- free(prop_value);
- }
- }
- } else {
- // Alternative for Oreo and greater
- interop_get_active_network_dns_servers(dns_servers, &dns_servers_count);
- }
-
- if (dns_servers_count > 0) {
- size_t ret_size = sizeof(char *) * (size_t)dns_servers_count;
- *r_dns_servers_array = malloc(ret_size); // freed by the BCL
- ERR_FAIL_NULL_V_MSG(*r_dns_servers_array, -1, "Out of memory.");
- memcpy(*r_dns_servers_array, dns_servers, ret_size);
- }
-
- return dns_servers_count;
-}
-
-GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() {
- // The JNI code is the equivalent of:
- //
- // TimeZone.getDefault().getID()
-
- JNIEnv *env = get_jni_env();
-
- ScopedLocalRef<jclass> timeZoneClass(env, env->FindClass("java/util/TimeZone"));
- ERR_FAIL_NULL_V(timeZoneClass, nullptr);
-
- jmethodID getDefault = env->GetStaticMethodID(timeZoneClass, "getDefault", "()Ljava/util/TimeZone;");
- ERR_FAIL_NULL_V(getDefault, nullptr);
-
- jmethodID getID = env->GetMethodID(timeZoneClass, "getID", "()Ljava/lang/String;");
- ERR_FAIL_NULL_V(getID, nullptr);
-
- ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault));
-
- if (!defaultTimeZone) {
- return nullptr;
- }
-
- ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID));
-
- if (!defaultTimeZoneID) {
- return nullptr;
- }
-
- const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0);
-
- char *result = strdup(default_time_zone_id); // freed by the BCL
-
- env->ReleaseStringUTFChars(defaultTimeZoneID, default_time_zone_id);
-
- return result;
-}
-
-GD_PINVOKE_EXPORT int32_t _monodroid_getifaddrs(struct ifaddrs **p_ifap) {
- return getifaddrs(p_ifap);
-}
-
-GD_PINVOKE_EXPORT void _monodroid_freeifaddrs(struct ifaddrs *p_ifap) {
- freeifaddrs(p_ifap);
-}
-
-#endif
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index 634d70d3bd..535091e5b6 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -32,12 +32,11 @@
#ifdef TOOLS_ENABLED
-#include "../navigation_mesh_generator.h"
-
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "editor/editor_node.h"
#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/navigation_region_3d.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
@@ -99,18 +98,16 @@ void NavigationMeshEditor::_bake_pressed() {
}
}
- NavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh());
- Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
- source_geometry_data.instantiate();
- NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(node->get_navigation_mesh(), source_geometry_data, node);
- NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(node->get_navigation_mesh(), source_geometry_data);
+ node->bake_navigation_mesh(false);
node->update_gizmos();
}
void NavigationMeshEditor::_clear_pressed() {
if (node) {
- NavigationMeshGenerator::get_singleton()->clear(node->get_navigation_mesh());
+ if (node->get_navigation_mesh().is_valid()) {
+ node->get_navigation_mesh()->clear();
+ }
}
button_bake->set_pressed(false);
@@ -139,14 +136,15 @@ NavigationMeshEditor::NavigationMeshEditor() {
button_bake->set_flat(true);
bake_hbox->add_child(button_bake);
button_bake->set_toggle_mode(true);
- button_bake->set_text(TTR("Bake NavMesh"));
+ button_bake->set_text(TTR("Bake NavigationMesh"));
+ button_bake->set_tooltip_text(TTR("Bakes the NavigationMesh by first parsing the scene for source geometry and then creating the navigation mesh vertices and polygons."));
button_bake->connect("pressed", callable_mp(this, &NavigationMeshEditor::_bake_pressed));
button_reset = memnew(Button);
button_reset->set_flat(true);
bake_hbox->add_child(button_reset);
- // No button text, we only use a revert icon which is set when entering the tree.
- button_reset->set_tooltip_text(TTR("Clear the navigation mesh."));
+ button_reset->set_text(TTR("Clear NavigationMesh"));
+ button_reset->set_tooltip_text(TTR("Clears the internal NavigationMesh vertices and polygons."));
button_reset->connect("pressed", callable_mp(this, &NavigationMeshEditor::_clear_pressed));
bake_info = memnew(Label);
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index b73c5ca3e2..8f56bf0f1d 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -31,8 +31,8 @@
#include "godot_navigation_server.h"
#ifndef _3D_DISABLED
-#include "navigation_mesh_generator.h"
-#endif
+#include "nav_mesh_generator_3d.h"
+#endif // _3D_DISABLED
#include "core/os/mutex.h"
@@ -340,6 +340,20 @@ RID GodotNavigationServer::region_create() {
return rid;
}
+COMMAND_2(region_set_enabled, RID, p_region, bool, p_enabled) {
+ NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_COND(region == nullptr);
+
+ region->set_enabled(p_enabled);
+}
+
+bool GodotNavigationServer::region_get_enabled(RID p_region) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_COND_V(region == nullptr, false);
+
+ return region->get_enabled();
+}
+
COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled) {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_COND(region == nullptr);
@@ -454,11 +468,11 @@ void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_na
WARN_PRINT_ONCE("NavigationServer3D::region_bake_navigation_mesh() is deprecated due to core threading changes. To upgrade existing code, first create a NavigationMeshSourceGeometryData3D resource. Use this resource with method parse_source_geometry_data() to parse the SceneTree for nodes that should contribute to the navigation mesh baking. The SceneTree parsing needs to happen on the main thread. After the parsing is finished use the resource with method bake_from_source_geometry_data() to bake a navigation mesh..");
#ifndef _3D_DISABLED
- NavigationMeshGenerator::get_singleton()->clear(p_navigation_mesh);
+ p_navigation_mesh->clear();
Ref<NavigationMeshSourceGeometryData3D> source_geometry_data;
source_geometry_data.instantiate();
- NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(p_navigation_mesh, source_geometry_data, p_root_node);
- NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, source_geometry_data);
+ parse_source_geometry_data(p_navigation_mesh, source_geometry_data, p_root_node);
+ bake_from_source_geometry_data(p_navigation_mesh, source_geometry_data);
#endif
}
#endif // DISABLE_DEPRECATED
@@ -512,6 +526,20 @@ RID GodotNavigationServer::link_get_map(const RID p_link) const {
return RID();
}
+COMMAND_2(link_set_enabled, RID, p_link, bool, p_enabled) {
+ NavLink *link = link_owner.get_or_null(p_link);
+ ERR_FAIL_COND(link == nullptr);
+
+ link->set_enabled(p_enabled);
+}
+
+bool GodotNavigationServer::link_get_enabled(RID p_link) const {
+ const NavLink *link = link_owner.get_or_null(p_link);
+ ERR_FAIL_COND_V(link == nullptr, false);
+
+ return link->get_enabled();
+}
+
COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional) {
NavLink *link = link_owner.get_or_null(p_link);
ERR_FAIL_COND(link == nullptr);
@@ -788,8 +816,8 @@ COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority) {
}
RID GodotNavigationServer::obstacle_create() {
- GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this);
- MutexLock lock(mut_this->operations_mutex);
+ MutexLock lock(operations_mutex);
+
RID rid = obstacle_owner.make_rid();
NavObstacle *obstacle = obstacle_owner.get_or_null(rid);
obstacle->set_self(rid);
@@ -904,14 +932,34 @@ COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) {
void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
#ifndef _3D_DISABLED
- NavigationMeshGenerator::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
-#endif
+ ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_COND_MSG(p_root_node == nullptr, "No parsing root node specified.");
+ ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree.");
+
+ ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
+ NavMeshGenerator3D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
+#endif // _3D_DISABLED
}
void GodotNavigationServer::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
#ifndef _3D_DISABLED
- NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
-#endif
+ ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
+ ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
+
+ if (!p_source_geometry_data->has_data()) {
+ p_navigation_mesh->clear();
+ if (p_callback.is_valid()) {
+ Callable::CallError ce;
+ Variant result;
+ p_callback.callp(nullptr, 0, result, ce);
+ }
+ return;
+ }
+
+ ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton());
+ NavMeshGenerator3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
+#endif // _3D_DISABLED
}
COMMAND_1(free, RID, p_object) {
@@ -1089,6 +1137,23 @@ void GodotNavigationServer::process(real_t p_delta_time) {
pm_edge_free_count = _new_pm_edge_free_count;
}
+void GodotNavigationServer::init() {
+#ifndef _3D_DISABLED
+ navmesh_generator_3d = memnew(NavMeshGenerator3D);
+#endif // _3D_DISABLED
+}
+
+void GodotNavigationServer::finish() {
+ flush_queries();
+#ifndef _3D_DISABLED
+ if (navmesh_generator_3d) {
+ navmesh_generator_3d->finish();
+ memdelete(navmesh_generator_3d);
+ navmesh_generator_3d = nullptr;
+ }
+#endif // _3D_DISABLED
+}
+
PathQueryResult GodotNavigationServer::_query_path(const PathQueryParameters &p_parameters) const {
PathQueryResult r_query_result;
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index 95aa88194e..8b91ca36bd 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -56,6 +56,7 @@
void MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1)
class GodotNavigationServer;
+class NavMeshGenerator3D;
struct SetCommand {
virtual ~SetCommand() {}
@@ -79,6 +80,8 @@ class GodotNavigationServer : public NavigationServer3D {
LocalVector<NavMap *> active_maps;
LocalVector<uint32_t> active_maps_update_id;
+ NavMeshGenerator3D *navmesh_generator_3d = nullptr;
+
// Performance Monitor
int pm_region_count = 0;
int pm_agent_count = 0;
@@ -135,6 +138,9 @@ public:
virtual RID region_create() override;
+ COMMAND_2(region_set_enabled, RID, p_region, bool, p_enabled);
+ virtual bool region_get_enabled(RID p_region) const override;
+
COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled);
virtual bool region_get_use_edge_connections(RID p_region) const override;
@@ -164,6 +170,8 @@ public:
virtual RID link_create() override;
COMMAND_2(link_set_map, RID, p_link, RID, p_map);
virtual RID link_get_map(RID p_link) const override;
+ COMMAND_2(link_set_enabled, RID, p_link, bool, p_enabled);
+ virtual bool link_get_enabled(RID p_link) const override;
COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional);
virtual bool link_is_bidirectional(RID p_link) const override;
COMMAND_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers);
@@ -229,6 +237,8 @@ public:
void flush_queries();
virtual void process(real_t p_delta_time) override;
+ virtual void init() override;
+ virtual void finish() override;
virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override;
diff --git a/modules/navigation/nav_link.cpp b/modules/navigation/nav_link.cpp
index d712987a46..c693cc91c8 100644
--- a/modules/navigation/nav_link.cpp
+++ b/modules/navigation/nav_link.cpp
@@ -49,6 +49,16 @@ void NavLink::set_map(NavMap *p_map) {
}
}
+void NavLink::set_enabled(bool p_enabled) {
+ if (enabled == p_enabled) {
+ return;
+ }
+ enabled = p_enabled;
+
+ // TODO: This should not require a full rebuild as the link has not really changed.
+ link_dirty = true;
+};
+
void NavLink::set_bidirectional(bool p_bidirectional) {
if (bidirectional == p_bidirectional) {
return;
diff --git a/modules/navigation/nav_link.h b/modules/navigation/nav_link.h
index 0b8ad4db69..a7609831db 100644
--- a/modules/navigation/nav_link.h
+++ b/modules/navigation/nav_link.h
@@ -39,6 +39,7 @@ class NavLink : public NavBase {
bool bidirectional = true;
Vector3 start_position;
Vector3 end_position;
+ bool enabled = true;
bool link_dirty = true;
@@ -52,6 +53,9 @@ public:
return map;
}
+ void set_enabled(bool p_enabled);
+ bool get_enabled() const { return enabled; }
+
void set_bidirectional(bool p_bidirectional);
bool is_bidirectional() const {
return bidirectional;
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 3a1d412618..737ccaf3cd 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -431,6 +431,17 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
if (p_optimize) {
// Set the apex poly/point to the end point
gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id];
+
+ Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end };
+ const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway);
+ if (end_point.is_equal_approx(back_edge_closest_point)) {
+ // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing.
+ // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon.
+ if (apex_poly->back_navigation_poly_id != -1) {
+ apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id];
+ }
+ }
+
Vector3 apex_point = end_point;
gd::NavigationPoly *left_poly = apex_poly;
@@ -804,6 +815,9 @@ void NavMap::sync() {
// Resize the polygon count.
int count = 0;
for (const NavRegion *region : regions) {
+ if (!region->get_enabled()) {
+ continue;
+ }
count += region->get_polygons().size();
}
polygons.resize(count);
@@ -811,6 +825,9 @@ void NavMap::sync() {
// Copy all region polygons in the map.
count = 0;
for (const NavRegion *region : regions) {
+ if (!region->get_enabled()) {
+ continue;
+ }
const LocalVector<gd::Polygon> &polygons_source = region->get_polygons();
for (uint32_t n = 0; n < polygons_source.size(); n++) {
polygons[count + n] = polygons_source[n];
@@ -1043,7 +1060,8 @@ void NavMap::sync() {
}
// Update the update ID.
- map_update_id = (map_update_id + 1) % 9999999;
+ // Some code treats 0 as a failure case, so we avoid returning 0.
+ map_update_id = map_update_id % 9999999 + 1;
}
// Do we have modified obstacle positions?
diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp
new file mode 100644
index 0000000000..ab25a18d28
--- /dev/null
+++ b/modules/navigation/nav_mesh_generator_3d.cpp
@@ -0,0 +1,728 @@
+/**************************************************************************/
+/* nav_mesh_generator_3d.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. */
+/**************************************************************************/
+
+#ifndef _3D_DISABLED
+
+#include "nav_mesh_generator_3d.h"
+
+#include "core/math/convex_hull.h"
+#include "core/os/thread.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/multimesh_instance_3d.h"
+#include "scene/3d/physics_body_3d.h"
+#include "scene/resources/box_shape_3d.h"
+#include "scene/resources/capsule_shape_3d.h"
+#include "scene/resources/concave_polygon_shape_3d.h"
+#include "scene/resources/convex_polygon_shape_3d.h"
+#include "scene/resources/cylinder_shape_3d.h"
+#include "scene/resources/height_map_shape_3d.h"
+#include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
+#include "scene/resources/primitive_meshes.h"
+#include "scene/resources/shape_3d.h"
+#include "scene/resources/sphere_shape_3d.h"
+#include "scene/resources/world_boundary_shape_3d.h"
+
+#include "modules/modules_enabled.gen.h" // For csg, gridmap.
+
+#ifdef MODULE_CSG_ENABLED
+#include "modules/csg/csg_shape.h"
+#endif
+#ifdef MODULE_GRIDMAP_ENABLED
+#include "modules/gridmap/grid_map.h"
+#endif
+
+#include <Recast.h>
+
+NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr;
+Mutex NavMeshGenerator3D::baking_navmesh_mutex;
+HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes;
+
+NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() {
+ return singleton;
+}
+
+NavMeshGenerator3D::NavMeshGenerator3D() {
+ ERR_FAIL_COND(singleton != nullptr);
+ singleton = this;
+}
+
+NavMeshGenerator3D::~NavMeshGenerator3D() {
+ cleanup();
+}
+
+void NavMeshGenerator3D::cleanup() {
+ baking_navmesh_mutex.lock();
+ baking_navmeshes.clear();
+ baking_navmesh_mutex.unlock();
+}
+
+void NavMeshGenerator3D::finish() {
+ cleanup();
+}
+
+void NavMeshGenerator3D::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
+ ERR_FAIL_COND(!Thread::is_main_thread());
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(p_root_node == nullptr);
+ ERR_FAIL_COND(!p_root_node->is_inside_tree());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+
+ generator_parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node);
+
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+}
+
+void NavMeshGenerator3D::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
+ ERR_FAIL_COND(!p_navigation_mesh.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data.is_valid());
+ ERR_FAIL_COND(!p_source_geometry_data->has_data());
+
+ baking_navmesh_mutex.lock();
+ if (baking_navmeshes.has(p_navigation_mesh)) {
+ baking_navmesh_mutex.unlock();
+ ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
+ } else {
+ baking_navmeshes.insert(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+ }
+
+ generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data);
+
+ baking_navmesh_mutex.lock();
+ baking_navmeshes.erase(p_navigation_mesh);
+ baking_navmesh_mutex.unlock();
+
+ if (p_callback.is_valid()) {
+ generator_emit_callback(p_callback);
+ }
+}
+
+void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children) {
+ generator_parse_meshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_multimeshinstance3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+ generator_parse_staticbody3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+#ifdef MODULE_CSG_ENABLED
+ generator_parse_csgshape3d_node(p_navigation_mesh, p_source_geometry_data, p_node);
+#endif
+#ifdef MODULE_GRIDMAP_ENABLED
+ generator_parse_gridmap_node(p_navigation_mesh, p_source_geometry_data, p_node);
+#endif
+
+ if (p_recurse_children) {
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children);
+ }
+ }
+}
+
+void NavMeshGenerator3D::generator_parse_meshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node);
+
+ if (mesh_instance) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ Ref<Mesh> mesh = mesh_instance->get_mesh();
+ if (mesh.is_valid()) {
+ p_source_geometry_data->add_mesh(mesh, mesh_instance->get_global_transform());
+ }
+ }
+ }
+}
+
+void NavMeshGenerator3D::generator_parse_multimeshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ MultiMeshInstance3D *multimesh_instance = Object::cast_to<MultiMeshInstance3D>(p_node);
+
+ if (multimesh_instance) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh();
+ if (multimesh.is_valid()) {
+ Ref<Mesh> mesh = multimesh->get_mesh();
+ if (mesh.is_valid()) {
+ int n = multimesh->get_visible_instance_count();
+ if (n == -1) {
+ n = multimesh->get_instance_count();
+ }
+ for (int i = 0; i < n; i++) {
+ p_source_geometry_data->add_mesh(mesh, multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i));
+ }
+ }
+ }
+ }
+ }
+}
+
+void NavMeshGenerator3D::generator_parse_staticbody3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ StaticBody3D *static_body = Object::cast_to<StaticBody3D>(p_node);
+
+ if (static_body) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
+
+ if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (static_body->get_collision_layer() & parsed_collision_mask)) {
+ List<uint32_t> shape_owners;
+ static_body->get_shape_owners(&shape_owners);
+ for (uint32_t shape_owner : shape_owners) {
+ if (static_body->is_shape_owner_disabled(shape_owner)) {
+ continue;
+ }
+ const int shape_count = static_body->shape_owner_get_shape_count(shape_owner);
+ for (int shape_index = 0; shape_index < shape_count; shape_index++) {
+ Ref<Shape3D> s = static_body->shape_owner_get_shape(shape_owner, shape_index);
+ if (s.is_null()) {
+ continue;
+ }
+
+ const Transform3D transform = static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner);
+
+ BoxShape3D *box = Object::cast_to<BoxShape3D>(*s);
+ if (box) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ BoxMesh::create_mesh_array(arr, box->get_size());
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ CapsuleShape3D *capsule = Object::cast_to<CapsuleShape3D>(*s);
+ if (capsule) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CapsuleMesh::create_mesh_array(arr, capsule->get_radius(), capsule->get_height());
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ CylinderShape3D *cylinder = Object::cast_to<CylinderShape3D>(*s);
+ if (cylinder) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CylinderMesh::create_mesh_array(arr, cylinder->get_radius(), cylinder->get_radius(), cylinder->get_height());
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ SphereShape3D *sphere = Object::cast_to<SphereShape3D>(*s);
+ if (sphere) {
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ SphereMesh::create_mesh_array(arr, sphere->get_radius(), sphere->get_radius() * 2.0);
+ p_source_geometry_data->add_mesh_array(arr, transform);
+ }
+
+ ConcavePolygonShape3D *concave_polygon = Object::cast_to<ConcavePolygonShape3D>(*s);
+ if (concave_polygon) {
+ p_source_geometry_data->add_faces(concave_polygon->get_faces(), transform);
+ }
+
+ ConvexPolygonShape3D *convex_polygon = Object::cast_to<ConvexPolygonShape3D>(*s);
+ if (convex_polygon) {
+ Vector<Vector3> varr = Variant(convex_polygon->get_points());
+ Geometry3D::MeshData md;
+
+ Error err = ConvexHullComputer::convex_hull(varr, md);
+
+ if (err == OK) {
+ PackedVector3Array faces;
+
+ for (const Geometry3D::MeshData::Face &face : md.faces) {
+ for (uint32_t k = 2; k < face.indices.size(); ++k) {
+ faces.push_back(md.vertices[face.indices[0]]);
+ faces.push_back(md.vertices[face.indices[k - 1]]);
+ faces.push_back(md.vertices[face.indices[k]]);
+ }
+ }
+
+ p_source_geometry_data->add_faces(faces, transform);
+ }
+ }
+
+ HeightMapShape3D *heightmap_shape = Object::cast_to<HeightMapShape3D>(*s);
+ if (heightmap_shape) {
+ int heightmap_depth = heightmap_shape->get_map_depth();
+ int heightmap_width = heightmap_shape->get_map_width();
+
+ if (heightmap_depth >= 2 && heightmap_width >= 2) {
+ const Vector<real_t> &map_data = heightmap_shape->get_map_data();
+
+ Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
+ Vector2 start = heightmap_gridsize * -0.5;
+
+ Vector<Vector3> vertex_array;
+ vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
+ int map_data_current_index = 0;
+
+ for (int d = 0; d < heightmap_depth; d++) {
+ for (int w = 0; w < heightmap_width; w++) {
+ if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
+ float top_left_height = map_data[map_data_current_index];
+ float top_right_height = map_data[map_data_current_index + 1];
+ float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
+ float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
+
+ Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
+ Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
+ Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
+ Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
+
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_left);
+ vertex_array.push_back(top_left);
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_right);
+ vertex_array.push_back(bottom_left);
+ }
+ map_data_current_index += 1;
+ }
+ }
+ if (vertex_array.size() > 0) {
+ p_source_geometry_data->add_faces(vertex_array, transform);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#ifdef MODULE_CSG_ENABLED
+void NavMeshGenerator3D::generator_parse_csgshape3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ CSGShape3D *csgshape3d = Object::cast_to<CSGShape3D>(p_node);
+
+ if (csgshape3d) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS && csgshape3d->is_using_collision() && (csgshape3d->get_collision_layer() & parsed_collision_mask)) || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_node);
+ Array meshes = csg_shape->get_meshes();
+ if (!meshes.is_empty()) {
+ Ref<Mesh> mesh = meshes[1];
+ if (mesh.is_valid()) {
+ p_source_geometry_data->add_mesh(mesh, csg_shape->get_global_transform());
+ }
+ }
+ }
+ }
+}
+#endif // MODULE_CSG_ENABLED
+
+#ifdef MODULE_GRIDMAP_ENABLED
+void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
+ GridMap *gridmap = Object::cast_to<GridMap>(p_node);
+
+ if (gridmap) {
+ NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
+ uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
+
+ if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
+ Array meshes = gridmap->get_meshes();
+ Transform3D xform = gridmap->get_global_transform();
+ for (int i = 0; i < meshes.size(); i += 2) {
+ Ref<Mesh> mesh = meshes[i + 1];
+ if (mesh.is_valid()) {
+ p_source_geometry_data->add_mesh(mesh, xform * (Transform3D)meshes[i]);
+ }
+ }
+ }
+
+ else if ((parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) && (gridmap->get_collision_layer() & parsed_collision_mask)) {
+ Array shapes = gridmap->get_collision_shapes();
+ for (int i = 0; i < shapes.size(); i += 2) {
+ RID shape = shapes[i + 1];
+ PhysicsServer3D::ShapeType type = PhysicsServer3D::get_singleton()->shape_get_type(shape);
+ Variant data = PhysicsServer3D::get_singleton()->shape_get_data(shape);
+
+ switch (type) {
+ case PhysicsServer3D::SHAPE_SPHERE: {
+ real_t radius = data;
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ SphereMesh::create_mesh_array(arr, radius, radius * 2.0);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_BOX: {
+ Vector3 extents = data;
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ BoxMesh::create_mesh_array(arr, extents * 2.0);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_CAPSULE: {
+ Dictionary dict = data;
+ real_t radius = dict["radius"];
+ real_t height = dict["height"];
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CapsuleMesh::create_mesh_array(arr, radius, height);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_CYLINDER: {
+ Dictionary dict = data;
+ real_t radius = dict["radius"];
+ real_t height = dict["height"];
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ CylinderMesh::create_mesh_array(arr, radius, radius, height);
+ p_source_geometry_data->add_mesh_array(arr, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_CONVEX_POLYGON: {
+ PackedVector3Array vertices = data;
+ Geometry3D::MeshData md;
+
+ Error err = ConvexHullComputer::convex_hull(vertices, md);
+
+ if (err == OK) {
+ PackedVector3Array faces;
+
+ for (const Geometry3D::MeshData::Face &face : md.faces) {
+ for (uint32_t k = 2; k < face.indices.size(); ++k) {
+ faces.push_back(md.vertices[face.indices[0]]);
+ faces.push_back(md.vertices[face.indices[k - 1]]);
+ faces.push_back(md.vertices[face.indices[k]]);
+ }
+ }
+
+ p_source_geometry_data->add_faces(faces, shapes[i]);
+ }
+ } break;
+ case PhysicsServer3D::SHAPE_CONCAVE_POLYGON: {
+ Dictionary dict = data;
+ PackedVector3Array faces = Variant(dict["faces"]);
+ p_source_geometry_data->add_faces(faces, shapes[i]);
+ } break;
+ case PhysicsServer3D::SHAPE_HEIGHTMAP: {
+ Dictionary dict = data;
+ ///< dict( int:"width", int:"depth",float:"cell_size", float_array:"heights"
+ int heightmap_depth = dict["depth"];
+ int heightmap_width = dict["width"];
+
+ if (heightmap_depth >= 2 && heightmap_width >= 2) {
+ const Vector<real_t> &map_data = dict["heights"];
+
+ Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
+ Vector2 start = heightmap_gridsize * -0.5;
+
+ Vector<Vector3> vertex_array;
+ vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
+ int map_data_current_index = 0;
+
+ for (int d = 0; d < heightmap_depth; d++) {
+ for (int w = 0; w < heightmap_width; w++) {
+ if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
+ float top_left_height = map_data[map_data_current_index];
+ float top_right_height = map_data[map_data_current_index + 1];
+ float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
+ float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
+
+ Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
+ Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
+ Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
+ Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
+
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_left);
+ vertex_array.push_back(top_left);
+ vertex_array.push_back(top_right);
+ vertex_array.push_back(bottom_right);
+ vertex_array.push_back(bottom_left);
+ }
+ map_data_current_index += 1;
+ }
+ }
+ if (vertex_array.size() > 0) {
+ p_source_geometry_data->add_faces(vertex_array, shapes[i]);
+ }
+ }
+ } break;
+ default: {
+ WARN_PRINT("Unsupported collision shape type.");
+ } break;
+ }
+ }
+ }
+ }
+}
+#endif // MODULE_GRIDMAP_ENABLED
+
+void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node) {
+ List<Node *> parse_nodes;
+
+ if (p_navigation_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
+ parse_nodes.push_back(p_root_node);
+ } else {
+ p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_group_name(), &parse_nodes);
+ }
+
+ Transform3D root_node_transform = Transform3D();
+ if (Object::cast_to<Node3D>(p_root_node)) {
+ root_node_transform = Object::cast_to<Node3D>(p_root_node)->get_global_transform().affine_inverse();
+ }
+
+ p_source_geometry_data->clear();
+ p_source_geometry_data->root_node_transform = root_node_transform;
+
+ bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT;
+
+ for (Node *parse_node : parse_nodes) {
+ generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, parse_node, recurse_children);
+ }
+};
+
+void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data) {
+ if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) {
+ return;
+ }
+
+ const Vector<float> vertices = p_source_geometry_data->get_vertices();
+ const Vector<int> indices = p_source_geometry_data->get_indices();
+
+ if (vertices.size() < 3 || indices.size() < 3) {
+ return;
+ }
+
+ rcHeightfield *hf = nullptr;
+ rcCompactHeightfield *chf = nullptr;
+ rcContourSet *cset = nullptr;
+ rcPolyMesh *poly_mesh = nullptr;
+ rcPolyMeshDetail *detail_mesh = nullptr;
+ rcContext ctx;
+
+ // added to keep track of steps, no functionality right now
+ String bake_state = "";
+
+ bake_state = "Setting up Configuration..."; // step #1
+
+ const float *verts = vertices.ptr();
+ const int nverts = vertices.size() / 3;
+ const int *tris = indices.ptr();
+ const int ntris = indices.size() / 3;
+
+ float bmin[3], bmax[3];
+ rcCalcBounds(verts, nverts, bmin, bmax);
+
+ rcConfig cfg;
+ memset(&cfg, 0, sizeof(cfg));
+
+ cfg.cs = p_navigation_mesh->get_cell_size();
+ cfg.ch = p_navigation_mesh->get_cell_height();
+ 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);
+ cfg.walkableRadius = (int)Math::ceil(p_navigation_mesh->get_agent_radius() / cfg.cs);
+ cfg.maxEdgeLen = (int)(p_navigation_mesh->get_edge_max_length() / p_navigation_mesh->get_cell_size());
+ cfg.maxSimplificationError = p_navigation_mesh->get_edge_max_error();
+ cfg.minRegionArea = (int)(p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size());
+ cfg.mergeRegionArea = (int)(p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size());
+ cfg.maxVertsPerPoly = (int)p_navigation_mesh->get_vertices_per_polygon();
+ 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 (!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.");
+ }
+ if (!Math::is_equal_approx((float)cfg.walkableClimb * cfg.ch, p_navigation_mesh->get_agent_max_climb())) {
+ WARN_PRINT("Property agent_max_climb is floored to cell_height voxel units and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.walkableRadius * cfg.cs, p_navigation_mesh->get_agent_radius())) {
+ WARN_PRINT("Property agent_radius is ceiled to cell_size voxel units and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.maxEdgeLen * cfg.cs, p_navigation_mesh->get_edge_max_length())) {
+ WARN_PRINT("Property edge_max_length is rounded to cell_size voxel units and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.minRegionArea, p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size())) {
+ WARN_PRINT("Property region_min_size is converted to int and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.mergeRegionArea, p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size())) {
+ WARN_PRINT("Property region_merge_size is converted to int and loses precision.");
+ }
+ if (!Math::is_equal_approx((float)cfg.maxVertsPerPoly, p_navigation_mesh->get_vertices_per_polygon())) {
+ WARN_PRINT("Property vertices_per_polygon is converted to int and loses precision.");
+ }
+ if (p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance() < 0.1f) {
+ WARN_PRINT("Property detail_sample_distance is clamped to 0.1 world units as the resulting value from multiplying with cell_size is too low.");
+ }
+
+ cfg.bmin[0] = bmin[0];
+ cfg.bmin[1] = bmin[1];
+ cfg.bmin[2] = bmin[2];
+ cfg.bmax[0] = bmax[0];
+ cfg.bmax[1] = bmax[1];
+ cfg.bmax[2] = bmax[2];
+
+ AABB baking_aabb = p_navigation_mesh->get_filter_baking_aabb();
+ if (baking_aabb.has_volume()) {
+ Vector3 baking_aabb_offset = p_navigation_mesh->get_filter_baking_aabb_offset();
+ cfg.bmin[0] = baking_aabb.position[0] + baking_aabb_offset.x;
+ cfg.bmin[1] = baking_aabb.position[1] + baking_aabb_offset.y;
+ cfg.bmin[2] = baking_aabb.position[2] + baking_aabb_offset.z;
+ cfg.bmax[0] = cfg.bmin[0] + baking_aabb.size[0];
+ cfg.bmax[1] = cfg.bmin[1] + baking_aabb.size[1];
+ cfg.bmax[2] = cfg.bmin[2] + baking_aabb.size[2];
+ }
+
+ bake_state = "Calculating grid size..."; // step #2
+ rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
+
+ // ~30000000 seems to be around sweetspot where Editor baking breaks
+ if ((cfg.width * cfg.height) > 30000000) {
+ WARN_PRINT("NavigationMesh baking process will likely fail."
+ "\nSource geometry is suspiciously big for the current Cell Size and Cell Height in the NavMesh Resource bake settings."
+ "\nIf baking does not fail, the resulting NavigationMesh will create serious pathfinding performance issues."
+ "\nIt is advised to increase Cell Size and/or Cell Height in the NavMesh Resource bake settings or reduce the size / scale of the source geometry.");
+ }
+
+ bake_state = "Creating heightfield..."; // step #3
+ hf = rcAllocHeightfield();
+
+ ERR_FAIL_COND(!hf);
+ ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch));
+
+ bake_state = "Marking walkable triangles..."; // step #4
+ {
+ Vector<unsigned char> tri_areas;
+ tri_areas.resize(ntris);
+
+ ERR_FAIL_COND(tri_areas.size() == 0);
+
+ memset(tri_areas.ptrw(), 0, ntris * sizeof(unsigned char));
+ rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptrw());
+
+ ERR_FAIL_COND(!rcRasterizeTriangles(&ctx, verts, nverts, tris, tri_areas.ptr(), ntris, *hf, cfg.walkableClimb));
+ }
+
+ if (p_navigation_mesh->get_filter_low_hanging_obstacles()) {
+ rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
+ }
+ if (p_navigation_mesh->get_filter_ledge_spans()) {
+ rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
+ }
+ if (p_navigation_mesh->get_filter_walkable_low_height_spans()) {
+ rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
+ }
+
+ bake_state = "Constructing compact heightfield..."; // step #5
+
+ chf = rcAllocCompactHeightfield();
+
+ ERR_FAIL_COND(!chf);
+ ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf));
+
+ rcFreeHeightField(hf);
+ hf = nullptr;
+
+ bake_state = "Eroding walkable area..."; // step #6
+
+ ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf));
+
+ bake_state = "Partitioning..."; // step #7
+
+ 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));
+ } else if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
+ ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
+ } else {
+ ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
+ }
+
+ bake_state = "Creating contours..."; // step #8
+
+ cset = rcAllocContourSet();
+
+ ERR_FAIL_COND(!cset);
+ ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset));
+
+ bake_state = "Creating polymesh..."; // step #9
+
+ poly_mesh = rcAllocPolyMesh();
+ ERR_FAIL_COND(!poly_mesh);
+ ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh));
+
+ detail_mesh = rcAllocPolyMeshDetail();
+ ERR_FAIL_COND(!detail_mesh);
+ ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh));
+
+ rcFreeCompactHeightfield(chf);
+ chf = nullptr;
+ rcFreeContourSet(cset);
+ cset = nullptr;
+
+ bake_state = "Converting to native navigation mesh..."; // step #10
+
+ Vector<Vector3> nav_vertices;
+
+ for (int i = 0; i < detail_mesh->nverts; i++) {
+ const float *v = &detail_mesh->verts[i * 3];
+ nav_vertices.push_back(Vector3(v[0], v[1], v[2]));
+ }
+ p_navigation_mesh->set_vertices(nav_vertices);
+ p_navigation_mesh->clear_polygons();
+
+ for (int i = 0; i < detail_mesh->nmeshes; i++) {
+ const unsigned int *detail_mesh_m = &detail_mesh->meshes[i * 4];
+ const unsigned int detail_mesh_bverts = detail_mesh_m[0];
+ const unsigned int detail_mesh_m_btris = detail_mesh_m[2];
+ const unsigned int detail_mesh_ntris = detail_mesh_m[3];
+ const unsigned char *detail_mesh_tris = &detail_mesh->tris[detail_mesh_m_btris * 4];
+ for (unsigned int j = 0; j < detail_mesh_ntris; j++) {
+ Vector<int> nav_indices;
+ nav_indices.resize(3);
+ // Polygon order in recast is opposite than godot's
+ nav_indices.write[0] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0]));
+ nav_indices.write[1] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2]));
+ nav_indices.write[2] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1]));
+ p_navigation_mesh->add_polygon(nav_indices);
+ }
+ }
+
+ bake_state = "Cleanup..."; // step #11
+
+ rcFreePolyMesh(poly_mesh);
+ poly_mesh = nullptr;
+ rcFreePolyMeshDetail(detail_mesh);
+ detail_mesh = nullptr;
+
+ bake_state = "Baking finished."; // step #12
+}
+
+bool NavMeshGenerator3D::generator_emit_callback(const Callable &p_callback) {
+ ERR_FAIL_COND_V(!p_callback.is_valid(), false);
+
+ Callable::CallError ce;
+ Variant result;
+ p_callback.callp(nullptr, 0, result, ce);
+
+ return ce.error == Callable::CallError::CALL_OK;
+}
+
+#endif // _3D_DISABLED
diff --git a/modules/navigation/nav_mesh_generator_3d.h b/modules/navigation/nav_mesh_generator_3d.h
new file mode 100644
index 0000000000..dc844c2595
--- /dev/null
+++ b/modules/navigation/nav_mesh_generator_3d.h
@@ -0,0 +1,81 @@
+/**************************************************************************/
+/* nav_mesh_generator_3d.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 NAV_MESH_GENERATOR_3D_H
+#define NAV_MESH_GENERATOR_3D_H
+
+#ifndef _3D_DISABLED
+
+#include "core/object/class_db.h"
+#include "modules/modules_enabled.gen.h" // For csg, gridmap.
+
+class Node;
+class NavigationMesh;
+class NavigationMeshSourceGeometryData3D;
+
+class NavMeshGenerator3D : public Object {
+ static NavMeshGenerator3D *singleton;
+
+ static Mutex baking_navmesh_mutex;
+
+ static HashSet<Ref<NavigationMesh>> baking_navmeshes;
+
+ static void generator_parse_geometry_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node, bool p_recurse_children);
+ static void generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node);
+ static void generator_bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data);
+
+ static void generator_parse_meshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_multimeshinstance3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+ static void generator_parse_staticbody3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+#ifdef MODULE_CSG_ENABLED
+ static void generator_parse_csgshape3d_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+#endif // MODULE_CSG_ENABLED
+#ifdef MODULE_GRIDMAP_ENABLED
+ static void generator_parse_gridmap_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
+#endif // MODULE_GRIDMAP_ENABLED
+
+ static bool generator_emit_callback(const Callable &p_callback);
+
+public:
+ static NavMeshGenerator3D *get_singleton();
+
+ static void cleanup();
+ static void finish();
+
+ static void parse_source_geometry_data(const 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, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable());
+
+ NavMeshGenerator3D();
+ ~NavMeshGenerator3D();
+};
+
+#endif // _3D_DISABLED
+
+#endif // NAV_MESH_GENERATOR_3D_H
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 9c0ce3e71e..4e7964ed76 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -51,6 +51,16 @@ void NavRegion::set_map(NavMap *p_map) {
}
}
+void NavRegion::set_enabled(bool p_enabled) {
+ if (enabled == p_enabled) {
+ return;
+ }
+ enabled = p_enabled;
+
+ // TODO: This should not require a full rebuild as the region has not really changed.
+ polygons_dirty = true;
+};
+
void NavRegion::set_use_edge_connections(bool p_enabled) {
if (use_edge_connections != p_enabled) {
use_edge_connections = p_enabled;
diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h
index 0c3c1b56b6..6a8ebe5336 100644
--- a/modules/navigation/nav_region.h
+++ b/modules/navigation/nav_region.h
@@ -41,6 +41,7 @@ class NavRegion : public NavBase {
Transform3D transform;
Ref<NavigationMesh> mesh;
Vector<gd::Edge::Connection> connections;
+ bool enabled = true;
bool use_edge_connections = true;
@@ -58,6 +59,9 @@ public:
polygons_dirty = true;
}
+ void set_enabled(bool p_enabled);
+ bool get_enabled() const { return enabled; }
+
void set_map(NavMap *p_map);
NavMap *get_map() const {
return map;
diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp
index 89afb4a8ea..8393896db1 100644
--- a/modules/navigation/navigation_mesh_generator.cpp
+++ b/modules/navigation/navigation_mesh_generator.cpp
@@ -32,451 +32,11 @@
#include "navigation_mesh_generator.h"
-#include "core/math/convex_hull.h"
-#include "core/os/thread.h"
-#include "scene/3d/mesh_instance_3d.h"
-#include "scene/3d/multimesh_instance_3d.h"
-#include "scene/3d/physics_body_3d.h"
-#include "scene/resources/box_shape_3d.h"
-#include "scene/resources/capsule_shape_3d.h"
-#include "scene/resources/concave_polygon_shape_3d.h"
-#include "scene/resources/convex_polygon_shape_3d.h"
-#include "scene/resources/cylinder_shape_3d.h"
-#include "scene/resources/height_map_shape_3d.h"
#include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
-#include "scene/resources/primitive_meshes.h"
-#include "scene/resources/shape_3d.h"
-#include "scene/resources/sphere_shape_3d.h"
-#include "scene/resources/world_boundary_shape_3d.h"
-
-#ifdef TOOLS_ENABLED
-#include "editor/editor_node.h"
-#endif
-
-#include "modules/modules_enabled.gen.h" // For csg, gridmap.
-
-#ifdef MODULE_CSG_ENABLED
-#include "modules/csg/csg_shape.h"
-#endif
-#ifdef MODULE_GRIDMAP_ENABLED
-#include "modules/gridmap/grid_map.h"
-#endif
+#include "servers/navigation_server_3d.h"
NavigationMeshGenerator *NavigationMeshGenerator::singleton = nullptr;
-void NavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices) {
- p_vertices.push_back(p_vec3.x);
- p_vertices.push_back(p_vec3.y);
- p_vertices.push_back(p_vec3.z);
-}
-
-void NavigationMeshGenerator::_add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices) {
- int current_vertex_count;
-
- for (int i = 0; i < p_mesh->get_surface_count(); i++) {
- current_vertex_count = p_vertices.size() / 3;
-
- if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) {
- continue;
- }
-
- int index_count = 0;
- if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
- index_count = p_mesh->surface_get_array_index_len(i);
- } else {
- index_count = p_mesh->surface_get_array_len(i);
- }
-
- ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
-
- int face_count = index_count / 3;
-
- Array a = p_mesh->surface_get_arrays(i);
- ERR_CONTINUE(a.is_empty() || (a.size() != Mesh::ARRAY_MAX));
-
- Vector<Vector3> mesh_vertices = a[Mesh::ARRAY_VERTEX];
- ERR_CONTINUE(mesh_vertices.is_empty());
- const Vector3 *vr = mesh_vertices.ptr();
-
- if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
- Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
- ERR_CONTINUE(mesh_indices.is_empty() || (mesh_indices.size() != index_count));
- const int *ir = mesh_indices.ptr();
-
- for (int j = 0; j < mesh_vertices.size(); j++) {
- _add_vertex(p_xform.xform(vr[j]), p_vertices);
- }
-
- for (int j = 0; j < face_count; j++) {
- // CCW
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 0]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 2]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 1]));
- }
- } else {
- ERR_CONTINUE(mesh_vertices.size() != index_count);
- face_count = mesh_vertices.size() / 3;
- for (int j = 0; j < face_count; j++) {
- _add_vertex(p_xform.xform(vr[j * 3 + 0]), p_vertices);
- _add_vertex(p_xform.xform(vr[j * 3 + 2]), p_vertices);
- _add_vertex(p_xform.xform(vr[j * 3 + 1]), p_vertices);
-
- p_indices.push_back(current_vertex_count + (j * 3 + 0));
- p_indices.push_back(current_vertex_count + (j * 3 + 1));
- p_indices.push_back(current_vertex_count + (j * 3 + 2));
- }
- }
- }
-}
-
-void NavigationMeshGenerator::_add_mesh_array(const Array &p_array, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices) {
- ERR_FAIL_COND(p_array.size() != Mesh::ARRAY_MAX);
-
- Vector<Vector3> mesh_vertices = p_array[Mesh::ARRAY_VERTEX];
- ERR_FAIL_COND(mesh_vertices.is_empty());
- const Vector3 *vr = mesh_vertices.ptr();
-
- Vector<int> mesh_indices = p_array[Mesh::ARRAY_INDEX];
- ERR_FAIL_COND(mesh_indices.is_empty());
- const int *ir = mesh_indices.ptr();
-
- const int face_count = mesh_indices.size() / 3;
- const int current_vertex_count = p_vertices.size() / 3;
-
- for (int j = 0; j < mesh_vertices.size(); j++) {
- _add_vertex(p_xform.xform(vr[j]), p_vertices);
- }
-
- for (int j = 0; j < face_count; j++) {
- // CCW
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 0]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 2]));
- p_indices.push_back(current_vertex_count + (ir[j * 3 + 1]));
- }
-}
-
-void NavigationMeshGenerator::_add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices) {
- ERR_FAIL_COND(p_faces.is_empty());
- ERR_FAIL_COND(p_faces.size() % 3 != 0);
- int face_count = p_faces.size() / 3;
- int current_vertex_count = p_vertices.size() / 3;
-
- for (int j = 0; j < face_count; j++) {
- _add_vertex(p_xform.xform(p_faces[j * 3 + 0]), p_vertices);
- _add_vertex(p_xform.xform(p_faces[j * 3 + 1]), p_vertices);
- _add_vertex(p_xform.xform(p_faces[j * 3 + 2]), p_vertices);
-
- p_indices.push_back(current_vertex_count + (j * 3 + 0));
- p_indices.push_back(current_vertex_count + (j * 3 + 2));
- p_indices.push_back(current_vertex_count + (j * 3 + 1));
- }
-}
-
-void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) {
- if (Object::cast_to<MeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node);
- Ref<Mesh> mesh = mesh_instance->get_mesh();
- if (mesh.is_valid()) {
- _add_mesh(mesh, p_navmesh_transform * mesh_instance->get_global_transform(), p_vertices, p_indices);
- }
- }
-
- if (Object::cast_to<MultiMeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- MultiMeshInstance3D *multimesh_instance = Object::cast_to<MultiMeshInstance3D>(p_node);
- Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh();
- if (multimesh.is_valid()) {
- Ref<Mesh> mesh = multimesh->get_mesh();
- if (mesh.is_valid()) {
- int n = multimesh->get_visible_instance_count();
- if (n == -1) {
- n = multimesh->get_instance_count();
- }
- for (int i = 0; i < n; i++) {
- _add_mesh(mesh, p_navmesh_transform * multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i), p_vertices, p_indices);
- }
- }
- }
- }
-
-#ifdef MODULE_CSG_ENABLED
- if (Object::cast_to<CSGShape3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_node);
- Array meshes = csg_shape->get_meshes();
- if (!meshes.is_empty()) {
- Ref<Mesh> mesh = meshes[1];
- if (mesh.is_valid()) {
- _add_mesh(mesh, p_navmesh_transform * csg_shape->get_global_transform(), p_vertices, p_indices);
- }
- }
- }
-#endif
-
- if (Object::cast_to<StaticBody3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES) {
- StaticBody3D *static_body = Object::cast_to<StaticBody3D>(p_node);
-
- if (static_body->get_collision_layer() & p_collision_mask) {
- List<uint32_t> shape_owners;
- static_body->get_shape_owners(&shape_owners);
- for (uint32_t shape_owner : shape_owners) {
- if (static_body->is_shape_owner_disabled(shape_owner)) {
- continue;
- }
- const int shape_count = static_body->shape_owner_get_shape_count(shape_owner);
- for (int i = 0; i < shape_count; i++) {
- Ref<Shape3D> s = static_body->shape_owner_get_shape(shape_owner, i);
- if (s.is_null()) {
- continue;
- }
-
- const Transform3D transform = p_navmesh_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner);
-
- BoxShape3D *box = Object::cast_to<BoxShape3D>(*s);
- if (box) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- BoxMesh::create_mesh_array(arr, box->get_size());
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- CapsuleShape3D *capsule = Object::cast_to<CapsuleShape3D>(*s);
- if (capsule) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CapsuleMesh::create_mesh_array(arr, capsule->get_radius(), capsule->get_height());
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- CylinderShape3D *cylinder = Object::cast_to<CylinderShape3D>(*s);
- if (cylinder) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CylinderMesh::create_mesh_array(arr, cylinder->get_radius(), cylinder->get_radius(), cylinder->get_height());
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- SphereShape3D *sphere = Object::cast_to<SphereShape3D>(*s);
- if (sphere) {
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- SphereMesh::create_mesh_array(arr, sphere->get_radius(), sphere->get_radius() * 2.0);
- _add_mesh_array(arr, transform, p_vertices, p_indices);
- }
-
- ConcavePolygonShape3D *concave_polygon = Object::cast_to<ConcavePolygonShape3D>(*s);
- if (concave_polygon) {
- _add_faces(concave_polygon->get_faces(), transform, p_vertices, p_indices);
- }
-
- ConvexPolygonShape3D *convex_polygon = Object::cast_to<ConvexPolygonShape3D>(*s);
- if (convex_polygon) {
- Vector<Vector3> varr = Variant(convex_polygon->get_points());
- Geometry3D::MeshData md;
-
- Error err = ConvexHullComputer::convex_hull(varr, md);
-
- if (err == OK) {
- PackedVector3Array faces;
-
- for (const Geometry3D::MeshData::Face &face : md.faces) {
- for (uint32_t k = 2; k < face.indices.size(); ++k) {
- faces.push_back(md.vertices[face.indices[0]]);
- faces.push_back(md.vertices[face.indices[k - 1]]);
- faces.push_back(md.vertices[face.indices[k]]);
- }
- }
-
- _add_faces(faces, transform, p_vertices, p_indices);
- }
- }
-
- HeightMapShape3D *heightmap_shape = Object::cast_to<HeightMapShape3D>(*s);
- if (heightmap_shape) {
- int heightmap_depth = heightmap_shape->get_map_depth();
- int heightmap_width = heightmap_shape->get_map_width();
-
- if (heightmap_depth >= 2 && heightmap_width >= 2) {
- const Vector<real_t> &map_data = heightmap_shape->get_map_data();
-
- Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
- Vector2 start = heightmap_gridsize * -0.5;
-
- Vector<Vector3> vertex_array;
- vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
- int map_data_current_index = 0;
-
- for (int d = 0; d < heightmap_depth; d++) {
- for (int w = 0; w < heightmap_width; w++) {
- if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
- float top_left_height = map_data[map_data_current_index];
- float top_right_height = map_data[map_data_current_index + 1];
- float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
- float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
-
- Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
- Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
- Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
- Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
-
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_left);
- vertex_array.push_back(top_left);
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_right);
- vertex_array.push_back(bottom_left);
- }
- map_data_current_index += 1;
- }
- }
- if (vertex_array.size() > 0) {
- _add_faces(vertex_array, transform, p_vertices, p_indices);
- }
- }
- }
- }
- }
- }
- }
-
-#ifdef MODULE_GRIDMAP_ENABLED
- GridMap *gridmap = Object::cast_to<GridMap>(p_node);
-
- if (gridmap) {
- if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) {
- Array meshes = gridmap->get_meshes();
- Transform3D xform = gridmap->get_global_transform();
- for (int i = 0; i < meshes.size(); i += 2) {
- Ref<Mesh> mesh = meshes[i + 1];
- if (mesh.is_valid()) {
- _add_mesh(mesh, p_navmesh_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices);
- }
- }
- }
-
- if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES && (gridmap->get_collision_layer() & p_collision_mask)) {
- Array shapes = gridmap->get_collision_shapes();
- for (int i = 0; i < shapes.size(); i += 2) {
- RID shape = shapes[i + 1];
- PhysicsServer3D::ShapeType type = PhysicsServer3D::get_singleton()->shape_get_type(shape);
- Variant data = PhysicsServer3D::get_singleton()->shape_get_data(shape);
-
- switch (type) {
- case PhysicsServer3D::SHAPE_SPHERE: {
- real_t radius = data;
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- SphereMesh::create_mesh_array(arr, radius, radius * 2.0);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_BOX: {
- Vector3 extents = data;
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- BoxMesh::create_mesh_array(arr, extents * 2.0);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_CAPSULE: {
- Dictionary dict = data;
- real_t radius = dict["radius"];
- real_t height = dict["height"];
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CapsuleMesh::create_mesh_array(arr, radius, height);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_CYLINDER: {
- Dictionary dict = data;
- real_t radius = dict["radius"];
- real_t height = dict["height"];
- Array arr;
- arr.resize(RS::ARRAY_MAX);
- CylinderMesh::create_mesh_array(arr, radius, radius, height);
- _add_mesh_array(arr, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_CONVEX_POLYGON: {
- PackedVector3Array vertices = data;
- Geometry3D::MeshData md;
-
- Error err = ConvexHullComputer::convex_hull(vertices, md);
-
- if (err == OK) {
- PackedVector3Array faces;
-
- for (const Geometry3D::MeshData::Face &face : md.faces) {
- for (uint32_t k = 2; k < face.indices.size(); ++k) {
- faces.push_back(md.vertices[face.indices[0]]);
- faces.push_back(md.vertices[face.indices[k - 1]]);
- faces.push_back(md.vertices[face.indices[k]]);
- }
- }
-
- _add_faces(faces, shapes[i], p_vertices, p_indices);
- }
- } break;
- case PhysicsServer3D::SHAPE_CONCAVE_POLYGON: {
- Dictionary dict = data;
- PackedVector3Array faces = Variant(dict["faces"]);
- _add_faces(faces, shapes[i], p_vertices, p_indices);
- } break;
- case PhysicsServer3D::SHAPE_HEIGHTMAP: {
- Dictionary dict = data;
- ///< dict( int:"width", int:"depth",float:"cell_size", float_array:"heights"
- int heightmap_depth = dict["depth"];
- int heightmap_width = dict["width"];
-
- if (heightmap_depth >= 2 && heightmap_width >= 2) {
- const Vector<real_t> &map_data = dict["heights"];
-
- Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1);
- Vector2 start = heightmap_gridsize * -0.5;
-
- Vector<Vector3> vertex_array;
- vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6);
- int map_data_current_index = 0;
-
- for (int d = 0; d < heightmap_depth; d++) {
- for (int w = 0; w < heightmap_width; w++) {
- if (map_data_current_index + 1 + heightmap_depth < map_data.size()) {
- float top_left_height = map_data[map_data_current_index];
- float top_right_height = map_data[map_data_current_index + 1];
- float bottom_left_height = map_data[map_data_current_index + heightmap_depth];
- float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth];
-
- Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d);
- Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d);
- Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0);
- Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0);
-
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_left);
- vertex_array.push_back(top_left);
- vertex_array.push_back(top_right);
- vertex_array.push_back(bottom_right);
- vertex_array.push_back(bottom_left);
- }
- map_data_current_index += 1;
- }
- }
- if (vertex_array.size() > 0) {
- _add_faces(vertex_array, shapes[i], p_vertices, p_indices);
- }
- }
- } break;
- default: {
- WARN_PRINT("Unsupported collision shape type.");
- } break;
- }
- }
- }
- }
-#endif
-
- if (p_recurse_children) {
- for (int i = 0; i < p_node->get_child_count(); i++) {
- _parse_geometry(p_navmesh_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children);
- }
- }
-}
-
NavigationMeshGenerator *NavigationMeshGenerator::get_singleton() {
return singleton;
}
@@ -500,285 +60,11 @@ void NavigationMeshGenerator::clear(Ref<NavigationMesh> p_navigation_mesh) {
}
void NavigationMeshGenerator::parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) {
- ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
- ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
- ERR_FAIL_COND_MSG(p_root_node == nullptr, "No parsing root node specified.");
- ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree.");
-
- Vector<float> vertices;
- Vector<int> indices;
-
- List<Node *> parse_nodes;
-
- if (p_navigation_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) {
- parse_nodes.push_back(p_root_node);
- } else {
- p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_group_name(), &parse_nodes);
- }
-
- Transform3D navmesh_xform = Transform3D();
- if (Object::cast_to<Node3D>(p_root_node)) {
- navmesh_xform = Object::cast_to<Node3D>(p_root_node)->get_global_transform().affine_inverse();
- }
- for (Node *E : parse_nodes) {
- NavigationMesh::ParsedGeometryType geometry_type = p_navigation_mesh->get_parsed_geometry_type();
- uint32_t collision_mask = p_navigation_mesh->get_collision_mask();
- bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT;
- _parse_geometry(navmesh_xform, E, vertices, indices, geometry_type, collision_mask, recurse_children);
- }
-
- p_source_geometry_data->set_vertices(vertices);
- p_source_geometry_data->set_indices(indices);
-
- if (p_callback.is_valid()) {
- Callable::CallError ce;
- Variant result;
- p_callback.callp(nullptr, 0, result, ce);
- if (ce.error == Callable::CallError::CALL_OK) {
- //
- }
- }
+ NavigationServer3D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback);
}
void NavigationMeshGenerator::bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback) {
- ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh.");
- ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData3D.");
- ERR_FAIL_COND_MSG(!p_source_geometry_data->has_data(), "NavigationMeshSourceGeometryData3D is empty. Parse source geometry first.");
-
- generator_mutex.lock();
- if (baking_navmeshes.has(p_navigation_mesh)) {
- generator_mutex.unlock();
- ERR_FAIL_MSG("NavigationMesh is already baking. Wait for current bake to finish.");
- } else {
- baking_navmeshes.insert(p_navigation_mesh);
- generator_mutex.unlock();
- }
-
-#ifndef _3D_DISABLED
- const Vector<float> vertices = p_source_geometry_data->get_vertices();
- const Vector<int> indices = p_source_geometry_data->get_indices();
-
- if (vertices.size() < 3 || indices.size() < 3) {
- return;
- }
-
- rcHeightfield *hf = nullptr;
- rcCompactHeightfield *chf = nullptr;
- rcContourSet *cset = nullptr;
- rcPolyMesh *poly_mesh = nullptr;
- rcPolyMeshDetail *detail_mesh = nullptr;
- rcContext ctx;
-
- // added to keep track of steps, no functionality right now
- String bake_state = "";
-
- bake_state = "Setting up Configuration..."; // step #1
-
- const float *verts = vertices.ptr();
- const int nverts = vertices.size() / 3;
- const int *tris = indices.ptr();
- const int ntris = indices.size() / 3;
-
- float bmin[3], bmax[3];
- rcCalcBounds(verts, nverts, bmin, bmax);
-
- rcConfig cfg;
- memset(&cfg, 0, sizeof(cfg));
-
- cfg.cs = p_navigation_mesh->get_cell_size();
- cfg.ch = p_navigation_mesh->get_cell_height();
- 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);
- cfg.walkableRadius = (int)Math::ceil(p_navigation_mesh->get_agent_radius() / cfg.cs);
- cfg.maxEdgeLen = (int)(p_navigation_mesh->get_edge_max_length() / p_navigation_mesh->get_cell_size());
- cfg.maxSimplificationError = p_navigation_mesh->get_edge_max_error();
- cfg.minRegionArea = (int)(p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size());
- cfg.mergeRegionArea = (int)(p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size());
- cfg.maxVertsPerPoly = (int)p_navigation_mesh->get_vertices_per_polygon();
- 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 (!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.");
- }
- if (!Math::is_equal_approx((float)cfg.walkableClimb * cfg.ch, p_navigation_mesh->get_agent_max_climb())) {
- WARN_PRINT("Property agent_max_climb is floored to cell_height voxel units and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.walkableRadius * cfg.cs, p_navigation_mesh->get_agent_radius())) {
- WARN_PRINT("Property agent_radius is ceiled to cell_size voxel units and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.maxEdgeLen * cfg.cs, p_navigation_mesh->get_edge_max_length())) {
- WARN_PRINT("Property edge_max_length is rounded to cell_size voxel units and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.minRegionArea, p_navigation_mesh->get_region_min_size() * p_navigation_mesh->get_region_min_size())) {
- WARN_PRINT("Property region_min_size is converted to int and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.mergeRegionArea, p_navigation_mesh->get_region_merge_size() * p_navigation_mesh->get_region_merge_size())) {
- WARN_PRINT("Property region_merge_size is converted to int and loses precision.");
- }
- if (!Math::is_equal_approx((float)cfg.maxVertsPerPoly, p_navigation_mesh->get_vertices_per_polygon())) {
- WARN_PRINT("Property vertices_per_polygon is converted to int and loses precision.");
- }
- if (p_navigation_mesh->get_cell_size() * p_navigation_mesh->get_detail_sample_distance() < 0.1f) {
- WARN_PRINT("Property detail_sample_distance is clamped to 0.1 world units as the resulting value from multiplying with cell_size is too low.");
- }
-
- cfg.bmin[0] = bmin[0];
- cfg.bmin[1] = bmin[1];
- cfg.bmin[2] = bmin[2];
- cfg.bmax[0] = bmax[0];
- cfg.bmax[1] = bmax[1];
- cfg.bmax[2] = bmax[2];
-
- AABB baking_aabb = p_navigation_mesh->get_filter_baking_aabb();
- if (baking_aabb.has_volume()) {
- Vector3 baking_aabb_offset = p_navigation_mesh->get_filter_baking_aabb_offset();
- cfg.bmin[0] = baking_aabb.position[0] + baking_aabb_offset.x;
- cfg.bmin[1] = baking_aabb.position[1] + baking_aabb_offset.y;
- cfg.bmin[2] = baking_aabb.position[2] + baking_aabb_offset.z;
- cfg.bmax[0] = cfg.bmin[0] + baking_aabb.size[0];
- cfg.bmax[1] = cfg.bmin[1] + baking_aabb.size[1];
- cfg.bmax[2] = cfg.bmin[2] + baking_aabb.size[2];
- }
-
- bake_state = "Calculating grid size..."; // step #2
- rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
-
- // ~30000000 seems to be around sweetspot where Editor baking breaks
- if ((cfg.width * cfg.height) > 30000000) {
- WARN_PRINT("NavigationMesh baking process will likely fail."
- "\nSource geometry is suspiciously big for the current Cell Size and Cell Height in the NavMesh Resource bake settings."
- "\nIf baking does not fail, the resulting NavigationMesh will create serious pathfinding performance issues."
- "\nIt is advised to increase Cell Size and/or Cell Height in the NavMesh Resource bake settings or reduce the size / scale of the source geometry.");
- }
-
- bake_state = "Creating heightfield..."; // step #3
- hf = rcAllocHeightfield();
-
- ERR_FAIL_COND(!hf);
- ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch));
-
- bake_state = "Marking walkable triangles..."; // step #4
- {
- Vector<unsigned char> tri_areas;
- tri_areas.resize(ntris);
-
- ERR_FAIL_COND(tri_areas.size() == 0);
-
- memset(tri_areas.ptrw(), 0, ntris * sizeof(unsigned char));
- rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptrw());
-
- ERR_FAIL_COND(!rcRasterizeTriangles(&ctx, verts, nverts, tris, tri_areas.ptr(), ntris, *hf, cfg.walkableClimb));
- }
-
- if (p_navigation_mesh->get_filter_low_hanging_obstacles()) {
- rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
- }
- if (p_navigation_mesh->get_filter_ledge_spans()) {
- rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
- }
- if (p_navigation_mesh->get_filter_walkable_low_height_spans()) {
- rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
- }
-
- bake_state = "Constructing compact heightfield..."; // step #5
-
- chf = rcAllocCompactHeightfield();
-
- ERR_FAIL_COND(!chf);
- ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf));
-
- rcFreeHeightField(hf);
- hf = nullptr;
-
- bake_state = "Eroding walkable area..."; // step #6
-
- ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf));
-
- bake_state = "Partitioning..."; // step #7
-
- 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));
- } else if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
- ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
- } else {
- ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
- }
-
- bake_state = "Creating contours..."; // step #8
-
- cset = rcAllocContourSet();
-
- ERR_FAIL_COND(!cset);
- ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset));
-
- bake_state = "Creating polymesh..."; // step #9
-
- poly_mesh = rcAllocPolyMesh();
- ERR_FAIL_COND(!poly_mesh);
- ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh));
-
- detail_mesh = rcAllocPolyMeshDetail();
- ERR_FAIL_COND(!detail_mesh);
- ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh));
-
- rcFreeCompactHeightfield(chf);
- chf = nullptr;
- rcFreeContourSet(cset);
- cset = nullptr;
-
- bake_state = "Converting to native navigation mesh..."; // step #10
-
- Vector<Vector3> nav_vertices;
-
- for (int i = 0; i < detail_mesh->nverts; i++) {
- const float *v = &detail_mesh->verts[i * 3];
- nav_vertices.push_back(Vector3(v[0], v[1], v[2]));
- }
- p_navigation_mesh->set_vertices(nav_vertices);
- p_navigation_mesh->clear_polygons();
-
- for (int i = 0; i < detail_mesh->nmeshes; i++) {
- const unsigned int *detail_mesh_m = &detail_mesh->meshes[i * 4];
- const unsigned int detail_mesh_bverts = detail_mesh_m[0];
- const unsigned int detail_mesh_m_btris = detail_mesh_m[2];
- const unsigned int detail_mesh_ntris = detail_mesh_m[3];
- const unsigned char *detail_mesh_tris = &detail_mesh->tris[detail_mesh_m_btris * 4];
- for (unsigned int j = 0; j < detail_mesh_ntris; j++) {
- Vector<int> nav_indices;
- nav_indices.resize(3);
- // Polygon order in recast is opposite than godot's
- nav_indices.write[0] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 0]));
- nav_indices.write[1] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 2]));
- nav_indices.write[2] = ((int)(detail_mesh_bverts + detail_mesh_tris[j * 4 + 1]));
- p_navigation_mesh->add_polygon(nav_indices);
- }
- }
-
- bake_state = "Cleanup..."; // step #11
-
- rcFreePolyMesh(poly_mesh);
- poly_mesh = nullptr;
- rcFreePolyMeshDetail(detail_mesh);
- detail_mesh = nullptr;
-
- bake_state = "Baking finished."; // step #12
-#endif // _3D_DISABLED
-
- generator_mutex.lock();
- baking_navmeshes.erase(p_navigation_mesh);
- generator_mutex.unlock();
-
- if (p_callback.is_valid()) {
- Callable::CallError ce;
- Variant result;
- p_callback.callp(nullptr, 0, result, ce);
- if (ce.error == Callable::CallError::CALL_OK) {
- //
- }
- }
+ NavigationServer3D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback);
}
void NavigationMeshGenerator::_bind_methods() {
diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h
index 4bf2b64f44..08fe9f9142 100644
--- a/modules/navigation/navigation_mesh_generator.h
+++ b/modules/navigation/navigation_mesh_generator.h
@@ -36,27 +36,16 @@
#include "scene/3d/navigation_region_3d.h"
#include "scene/resources/navigation_mesh.h"
-#include <Recast.h>
-
class NavigationMeshSourceGeometryData3D;
class NavigationMeshGenerator : public Object {
GDCLASS(NavigationMeshGenerator, Object);
- Mutex generator_mutex;
static NavigationMeshGenerator *singleton;
- HashSet<Ref<NavigationMesh>> baking_navmeshes;
-
protected:
static void _bind_methods();
- static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices);
- static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices);
- static void _add_mesh_array(const Array &p_array, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices);
- static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices);
- static void _parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children);
-
public:
static NavigationMeshGenerator *get_singleton();
diff --git a/modules/navigation/register_types.cpp b/modules/navigation/register_types.cpp
index 1401833d0e..1548ff4b9c 100644
--- a/modules/navigation/register_types.cpp
+++ b/modules/navigation/register_types.cpp
@@ -32,9 +32,11 @@
#include "godot_navigation_server.h"
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
#include "navigation_mesh_generator.h"
#endif
+#endif // DISABLE_DEPRECATED
#ifdef TOOLS_ENABLED
#include "editor/navigation_mesh_editor_plugin.h"
@@ -43,9 +45,11 @@
#include "core/config/engine.h"
#include "servers/navigation_server_3d.h"
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
NavigationMeshGenerator *_nav_mesh_generator = nullptr;
#endif
+#endif // DISABLE_DEPRECATED
NavigationServer3D *new_server() {
return memnew(GodotNavigationServer);
@@ -55,11 +59,13 @@ void initialize_navigation_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
NavigationServer3DManager::set_default_server(new_server);
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
_nav_mesh_generator = memnew(NavigationMeshGenerator);
GDREGISTER_CLASS(NavigationMeshGenerator);
Engine::get_singleton()->add_singleton(Engine::Singleton("NavigationMeshGenerator", NavigationMeshGenerator::get_singleton()));
#endif
+#endif // DISABLE_DEPRECATED
}
#ifdef TOOLS_ENABLED
@@ -74,9 +80,11 @@ void uninitialize_navigation_module(ModuleInitializationLevel p_level) {
return;
}
+#ifndef DISABLE_DEPRECATED
#ifndef _3D_DISABLED
if (_nav_mesh_generator) {
memdelete(_nav_mesh_generator);
}
#endif
+#endif // DISABLE_DEPRECATED
}
diff --git a/modules/noise/icons/NoiseTexture3D.svg b/modules/noise/icons/NoiseTexture3D.svg
new file mode 100644
index 0000000000..92da633dce
--- /dev/null
+++ b/modules/noise/icons/NoiseTexture3D.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M1 14a1 1 0 0 0 1 1h9.5a1 1 0 0 0 .707-.293l2.5-2.5A1 1 0 0 0 15 11.5V2a1 1 0 0 0-1-1H4.5a1 1 0 0 0-.707.293l-2.5 2.5A1 1 0 0 0 1 4.5zm1.25-9H11v7H2.25zm10 6.25v-6.5L14 3v6.5zm-1-7.5H3L4.75 2H13zM3 11h4l1.25-1.25V9H9l1.25-1.25v-2h-2L7 7v.75h-.75v-2h-2L3 7z" fill="#e0e0e0"/><path d="M3 7h2l1.25-1.25h-2zm2 2h2V7.75h-.75zm2-2h2l1.25-1.25H8z" fill="#000" fill-opacity=".4"/><path d="M5 7v2l1.25-1.25v-2zm2 2v2l1.25-1.25V9zm2 0V7l1.25-1.25v2z" fill="#000" fill-opacity=".2"/></svg>
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index fbff4c7f8f..f49dc390de 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -114,6 +114,8 @@ env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_ra
env_openxr.add_source_files(module_obj, "extensions/openxr_pico_controller_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_wmr_controller_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_ml2_controller_extension.cpp")
+env_openxr.add_source_files(module_obj, "extensions/openxr_extension_wrapper_extension.cpp")
+env_openxr.add_source_files(module_obj, "extensions/openxr_api_extension.cpp")
env.modules_sources += module_obj
diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp
index 98687d5f20..c6082ca404 100644
--- a/modules/openxr/extensions/openxr_android_extension.cpp
+++ b/modules/openxr/extensions/openxr_android_extension.cpp
@@ -53,6 +53,7 @@ OpenXRAndroidExtension::OpenXRAndroidExtension() {
HashMap<String, bool *> OpenXRAndroidExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
+ request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = &loader_init_extension_available;
request_extensions[XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME] = &create_instance_extension_available;
return request_extensions;
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
new file mode 100644
index 0000000000..81ba9c56b8
--- /dev/null
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -0,0 +1,207 @@
+/**************************************************************************/
+/* openxr_extension_wrapper_extension.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 "openxr_extension_wrapper_extension.h"
+
+#include "../openxr_api.h"
+
+void OpenXRExtensionWrapperExtension::_bind_methods() {
+ GDVIRTUAL_BIND(_get_requested_extensions);
+ GDVIRTUAL_BIND(_set_system_properties_and_get_next_pointer, "next_pointer");
+ GDVIRTUAL_BIND(_set_instance_create_info_and_get_next_pointer, "next_pointer");
+ GDVIRTUAL_BIND(_set_session_create_and_get_next_pointer, "next_pointer");
+ GDVIRTUAL_BIND(_set_swapchain_create_info_and_get_next_pointer, "next_pointer");
+ GDVIRTUAL_BIND(_on_register_metadata);
+ GDVIRTUAL_BIND(_on_before_instance_created);
+ GDVIRTUAL_BIND(_on_instance_created, "instance");
+ GDVIRTUAL_BIND(_on_instance_destroyed);
+ GDVIRTUAL_BIND(_on_session_created, "session");
+ GDVIRTUAL_BIND(_on_process);
+ GDVIRTUAL_BIND(_on_pre_render);
+ GDVIRTUAL_BIND(_on_session_destroyed);
+ GDVIRTUAL_BIND(_on_state_idle);
+ GDVIRTUAL_BIND(_on_state_ready);
+ GDVIRTUAL_BIND(_on_state_synchronized);
+ GDVIRTUAL_BIND(_on_state_visible);
+ GDVIRTUAL_BIND(_on_state_focused);
+ GDVIRTUAL_BIND(_on_state_stopping);
+ GDVIRTUAL_BIND(_on_state_loss_pending);
+ GDVIRTUAL_BIND(_on_state_exiting);
+ GDVIRTUAL_BIND(_on_event_polled, "event");
+
+ ClassDB::bind_method(D_METHOD("get_openxr_api"), &OpenXRExtensionWrapperExtension::get_openxr_api);
+ ClassDB::bind_method(D_METHOD("register_extension_wrapper"), &OpenXRExtensionWrapperExtension::register_extension_wrapper);
+}
+
+HashMap<String, bool *> OpenXRExtensionWrapperExtension::get_requested_extensions() {
+ Dictionary request_extension;
+
+ if (GDVIRTUAL_CALL(_get_requested_extensions, request_extension)) {
+ HashMap<String, bool *> result;
+ Array keys = request_extension.keys();
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ GDExtensionPtr<bool> value = VariantCaster<GDExtensionPtr<bool>>::cast(request_extension.get(key, GDExtensionPtr<bool>(nullptr)));
+ result.insert(key, value);
+ }
+ return result;
+ }
+
+ return HashMap<String, bool *>();
+}
+
+void *OpenXRExtensionWrapperExtension::set_system_properties_and_get_next_pointer(void *p_next_pointer) {
+ uint64_t pointer;
+
+ if (GDVIRTUAL_CALL(_set_system_properties_and_get_next_pointer, GDExtensionPtr<void>(p_next_pointer), pointer)) {
+ return reinterpret_cast<void *>(pointer);
+ }
+
+ return nullptr;
+}
+
+void *OpenXRExtensionWrapperExtension::set_instance_create_info_and_get_next_pointer(void *p_next_pointer) {
+ uint64_t pointer;
+
+ if (GDVIRTUAL_CALL(_set_instance_create_info_and_get_next_pointer, GDExtensionPtr<void>(p_next_pointer), pointer)) {
+ return reinterpret_cast<void *>(pointer);
+ }
+
+ return nullptr;
+}
+
+void *OpenXRExtensionWrapperExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) {
+ uint64_t pointer;
+
+ if (GDVIRTUAL_CALL(_set_session_create_and_get_next_pointer, GDExtensionPtr<void>(p_next_pointer), pointer)) {
+ return reinterpret_cast<void *>(pointer);
+ }
+
+ return nullptr;
+}
+
+void *OpenXRExtensionWrapperExtension::set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) {
+ uint64_t pointer;
+
+ if (GDVIRTUAL_CALL(_set_swapchain_create_info_and_get_next_pointer, GDExtensionPtr<void>(p_next_pointer), pointer)) {
+ return reinterpret_cast<void *>(pointer);
+ }
+
+ return nullptr;
+}
+
+void OpenXRExtensionWrapperExtension::on_register_metadata() {
+ GDVIRTUAL_CALL(_on_register_metadata);
+}
+
+void OpenXRExtensionWrapperExtension::on_before_instance_created() {
+ GDVIRTUAL_CALL(_on_before_instance_created);
+}
+
+void OpenXRExtensionWrapperExtension::on_instance_created(const XrInstance p_instance) {
+ uint64_t instance = reinterpret_cast<uint64_t>(p_instance);
+ GDVIRTUAL_CALL(_on_instance_created, instance);
+}
+
+void OpenXRExtensionWrapperExtension::on_instance_destroyed() {
+ GDVIRTUAL_CALL(_on_instance_destroyed);
+}
+
+void OpenXRExtensionWrapperExtension::on_session_created(const XrSession p_session) {
+ uint64_t session = reinterpret_cast<uint64_t>(p_session);
+ GDVIRTUAL_CALL(_on_session_created, session);
+}
+
+void OpenXRExtensionWrapperExtension::on_process() {
+ GDVIRTUAL_CALL(_on_process);
+}
+
+void OpenXRExtensionWrapperExtension::on_pre_render() {
+ GDVIRTUAL_CALL(_on_pre_render);
+}
+
+void OpenXRExtensionWrapperExtension::on_session_destroyed() {
+ GDVIRTUAL_CALL(_on_session_destroyed);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_idle() {
+ GDVIRTUAL_CALL(_on_state_idle);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_ready() {
+ GDVIRTUAL_CALL(_on_state_ready);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_synchronized() {
+ GDVIRTUAL_CALL(_on_state_synchronized);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_visible() {
+ GDVIRTUAL_CALL(_on_state_visible);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_focused() {
+ GDVIRTUAL_CALL(_on_state_focused);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_stopping() {
+ GDVIRTUAL_CALL(_on_state_stopping);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_loss_pending() {
+ GDVIRTUAL_CALL(_on_state_loss_pending);
+}
+
+void OpenXRExtensionWrapperExtension::on_state_exiting() {
+ GDVIRTUAL_CALL(_on_state_exiting);
+}
+
+bool OpenXRExtensionWrapperExtension::on_event_polled(const XrEventDataBuffer &p_event) {
+ bool event_polled;
+
+ if (GDVIRTUAL_CALL(_on_event_polled, GDExtensionConstPtr<void>(&p_event), event_polled)) {
+ return event_polled;
+ }
+
+ return false;
+}
+
+Ref<OpenXRAPIExtension> OpenXRExtensionWrapperExtension::get_openxr_api() {
+ return openxr_api;
+}
+
+void OpenXRExtensionWrapperExtension::register_extension_wrapper() {
+ OpenXRAPI::register_extension_wrapper(this);
+}
+
+OpenXRExtensionWrapperExtension::OpenXRExtensionWrapperExtension() :
+ Object(), OpenXRExtensionWrapper() {
+ openxr_api.instantiate();
+}
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
new file mode 100644
index 0000000000..5c5e64f927
--- /dev/null
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
@@ -0,0 +1,115 @@
+/**************************************************************************/
+/* openxr_extension_wrapper_extension.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 OPENXR_EXTENSION_WRAPPER_EXTENSION_H
+#define OPENXR_EXTENSION_WRAPPER_EXTENSION_H
+
+#include "../openxr_api_extension.h"
+#include "openxr_extension_wrapper.h"
+
+#include "core/object/ref_counted.h"
+#include "core/os/os.h"
+#include "core/os/thread_safe.h"
+#include "core/variant/native_ptr.h"
+
+class OpenXRExtensionWrapperExtension : public Object, OpenXRExtensionWrapper {
+ GDCLASS(OpenXRExtensionWrapperExtension, Object);
+
+protected:
+ _THREAD_SAFE_CLASS_
+
+ static void _bind_methods();
+
+ Ref<OpenXRAPIExtension> openxr_api;
+
+public:
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ GDVIRTUAL0R(Dictionary, _get_requested_extensions);
+
+ virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) override;
+ virtual void *set_instance_create_info_and_get_next_pointer(void *p_next_pointer) override;
+ virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override;
+ virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override;
+
+ //TODO workaround as GDExtensionPtr<void> return type results in build error in godot-cpp
+ GDVIRTUAL1R(uint64_t, _set_system_properties_and_get_next_pointer, GDExtensionPtr<void>);
+ GDVIRTUAL1R(uint64_t, _set_instance_create_info_and_get_next_pointer, GDExtensionPtr<void>);
+ GDVIRTUAL1R(uint64_t, _set_session_create_and_get_next_pointer, GDExtensionPtr<void>);
+ GDVIRTUAL1R(uint64_t, _set_swapchain_create_info_and_get_next_pointer, GDExtensionPtr<void>);
+
+ virtual void on_register_metadata() override;
+ virtual void on_before_instance_created() override;
+ virtual void on_instance_created(const XrInstance p_instance) override;
+ virtual void on_instance_destroyed() override;
+ virtual void on_session_created(const XrSession p_session) override;
+ virtual void on_process() override;
+ virtual void on_pre_render() override;
+ virtual void on_session_destroyed() override;
+
+ GDVIRTUAL0(_on_register_metadata);
+ GDVIRTUAL0(_on_before_instance_created);
+ GDVIRTUAL1(_on_instance_created, uint64_t);
+ GDVIRTUAL0(_on_instance_destroyed);
+ GDVIRTUAL1(_on_session_created, uint64_t);
+ GDVIRTUAL0(_on_process);
+ GDVIRTUAL0(_on_pre_render);
+ GDVIRTUAL0(_on_session_destroyed);
+
+ virtual void on_state_idle() override;
+ virtual void on_state_ready() override;
+ virtual void on_state_synchronized() override;
+ virtual void on_state_visible() override;
+ virtual void on_state_focused() override;
+ virtual void on_state_stopping() override;
+ virtual void on_state_loss_pending() override;
+ virtual void on_state_exiting() override;
+
+ GDVIRTUAL0(_on_state_idle);
+ GDVIRTUAL0(_on_state_ready);
+ GDVIRTUAL0(_on_state_synchronized);
+ GDVIRTUAL0(_on_state_visible);
+ GDVIRTUAL0(_on_state_focused);
+ GDVIRTUAL0(_on_state_stopping);
+ GDVIRTUAL0(_on_state_loss_pending);
+ GDVIRTUAL0(_on_state_exiting);
+
+ virtual bool on_event_polled(const XrEventDataBuffer &p_event) override;
+
+ GDVIRTUAL1R(bool, _on_event_polled, GDExtensionConstPtr<void>);
+
+ Ref<OpenXRAPIExtension> get_openxr_api();
+
+ void register_extension_wrapper();
+
+ OpenXRExtensionWrapperExtension();
+};
+
+#endif // OPENXR_EXTENSION_WRAPPER_EXTENSION_H
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index c5efe609c2..9885190cb1 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -30,6 +30,7 @@
#include "openxr_api.h"
+#include "extensions/openxr_extension_wrapper_extension.h"
#include "openxr_interface.h"
#include "openxr_util.h"
@@ -794,7 +795,7 @@ bool OpenXRAPI::create_swapchains() {
Also Godot only creates a swapchain for the main output.
OpenXR will require us to create swapchains as the render target for additional viewports if we want to use the layer system
- to optimize text rendering and background rendering as OpenXR may choose to re-use the results for reprojection while we're
+ to optimize text rendering and background rendering as OpenXR may choose to reuse the results for reprojection while we're
already rendering the next frame.
Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create,
@@ -1667,7 +1668,7 @@ bool OpenXRAPI::process() {
}
bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) {
- ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // this was not released when it should be, error out and re-use...
+ ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // This was not released when it should be, error out and reuse...
XrResult result;
XrSwapchainImageAcquireInfo swapchain_image_acquire_info = {
diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp
new file mode 100644
index 0000000000..f0f0835f78
--- /dev/null
+++ b/modules/openxr/openxr_api_extension.cpp
@@ -0,0 +1,130 @@
+/**************************************************************************/
+/* openxr_api_extension.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 "openxr_api_extension.h"
+
+void OpenXRAPIExtension::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_instance"), &OpenXRAPIExtension::get_instance);
+ ClassDB::bind_method(D_METHOD("get_system_id"), &OpenXRAPIExtension::get_system_id);
+ ClassDB::bind_method(D_METHOD("get_session"), &OpenXRAPIExtension::get_session);
+
+ ClassDB::bind_method(D_METHOD("transform_from_pose", "pose"), &OpenXRAPIExtension::transform_from_pose);
+ ClassDB::bind_method(D_METHOD("xr_result", "result", "format", "args"), &OpenXRAPIExtension::xr_result);
+ ClassDB::bind_static_method("OpenXRAPIExtension", D_METHOD("openxr_is_enabled", "check_run_in_editor"), &OpenXRAPIExtension::openxr_is_enabled);
+ ClassDB::bind_method(D_METHOD("get_instance_proc_addr", "name"), &OpenXRAPIExtension::get_instance_proc_addr);
+ ClassDB::bind_method(D_METHOD("get_error_string", "result"), &OpenXRAPIExtension::get_error_string);
+ ClassDB::bind_method(D_METHOD("get_swapchain_format_name", "swapchain_format"), &OpenXRAPIExtension::get_swapchain_format_name);
+
+ ClassDB::bind_method(D_METHOD("is_initialized"), &OpenXRAPIExtension::is_initialized);
+ ClassDB::bind_method(D_METHOD("is_running"), &OpenXRAPIExtension::is_running);
+
+ ClassDB::bind_method(D_METHOD("get_play_space"), &OpenXRAPIExtension::get_play_space);
+ ClassDB::bind_method(D_METHOD("get_next_frame_time"), &OpenXRAPIExtension::get_next_frame_time);
+ ClassDB::bind_method(D_METHOD("can_render"), &OpenXRAPIExtension::can_render);
+}
+
+uint64_t OpenXRAPIExtension::get_instance() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+ return (uint64_t)OpenXRAPI::get_singleton()->get_instance();
+}
+
+uint64_t OpenXRAPIExtension::get_system_id() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+ return (uint64_t)OpenXRAPI::get_singleton()->get_system_id();
+}
+
+uint64_t OpenXRAPIExtension::get_session() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+ return (uint64_t)OpenXRAPI::get_singleton()->get_session();
+}
+
+Transform3D OpenXRAPIExtension::transform_from_pose(GDExtensionConstPtr<const void> p_pose) {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), Transform3D());
+ return OpenXRAPI::get_singleton()->transform_from_pose(*(XrPosef *)p_pose.data);
+}
+
+bool OpenXRAPIExtension::xr_result(uint64_t result, String format, Array args) {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
+ return OpenXRAPI::get_singleton()->xr_result((XrResult)result, format.utf8().get_data(), args);
+}
+
+bool OpenXRAPIExtension::openxr_is_enabled(bool p_check_run_in_editor) {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
+ return OpenXRAPI::openxr_is_enabled(p_check_run_in_editor);
+}
+
+uint64_t OpenXRAPIExtension::get_instance_proc_addr(String p_name) {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+ CharString str = p_name.utf8();
+ PFN_xrVoidFunction addr = nullptr;
+ XrResult result = OpenXRAPI::get_singleton()->get_instance_proc_addr(str.get_data(), &addr);
+ if (result != XR_SUCCESS) {
+ return 0;
+ }
+ return reinterpret_cast<uint64_t>(addr);
+}
+
+String OpenXRAPIExtension::get_error_string(uint64_t result) {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), String());
+ return OpenXRAPI::get_singleton()->get_error_string((XrResult)result);
+}
+
+String OpenXRAPIExtension::get_swapchain_format_name(int64_t p_swapchain_format) {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), String());
+ return OpenXRAPI::get_singleton()->get_swapchain_format_name(p_swapchain_format);
+}
+
+bool OpenXRAPIExtension::is_initialized() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
+ return OpenXRAPI::get_singleton()->is_initialized();
+}
+
+bool OpenXRAPIExtension::is_running() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
+ return OpenXRAPI::get_singleton()->is_running();
+}
+
+uint64_t OpenXRAPIExtension::get_play_space() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+ return (uint64_t)OpenXRAPI::get_singleton()->get_play_space();
+}
+
+int64_t OpenXRAPIExtension::get_next_frame_time() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0);
+ return (XrTime)OpenXRAPI::get_singleton()->get_next_frame_time();
+}
+
+bool OpenXRAPIExtension::can_render() {
+ ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
+ return OpenXRAPI::get_singleton()->can_render();
+}
+
+OpenXRAPIExtension::OpenXRAPIExtension() {
+}
diff --git a/modules/mono/mono_gd/support/android_support.h b/modules/openxr/openxr_api_extension.h
index 5be4bac6e1..98f87c7aa1 100644
--- a/modules/mono/mono_gd/support/android_support.h
+++ b/modules/openxr/openxr_api_extension.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* android_support.h */
+/* openxr_api_extension.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,27 +28,49 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef ANDROID_SUPPORT_H
-#define ANDROID_SUPPORT_H
+#ifndef OPENXR_API_EXTENSION_H
+#define OPENXR_API_EXTENSION_H
-#if defined(ANDROID_ENABLED)
+#include "openxr_api.h"
-#include "core/string/ustring.h"
+#include "core/object/ref_counted.h"
+#include "core/os/os.h"
+#include "core/os/thread_safe.h"
+#include "core/variant/native_ptr.h"
-namespace gdmono {
-namespace android {
-namespace support {
+class OpenXRAPIExtension : public RefCounted {
+ GDCLASS(OpenXRAPIExtension, RefCounted);
-String get_app_native_lib_dir();
+protected:
+ _THREAD_SAFE_CLASS_
-void initialize();
-void cleanup();
+ static void _bind_methods();
-void register_internal_calls();
-} // namespace support
-} // namespace android
-} // namespace gdmono
+public:
+ uint64_t get_instance();
+ uint64_t get_system_id();
+ uint64_t get_session();
-#endif // ANDROID_ENABLED
+ // Helper method to convert an XrPosef to a Transform3D.
+ Transform3D transform_from_pose(GDExtensionConstPtr<const void> p_pose);
-#endif // ANDROID_SUPPORT_H
+ bool xr_result(uint64_t result, String format, Array args = Array());
+
+ static bool openxr_is_enabled(bool p_check_run_in_editor = true);
+
+ //TODO workaround as GDExtensionPtr<void> return type results in build error in godot-cpp
+ uint64_t get_instance_proc_addr(String p_name);
+ String get_error_string(uint64_t result);
+ String get_swapchain_format_name(int64_t p_swapchain_format);
+
+ bool is_initialized();
+ bool is_running();
+
+ uint64_t get_play_space();
+ int64_t get_next_frame_time();
+ bool can_render();
+
+ OpenXRAPIExtension();
+};
+
+#endif // OPENXR_API_EXTENSION_H
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 27b179a788..5d636c2b70 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -36,6 +36,9 @@
#include "action_map/openxr_interaction_profile.h"
#include "action_map/openxr_interaction_profile_meta_data.h"
#include "openxr_interface.h"
+
+#include "extensions/openxr_extension_wrapper_extension.h"
+
#include "scene/openxr_hand.h"
#include "extensions/openxr_composition_layer_depth_extension.h"
@@ -87,6 +90,11 @@ static void _editor_init() {
#endif
void initialize_openxr_module(ModuleInitializationLevel p_level) {
+ if (p_level == MODULE_INITIALIZATION_LEVEL_CORE) {
+ GDREGISTER_CLASS(OpenXRExtensionWrapperExtension);
+ GDREGISTER_CLASS(OpenXRAPIExtension);
+ }
+
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
if (OpenXRAPI::openxr_is_enabled(false)) {
// Always register our extension wrappers even if we don't initialize OpenXR.
diff --git a/modules/openxr/util.h b/modules/openxr/util.h
index 6665d45007..d95bc3bb8e 100644
--- a/modules/openxr/util.h
+++ b/modules/openxr/util.h
@@ -58,6 +58,17 @@
#define EXT_TRY_INIT_XR_FUNC(name) TRY_INIT_XR_FUNC(OpenXRAPI::get_singleton(), name)
#define OPENXR_TRY_API_INIT_XR_FUNC(name) TRY_INIT_XR_FUNC(this, name)
+#define GDEXTENSION_INIT_XR_FUNC(name) \
+ do { \
+ name##_ptr = reinterpret_cast<PFN_##name>(get_openxr_api()->get_instance_proc_addr(#name)); \
+ ERR_FAIL_COND(name##_ptr == nullptr); \
+ } while (0)
+
+#define GDEXTENSION_INIT_XR_FUNC_V(name) \
+ do { \
+ name##_ptr = reinterpret_cast<PFN_##name>(get_openxr_api()->get_instance_proc_addr(#name)); \
+ ERR_FAIL_COND_V(name##_ptr == nullptr, false); \
+ } while (0)
#define EXT_PROTO_XRRESULT_FUNC1(func_name, arg1_type, arg1) \
PFN_##func_name func_name##_ptr = nullptr; \
diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp
index eee0de967e..69fbf87483 100644
--- a/modules/raycast/raycast_occlusion_cull.cpp
+++ b/modules/raycast/raycast_occlusion_cull.cpp
@@ -355,14 +355,41 @@ void RaycastOcclusionCull::Scenario::_update_dirty_instance(int p_idx, RID *p_in
// Embree requires the last element to be readable by a 16-byte SSE load instruction, so we add padding to be safe.
occ_inst->xformed_vertices.resize(vertices_size + 1);
- for_range(0, vertices_size, vertices_size > 1024, SNAME("RaycastOcclusionCull"), [&](const int i) {
- occ_inst->xformed_vertices[i] = occ_inst->xform.xform(occ->vertices[i]);
- });
+ const Vector3 *read_ptr = occ->vertices.ptr();
+ Vector3 *write_ptr = occ_inst->xformed_vertices.ptr();
+
+ if (vertices_size > 1024) {
+ TransformThreadData td;
+ td.xform = occ_inst->xform;
+ td.read = read_ptr;
+ td.write = write_ptr;
+ td.vertex_count = vertices_size;
+ td.thread_count = WorkerThreadPool::get_singleton()->get_thread_count();
+ WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &Scenario::_transform_vertices_thread, &td, td.thread_count, -1, true, SNAME("RaycastOcclusionCull"));
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+
+ } else {
+ _transform_vertices_range(read_ptr, write_ptr, occ_inst->xform, 0, vertices_size);
+ }
occ_inst->indices.resize(occ->indices.size());
memcpy(occ_inst->indices.ptr(), occ->indices.ptr(), occ->indices.size() * sizeof(int32_t));
}
+void RaycastOcclusionCull::Scenario::_transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data) {
+ uint32_t vertex_total = p_data->vertex_count;
+ uint32_t total_threads = p_data->thread_count;
+ uint32_t from = p_thread * vertex_total / total_threads;
+ uint32_t to = (p_thread + 1 == total_threads) ? vertex_total : ((p_thread + 1) * vertex_total / total_threads);
+ _transform_vertices_range(p_data->read, p_data->write, p_data->xform, from, to);
+}
+
+void RaycastOcclusionCull::Scenario::_transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform3D &p_xform, int p_from, int p_to) {
+ for (int i = p_from; i < p_to; i++) {
+ p_write[i] = p_xform.xform(p_read[i]);
+ }
+}
+
void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) {
Scenario *scenario = (Scenario *)p_ud;
int commit_idx = 1 - (scenario->current_scene_idx);
diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h
index 7a5346878b..c4e733b664 100644
--- a/modules/raycast/raycast_occlusion_cull.h
+++ b/modules/raycast/raycast_occlusion_cull.h
@@ -121,6 +121,14 @@ private:
const uint32_t *masks;
};
+ struct TransformThreadData {
+ uint32_t thread_count;
+ uint32_t vertex_count;
+ Transform3D xform;
+ const Vector3 *read;
+ Vector3 *write = nullptr;
+ };
+
Thread *commit_thread = nullptr;
bool commit_done = true;
bool dirty = false;
@@ -136,6 +144,8 @@ private:
void _update_dirty_instance_thread(int p_idx, RID *p_instances);
void _update_dirty_instance(int p_idx, RID *p_instances);
+ void _transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data);
+ void _transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform3D &p_xform, int p_from, int p_to);
static void _commit_scene(void *p_ud);
bool update();
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index af9dae84e3..4b12a4e41d 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -262,7 +262,6 @@ if env["freetype_enabled"]:
CPPDEFINES=[
"FT2_BUILD_LIBRARY",
"FT_CONFIG_OPTION_USE_PNG",
- ("PNG_ARM_NEON_OPT", 0),
"FT_CONFIG_OPTION_SYSTEM_ZLIB",
]
)
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 13d8a2c17a..043a33ab35 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -4771,7 +4771,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
- // Insert an additional space when cutting word bound for aesthetics.
+ // Insert an additional space when cutting word bound for esthetics.
if (cut_per_word && (ellipsis_pos > 0)) {
Glyph gl;
gl.count = 1;
diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct
index 51a6ee06be..fcf8976019 100644
--- a/modules/text_server_fb/gdextension_build/SConstruct
+++ b/modules/text_server_fb/gdextension_build/SConstruct
@@ -257,7 +257,6 @@ if env["freetype_enabled"]:
CPPDEFINES=[
"FT2_BUILD_LIBRARY",
"FT_CONFIG_OPTION_USE_PNG",
- ("PNG_ARM_NEON_OPT", 0),
"FT_CONFIG_OPTION_SYSTEM_ZLIB",
]
)
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index dc0a7df81a..70da5e2782 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -3563,7 +3563,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
- // Insert an additional space when cutting word bound for aesthetics.
+ // Insert an additional space when cutting word bound for esthetics.
if (cut_per_word && (ellipsis_pos > 0)) {
Glyph gl;
gl.count = 1;
diff --git a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
index 10c87b899f..c2dcb832e0 100644
--- a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
+++ b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
@@ -1,10 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ResourceImporterOggVorbis" inherits="ResourceImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
+ Imports an Ogg Vorbis audio file for playback.
</brief_description>
<description>
+ Ogg Vorbis is a lossy audio format, with better audio quality compared to [ResourceImporterMP3] at a given bitrate.
+ In most cases, it's recommended to use Ogg Vorbis over MP3. However, if you're using a MP3 sound source with no higher quality source available, then it's recommended to use the MP3 file directly to avoid double lossy compression.
+ Ogg Vorbis requires more CPU to decode than [ResourceImporterWAV]. If you need to play a lot of simultaneous sounds, it's recommended to use WAV for those sounds instead, especially if targeting low-end devices.
</description>
<tutorials>
+ <link title="Importing audio samples">https://docs.godotengine.org/en/latest/tutorials/assets_pipeline/importing_audio_samples.html</link>
</tutorials>
<methods>
<method name="load_from_buffer" qualifiers="static">
@@ -24,15 +29,25 @@
</methods>
<members>
<member name="bar_beats" type="int" setter="" getter="" default="4">
+ The number of bars within a single beat in the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ A more convenient editor for [member bar_beats] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="beat_count" type="int" setter="" getter="" default="0">
+ The beat count of the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ A more convenient editor for [member beat_count] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="bpm" type="float" setter="" getter="" default="0">
+ The Beats Per Minute of the audio track. This should match the BPM measure that was used to compose the track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ A more convenient editor for [member bpm] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="loop" type="bool" setter="" getter="" default="false">
- If [code]true[/code], the audio will play again from the specified [member loop_offset] once it is done playing. Useful for ambient sounds and background music.
+ If enabled, the audio will begin playing at the beginning after playback ends by reaching the end of the audio.
+ [b]Note:[/b] In [AudioStreamPlayer], the [signal AudioStreamPlayer.finished] signal won't be emitted for looping audio when it reaches the end of the audio file, as the audio will keep playing indefinitely.
</member>
<member name="loop_offset" type="float" setter="" getter="" default="0">
+ Determines where audio will start to loop after playback reaches the end of the audio. This can be used to only loop a part of the audio file, which is useful for some ambient sounds or music. The value is determined in seconds relative to the beginning of the audio. A value of [code]0.0[/code] will loop the entire audio file.
+ Only has an effect if [member loop] is [code]true[/code].
+ A more convenient editor for [member loop_offset] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
</members>
</class>