diff options
Diffstat (limited to 'modules')
175 files changed, 4030 insertions, 2969 deletions
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index c241f1cabd..4c217dac28 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -953,12 +953,12 @@ void CSGMesh3D::set_mesh(const Ref<Mesh> &p_mesh) { return; } if (mesh.is_valid()) { - mesh->disconnect("changed", callable_mp(this, &CSGMesh3D::_mesh_changed)); + mesh->disconnect_changed(callable_mp(this, &CSGMesh3D::_mesh_changed)); } mesh = p_mesh; if (mesh.is_valid()) { - mesh->connect("changed", callable_mp(this, &CSGMesh3D::_mesh_changed)); + mesh->connect_changed(callable_mp(this, &CSGMesh3D::_mesh_changed)); } _mesh_changed(); 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 e6523e3d09..861cf20cc2 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -29,70 +29,10 @@ /**************************************************************************/ #include "texture_loader_dds.h" +#include "image_loader_dds.h" #include "core/io/file_access.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 } -}; +#include "scene/resources/image_texture.h" 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) { @@ -112,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/dds/texture_loader_dds.h b/modules/dds/texture_loader_dds.h index dc3df1fcee..3763700ff1 100644 --- a/modules/dds/texture_loader_dds.h +++ b/modules/dds/texture_loader_dds.h @@ -32,7 +32,6 @@ #define TEXTURE_LOADER_DDS_H #include "core/io/resource_loader.h" -#include "scene/resources/texture.h" class ResourceFormatDDS : public ResourceFormatLoader { public: 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/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp index f64704c67d..a131841a07 100644 --- a/modules/enet/enet_packet_peer.cpp +++ b/modules/enet/enet_packet_peer.cpp @@ -76,7 +76,7 @@ void ENetPacketPeer::throttle_configure(int p_interval, int p_acceleration, int void ENetPacketPeer::set_timeout(int p_timeout, int p_timeout_min, int p_timeout_max) { ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected"); - ERR_FAIL_COND_MSG(p_timeout > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less then maximum timeout"); + ERR_FAIL_COND_MSG(p_timeout > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less than maximum timeout"); enet_peer_timeout(peer, p_timeout, p_timeout_min, p_timeout_max); } 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_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 3d6d133579..42b08f8a68 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -787,11 +787,11 @@ Error GDScript::reload(bool p_keep_state) { err = compiler.compile(&parser, this, p_keep_state); if (err) { + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); if (can_run) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); } - _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); reloading = false; return ERR_COMPILATION_FAILED; } else { @@ -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 d3445b8cc0..cb04913620 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1553,7 +1553,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, visible_name, p_function->parameters[i]->identifier->name); } - is_shadowing(p_function->parameters[i]->identifier, "function parameter"); + is_shadowing(p_function->parameters[i]->identifier, "function parameter", true); #endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED if (p_function->parameters[i]->initializer) { @@ -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) { @@ -1874,9 +1883,8 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } else if (p_variable->assignments == 0) { parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); } - - is_shadowing(p_variable->identifier, kind); } + is_shadowing(p_variable->identifier, kind, p_is_local); #endif } @@ -1889,9 +1897,8 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant if (p_constant->usages == 0) { parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); } - - is_shadowing(p_constant->identifier, kind); } + is_shadowing(p_constant->identifier, kind, p_is_local); #endif } @@ -2052,7 +2059,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { p_for->set_datatype(p_for->loop->get_datatype()); #ifdef DEBUG_ENABLED if (p_for->variable) { - is_shadowing(p_for->variable, R"("for" iterator variable)"); + is_shadowing(p_for->variable, R"("for" iterator variable)", true); } #endif } @@ -2148,7 +2155,7 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc } p_match_pattern->bind->set_datatype(result); #ifdef DEBUG_ENABLED - is_shadowing(p_match_pattern->bind, "pattern bind"); + is_shadowing(p_match_pattern->bind, "pattern bind", true); if (p_match_pattern->bind->usages == 0 && !String(p_match_pattern->bind->name).begins_with("_")) { parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNUSED_VARIABLE, p_match_pattern->bind->name); } @@ -3520,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; } @@ -3664,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); @@ -4517,6 +4534,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo return result; } +const HashMap<String, Ref<GDScriptParserRef>> &GDScriptAnalyzer::get_depended_parsers() { + return depended_parsers; +} + GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.is_constant = true; @@ -4890,8 +4911,8 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p } #ifdef DEBUG_ENABLED -void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) { - const StringName &name = p_local->name; +void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) { + const StringName &name = p_identifier->name; GDScriptParser::DataType base = parser->current_class->get_datatype(); GDScriptParser::ClassNode *base_class = base.class_type; @@ -4901,29 +4922,30 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con for (MethodInfo &info : gdscript_funcs) { if (info.name == name) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } } - if (Variant::has_utility_function(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } else if (ClassDB::class_exists(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); return; } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); return; } } - while (base_class != nullptr) { - if (base_class->has_member(name)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_local->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); - return; + if (p_in_local_scope) { + while (base_class != nullptr) { + if (base_class->has_member(name)) { + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); + return; + } + base_class = base_class->base_type.class_type; } - base_class = base_class->base_type.class_type; } StringName parent = base.native_type; @@ -4931,19 +4953,19 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class."); if (ClassDB::has_method(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent); return; } else if (ClassDB::has_signal(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent); return; } else if (ClassDB::has_property(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent); return; } else if (ClassDB::has_integer_constant(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent); return; } else if (ClassDB::has_enum(parent, name, true)) { - parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent); return; } parent = ClassDB::get_parent_class(parent); diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 195e23b503..5bc2c89a87 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -133,7 +133,7 @@ class GDScriptAnalyzer { Ref<GDScriptParserRef> get_parser_for(const String &p_path); void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); #ifdef DEBUG_ENABLED - void is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); + void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope); #endif public: @@ -144,6 +144,7 @@ public: Error analyze(); Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); + const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers(); GDScriptAnalyzer(GDScriptParser *p_parser); }; 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_cache.cpp b/modules/gdscript/gdscript_cache.cpp index e5bb93e3c8..d191bd0224 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -294,8 +294,12 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (p_update_from_disk) { r_error = script->load_source_code(p_path); + if (r_error) { + return script; + } } + r_error = script->reload(true); if (r_error) { return script; } @@ -303,7 +307,6 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro singleton->full_gdscript_cache[p_path] = script; singleton->shallow_gdscript_cache.erase(p_path); - script->reload(true); return script; } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 004af80a91..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; @@ -2579,9 +2598,9 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } } else if (!base->is_valid()) { Error err = OK; - Ref<GDScript> base_root = GDScriptCache::get_full_script(base->path, err, p_script->path); + Ref<GDScript> base_root = GDScriptCache::get_shallow_script(base->path, err, p_script->path); if (err) { - _set_error(vformat(R"(Could not compile base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); + _set_error(vformat(R"(Could not parse base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); return err; } if (base_root.is_valid()) { @@ -2591,7 +2610,12 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); return ERR_COMPILATION_FAILED; } - ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG); + + err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + if (err) { + _set_error(vformat(R"(Could not populate class members of base class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); + return err; + } } p_script->base = base; @@ -2705,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: @@ -2968,7 +2993,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri GDScriptCache::add_static_script(p_script); } - return GDScriptCache::finish_compiling(main_script->get_path()); + return GDScriptCache::finish_compiling(main_script->path); } String GDScriptCompiler::get_error() const { 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 cd34feb8b3..d27ea974e3 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -143,14 +143,26 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li #endif if (err) { if (r_errors) { - for (const GDScriptParser::ParserError &E : parser.get_errors()) { - const GDScriptParser::ParserError &pe = E; + for (const GDScriptParser::ParserError &pe : parser.get_errors()) { ScriptLanguage::ScriptError e; + e.path = p_path; e.line = pe.line; e.column = pe.column; e.message = pe.message; r_errors->push_back(e); } + + for (KeyValue<String, Ref<GDScriptParserRef>> E : analyzer.get_depended_parsers()) { + GDScriptParser *depended_parser = E.value->get_parser(); + for (const GDScriptParser::ParserError &pe : depended_parser->get_errors()) { + ScriptLanguage::ScriptError e; + e.path = E.key; + e.line = pe.line; + e.column = pe.column; + e.message = pe.message; + r_errors->push_back(e); + } + } } return false; } else { @@ -221,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 @@ -229,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; @@ -236,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; @@ -257,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 { @@ -265,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 { @@ -277,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 { @@ -287,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) { @@ -297,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]); } } @@ -316,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; @@ -341,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; } @@ -796,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; } @@ -806,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); @@ -1381,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); @@ -1435,17 +1469,17 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } break; case GDScriptParser::Node::SELF: { if (p_context.current_class) { - r_type.type.kind = GDScriptParser::DataType::CLASS; - r_type.type.type_source = GDScriptParser::DataType::INFERRED; - r_type.type.is_constant = true; - r_type.type.class_type = p_context.current_class; - r_type.value = p_context.base; + if (p_context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { + r_type.type = p_context.current_class->get_datatype(); + } else { + r_type.type = p_context.current_class->base_type; + } found = true; } } 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. @@ -1888,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())) { @@ -1902,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; } } @@ -1946,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; } @@ -1964,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; @@ -2000,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(); } @@ -2026,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; } @@ -2054,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); @@ -2077,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; @@ -2088,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 e17ee0668f..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,32 +771,22 @@ 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 if (member->identifier != nullptr) { if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed. - -#ifdef DEBUG_ENABLED - List<MethodInfo> gdscript_funcs; - GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); - for (MethodInfo &info : gdscript_funcs) { - if (info.name == member->identifier->name) { - push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); - } - } - if (Variant::has_utility_function(member->identifier->name)) { - push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); - } -#endif - if (current_class->members_indices.has(member->identifier->name)) { push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); } else { @@ -1139,6 +1129,7 @@ GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) { ConstantNode *constant = alloc_node<ConstantNode>(); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) { + complete_extents(constant); return nullptr; } @@ -1327,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 @@ -2147,6 +2147,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + complete_extents(pattern); return nullptr; } else { if (expression->type == GDScriptParser::Node::LITERAL) { @@ -2279,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); @@ -3229,7 +3233,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode * } GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) { - push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)"); + push_error(R"("yield" was removed in Godot 4. Use "await" instead.)"); return nullptr; } @@ -3423,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; @@ -3463,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)) { @@ -3512,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. @@ -3565,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); } } @@ -3590,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 @@ -3879,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); @@ -3904,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) { @@ -3921,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) { @@ -3950,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/register_types.cpp b/modules/gdscript/register_types.cpp index e23bd50b8b..605e82be6e 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -98,7 +98,7 @@ public: return; } - virtual String _get_name() const override { return "GDScript"; } + virtual String get_name() const override { return "GDScript"; } }; static void _editor_init() { diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd index 38c2faa859..8709b89b54 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd @@ -1,2 +1,2 @@ func test(): - CanvasItem.new() + InstancePlaceholder.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out index 9eff912b59..36224c6b6f 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Native class "CanvasItem" cannot be constructed as it is abstract. +Native class "InstancePlaceholder" cannot be constructed as it is abstract. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd index 118e7e8a45..be67182efb 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd @@ -1,4 +1,4 @@ -class A extends CanvasItem: +class A extends InstancePlaceholder: func _init(): print('no') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out index 8b956f5974..260f062555 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "CanvasItem". +Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "InstancePlaceholder". 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/allow_get_node_with_onready.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd index a9004a346b..3e647407cd 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd @@ -1,7 +1,7 @@ extends Node @onready var shorthand = $Node -@onready var call = get_node(^"Node") +@onready var call_no_cast = get_node(^"Node") @onready var shorthand_with_cast = $Node as Node @onready var call_with_cast = get_node(^"Node") as Node @@ -13,6 +13,6 @@ func _init(): func test(): # Those are expected to be `null` since `_ready()` is never called on tests. prints("shorthand", shorthand) - prints("call", call) + prints("call_no_cast", call_no_cast) prints("shorthand_with_cast", shorthand_with_cast) prints("call_with_cast", call_with_cast) diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out index eddc2deec0..78b830aad0 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out @@ -1,5 +1,5 @@ GDTEST_OK shorthand <null> -call <null> +call_no_cast <null> shorthand_with_cast <null> call_with_cast <null> 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/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd index 61945c9c8f..29239a19da 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd @@ -1,5 +1,9 @@ var member: int = 0 +var print_debug := 'print_debug' +@warning_ignore("shadowed_global_identifier") +var print := 'print' + @warning_ignore("unused_variable") func test(): var Array := 'Array' diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index 8467697a96..accc791d8a 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -1,26 +1,30 @@ GDTEST_OK >> WARNING ->> Line: 5 +>> Line: 3 +>> SHADOWED_GLOBAL_IDENTIFIER +>> The variable "print_debug" has the same name as a built-in function. +>> WARNING +>> Line: 9 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Array" has the same name as a built-in type. >> WARNING ->> Line: 6 +>> Line: 10 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Node" has the same name as a global class. >> WARNING ->> Line: 7 +>> Line: 11 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "is_same" has the same name as a built-in function. >> WARNING ->> Line: 8 +>> Line: 12 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "sqrt" has the same name as a built-in function. >> WARNING ->> Line: 9 +>> Line: 13 >> SHADOWED_VARIABLE >> The local variable "member" is shadowing an already-declared variable at line 1. >> WARNING ->> Line: 10 +>> Line: 14 >> SHADOWED_VARIABLE_BASE_CLASS >> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted". warn diff --git a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out index 36cb699e92..f68d76d101 100644 --- a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out +++ b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out @@ -1,2 +1,2 @@ GDTEST_PARSER_ERROR -"yield" was removed in Godot 4.0. Use "await" instead. +"yield" was removed in Godot 4. Use "await" instead. 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=""""> + 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=""""> + 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=""""> + 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 00bf3e58b0..572ef44876 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -49,6 +49,8 @@ #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/portable_compressed_texture.h" #include "scene/resources/skin.h" #include "scene/resources/surface_tool.h" @@ -108,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); @@ -159,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; } @@ -205,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; } @@ -241,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; } @@ -2998,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; @@ -3034,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())); @@ -3067,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. @@ -3076,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; } } @@ -3083,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 @@ -3105,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>()); @@ -3122,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(); @@ -3147,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; @@ -3298,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())); @@ -3315,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; @@ -3338,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; @@ -3769,6 +3784,12 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) { extensions["KHR_materials_unlit"] = mat_unlit; p_state->add_used_extension("KHR_materials_unlit"); } + if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION) && !Math::is_equal_approx(base_material->get_emission_energy_multiplier(), 1.0f)) { + Dictionary mat_emissive_strength; + mat_emissive_strength["emissiveStrength"] = base_material->get_emission_energy_multiplier(); + extensions["KHR_materials_emissive_strength"] = mat_emissive_strength; + p_state->add_used_extension("KHR_materials_emissive_strength"); + } d["extensions"] = extensions; materials.push_back(d); @@ -3789,28 +3810,35 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { const Array &materials = p_state->json["materials"]; for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { - const Dictionary &d = materials[i]; + const Dictionary &material_dict = materials[i]; Ref<StandardMaterial3D> material; material.instantiate(); - if (d.has("name") && !String(d["name"]).is_empty()) { - material->set_name(d["name"]); + if (material_dict.has("name") && !String(material_dict["name"]).is_empty()) { + material->set_name(material_dict["name"]); } else { material->set_name(vformat("material_%s", itos(i))); } material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - Dictionary pbr_spec_gloss_extensions; - if (d.has("extensions")) { - pbr_spec_gloss_extensions = d["extensions"]; + Dictionary material_extensions; + if (material_dict.has("extensions")) { + material_extensions = material_dict["extensions"]; } - if (pbr_spec_gloss_extensions.has("KHR_materials_unlit")) { + if (material_extensions.has("KHR_materials_unlit")) { material->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED); } - if (pbr_spec_gloss_extensions.has("KHR_materials_pbrSpecularGlossiness")) { + if (material_extensions.has("KHR_materials_emissive_strength")) { + Dictionary emissive_strength = material_extensions["KHR_materials_emissive_strength"]; + if (emissive_strength.has("emissiveStrength")) { + material->set_emission_energy_multiplier(emissive_strength["emissiveStrength"]); + } + } + + if (material_extensions.has("KHR_materials_pbrSpecularGlossiness")) { WARN_PRINT("Material uses a specular and glossiness workflow. Textures will be converted to roughness and metallic workflow, which may not be 100% accurate."); - Dictionary sgm = pbr_spec_gloss_extensions["KHR_materials_pbrSpecularGlossiness"]; + Dictionary sgm = material_extensions["KHR_materials_pbrSpecularGlossiness"]; Ref<GLTFSpecGloss> spec_gloss; spec_gloss.instantiate(); @@ -3858,8 +3886,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } spec_gloss_to_rough_metal(spec_gloss, material); - } else if (d.has("pbrMetallicRoughness")) { - const Dictionary &mr = d["pbrMetallicRoughness"]; + } else if (material_dict.has("pbrMetallicRoughness")) { + const Dictionary &mr = material_dict["pbrMetallicRoughness"]; if (mr.has("baseColorFactor")) { const Array &arr = mr["baseColorFactor"]; ERR_FAIL_COND_V(arr.size() != 4, ERR_PARSE_ERROR); @@ -3911,8 +3939,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } - if (d.has("normalTexture")) { - const Dictionary &bct = d["normalTexture"]; + if (material_dict.has("normalTexture")) { + const Dictionary &bct = material_dict["normalTexture"]; if (bct.has("index")) { material->set_texture(BaseMaterial3D::TEXTURE_NORMAL, _get_texture(p_state, bct["index"], TEXTURE_TYPE_NORMAL)); material->set_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING, true); @@ -3921,8 +3949,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { material->set_normal_scale(bct["scale"]); } } - if (d.has("occlusionTexture")) { - const Dictionary &bct = d["occlusionTexture"]; + if (material_dict.has("occlusionTexture")) { + const Dictionary &bct = material_dict["occlusionTexture"]; if (bct.has("index")) { material->set_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, _get_texture(p_state, bct["index"], TEXTURE_TYPE_GENERIC)); material->set_ao_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_RED); @@ -3930,8 +3958,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } - if (d.has("emissiveFactor")) { - const Array &arr = d["emissiveFactor"]; + if (material_dict.has("emissiveFactor")) { + const Array &arr = material_dict["emissiveFactor"]; ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); const Color c = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); material->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); @@ -3939,8 +3967,8 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { material->set_emission(c); } - if (d.has("emissiveTexture")) { - const Dictionary &bct = d["emissiveTexture"]; + if (material_dict.has("emissiveTexture")) { + const Dictionary &bct = material_dict["emissiveTexture"]; if (bct.has("index")) { material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, _get_texture(p_state, bct["index"], TEXTURE_TYPE_GENERIC)); material->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); @@ -3948,20 +3976,20 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } - if (d.has("doubleSided")) { - const bool ds = d["doubleSided"]; + if (material_dict.has("doubleSided")) { + const bool ds = material_dict["doubleSided"]; if (ds) { material->set_cull_mode(BaseMaterial3D::CULL_DISABLED); } } - if (d.has("alphaMode")) { - const String &am = d["alphaMode"]; + if (material_dict.has("alphaMode")) { + const String &am = material_dict["alphaMode"]; if (am == "BLEND") { material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); } else if (am == "MASK") { material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR); - if (d.has("alphaCutoff")) { - material->set_alpha_scissor_threshold(d["alphaCutoff"]); + if (material_dict.has("alphaCutoff")) { + material->set_alpha_scissor_threshold(material_dict["alphaCutoff"]); } else { material->set_alpha_scissor_threshold(0.5f); } @@ -5239,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())); } } @@ -5447,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++) { @@ -5736,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; } @@ -5777,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; @@ -5892,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); } } @@ -6984,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) { @@ -7054,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; @@ -7223,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; @@ -7231,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; } @@ -7286,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()); @@ -7295,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; } @@ -7323,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; @@ -7409,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); @@ -7435,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; @@ -7461,6 +7501,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); supported_extensions.insert("KHR_texture_transform"); supported_extensions.insert("KHR_materials_unlit"); + supported_extensions.insert("KHR_materials_emissive_strength"); for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Vector<String> ext_supported_extensions = ext->get_supported_extensions(); 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/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 87149fa11d..1788ffac3a 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -70,9 +70,9 @@ static void _editor_init() { if (blend_enabled) { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (blender3_path.is_empty()) { - WARN_PRINT("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported."); + WARN_PRINT(TTR("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported.")); } else if (!da->dir_exists(blender3_path)) { - WARN_PRINT("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported."); + WARN_PRINT(TTR("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported.")); } else { Ref<EditorSceneFormatImporterBlend> importer; importer.instantiate(); diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 6973bd3cd8..f9c3ca476a 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -138,11 +138,11 @@ Returns the position of a grid cell in the GridMap's local coordinate space. To convert the returned value into global coordinates, use [method Node3D.to_global]. See also [method map_to_local]. </description> </method> - <method name="resource_changed"> + <method name="resource_changed" is_deprecated="true"> <return type="void" /> <param index="0" name="resource" type="Resource" /> <description> - Notifies the [GridMap] about changed resource and recreates octant data. + [i]Obsoleted.[/i] Use [signal Resource.changed] instead. </description> </method> <method name="set_cell_item"> diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index c77fa98be2..f1e2218434 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -258,11 +258,11 @@ RID GridMap::get_navigation_map() const { void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) { if (!mesh_library.is_null()) { - mesh_library->unregister_owner(this); + mesh_library->disconnect_changed(callable_mp(this, &GridMap::_recreate_octant_data)); } mesh_library = p_mesh_library; if (!mesh_library.is_null()) { - mesh_library->register_owner(this); + mesh_library->connect_changed(callable_mp(this, &GridMap::_recreate_octant_data)); } _recreate_octant_data(); @@ -1005,9 +1005,10 @@ void GridMap::clear() { clear_baked_meshes(); } +#ifndef DISABLE_DEPRECATED void GridMap::resource_changed(const Ref<Resource> &p_res) { - _recreate_octant_data(); } +#endif void GridMap::_update_octants_callback() { if (!awaiting_update) { @@ -1079,7 +1080,9 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("map_to_local", "map_position"), &GridMap::map_to_local); ClassDB::bind_method(D_METHOD("_update_octants_callback"), &GridMap::_update_octants_callback); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("resource_changed", "resource"), &GridMap::resource_changed); +#endif ClassDB::bind_method(D_METHOD("set_center_x", "enable"), &GridMap::set_center_x); ClassDB::bind_method(D_METHOD("get_center_x"), &GridMap::get_center_x); @@ -1336,10 +1339,6 @@ void GridMap::_navigation_map_changed(RID p_map) { #endif // DEBUG_ENABLED GridMap::~GridMap() { - if (!mesh_library.is_null()) { - mesh_library->unregister_owner(this); - } - clear(); #ifdef DEBUG_ENABLED NavigationServer3D::get_singleton()->disconnect("map_changed", callable_mp(this, &GridMap::_navigation_map_changed)); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 18c3f90269..e05979efbc 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -203,7 +203,9 @@ class GridMap : public Node3D { void _queue_octants_dirty(); void _update_octants_callback(); +#ifndef DISABLE_DEPRECATED void resource_changed(const Ref<Resource> &p_res); +#endif void _clear_internal(); diff --git a/modules/minimp3/config.py b/modules/minimp3/config.py index bd35d099b9..e6bdcb2a83 100644 --- a/modules/minimp3/config.py +++ b/modules/minimp3/config.py @@ -9,6 +9,7 @@ def configure(env): def get_doc_classes(): return [ "AudioStreamMP3", + "ResourceImporterMP3", ] diff --git a/modules/minimp3/doc_classes/ResourceImporterMP3.xml b/modules/minimp3/doc_classes/ResourceImporterMP3.xml new file mode 100644 index 0000000000..a84c51cf68 --- /dev/null +++ b/modules/minimp3/doc_classes/ResourceImporterMP3.xml @@ -0,0 +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 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/minimp3/register_types.cpp b/modules/minimp3/register_types.cpp index da89321018..627d093bc1 100644 --- a/modules/minimp3/register_types.cpp +++ b/modules/minimp3/register_types.cpp @@ -51,7 +51,11 @@ void initialize_minimp3_module(ModuleInitializationLevel p_level) { mp3_import.instantiate(); ResourceFormatImporter::get_singleton()->add_importer(mp3_import); } + + // Required to document import options in the class reference. + GDREGISTER_CLASS(ResourceImporterMP3); #endif + GDREGISTER_CLASS(AudioStreamMP3); } 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 3e032f213b..f592533a5a 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -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; @@ -2272,7 +2270,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); - if (efs) { + if (efs && !p_script->get_path().is_empty()) { efs->update_file(p_script->get_path()); } #endif diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 8be1151142..72614dd7e0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -384,5 +384,65 @@ namespace Godot.SourceGenerators typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } + + public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule = + new DiagnosticDescriptor(id: "GD0401", + title: "The class must derive from GodotObject or a derived class", + messageFormat: "The class '{0}' must derive from GodotObject or a derived class.", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute."); + + public static void ReportGlobalClassMustDeriveFromGodotObject( + SyntaxNodeAnalysisContext context, + SyntaxNode classSyntax, + ISymbol typeSymbol) + { + string message = $"The class '{typeSymbol.ToDisplayString()}' must derive from GodotObject or a derived class"; + + string description = $"{message}. Change the base class or remove the '[GlobalClass]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0401", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + classSyntax.GetLocation(), + classSyntax.SyntaxTree.FilePath)); + } + + public static readonly DiagnosticDescriptor GlobalClassMustNotBeGenericRule = + new DiagnosticDescriptor(id: "GD0402", + title: "The class must not contain generic arguments", + messageFormat: "The class '{0}' must not contain generic arguments", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute."); + + public static void ReportGlobalClassMustNotBeGeneric( + SyntaxNodeAnalysisContext context, + SyntaxNode classSyntax, + ISymbol typeSymbol) + { + string message = $"The class '{typeSymbol.ToDisplayString()}' must not contain generic arguments"; + + string description = $"{message}. Remove the generic arguments or the '[GlobalClass]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0402", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + classSyntax.GetLocation(), + classSyntax.SyntaxTree.FilePath)); + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index b2a3c046e5..b6ea4b8e88 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -81,7 +81,7 @@ namespace Godot.SourceGenerators return godotClassName ?? nativeType.Name; } - private static bool IsGodotScriptClass( + private static bool TryGetGodotScriptClass( this ClassDeclarationSyntax cds, Compilation compilation, out INamedTypeSymbol? symbol ) @@ -108,7 +108,7 @@ namespace Godot.SourceGenerators { foreach (var cds in source) { - if (cds.IsGodotScriptClass(compilation, out var symbol)) + if (cds.TryGetGodotScriptClass(compilation, out var symbol)) yield return (cds, symbol!); } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs new file mode 100644 index 0000000000..bcb35dae8a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using System.Linq; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Godot.SourceGenerators +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class GlobalClassAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics + => ImmutableArray.Create( + Common.GlobalClassMustDeriveFromGodotObjectRule, + Common.GlobalClassMustNotBeGenericRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); + } + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var typeClassDecl = (ClassDeclarationSyntax)context.Node; + + // Return if not a type symbol or the type is not a global class. + if (context.ContainingSymbol is not INamedTypeSymbol typeSymbol || + !typeSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotGlobalClassAttribute() ?? false)) + return; + + if (typeSymbol.IsGenericType) + Common.ReportGlobalClassMustNotBeGeneric(context, typeClassDecl, typeSymbol); + + if (!typeSymbol.InheritsFrom("GodotSharp", GodotClasses.GodotObject)) + Common.ReportGlobalClassMustDeriveFromGodotObject(context, typeClassDecl, typeSymbol); + } + } +} 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/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 1affe692d0..5ea0ca53c3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -135,6 +135,10 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" /// <summary>\n") + .Append(" /// Cached StringNames for the methods contained in this class, for fast lookup.\n") + .Append(" /// </summary>\n"); + source.Append( $" public new class MethodName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.MethodName {{\n"); @@ -147,6 +151,12 @@ namespace Godot.SourceGenerators foreach (string methodName in distinctMethodNames) { + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(methodName) + .Append("' method.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(methodName); source.Append(" = \""); @@ -162,6 +172,14 @@ namespace Godot.SourceGenerators { const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + source.Append(" /// <summary>\n") + .Append(" /// Get the method information for all the methods declared in this class.\n") + .Append(" /// This method is used by Godot to register the available methods in the editor.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static ") .Append(listType) .Append(" GetGodotMethodList()\n {\n"); @@ -188,6 +206,8 @@ namespace Godot.SourceGenerators if (godotClassMethods.Length > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, "); source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n"); @@ -205,6 +225,8 @@ namespace Godot.SourceGenerators if (distinctMethodNames.Length > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n"); bool isFirstEntry = true; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 3e6858485d..94d8696717 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -124,6 +124,10 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" /// <summary>\n") + .Append(" /// Cached StringNames for the properties and fields contained in this class, for fast lookup.\n") + .Append(" /// </summary>\n"); + source.Append( $" public new class PropertyName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n"); @@ -132,6 +136,13 @@ namespace Godot.SourceGenerators foreach (var property in godotClassProperties) { string propertyName = property.PropertySymbol.Name; + + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(propertyName) + .Append("' property.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(propertyName); source.Append(" = \""); @@ -142,6 +153,13 @@ namespace Godot.SourceGenerators foreach (var field in godotClassFields) { string fieldName = field.FieldSymbol.Name; + + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(fieldName) + .Append("' field.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(fieldName); source.Append(" = \""); @@ -162,6 +180,8 @@ namespace Godot.SourceGenerators if (!allPropertiesAreReadOnly) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, "); source.Append("in godot_variant value)\n {\n"); @@ -193,6 +213,8 @@ namespace Godot.SourceGenerators // Generate GetGodotClassPropertyValue + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, "); source.Append("out godot_variant value)\n {\n"); @@ -217,7 +239,15 @@ namespace Godot.SourceGenerators // Generate GetGodotPropertyList - string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + const string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + + source.Append(" /// <summary>\n") + .Append(" /// Get the property information for all the properties declared in this class.\n") + .Append(" /// This method is used by Godot to register the available properties in the editor.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append(" internal new static ") .Append(dictionaryType) @@ -620,7 +650,7 @@ namespace Godot.SourceGenerators bool isPresetHint = false; - if (elementVariantType == VariantType.String) + if (elementVariantType == VariantType.String || elementVariantType == VariantType.StringName) isPresetHint = GetStringArrayEnumHint(elementVariantType, exportAttr, out hintString); if (!isPresetHint) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index ac908a6de3..4df16d05f0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -285,10 +285,20 @@ namespace Godot.SourceGenerators { source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - string dictionaryType = + const string dictionaryType = "global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>"; source.Append("#if TOOLS\n"); + + source.Append(" /// <summary>\n") + .Append(" /// Get the default values for all properties declared in this class.\n") + .Append(" /// This method is used by Godot to determine the value that will be\n") + .Append(" /// used by the inspector when resetting properties.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static "); source.Append(dictionaryType); source.Append(" GetGodotPropertyDefaultValues()\n {\n"); @@ -320,7 +330,8 @@ namespace Godot.SourceGenerators source.Append(" return values;\n"); source.Append(" }\n"); - source.Append("#endif\n"); + + source.Append("#endif // TOOLS\n"); source.Append("#pragma warning restore CS0109\n"); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 97771b721d..231a7be021 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -149,6 +149,8 @@ namespace Godot.SourceGenerators godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); } + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); source.Append(" base.SaveGodotObjectData(info);\n"); @@ -196,6 +198,8 @@ namespace Godot.SourceGenerators source.Append(" }\n"); + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); source.Append(" base.RestoreGodotObjectData(info);\n"); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 7e3323f588..8f2774d5ae 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -176,6 +176,10 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" /// <summary>\n") + .Append(" /// Cached StringNames for the signals contained in this class, for fast lookup.\n") + .Append(" /// </summary>\n"); + source.Append( $" public new class SignalName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.SignalName {{\n"); @@ -184,6 +188,13 @@ namespace Godot.SourceGenerators foreach (var signalDelegate in godotSignalDelegates) { string signalName = signalDelegate.Name; + + source.Append(" /// <summary>\n") + .Append(" /// Cached name for the '") + .Append(signalName) + .Append("' signal.\n") + .Append(" /// </summary>\n"); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(signalName); source.Append(" = \""); @@ -199,6 +210,14 @@ namespace Godot.SourceGenerators { const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + source.Append(" /// <summary>\n") + .Append(" /// Get the signal information for all the signals declared in this class.\n") + .Append(" /// This method is used by Godot to register the available signals in the editor.\n") + .Append(" /// Do not call this method.\n") + .Append(" /// </summary>\n"); + + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static ") .Append(listType) .Append(" GetGodotSignalList()\n {\n"); @@ -258,6 +277,8 @@ namespace Godot.SourceGenerators if (godotSignalDelegates.Count > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, "); source.Append("NativeVariantPtrArgs args)\n {\n"); @@ -276,6 +297,8 @@ namespace Godot.SourceGenerators if (godotSignalDelegates.Count > 0) { + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); source.Append( " protected override bool HasGodotClassSignal(in godot_string_name signal)\n {\n"); 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 30525ba04a..56ae37b4dd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -28,10 +28,9 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" /> + <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" /> - <!-- For RiderPathLocator --> - <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <Reference Include="GodotSharp"> <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath> <Private>False</Private> diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs new file mode 100644 index 0000000000..61c1581281 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs @@ -0,0 +1,56 @@ +using System; +using Godot; +using JetBrains.Rider.PathLocator; +using Newtonsoft.Json; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Ides.Rider; + +public class RiderLocatorEnvironment : IRiderLocatorEnvironment +{ + public JetBrains.Rider.PathLocator.OS CurrentOS + { + get + { + if (OS.IsWindows) + return JetBrains.Rider.PathLocator.OS.Windows; + if (OS.IsMacOS) return JetBrains.Rider.PathLocator.OS.MacOSX; + if (OS.IsUnixLike) return JetBrains.Rider.PathLocator.OS.Linux; + return JetBrains.Rider.PathLocator.OS.Other; + } + } + + public T FromJson<T>(string json) + { + return JsonConvert.DeserializeObject<T>(json); + } + + public void Info(string message, Exception e = null) + { + if (e == null) + GD.Print(message); + else + GD.Print(message, e); + } + + public void Warn(string message, Exception e = null) + { + if (e == null) + GD.PushWarning(message); + else + GD.PushWarning(message, e); + } + + public void Error(string message, Exception e = null) + { + if (e == null) + GD.PushError(message); + 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/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs deleted file mode 100644 index dad6e35344..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ /dev/null @@ -1,474 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.Versioning; -using Godot; -using Microsoft.Win32; -using Newtonsoft.Json; -using Directory = System.IO.Directory; -using Environment = System.Environment; -using File = System.IO.File; -using Path = System.IO.Path; -using OS = GodotTools.Utils.OS; - -// ReSharper disable UnassignedField.Local -// ReSharper disable InconsistentNaming -// ReSharper disable UnassignedField.Global -// ReSharper disable MemberHidesStaticFromOuterClass - -namespace GodotTools.Ides.Rider -{ - /// <summary> - /// This code is a modified version of the JetBrains resharper-unity plugin listed under Apache License 2.0 license: - /// https://github.com/JetBrains/resharper-unity/blob/master/unity/JetBrains.Rider.Unity.Editor/EditorPlugin/RiderPathLocator.cs - /// </summary> - public static class RiderPathLocator - { - public static RiderInfo[] GetAllRiderPaths() - { - try - { - if (OS.IsWindows) - { - return CollectRiderInfosWindows(); - } - if (OS.IsMacOS) - { - return CollectRiderInfosMac(); - } - if (OS.IsUnixLike) - { - return CollectAllRiderPathsLinux(); - } - throw new InvalidOperationException("Unexpected OS."); - } - catch (Exception e) - { - GD.PushWarning(e.Message); - } - - return Array.Empty<RiderInfo>(); - } - - private static RiderInfo[] CollectAllRiderPathsLinux() - { - var installInfos = new List<RiderInfo>(); - string home = Environment.GetEnvironmentVariable("HOME"); - if (!string.IsNullOrEmpty(home)) - { - string toolboxRiderRootPath = GetToolboxBaseDir(); - installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false) - .Select(a => new RiderInfo(a, true)).ToList()); - - //$Home/.local/share/applications/jetbrains-rider.desktop - var shortcut = new FileInfo(Path.Combine(home, @".local/share/applications/jetbrains-rider.desktop")); - - if (shortcut.Exists) - { - string[] lines = File.ReadAllLines(shortcut.FullName); - foreach (string line in lines) - { - if (!line.StartsWith("Exec=\"")) - continue; - string path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); - if (string.IsNullOrEmpty(path)) - continue; - - if (installInfos.Any(a => a.Path == path)) // avoid adding similar build as from toolbox - continue; - installInfos.Add(new RiderInfo(path, false)); - } - } - } - - // snap install - string snapInstallPath = "/snap/rider/current/bin/rider.sh"; - if (new FileInfo(snapInstallPath).Exists) - installInfos.Add(new RiderInfo(snapInstallPath, false)); - - return installInfos.ToArray(); - } - - private static RiderInfo[] CollectRiderInfosMac() - { - var installInfos = new List<RiderInfo>(); - // "/Applications/*Rider*.app" - // should be combined with "Contents/MacOS/rider" - var folder = new DirectoryInfo("/Applications"); - if (folder.Exists) - { - installInfos.AddRange(folder.GetDirectories("*Rider*.app") - .Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false)) - .ToList()); - } - - // /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app - // should be combined with "Contents/MacOS/rider" - string toolboxRiderRootPath = GetToolboxBaseDir(); - var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true) - .Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true)); - installInfos.AddRange(paths); - - return installInfos.ToArray(); - } - - [SupportedOSPlatform("windows")] - private static RiderInfo[] CollectRiderInfosWindows() - { - var installInfos = new List<RiderInfo>(); - var toolboxRiderRootPath = GetToolboxBaseDir(); - var installPathsToolbox = CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider64.exe", false).ToList(); - installInfos.AddRange(installPathsToolbox.Select(a => new RiderInfo(a, true)).ToList()); - - var installPaths = new List<string>(); - const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; - CollectPathsFromRegistry(registryKey, installPaths); - const string wowRegistryKey = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; - CollectPathsFromRegistry(wowRegistryKey, installPaths); - - installInfos.AddRange(installPaths.Select(a => new RiderInfo(a, false)).ToList()); - - return installInfos.ToArray(); - } - - private static string GetToolboxBaseDir() - { - if (OS.IsWindows) - { - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - return GetToolboxRiderRootPath(localAppData); - } - - if (OS.IsMacOS) - { - var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) - return string.Empty; - var localAppData = Path.Combine(home, @"Library/Application Support"); - return GetToolboxRiderRootPath(localAppData); - } - - if (OS.IsUnixLike) - { - var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) - return string.Empty; - var localAppData = Path.Combine(home, @".local/share"); - return GetToolboxRiderRootPath(localAppData); - } - - return string.Empty; - } - - - private static string GetToolboxRiderRootPath(string localAppData) - { - var toolboxPath = Path.Combine(localAppData, @"JetBrains/Toolbox"); - var settingsJson = Path.Combine(toolboxPath, ".settings.json"); - - if (File.Exists(settingsJson)) - { - var path = SettingsJson.GetInstallLocationFromJson(File.ReadAllText(settingsJson)); - if (!string.IsNullOrEmpty(path)) - toolboxPath = path; - } - - var toolboxRiderRootPath = Path.Combine(toolboxPath, @"apps/Rider"); - return toolboxRiderRootPath; - } - - internal static ProductInfo GetBuildVersion(string path) - { - var buildTxtFileInfo = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); - var dir = buildTxtFileInfo.DirectoryName; - if (!Directory.Exists(dir)) - return null; - var buildVersionFile = new FileInfo(Path.Combine(dir, "product-info.json")); - if (!buildVersionFile.Exists) - return null; - var json = File.ReadAllText(buildVersionFile.FullName); - return ProductInfo.GetProductInfo(json); - } - - internal static Version GetBuildNumber(string path) - { - var file = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); - if (!file.Exists) - return null; - var text = File.ReadAllText(file.FullName); - if (text.Length <= 3) - return null; - - var versionText = text.Substring(3); - return Version.TryParse(versionText, out var v) ? v : null; - } - - internal static bool IsToolbox(string path) - { - return path.StartsWith(GetToolboxBaseDir()); - } - - private static string GetRelativePathToBuildTxt() - { - if (OS.IsWindows || OS.IsUnixLike) - return "../../build.txt"; - if (OS.IsMacOS) - return "Contents/Resources/build.txt"; - throw new InvalidOperationException("Unknown OS."); - } - - [SupportedOSPlatform("windows")] - private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths) - { - using (var key = Registry.CurrentUser.OpenSubKey(registryKey)) - { - CollectPathsFromRegistry(installPaths, key); - } - using (var key = Registry.LocalMachine.OpenSubKey(registryKey)) - { - CollectPathsFromRegistry(installPaths, key); - } - } - - [SupportedOSPlatform("windows")] - private static void CollectPathsFromRegistry(List<string> installPaths, RegistryKey key) - { - if (key == null) return; - foreach (var subkeyName in key.GetSubKeyNames().Where(a => a.Contains("Rider"))) - { - using (var subkey = key.OpenSubKey(subkeyName)) - { - var folderObject = subkey?.GetValue("InstallLocation"); - if (folderObject == null) continue; - var folder = folderObject.ToString(); - var possiblePath = Path.Combine(folder, @"bin\rider64.exe"); - if (File.Exists(possiblePath)) - installPaths.Add(possiblePath); - } - } - } - - private static string[] CollectPathsFromToolbox(string toolboxRiderRootPath, string dirName, string searchPattern, - bool isMac) - { - if (!Directory.Exists(toolboxRiderRootPath)) - return Array.Empty<string>(); - - var channelDirs = Directory.GetDirectories(toolboxRiderRootPath); - var paths = channelDirs.SelectMany(channelDir => - { - try - { - // use history.json - last entry stands for the active build https://jetbrains.slack.com/archives/C07KNP99D/p1547807024066500?thread_ts=1547731708.057700&cid=C07KNP99D - var historyFile = Path.Combine(channelDir, ".history.json"); - if (File.Exists(historyFile)) - { - var json = File.ReadAllText(historyFile); - var build = ToolboxHistory.GetLatestBuildFromJson(json); - if (build != null) - { - var buildDir = Path.Combine(channelDir, build); - var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); - if (executablePaths.Any()) - return executablePaths; - } - } - - var channelFile = Path.Combine(channelDir, ".channel.settings.json"); - if (File.Exists(channelFile)) - { - var json = File.ReadAllText(channelFile).Replace("active-application", "active_application"); - var build = ToolboxInstallData.GetLatestBuildFromJson(json); - if (build != null) - { - var buildDir = Path.Combine(channelDir, build); - var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); - if (executablePaths.Any()) - return executablePaths; - } - } - - // changes in toolbox json files format may brake the logic above, so return all found Rider installations - return Directory.GetDirectories(channelDir) - .SelectMany(buildDir => GetExecutablePaths(dirName, searchPattern, isMac, buildDir)); - } - catch (Exception e) - { - // do not write to Debug.Log, just log it. - Logger.Warn($"Failed to get RiderPath from {channelDir}", e); - } - - return Array.Empty<string>(); - }) - .Where(c => !string.IsNullOrEmpty(c)) - .ToArray(); - return paths; - } - - private static string[] GetExecutablePaths(string dirName, string searchPattern, bool isMac, string buildDir) - { - var folder = new DirectoryInfo(Path.Combine(buildDir, dirName)); - if (!folder.Exists) - return Array.Empty<string>(); - - if (!isMac) - return new[] { Path.Combine(folder.FullName, searchPattern) }.Where(File.Exists).ToArray(); - return folder.GetDirectories(searchPattern).Select(f => f.FullName) - .Where(Directory.Exists).ToArray(); - } - - // Disable the "field is never assigned" compiler warning. We never assign it, but Unity does. - // Note that Unity disable this warning in the generated C# projects -#pragma warning disable 0649 - - [Serializable] - class SettingsJson - { - public string install_location; - - [return: MaybeNull] - public static string GetInstallLocationFromJson(string json) - { - try - { - return JsonConvert.DeserializeObject<SettingsJson>(json).install_location; - } - catch (Exception) - { - Logger.Warn($"Failed to get install_location from json {json}"); - } - - return null; - } - } - - [Serializable] - class ToolboxHistory - { - public List<ItemNode> history; - - public static string GetLatestBuildFromJson(string json) - { - try - { - return JsonConvert.DeserializeObject<ToolboxHistory>(json).history.LastOrDefault()?.item.build; - } - catch (Exception) - { - Logger.Warn($"Failed to get latest build from json {json}"); - } - - return null; - } - } - - [Serializable] - class ItemNode - { - public BuildNode item; - } - - [Serializable] - class BuildNode - { - public string build; - } - - [Serializable] - public class ProductInfo - { - public string version; - public string versionSuffix; - - [return: MaybeNull] - internal static ProductInfo GetProductInfo(string json) - { - try - { - var productInfo = JsonConvert.DeserializeObject<ProductInfo>(json); - return productInfo; - } - catch (Exception) - { - Logger.Warn($"Failed to get version from json {json}"); - } - - return null; - } - } - - // ReSharper disable once ClassNeverInstantiated.Global - [Serializable] - class ToolboxInstallData - { - // ReSharper disable once InconsistentNaming - public ActiveApplication active_application; - - [return: MaybeNull] - public static string GetLatestBuildFromJson(string json) - { - try - { - var toolbox = JsonConvert.DeserializeObject<ToolboxInstallData>(json); - var builds = toolbox.active_application.builds; - if (builds != null && builds.Any()) - return builds.First(); - } - catch (Exception) - { - Logger.Warn($"Failed to get latest build from json {json}"); - } - - return null; - } - } - - [Serializable] - class ActiveApplication - { - public List<string> builds; - } - -#pragma warning restore 0649 - - public struct RiderInfo - { - // ReSharper disable once NotAccessedField.Global - public bool IsToolbox; - public string Presentation; - public Version BuildNumber; - public ProductInfo ProductInfo; - public string Path; - - public RiderInfo(string path, bool isToolbox) - { - BuildNumber = GetBuildNumber(path); - ProductInfo = GetBuildVersion(path); - Path = new FileInfo(path).FullName; // normalize separators - var presentation = $"Rider {BuildNumber}"; - - if (ProductInfo != null && !string.IsNullOrEmpty(ProductInfo.version)) - { - var suffix = string.IsNullOrEmpty(ProductInfo.versionSuffix) ? "" : $" {ProductInfo.versionSuffix}"; - presentation = $"Rider {ProductInfo.version}{suffix}"; - } - - if (isToolbox) - presentation += " (JetBrains Toolbox)"; - - Presentation = presentation; - IsToolbox = isToolbox; - } - } - - private static class Logger - { - internal static void Warn(string message, Exception e = null) - { - throw new Exception(message, e); - } - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index f55ca4c7d7..0d77b8999a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -1,14 +1,24 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using Godot; using GodotTools.Internals; +using JetBrains.Rider.PathLocator; namespace GodotTools.Ides.Rider { public static class RiderPathManager { + private static readonly RiderPathLocator RiderPathLocator; + private static readonly RiderFileOpener RiderFileOpener; + + static RiderPathManager() + { + var riderLocatorEnvironment = new RiderLocatorEnvironment(); + RiderPathLocator = new RiderPathLocator(riderLocatorEnvironment); + RiderFileOpener = new RiderFileOpener(riderLocatorEnvironment); + } + public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional"; private static string GetRiderPathFromSettings() @@ -38,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; @@ -55,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)) @@ -76,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 15bb574f4d..583cab6f45 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1616,7 +1616,16 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Generate InvokeGodotClassMethod - output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Invokes the method with the given name, using the given arguments.\n" + << INDENT1 "/// This method is used by Godot to invoke methods from the engine side.\n" + << INDENT1 "/// Do not call or override this method.\n" + << INDENT1 "/// </summary>\n" + << INDENT1 "/// <param name=\"method\">Name of the method to invoke.</param>\n" + << INDENT1 "/// <param name=\"args\">Arguments to use with the invoked method.</param>\n" + << INDENT1 "/// <param name=\"ret\">Value returned by the invoked method.</param>\n"; + + output << INDENT1 "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, " << "NativeVariantPtrArgs args, out godot_variant ret)\n" << INDENT1 "{\n"; @@ -1696,6 +1705,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Generate HasGodotClassMethod + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Check if the type contains a method with the given name.\n" + << INDENT1 "/// This method is used by Godot to check if a method exists before invoking it.\n" + << INDENT1 "/// Do not call or override this method.\n" + << INDENT1 "/// </summary>\n" + << INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n"; + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n" << INDENT1 "{\n"; @@ -1728,6 +1744,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Generate HasGodotClassSignal + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Check if the type contains a signal with the given name.\n" + << INDENT1 "/// This method is used by Godot to check if a signal exists before raising it.\n" + << INDENT1 "/// Do not call or override this method.\n" + << INDENT1 "/// </summary>\n" + << INDENT1 "/// <param name=\"method\">Name of the method to check for.</param>\n"; + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_HAS_GODOT_CLASS_SIGNAL "(in godot_string_name signal)\n" << INDENT1 "{\n"; @@ -1758,39 +1781,57 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str //Generate StringName for all class members bool is_inherit = !itype.is_singleton && obj_types.has(itype.base_name); //PropertyName + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Cached StringNames for the properties and fields contained in this class, for fast lookup.\n" + << INDENT1 "/// </summary>\n"; if (is_inherit) { - output << MEMBER_BEGIN "public new class PropertyName : " << obj_types[itype.base_name].proxy_name << ".PropertyName"; + output << INDENT1 "public new class PropertyName : " << obj_types[itype.base_name].proxy_name << ".PropertyName"; } else { - output << MEMBER_BEGIN "public class PropertyName"; + output << INDENT1 "public class PropertyName"; } output << "\n" << INDENT1 "{\n"; for (const PropertyInterface &iprop : itype.properties) { - output << INDENT2 "public static readonly StringName " << iprop.proxy_name << " = \"" << iprop.cname << "\";\n"; + output << INDENT2 "/// <summary>\n" + << INDENT2 "/// Cached name for the '" << iprop.cname << "' property.\n" + << INDENT2 "/// </summary>\n" + << INDENT2 "public static readonly StringName " << iprop.proxy_name << " = \"" << iprop.cname << "\";\n"; } output << INDENT1 "}\n"; //MethodName + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Cached StringNames for the methods contained in this class, for fast lookup.\n" + << INDENT1 "/// </summary>\n"; if (is_inherit) { - output << MEMBER_BEGIN "public new class MethodName : " << obj_types[itype.base_name].proxy_name << ".MethodName"; + output << INDENT1 "public new class MethodName : " << obj_types[itype.base_name].proxy_name << ".MethodName"; } else { - output << MEMBER_BEGIN "public class MethodName"; + output << INDENT1 "public class MethodName"; } output << "\n" << INDENT1 "{\n"; for (const MethodInterface &imethod : itype.methods) { - output << INDENT2 "public static readonly StringName " << imethod.proxy_name << " = \"" << imethod.cname << "\";\n"; + output << INDENT2 "/// <summary>\n" + << INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n" + << INDENT2 "/// </summary>\n" + << INDENT2 "public static readonly StringName " << imethod.proxy_name << " = \"" << imethod.cname << "\";\n"; } output << INDENT1 "}\n"; //SignalName + output << MEMBER_BEGIN "/// <summary>\n" + << INDENT1 "/// Cached StringNames for the signals contained in this class, for fast lookup.\n" + << INDENT1 "/// </summary>\n"; if (is_inherit) { - output << MEMBER_BEGIN "public new class SignalName : " << obj_types[itype.base_name].proxy_name << ".SignalName"; + output << INDENT1 "public new class SignalName : " << obj_types[itype.base_name].proxy_name << ".SignalName"; } else { - output << MEMBER_BEGIN "public class SignalName"; + output << INDENT1 "public class SignalName"; } output << "\n" << INDENT1 "{\n"; for (const SignalInterface &isignal : itype.signals_) { - output << INDENT2 "public static readonly StringName " << isignal.proxy_name << " = \"" << isignal.cname << "\";\n"; + output << INDENT2 "/// <summary>\n" + << INDENT2 "/// Cached name for the '" << isignal.cname << "' signal.\n" + << INDENT2 "/// </summary>\n" + << INDENT2 "public static readonly StringName " << isignal.proxy_name << " = \"" << isignal.cname << "\";\n"; } output << INDENT1 "}\n"; @@ -2087,6 +2128,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) { @@ -2460,6 +2506,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 @@ -2525,7 +2576,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) { @@ -3765,7 +3821,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));"; @@ -3792,7 +3848,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));"; 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/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index af83cc24bf..d25944dceb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -564,7 +564,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this <see cref="Aabb"/> is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -683,7 +683,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the AABB is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 36f5d8e2ab..d53dd9a9af 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.ComponentModel; namespace Godot { @@ -595,7 +596,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this basis is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -623,21 +624,31 @@ namespace Godot /// </summary> /// <param name="target">The position to look at.</param> /// <param name="up">The relative up direction.</param> + /// <param name="useModelFront"> + /// If true, then the model is oriented in reverse, + /// towards the model front axis (+Z, Vector3.ModelFront), + /// which is more useful for orienting 3D models. + /// </param> /// <returns>The resulting basis matrix.</returns> - public static Basis LookingAt(Vector3 target, Vector3 up) + public static Basis LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false) { + up ??= Vector3.Up; #if DEBUG if (target.IsZeroApprox()) { throw new ArgumentException("The vector can't be zero.", nameof(target)); } - if (up.IsZeroApprox()) + if (up.Value.IsZeroApprox()) { throw new ArgumentException("The vector can't be zero.", nameof(up)); } #endif - Vector3 column2 = -target.Normalized(); - Vector3 column0 = up.Cross(column2); + Vector3 column2 = target.Normalized(); + if (!useModelFront) + { + column2 = -column2; + } + Vector3 column0 = up.Value.Cross(column2); #if DEBUG if (column0.IsZeroApprox()) { @@ -649,6 +660,13 @@ namespace Godot return new Basis(column0, column1, column2); } + /// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public static Basis LookingAt(Vector3 target, Vector3 up) + { + return LookingAt(target, up, false); + } + /// <summary> /// Returns the orthonormalized version of the basis matrix (useful to /// call occasionally to avoid rounding errors for orthogonal matrices). @@ -1065,7 +1083,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the <see cref="Basis"/> is - /// exactly equal to the given object (<see paramref="obj"/>). + /// exactly equal to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> 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/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 5d03379430..6c2fb7374c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -500,24 +500,17 @@ namespace Godot Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void); int parametersCount = reader.ReadInt32(); + var parameterTypes = parametersCount == 0 ? Type.EmptyTypes : new Type[parametersCount]; - if (parametersCount > 0) + for (int i = 0; i < parametersCount; i++) { - var parameterTypes = new Type[parametersCount]; - - for (int i = 0; i < parametersCount; i++) - { - Type? parameterType = DeserializeType(reader); - if (parameterType == null) - return false; - parameterTypes[i] = parameterType; - } - - methodInfo = declaringType.GetMethod(methodName, (BindingFlags)flags, null, parameterTypes, null); - return methodInfo != null && methodInfo.ReturnType == returnType; + Type? parameterType = DeserializeType(reader); + if (parameterType == null) + return false; + parameterTypes[i] = parameterType; } - methodInfo = declaringType.GetMethod(methodName, (BindingFlags)flags); + methodInfo = declaringType.GetMethod(methodName, (BindingFlags)flags, null, parameterTypes, null); return methodInfo != null && methodInfo.ReturnType == returnType; } 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/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index b9a5ac82d1..c6337e56ef 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -125,7 +125,10 @@ namespace Godot NativePtr = IntPtr.Zero; } - DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf); + if (_weakReferenceToSelf != null) + { + DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf); + } } /// <summary> @@ -188,12 +191,30 @@ namespace Godot } // ReSharper disable once VirtualMemberNeverOverridden.Global + /// <summary> + /// Set the value of a property contained in this class. + /// This method is used by Godot to assign property values. + /// Do not call or override this method. + /// </summary> + /// <param name="name">Name of the property to set.</param> + /// <param name="value">Value to set the property to if it was found.</param> + /// <returns><see langword="true"/> if a property with the given name was found.</returns> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { return false; } // ReSharper disable once VirtualMemberNeverOverridden.Global + /// <summary> + /// Get the value of a property contained in this class. + /// This method is used by Godot to retrieve property values. + /// Do not call or override this method. + /// </summary> + /// <param name="name">Name of the property to get.</param> + /// <param name="value">Value of the property if it was found.</param> + /// <returns><see langword="true"/> if a property with the given name was found.</returns> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { value = default; @@ -201,6 +222,14 @@ namespace Godot } // ReSharper disable once VirtualMemberNeverOverridden.Global + /// <summary> + /// Raises the signal with the given name, using the given arguments. + /// This method is used by Godot to raise signals from the engine side.\n" + /// Do not call or override this method. + /// </summary> + /// <param name="signal">Name of the signal to raise.</param> + /// <param name="args">Arguments to use with the raised signal.</param> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal, NativeVariantPtrArgs args) { @@ -230,11 +259,25 @@ namespace Godot return nativeConstructor; } + /// <summary> + /// Saves this instance's state to be restored when reloading assemblies. + /// Do not call or override this method. + /// To add data to be saved and restored, implement <see cref="ISerializationListener"/>. + /// </summary> + /// <param name="info">Object used to save the data.</param> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info) { } // TODO: Should this be a constructor overload? + /// <summary> + /// Restores this instance's state after reloading assemblies. + /// Do not call or override this method. + /// To add data to be saved and restored, implement <see cref="ISerializationListener"/>. + /// </summary> + /// <param name="info">Object that contains the previously saved data.</param> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info) { } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs index 90b4d1b8d3..3288705dab 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISerializationListener.cs @@ -1,11 +1,21 @@ namespace Godot { /// <summary> - /// An interface that requires methods for before and after serialization. + /// Allows a GodotObject to react to the serialization/deserialization + /// that occurs when Godot reloads assemblies. /// </summary> public interface ISerializationListener { + /// <summary> + /// Executed before serializing this instance's state when reloading assemblies. + /// Clear any data that should not be serialized. + /// </summary> void OnBeforeSerialize(); + + /// <summary> + /// Executed after deserializing this instance's state after reloading assemblies. + /// Restore any state that has been lost. + /// </summary> void OnAfterDeserialize(); } } 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/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 55b7a83fc2..3c7455a76c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -204,7 +204,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this plane is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index 84fc73b87a..998a2786a7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -976,7 +976,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the projection is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 9c2a6fc654..2e282447bd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -103,7 +103,7 @@ namespace Godot /// /// Note: This method has an abnormally high amount /// of floating-point error, so methods such as - /// <see cref="Mathf.IsZeroApprox"/> will not work reliably. + /// <see cref="Mathf.IsZeroApprox(real_t)"/> will not work reliably. /// </summary> /// <param name="to">The other quaternion.</param> /// <returns>The angle between the quaternions.</returns> @@ -320,7 +320,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this quaternion is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 69444f8035..458802f95d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -102,7 +102,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this <see cref="Rect2"/> is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public bool IsFinite() diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index d7392dbda8..618c892681 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -232,7 +232,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this transform is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -586,7 +586,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the transform is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 1e2aaa299f..b16e6e592e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.ComponentModel; namespace Godot { @@ -155,7 +156,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this transform is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -175,14 +176,26 @@ namespace Godot /// </summary> /// <param name="target">The object to look at.</param> /// <param name="up">The relative up direction.</param> + /// <param name="useModelFront"> + /// If true, then the model is oriented in reverse, + /// towards the model front axis (+Z, Vector3.ModelFront), + /// which is more useful for orienting 3D models. + /// </param> /// <returns>The resulting transform.</returns> - public readonly Transform3D LookingAt(Vector3 target, Vector3 up) + public readonly Transform3D LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false) { Transform3D t = this; - t.SetLookAt(Origin, target, up); + t.SetLookAt(Origin, target, up ?? Vector3.Up, useModelFront); return t; } + /// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public readonly Transform3D LookingAt(Vector3 target, Vector3 up) + { + return LookingAt(target, up, false); + } + /// <summary> /// Returns the transform with the basis orthogonal (90 degrees), /// and normalized axis vectors (scale of 1 or -1). @@ -247,9 +260,9 @@ namespace Godot return new Transform3D(Basis * tmpBasis, Origin); } - private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) + private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up, bool useModelFront = false) { - Basis = Basis.LookingAt(target - eye, up); + Basis = Basis.LookingAt(target - eye, up, useModelFront); Origin = eye; } @@ -600,7 +613,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the transform is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 0bf8f25f06..642ef231f3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -164,7 +164,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Ceil(real_t)"/> called on each component.</returns> public readonly Vector2 Ceil() { return new Vector2(Mathf.Ceil(X), Mathf.Ceil(Y)); @@ -318,7 +318,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Floor(real_t)"/> called on each component.</returns> public readonly Vector2 Floor() { return new Vector2(Mathf.Floor(X), Mathf.Floor(Y)); @@ -335,7 +335,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this vector is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -948,7 +948,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs index 0dac8205b6..231e791904 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs @@ -517,7 +517,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index c773c0fda6..7d548f1d10 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -150,7 +150,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Ceil(real_t)"/> called on each component.</returns> public readonly Vector3 Ceil() { return new Vector3(Mathf.Ceil(X), Mathf.Ceil(Y), Mathf.Ceil(Z)); @@ -315,7 +315,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Floor(real_t)"/> called on each component.</returns> public readonly Vector3 Floor() { return new Vector3(Mathf.Floor(X), Mathf.Floor(Y), Mathf.Floor(Z)); @@ -332,7 +332,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this vector is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -660,6 +660,13 @@ namespace Godot private static readonly Vector3 _forward = new Vector3(0, 0, -1); private static readonly Vector3 _back = new Vector3(0, 0, 1); + private static readonly Vector3 _modelLeft = new Vector3(1, 0, 0); + private static readonly Vector3 _modelRight = new Vector3(-1, 0, 0); + private static readonly Vector3 _modelTop = new Vector3(0, 1, 0); + private static readonly Vector3 _modelBottom = new Vector3(0, -1, 0); + private static readonly Vector3 _modelFront = new Vector3(0, 0, 1); + private static readonly Vector3 _modelRear = new Vector3(0, 0, -1); + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> @@ -712,6 +719,31 @@ namespace Godot public static Vector3 Back { get { return _back; } } /// <summary> + /// Unit vector pointing towards the left side of imported 3D assets. + /// </summary> + public static Vector3 ModelLeft { get { return _modelLeft; } } + /// <summary> + /// Unit vector pointing towards the right side of imported 3D assets. + /// </summary> + public static Vector3 ModelRight { get { return _modelRight; } } + /// <summary> + /// Unit vector pointing towards the top side (up) of imported 3D assets. + /// </summary> + public static Vector3 ModelTop { get { return _modelTop; } } + /// <summary> + /// Unit vector pointing towards the bottom side (down) of imported 3D assets. + /// </summary> + public static Vector3 ModelBottom { get { return _modelBottom; } } + /// <summary> + /// Unit vector pointing towards the front side (facing forward) of imported 3D assets. + /// </summary> + public static Vector3 ModelFront { get { return _modelFront; } } + /// <summary> + /// Unit vector pointing towards the rear side (back) of imported 3D assets. + /// </summary> + public static Vector3 ModelRear { get { return _modelRear; } } + + /// <summary> /// Constructs a new <see cref="Vector3"/> with the given components. /// </summary> /// <param name="x">The vector's X component.</param> @@ -1018,7 +1050,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs index a2927533f8..8543052f56 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs @@ -572,7 +572,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 1fd39632b0..10a0b14162 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -147,7 +147,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Ceil(real_t)"/> called on each component.</returns> public readonly Vector4 Ceil() { return new Vector4(Mathf.Ceil(X), Mathf.Ceil(Y), Mathf.Ceil(Z), Mathf.Ceil(W)); @@ -264,7 +264,7 @@ namespace Godot /// <summary> /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> - /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> + /// <returns>A vector with <see cref="Mathf.Floor(real_t)"/> called on each component.</returns> public readonly Vector4 Floor() { return new Vector4(Mathf.Floor(X), Mathf.Floor(Y), Mathf.Floor(Z), Mathf.Floor(W)); @@ -281,7 +281,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if this vector is finite, by calling - /// <see cref="Mathf.IsFinite"/> on each component. + /// <see cref="Mathf.IsFinite(real_t)"/> on each component. /// </summary> /// <returns>Whether this vector is finite or not.</returns> public readonly bool IsFinite() @@ -832,7 +832,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is exactly equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// Note: Due to floating-point precision errors, consider using /// <see cref="IsEqualApprox"/> instead, which is more reliable. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs index bb552b939d..f813903177 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs @@ -593,7 +593,7 @@ namespace Godot /// <summary> /// Returns <see langword="true"/> if the vector is equal - /// to the given object (<see paramref="obj"/>). + /// to the given object (<paramref name="obj"/>). /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> 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/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index e5207fdae2..9c2d281f72 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -157,7 +157,7 @@ Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Obj bool valid = false; const Object *obj = _get_prop_target(p_obj, prop); ERR_FAIL_COND_V(!obj, FAILED); - r_variant.write[i] = obj->get(prop.get_concatenated_subnames(), &valid); + r_variant.write[i] = obj->get_indexed(prop.get_subnames(), &valid); r_variant_ptrs.write[i] = &r_variant[i]; ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); i++; @@ -171,7 +171,7 @@ Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Obj for (const NodePath &prop : p_properties) { Object *obj = _get_prop_target(p_obj, prop); ERR_FAIL_COND_V(!obj, FAILED); - obj->set(prop.get_concatenated_subnames(), p_state[i]); + obj->set_indexed(prop.get_subnames(), p_state[i]); i += 1; } return OK; diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index bf34779735..c0fa6eef9e 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -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); @@ -446,10 +460,13 @@ COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navi region->set_mesh(p_navigation_mesh); } +#ifndef DISABLE_DEPRECATED void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) { ERR_FAIL_COND(p_navigation_mesh.is_null()); ERR_FAIL_COND(p_root_node == nullptr); + 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); Ref<NavigationMeshSourceGeometryData3D> source_geometry_data; @@ -458,6 +475,7 @@ void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_na NavigationMeshGenerator::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, source_geometry_data); #endif } +#endif // DISABLE_DEPRECATED int GodotNavigationServer::region_get_connections_count(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); @@ -508,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); @@ -784,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); diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index 6b394157bc..0b3789102c 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -135,6 +135,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; @@ -154,7 +157,9 @@ public: virtual uint32_t region_get_navigation_layers(RID p_region) const override; COMMAND_2(region_set_transform, RID, p_region, Transform3D, p_transform); COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navigation_mesh); +#ifndef DISABLE_DEPRECATED virtual void region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) override; +#endif // DISABLE_DEPRECATED virtual int region_get_connections_count(RID p_region) const override; virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override; virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; @@ -162,6 +167,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); 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 2e4bf38f50..737ccaf3cd 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -301,6 +301,46 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p } } + // Search all faces of start polygon as well. + bool closest_point_on_start_poly = false; + for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { + Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + closest_point_on_start_poly = true; + } + } + + if (closest_point_on_start_poly) { + // No point to run PostProcessing when start and end convex polygon is the same. + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = begin_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = begin_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = begin_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; + } + // Reset open and navigation_polys gd::NavigationPoly np = navigation_polys[0]; navigation_polys.clear(); @@ -346,9 +386,44 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p } } - // If we did not find a route, return an empty path. + // We did not find a route but we have both a start polygon and an end polygon at this point. + // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon. if (!found_route) { - return Vector<Vector3>(); + end_d = FLT_MAX; + // Search all faces of the start polygon for the closest point to our target position. + for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { + Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + } + } + + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = begin_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = begin_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = begin_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; } Vector<Vector3> path; @@ -356,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; @@ -729,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); @@ -736,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]; @@ -968,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_region.cpp b/modules/navigation/nav_region.cpp index 867cf5d8fc..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; @@ -115,11 +125,11 @@ void NavRegion::update_polygons() { #ifdef DEBUG_ENABLED if (!Math::is_equal_approx(double(map->get_cell_size()), double(mesh->get_cell_size()))) { - ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a different `cell_size` than the `cell_size` set on the navigation map."); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_size()), double(mesh->get_cell_size()))); } if (!Math::is_equal_approx(double(map->get_cell_height()), double(mesh->get_cell_height()))) { - ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a different `cell_height` than the `cell_height` set on the navigation map."); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_height()), double(mesh->get_cell_height()))); } if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) { 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/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/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp index a7176e0816..1b0c5cb9e3 100644 --- a/modules/noise/noise_texture_2d.cpp +++ b/modules/noise/noise_texture_2d.cpp @@ -32,8 +32,6 @@ #include "noise.h" -#include "core/core_string_names.h" - NoiseTexture2D::NoiseTexture2D() { noise = Ref<Noise>(); @@ -223,11 +221,11 @@ void NoiseTexture2D::set_noise(Ref<Noise> p_noise) { return; } if (noise.is_valid()) { - noise->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + noise->disconnect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } noise = p_noise; if (noise.is_valid()) { - noise->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + noise->connect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } _queue_update(); } @@ -347,11 +345,11 @@ void NoiseTexture2D::set_color_ramp(const Ref<Gradient> &p_gradient) { return; } if (color_ramp.is_valid()) { - color_ramp->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + color_ramp->disconnect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } color_ramp = p_gradient; if (color_ramp.is_valid()) { - color_ramp->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture2D::_queue_update)); + color_ramp->connect_changed(callable_mp(this, &NoiseTexture2D::_queue_update)); } _queue_update(); } diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp index f6c67b0f2d..ed242e7faa 100644 --- a/modules/noise/noise_texture_3d.cpp +++ b/modules/noise/noise_texture_3d.cpp @@ -32,8 +32,6 @@ #include "noise.h" -#include "core/core_string_names.h" - NoiseTexture3D::NoiseTexture3D() { noise = Ref<Noise>(); @@ -214,11 +212,11 @@ void NoiseTexture3D::set_noise(Ref<Noise> p_noise) { return; } if (noise.is_valid()) { - noise->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + noise->disconnect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } noise = p_noise; if (noise.is_valid()) { - noise->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + noise->connect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } _queue_update(); } @@ -297,11 +295,11 @@ void NoiseTexture3D::set_color_ramp(const Ref<Gradient> &p_gradient) { return; } if (color_ramp.is_valid()) { - color_ramp->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + color_ramp->disconnect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } color_ramp = p_gradient; if (color_ramp.is_valid()) { - color_ramp->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &NoiseTexture3D::_queue_update)); + color_ramp->connect_changed(callable_mp(this, &NoiseTexture3D::_queue_update)); } _queue_update(); } diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h index e2ec39ef48..938e8fd6ab 100644 --- a/modules/noise/tests/test_noise_texture_2d.h +++ b/modules/noise/tests/test_noise_texture_2d.h @@ -210,7 +210,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a basic noise texture with mip noise_texture->set_generate_mipmaps(true); Ref<NoiseTextureTester> tester = memnew(NoiseTextureTester(noise_texture.ptr())); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_mip_and_color_ramp)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_mip_and_color_ramp)); MessageQueue::get_singleton()->flush(); } @@ -227,7 +227,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a normal map without mipmaps") noise_texture->set_generate_mipmaps(false); Ref<NoiseTextureTester> tester = memnew(NoiseTextureTester(noise_texture.ptr())); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_normal_map)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_normal_map)); MessageQueue::get_singleton()->flush(); } @@ -245,7 +245,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a seamless noise texture") { SUBCASE("Grayscale(L8) 16x16, with seamless blend skirt of 0.05") { noise_texture->set_seamless_blend_skirt(0.05); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_grayscale)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_grayscale)); MessageQueue::get_singleton()->flush(); } @@ -257,7 +257,7 @@ TEST_CASE("[NoiseTexture2D][SceneTree] Generating a seamless noise texture") { gradient->set_points(points); noise_texture->set_color_ramp(gradient); noise_texture->set_seamless_blend_skirt(1.0); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_rgba)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTextureTester::check_seamless_texture_rgba)); MessageQueue::get_singleton()->flush(); } } diff --git a/modules/noise/tests/test_noise_texture_3d.h b/modules/noise/tests/test_noise_texture_3d.h index a612f2920a..b708eac43b 100644 --- a/modules/noise/tests/test_noise_texture_3d.h +++ b/modules/noise/tests/test_noise_texture_3d.h @@ -194,7 +194,7 @@ TEST_CASE("[NoiseTexture3D][SceneTree] Generating a basic noise texture with mip noise_texture->set_depth(16); Ref<NoiseTexture3DTester> tester = memnew(NoiseTexture3DTester(noise_texture.ptr())); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTexture3DTester::check_mip_and_color_ramp)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTexture3DTester::check_mip_and_color_ramp)); MessageQueue::get_singleton()->flush(); } @@ -213,7 +213,7 @@ TEST_CASE("[NoiseTexture3D][SceneTree] Generating a seamless noise texture") { SUBCASE("Grayscale(L8) 16x16x16, with seamless blend skirt of 0.05") { noise_texture->set_seamless_blend_skirt(0.05); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_grayscale)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_grayscale)); MessageQueue::get_singleton()->flush(); } @@ -225,7 +225,7 @@ TEST_CASE("[NoiseTexture3D][SceneTree] Generating a seamless noise texture") { gradient->set_points(points); noise_texture->set_color_ramp(gradient); noise_texture->set_seamless_blend_skirt(1.0); - noise_texture->connect("changed", callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_rgba)); + noise_texture->connect_changed(callable_mp(tester.ptr(), &NoiseTexture3DTester::check_seamless_texture_rgba)); MessageQueue::get_singleton()->flush(); } } diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 0dd41675b6..f49dc390de 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -5,22 +5,13 @@ Import("env_modules") env_openxr = env_modules.Clone() -################################################# -# Add in our Khronos OpenXR loader +# Thirdparty source files thirdparty_obj = [] -thirdparty_dir = "#thirdparty/openxr" - -env_openxr.Prepend( - CPPPATH=[ - thirdparty_dir, - thirdparty_dir + "/include", - thirdparty_dir + "/src", - thirdparty_dir + "/src/common", - thirdparty_dir + "/src/external/jsoncpp/include", - ] -) +# Khronos OpenXR loader + +# Needs even for build against shared library, at least the defines used in public headers. if env["platform"] == "android": # may need to set OPENXR_ANDROID_VERSION_SUFFIX env_openxr.AppendUnique(CPPDEFINES=["XR_OS_ANDROID", "XR_USE_PLATFORM_ANDROID"]) @@ -37,47 +28,66 @@ elif env["platform"] == "linuxbsd": env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) elif env["platform"] == "windows": env_openxr.AppendUnique(CPPDEFINES=["XR_OS_WINDOWS", "NOMINMAX", "XR_USE_PLATFORM_WIN32"]) +elif env["platform"] == "macos": + env_openxr.AppendUnique(CPPDEFINES=["XR_OS_APPLE"]) -# may need to check and set: -# - XR_USE_TIMESPEC - -env_thirdparty = env_openxr.Clone() -env_thirdparty.disable_warnings() -env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"]) - -if "-fno-exceptions" in env_thirdparty["CXXFLAGS"]: - env_thirdparty["CXXFLAGS"].remove("-fno-exceptions") -env_thirdparty.Append(CPPPATH=[thirdparty_dir + "/src/loader"]) + # There does not seem to be a XR_USE_PLATFORM_XYZ for Apple -# add in external jsoncpp dependency -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_reader.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_value.cpp") -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_writer.cpp") -# add in load -if env["platform"] != "android": - # On Android the openxr_loader is provided by separate plugins for each device - # Build the engine using object files - khrloader_obj = [] - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") - - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/object_info.cpp") - - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/api_layer_interface.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_core.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_instance.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_logger_recorders.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_logger.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/manifest_file.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/runtime_interface.cpp") - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/xr_generated_loader.cpp") - env.modules_sources += khrloader_obj - -env.modules_sources += thirdparty_obj +# may need to check and set: +# - XR_USE_TIMESPEC -################################################# -# And include our module source +if env["builtin_openxr"]: + thirdparty_dir = "#thirdparty/openxr" + + env_openxr.Prepend( + CPPPATH=[ + thirdparty_dir, + thirdparty_dir + "/include", + thirdparty_dir + "/src", + thirdparty_dir + "/src/common", + thirdparty_dir + "/src/external/jsoncpp/include", + ] + ) + + env_thirdparty = env_openxr.Clone() + env_thirdparty.disable_warnings() + env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"]) + + if "-fno-exceptions" in env_thirdparty["CXXFLAGS"]: + env_thirdparty["CXXFLAGS"].remove("-fno-exceptions") + env_thirdparty.Append(CPPPATH=[thirdparty_dir + "/src/loader"]) + + # add in external jsoncpp dependency + thirdparty_jsoncpp_dir = thirdparty_dir + "/src/external/jsoncpp/src/lib_json/" + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_jsoncpp_dir + "json_reader.cpp") + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_jsoncpp_dir + "json_value.cpp") + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_jsoncpp_dir + "json_writer.cpp") + + # add in load + if env["platform"] != "android": + # On Android the openxr_loader is provided by separate plugins for each device + # Build the engine using object files + khrloader_obj = [] + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") + + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/object_info.cpp") + + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/api_layer_interface.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_core.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_instance.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_logger_recorders.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/loader_logger.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/manifest_file.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/runtime_interface.cpp") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/loader/xr_generated_loader.cpp") + env.modules_sources += khrloader_obj + + env.modules_sources += thirdparty_obj + + +# Godot source files module_obj = [] @@ -90,7 +100,7 @@ if env["platform"] == "android": env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") if env["vulkan"]: env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") -if env["opengl3"]: +if env["opengl3"] and env["platform"] != "macos": env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") @@ -104,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/config.py b/modules/openxr/config.py index e503f12739..8ed06a1606 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - if platform in ("linuxbsd", "windows", "android"): + if platform in ("linuxbsd", "windows", "android", "macos"): return env["openxr"] and not env["disable_3d"] else: # not supported on these platforms 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.h b/modules/openxr/extensions/openxr_extension_wrapper.h index 920bfe74b7..31f8d23268 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -36,8 +36,6 @@ #include "core/templates/hash_map.h" #include "core/templates/rid.h" -#include "thirdparty/openxr/src/common/xr_linear.h" - #include <openxr/openxr.h> class OpenXRAPI; 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/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index 6fffa1ed07..65559afed0 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -134,6 +134,10 @@ void OpenXRHandTrackingExtension::on_process() { // process our hands const XrTime time = OpenXRAPI::get_singleton()->get_next_frame_time(); // This data will be used for the next frame we render + if (time == 0) { + // we don't have timing info yet, or we're skipping a frame... + return; + } XrResult result; diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp index 39b5c61e8e..9038e9f458 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -278,8 +278,8 @@ bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in } bool OpenXROpenGLExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) { - XrMatrix4x4f matrix; - XrMatrix4x4f_CreateProjectionFov(&matrix, GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); + OpenXRUtil::XrMatrix4x4f matrix; + OpenXRUtil::XrMatrix4x4f_CreateProjectionFov(&matrix, OpenXRUtil::GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); for (int j = 0; j < 4; j++) { for (int i = 0; i < 4; i++) { diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp index 2902c16baf..9429d9e082 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp @@ -381,8 +381,8 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, Projection &r_camera_matrix) { // Even though this is a Vulkan renderer we're using OpenGL coordinate systems - XrMatrix4x4f matrix; - XrMatrix4x4f_CreateProjectionFov(&matrix, GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); + OpenXRUtil::XrMatrix4x4f matrix; + OpenXRUtil::XrMatrix4x4f_CreateProjectionFov(&matrix, OpenXRUtil::GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); for (int j = 0; j < 4; j++) { for (int i = 0; i < 4; i++) { diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 4ab280f3c3..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" @@ -47,7 +48,7 @@ #ifdef VULKAN_ENABLED #define XR_USE_GRAPHICS_API_VULKAN #endif -#ifdef GLES3_ENABLED +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) #ifdef ANDROID_ENABLED #define XR_USE_GRAPHICS_API_OPENGL_ES #include <EGL/egl.h> @@ -72,7 +73,7 @@ #include "extensions/openxr_vulkan_extension.h" #endif -#ifdef GLES3_ENABLED +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) #include "extensions/openxr_opengl_extension.h" #endif @@ -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, @@ -1306,7 +1307,7 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { ERR_FAIL_V(false); #endif } else if (p_rendering_driver == "opengl3") { -#ifdef GLES3_ENABLED +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) graphics_extension = memnew(OpenXROpenGLExtension); register_extension_wrapper(graphics_extension); #else @@ -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.h b/modules/openxr/openxr_api.h index 96af2bfc49..9374cb7afa 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -48,8 +48,6 @@ #include "core/templates/vector.h" #include "servers/xr/xr_pose.h" -#include "thirdparty/openxr/src/common/xr_linear.h" - #include <openxr/openxr.h> // Note, OpenXR code that we wrote for our plugin makes use of C++20 notation for initializing structs which ensures zeroing out unspecified members. 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/openxr_util.cpp b/modules/openxr/openxr_util.cpp index 0c5cdd7113..1d44233337 100644 --- a/modules/openxr/openxr_util.cpp +++ b/modules/openxr/openxr_util.cpp @@ -32,6 +32,8 @@ #include <openxr/openxr_reflection.h> +#include <math.h> + #define XR_ENUM_CASE_STR(name, val) \ case name: \ return #name; @@ -75,3 +77,89 @@ String OpenXRUtil::make_xr_version_string(XrVersion p_version) { return version; } + +// Copied from OpenXR xr_linear.h private header, so we can still link against +// system-provided packages without relying on our `thirdparty` code. + +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2016 Oculus VR, LLC. +// +// SPDX-License-Identifier: Apache-2.0 + +// Creates a projection matrix based on the specified dimensions. +// The projection matrix transforms -Z=forward, +Y=up, +X=right to the appropriate clip space for the graphics API. +// The far plane is placed at infinity if farZ <= nearZ. +// An infinite projection matrix is preferred for rasterization because, except for +// things *right* up against the near plane, it always provides better precision: +// "Tightening the Precision of Perspective Rendering" +// Paul Upchurch, Mathieu Desbrun +// Journal of Graphics Tools, Volume 16, Issue 1, 2012 +void OpenXRUtil::XrMatrix4x4f_CreateProjection(XrMatrix4x4f *result, GraphicsAPI graphicsApi, const float tanAngleLeft, + const float tanAngleRight, const float tanAngleUp, float const tanAngleDown, + const float nearZ, const float farZ) { + const float tanAngleWidth = tanAngleRight - tanAngleLeft; + + // Set to tanAngleDown - tanAngleUp for a clip space with positive Y down (Vulkan). + // Set to tanAngleUp - tanAngleDown for a clip space with positive Y up (OpenGL / D3D / Metal). + const float tanAngleHeight = graphicsApi == GRAPHICS_VULKAN ? (tanAngleDown - tanAngleUp) : (tanAngleUp - tanAngleDown); + + // Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES). + // Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal). + const float offsetZ = (graphicsApi == GRAPHICS_OPENGL || graphicsApi == GRAPHICS_OPENGL_ES) ? nearZ : 0; + + if (farZ <= nearZ) { + // place the far plane at infinity + result->m[0] = 2.0f / tanAngleWidth; + result->m[4] = 0.0f; + result->m[8] = (tanAngleRight + tanAngleLeft) / tanAngleWidth; + result->m[12] = 0.0f; + + result->m[1] = 0.0f; + result->m[5] = 2.0f / tanAngleHeight; + result->m[9] = (tanAngleUp + tanAngleDown) / tanAngleHeight; + result->m[13] = 0.0f; + + result->m[2] = 0.0f; + result->m[6] = 0.0f; + result->m[10] = -1.0f; + result->m[14] = -(nearZ + offsetZ); + + result->m[3] = 0.0f; + result->m[7] = 0.0f; + result->m[11] = -1.0f; + result->m[15] = 0.0f; + } else { + // normal projection + result->m[0] = 2.0f / tanAngleWidth; + result->m[4] = 0.0f; + result->m[8] = (tanAngleRight + tanAngleLeft) / tanAngleWidth; + result->m[12] = 0.0f; + + result->m[1] = 0.0f; + result->m[5] = 2.0f / tanAngleHeight; + result->m[9] = (tanAngleUp + tanAngleDown) / tanAngleHeight; + result->m[13] = 0.0f; + + result->m[2] = 0.0f; + result->m[6] = 0.0f; + result->m[10] = -(farZ + offsetZ) / (farZ - nearZ); + result->m[14] = -(farZ * (nearZ + offsetZ)) / (farZ - nearZ); + + result->m[3] = 0.0f; + result->m[7] = 0.0f; + result->m[11] = -1.0f; + result->m[15] = 0.0f; + } +} + +// Creates a projection matrix based on the specified FOV. +void OpenXRUtil::XrMatrix4x4f_CreateProjectionFov(XrMatrix4x4f *result, GraphicsAPI graphicsApi, const XrFovf fov, + const float nearZ, const float farZ) { + const float tanLeft = tanf(fov.angleLeft); + const float tanRight = tanf(fov.angleRight); + + const float tanDown = tanf(fov.angleDown); + const float tanUp = tanf(fov.angleUp); + + XrMatrix4x4f_CreateProjection(result, graphicsApi, tanLeft, tanRight, tanUp, tanDown, nearZ, farZ); +} diff --git a/modules/openxr/openxr_util.h b/modules/openxr/openxr_util.h index 8ad68c0b02..3f36ab9fca 100644 --- a/modules/openxr/openxr_util.h +++ b/modules/openxr/openxr_util.h @@ -44,6 +44,27 @@ public: static String get_action_type_name(XrActionType p_action_type); static String get_environment_blend_mode_name(XrEnvironmentBlendMode p_blend_mode); static String make_xr_version_string(XrVersion p_version); + + // Copied from OpenXR xr_linear.h private header, so we can still link against + // system-provided packages without relying on our `thirdparty` code. + + // Column-major, pre-multiplied. This type does not exist in the OpenXR API and is provided for convenience. + typedef struct XrMatrix4x4f { + float m[16]; + } XrMatrix4x4f; + + typedef enum GraphicsAPI { + GRAPHICS_VULKAN, + GRAPHICS_OPENGL, + GRAPHICS_OPENGL_ES, + GRAPHICS_D3D + } GraphicsAPI; + + static void XrMatrix4x4f_CreateProjection(XrMatrix4x4f *result, GraphicsAPI graphicsApi, const float tanAngleLeft, + const float tanAngleRight, const float tanAngleUp, float const tanAngleDown, + const float nearZ, const float farZ); + static void XrMatrix4x4f_CreateProjectionFov(XrMatrix4x4f *result, GraphicsAPI graphicsApi, const XrFovf fov, + const float nearZ, const float farZ); }; #endif // OPENXR_UTIL_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/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index ad7feeda49..7639155914 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -67,12 +67,22 @@ void ImageLoaderSVG::_replace_color_property(const HashMap<Color, Color> &p_colo } } -Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const PackedByteArray &p_buffer, float p_scale, bool p_upsample) { +Ref<Image> ImageLoaderSVG::load_mem_svg(const uint8_t *p_svg, int p_size, float p_scale) { + Ref<Image> img; + img.instantiate(); + + Error err = create_image_from_utf8_buffer(img, p_svg, p_size, p_scale, false); + ERR_FAIL_COND_V(err, Ref<Image>()); + + return img; +} + +Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const uint8_t *p_buffer, int p_buffer_size, float p_scale, bool p_upsample) { ERR_FAIL_COND_V_MSG(Math::is_zero_approx(p_scale), ERR_INVALID_PARAMETER, "ImageLoaderSVG: Can't load SVG with a scale of 0."); std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); - tvg::Result result = picture->load((const char *)p_buffer.ptr(), p_buffer.size(), "svg", true); + tvg::Result result = picture->load((const char *)p_buffer, p_buffer_size, "svg", true); if (result != tvg::Result::Success) { return ERR_INVALID_DATA; } @@ -142,6 +152,10 @@ Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const Pa return OK; } +Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const PackedByteArray &p_buffer, float p_scale, bool p_upsample) { + return create_image_from_utf8_buffer(p_image, p_buffer.ptr(), p_buffer.size(), p_scale, p_upsample); +} + Error ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map) { if (p_color_map.size()) { _replace_color_property(p_color_map, "stop-color=\"", p_string); @@ -179,3 +193,7 @@ Error ImageLoaderSVG::load_image(Ref<Image> p_image, Ref<FileAccess> p_fileacces } return OK; } + +ImageLoaderSVG::ImageLoaderSVG() { + Image::_svg_scalable_mem_loader_func = load_mem_svg; +} diff --git a/modules/svg/image_loader_svg.h b/modules/svg/image_loader_svg.h index e0d2849a62..90e9458c37 100644 --- a/modules/svg/image_loader_svg.h +++ b/modules/svg/image_loader_svg.h @@ -36,16 +36,22 @@ class ImageLoaderSVG : public ImageFormatLoader { static HashMap<Color, Color> forced_color_map; - void _replace_color_property(const HashMap<Color, Color> &p_color_map, const String &p_prefix, String &r_string); + static void _replace_color_property(const HashMap<Color, Color> &p_color_map, const String &p_prefix, String &r_string); + + static Ref<Image> load_mem_svg(const uint8_t *p_svg, int p_size, float p_scale); public: static void set_forced_color_map(const HashMap<Color, Color> &p_color_map); - Error create_image_from_utf8_buffer(Ref<Image> p_image, const PackedByteArray &p_buffer, float p_scale, bool p_upsample); - Error create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map); + static Error create_image_from_utf8_buffer(Ref<Image> p_image, const uint8_t *p_buffer, int p_buffer_size, float p_scale, bool p_upsample); + static Error create_image_from_utf8_buffer(Ref<Image> p_image, const PackedByteArray &p_buffer, float p_scale, bool p_upsample); + + static Error create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map); virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) override; virtual void get_recognized_extensions(List<String> *p_extensions) const override; + + ImageLoaderSVG(); }; #endif // IMAGE_LOADER_SVG_H diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 82fbae5669..4b12a4e41d 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -227,7 +227,6 @@ if env["freetype_enabled"]: "compress.c", "crc32.c", "deflate.c", - "infback.c", "inffast.c", "inflate.c", "inftrees.c", @@ -263,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 b8010e6692..043a33ab35 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -52,6 +52,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/string/print_string.h" #include "core/string/translation.h" +#include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. @@ -4770,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_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index aba727edaa..44700e045b 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -85,7 +85,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 7b4c548a21..fcf8976019 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -222,7 +222,6 @@ if env["freetype_enabled"]: "compress.c", "crc32.c", "deflate.c", - "infback.c", "inffast.c", "inflate.c", "inftrees.c", @@ -258,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/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index d81b50779e..54311caaf9 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -83,7 +83,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 6c961813b4..d964fd7627 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "scene/resources/image_texture.h" #ifdef _MSC_VER #pragma warning(push) diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 32adc28a88..21d4caef45 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -43,6 +43,8 @@ #include <theora/theoradec.h> #include <vorbis/codec.h> +class ImageTexture; + //#define THEORA_USE_THREAD_STREAMING class VideoStreamPlaybackTheora : public VideoStreamPlayback { diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index fcd717cfec..b54335b724 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -33,6 +33,7 @@ #include "core/io/file_access.h" #include "core/variant/typed_array.h" +#include "modules/vorbis/resource_importer_ogg_vorbis.h" #include <ogg/ogg.h> int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { @@ -520,6 +521,9 @@ bool AudioStreamOggVorbis::is_monophonic() const { } void AudioStreamOggVorbis::_bind_methods() { + ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "buffer"), &AudioStreamOggVorbis::load_from_buffer); + ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file); + ClassDB::bind_method(D_METHOD("set_packet_sequence", "packet_sequence"), &AudioStreamOggVorbis::set_packet_sequence); ClassDB::bind_method(D_METHOD("get_packet_sequence"), &AudioStreamOggVorbis::get_packet_sequence); @@ -549,3 +553,11 @@ void AudioStreamOggVorbis::_bind_methods() { AudioStreamOggVorbis::AudioStreamOggVorbis() {} AudioStreamOggVorbis::~AudioStreamOggVorbis() {} + +Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_buffer(const Vector<uint8_t> &file_data) { + return ResourceImporterOggVorbis::load_from_buffer(file_data); +} + +Ref<AudioStreamOggVorbis> AudioStreamOggVorbis::load_from_file(const String &p_path) { + return ResourceImporterOggVorbis::load_from_file(p_path); +} diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index c76df7f84d..41ce942eec 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -125,6 +125,8 @@ protected: static void _bind_methods(); public: + static Ref<AudioStreamOggVorbis> load_from_file(const String &p_path); + static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &file_data); void set_loop(bool p_enable); virtual bool has_loop() const override; diff --git a/modules/vorbis/config.py b/modules/vorbis/config.py index a231ef179d..9e10a58849 100644 --- a/modules/vorbis/config.py +++ b/modules/vorbis/config.py @@ -11,6 +11,7 @@ def get_doc_classes(): return [ "AudioStreamOggVorbis", "AudioStreamPlaybackOggVorbis", + "ResourceImporterOggVorbis", ] diff --git a/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml index e498253892..7e3af6688a 100644 --- a/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml +++ b/modules/vorbis/doc_classes/AudioStreamOggVorbis.xml @@ -1,11 +1,29 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="AudioStreamOggVorbis" inherits="AudioStream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> + A class representing an Ogg Vorbis audio stream. </brief_description> <description> + The AudioStreamOggVorbis class is a specialized [AudioStream] for handling Ogg Vorbis file formats. It offers functionality for loading and playing back Ogg Vorbis files, as well as managing looping and other playback properties. This class is part of the audio stream system, which also supports WAV files through the [AudioStreamWAV] class. </description> <tutorials> </tutorials> + <methods> + <method name="load_from_buffer" qualifiers="static"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="buffer" type="PackedByteArray" /> + <description> + Creates a new AudioStreamOggVorbis instance from the given buffer. The buffer must contain Ogg Vorbis data. + </description> + </method> + <method name="load_from_file" qualifiers="static"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="path" type="String" /> + <description> + Creates a new AudioStreamOggVorbis instance from the given file path. The file must be in Ogg Vorbis format. + </description> + </method> + </methods> <members> <member name="bar_beats" type="int" setter="set_bar_beats" getter="get_bar_beats" default="4"> </member> @@ -14,7 +32,7 @@ <member name="bpm" type="float" setter="set_bpm" getter="get_bpm" default="0.0"> </member> <member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false"> - If [code]true[/code], the stream will automatically loop when it reaches the end. + 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. </member> <member name="loop_offset" type="float" setter="set_loop_offset" getter="get_loop_offset" default="0.0"> Time in seconds at which the stream starts after being looped. diff --git a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml new file mode 100644 index 0000000000..c2dcb832e0 --- /dev/null +++ b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml @@ -0,0 +1,53 @@ +<?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"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="buffer" type="PackedByteArray" /> + <description> + This method loads audio data from a PackedByteArray buffer into an AudioStreamOggVorbis object. + </description> + </method> + <method name="load_from_file" qualifiers="static"> + <return type="AudioStreamOggVorbis" /> + <param index="0" name="path" type="String" /> + <description> + This method loads audio data from a file into an AudioStreamOggVorbis object. The file path is provided as a string. + </description> + </method> + </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 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/vorbis/register_types.cpp b/modules/vorbis/register_types.cpp index e131ff6dc9..26af912999 100644 --- a/modules/vorbis/register_types.cpp +++ b/modules/vorbis/register_types.cpp @@ -31,7 +31,10 @@ #include "register_types.h" #include "audio_stream_ogg_vorbis.h" + +#ifdef TOOLS_ENABLED #include "resource_importer_ogg_vorbis.h" +#endif void initialize_vorbis_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { @@ -44,7 +47,11 @@ void initialize_vorbis_module(ModuleInitializationLevel p_level) { ogg_vorbis_importer.instantiate(); ResourceFormatImporter::get_singleton()->add_importer(ogg_vorbis_importer); } + + // Required to document import options in the class reference. + GDREGISTER_CLASS(ResourceImporterOggVorbis); #endif + GDREGISTER_CLASS(AudioStreamOggVorbis); GDREGISTER_CLASS(AudioStreamPlaybackOggVorbis); } diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index 8392750798..b42cd20589 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -81,18 +81,50 @@ void ResourceImporterOggVorbis::get_import_options(const String &p_path, List<Im r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "bar_beats", PROPERTY_HINT_RANGE, "2,32,or_greater"), 4)); } -Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const String &p_path) { - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(f.is_null(), Ref<AudioStreamOggVorbis>(), "Cannot open file '" + p_path + "'."); +#ifdef TOOLS_ENABLED + +bool ResourceImporterOggVorbis::has_advanced_options() const { + return true; +} + +void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) { + Ref<AudioStreamOggVorbis> ogg_stream = load_from_file(p_path); + if (ogg_stream.is_valid()) { + AudioStreamImportSettings::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream); + } +} +#endif + +Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { + bool loop = p_options["loop"]; + float loop_offset = p_options["loop_offset"]; + double bpm = p_options["bpm"]; + int beat_count = p_options["beat_count"]; + int bar_beats = p_options["bar_beats"]; - uint64_t len = f->get_length(); + Ref<AudioStreamOggVorbis> ogg_vorbis_stream = load_from_file(p_source_file); + if (ogg_vorbis_stream.is_null()) { + return ERR_CANT_OPEN; + } - Vector<uint8_t> file_data; - file_data.resize(len); - uint8_t *w = file_data.ptrw(); + ogg_vorbis_stream->set_loop(loop); + ogg_vorbis_stream->set_loop_offset(loop_offset); + ogg_vorbis_stream->set_bpm(bpm); + ogg_vorbis_stream->set_beat_count(beat_count); + ogg_vorbis_stream->set_bar_beats(bar_beats); + + return ResourceSaver::save(ogg_vorbis_stream, p_save_path + ".oggvorbisstr"); +} - f->get_buffer(w, len); +ResourceImporterOggVorbis::ResourceImporterOggVorbis() { +} + +void ResourceImporterOggVorbis::_bind_methods() { + ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_buffer", "buffer"), &ResourceImporterOggVorbis::load_from_buffer); + ClassDB::bind_static_method("ResourceImporterOggVorbis", D_METHOD("load_from_file", "path"), &ResourceImporterOggVorbis::load_from_file); +} +Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vector<uint8_t> &file_data) { Ref<AudioStreamOggVorbis> ogg_vorbis_stream; ogg_vorbis_stream.instantiate(); @@ -114,7 +146,7 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const Str err = ogg_sync_check(&sync_state); ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err)); while (ogg_sync_pageout(&sync_state, &page) != 1) { - if (cursor >= len) { + if (cursor >= size_t(file_data.size())) { done = true; break; } @@ -123,8 +155,8 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const Str char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE); err = ogg_sync_check(&sync_state); ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err)); - ERR_FAIL_COND_V(cursor > len, Ref<AudioStreamOggVorbis>()); - size_t copy_size = len - cursor; + ERR_FAIL_COND_V(cursor > size_t(file_data.size()), Ref<AudioStreamOggVorbis>()); + size_t copy_size = file_data.size() - cursor; if (copy_size > OGG_SYNC_BUFFER_SIZE) { copy_size = OGG_SYNC_BUFFER_SIZE; } @@ -201,40 +233,8 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::import_ogg_vorbis(const Str return ogg_vorbis_stream; } -#ifdef TOOLS_ENABLED - -bool ResourceImporterOggVorbis::has_advanced_options() const { - return true; -} - -void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) { - Ref<AudioStreamOggVorbis> ogg_stream = import_ogg_vorbis(p_path); - if (ogg_stream.is_valid()) { - AudioStreamImportSettings::get_singleton()->edit(p_path, "oggvorbisstr", ogg_stream); - } -} -#endif - -Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { - bool loop = p_options["loop"]; - float loop_offset = p_options["loop_offset"]; - double bpm = p_options["bpm"]; - int beat_count = p_options["beat_count"]; - int bar_beats = p_options["bar_beats"]; - - Ref<AudioStreamOggVorbis> ogg_vorbis_stream = import_ogg_vorbis(p_source_file); - if (ogg_vorbis_stream.is_null()) { - return ERR_CANT_OPEN; - } - - ogg_vorbis_stream->set_loop(loop); - ogg_vorbis_stream->set_loop_offset(loop_offset); - ogg_vorbis_stream->set_bpm(bpm); - ogg_vorbis_stream->set_beat_count(beat_count); - ogg_vorbis_stream->set_bar_beats(bar_beats); - - return ResourceSaver::save(ogg_vorbis_stream, p_save_path + ".oggvorbisstr"); -} - -ResourceImporterOggVorbis::ResourceImporterOggVorbis() { +Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_file(const String &p_path) { + Vector<uint8_t> file_data = FileAccess::get_file_as_bytes(p_path); + ERR_FAIL_COND_V_MSG(file_data.is_empty(), Ref<AudioStreamOggVorbis>(), "Cannot open file '" + p_path + "'."); + return load_from_buffer(file_data); } diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h index 4874419834..59ae3378a0 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.h +++ b/modules/vorbis/resource_importer_ogg_vorbis.h @@ -42,16 +42,17 @@ class ResourceImporterOggVorbis : public ResourceImporter { OGG_SYNC_BUFFER_SIZE = 8192, }; -private: - // virtual int get_samples_in_packet(Vector<uint8_t> p_packet) = 0; - - static Ref<AudioStreamOggVorbis> import_ogg_vorbis(const String &p_path); +protected: + static void _bind_methods(); public: #ifdef TOOLS_ENABLED virtual bool has_advanced_options() const override; virtual void show_advanced_options(const String &p_path) override; #endif + + static Ref<AudioStreamOggVorbis> load_from_file(const String &p_path); + static Ref<AudioStreamOggVorbis> load_from_buffer(const Vector<uint8_t> &file_data); virtual void get_recognized_extensions(List<String> *p_extensions) const override; virtual String get_save_extension() const override; virtual String get_resource_type() const override; diff --git a/modules/webp/resource_saver_webp.cpp b/modules/webp/resource_saver_webp.cpp index 92285e2eab..52289334f8 100644 --- a/modules/webp/resource_saver_webp.cpp +++ b/modules/webp/resource_saver_webp.cpp @@ -34,7 +34,7 @@ #include "core/io/file_access.h" #include "core/io/image.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" Error ResourceSaverWebP::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) { Ref<ImageTexture> texture = p_resource; diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp index 60cb0091e1..bc34a25733 100644 --- a/modules/webp/webp_common.cpp +++ b/modules/webp/webp_common.cpp @@ -84,6 +84,7 @@ Vector<uint8_t> _webp_packer(const Ref<Image> &p_image, float p_quality, bool p_ } config.method = compression_method; config.quality = p_quality; + config.use_sharp_yuv = 1; pic.use_argb = 1; pic.width = s.width; pic.height = s.height; |