diff options
Diffstat (limited to 'modules')
250 files changed, 7208 insertions, 2979 deletions
diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp index 86f3385ae3..7c0bc4ac82 100644 --- a/modules/basis_universal/register_types.cpp +++ b/modules/basis_universal/register_types.cpp @@ -162,7 +162,7 @@ static Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size const uint8_t *ptr = p_data; int size = p_size; - ERR_FAIL_COND_V_MSG(p_data == nullptr, image, "Cannot unpack invalid basis universal data."); + ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid basis universal data."); basist::transcoder_texture_format format = basist::transcoder_texture_format::cTFTotalTextureFormats; Image::Format imgfmt = Image::FORMAT_MAX; diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 7cafccfdcb..0656f8224c 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -300,7 +300,7 @@ void CSGShape3D::_update_shape() { root_mesh.unref(); //byebye root mesh CSGBrush *n = _get_brush(); - ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush."); + ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush."); OAHashMap<Vector3, Vector3> vec_map; @@ -458,7 +458,7 @@ void CSGShape3D::_update_shape() { void CSGShape3D::_update_collision_faces() { if (use_collision && is_root_shape() && root_collision_shape.is_valid()) { CSGBrush *n = _get_brush(); - ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush."); + ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush."); Vector<Vector3> physics_faces; physics_faces.resize(n->faces.size() * 3); Vector3 *physicsw = physics_faces.ptrw(); diff --git a/modules/csg/icons/CSGTorus3D.svg b/modules/csg/icons/CSGTorus3D.svg index 5672244e5c..27a6b422f9 100644 --- a/modules/csg/icons/CSGTorus3D.svg +++ b/modules/csg/icons/CSGTorus3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><ellipse cx="8" cy="7.5" fill="none" rx="6" ry="3.5" stroke="#fc7f7f" stroke-width="2" mask="url(#a)"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><g fill="none" stroke="#fc7f7f" mask="url(#a)"><path d="M2.5 10a6 4 0 0 0 11 0 4 4 0 0 0 0-4 6 4 0 0 0-11 0 4 4 0 0 0 0 4z" stroke-width="2"/><path d="M6.2 7.2a2 1 0 1 0 3.6 0" stroke-width="1.75" stroke-linecap="round"/></g></svg> diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub deleted file mode 100644 index 967a511e1e..0000000000 --- a/modules/denoise/SCsub +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python - -import resource_to_cpp - -Import("env") -Import("env_modules") - -env_oidn = env_modules.Clone() - -# Thirdparty source files - -thirdparty_obj = [] - -thirdparty_dir = "#thirdparty/oidn/" -thirdparty_sources = [ - "core/api.cpp", - "core/device.cpp", - "core/filter.cpp", - "core/network.cpp", - "core/autoencoder.cpp", - "core/transfer_function.cpp", - "weights/rtlightmap_hdr.gen.cpp", - "mkl-dnn/src/common/batch_normalization.cpp", - "mkl-dnn/src/common/concat.cpp", - "mkl-dnn/src/common/convolution.cpp", - "mkl-dnn/src/common/convolution_pd.cpp", - "mkl-dnn/src/common/deconvolution.cpp", - "mkl-dnn/src/common/eltwise.cpp", - "mkl-dnn/src/common/engine.cpp", - "mkl-dnn/src/common/inner_product.cpp", - "mkl-dnn/src/common/inner_product_pd.cpp", - "mkl-dnn/src/common/lrn.cpp", - "mkl-dnn/src/common/memory.cpp", - "mkl-dnn/src/common/memory_desc_wrapper.cpp", - "mkl-dnn/src/common/mkldnn_debug.cpp", - "mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp", - "mkl-dnn/src/common/pooling.cpp", - "mkl-dnn/src/common/primitive.cpp", - "mkl-dnn/src/common/primitive_attr.cpp", - "mkl-dnn/src/common/primitive_desc.cpp", - "mkl-dnn/src/common/primitive_exec_types.cpp", - "mkl-dnn/src/common/primitive_iterator.cpp", - "mkl-dnn/src/common/query.cpp", - "mkl-dnn/src/common/reorder.cpp", - "mkl-dnn/src/common/rnn.cpp", - "mkl-dnn/src/common/scratchpad.cpp", - "mkl-dnn/src/common/shuffle.cpp", - "mkl-dnn/src/common/softmax.cpp", - "mkl-dnn/src/common/stream.cpp", - "mkl-dnn/src/common/sum.cpp", - "mkl-dnn/src/common/utils.cpp", - "mkl-dnn/src/common/verbose.cpp", - "mkl-dnn/src/cpu/cpu_barrier.cpp", - "mkl-dnn/src/cpu/cpu_concat.cpp", - "mkl-dnn/src/cpu/cpu_engine.cpp", - "mkl-dnn/src/cpu/cpu_memory.cpp", - "mkl-dnn/src/cpu/cpu_reducer.cpp", - "mkl-dnn/src/cpu/cpu_reorder.cpp", - "mkl-dnn/src/cpu/cpu_sum.cpp", - "mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_avx2_convolution.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp", - "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp", - "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp", - "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp", - "mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_sse42_convolution.cpp", - "mkl-dnn/src/cpu/jit_transpose_src_utils.cpp", - "mkl-dnn/src/cpu/jit_uni_eltwise.cpp", - "mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_uni_pooling.cpp", - "mkl-dnn/src/cpu/jit_uni_reorder.cpp", - "mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp", - "mkl-dnn/src/cpu/jit_utils/jit_utils.cpp", - "mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c", - "common/platform.cpp", - "common/thread.cpp", - "common/tensor.cpp", -] -thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - -thirdparty_include_dirs = [ - "", - "include", - "mkl-dnn/include", - "mkl-dnn/src", - "mkl-dnn/src/common", - "mkl-dnn/src/cpu/xbyak", - "mkl-dnn/src/cpu", -] -thirdparty_include_dirs = [thirdparty_dir + file for file in thirdparty_include_dirs] - - -env_oidn.Prepend(CPPPATH=thirdparty_include_dirs) -env_oidn.Append( - CPPDEFINES=[ - "MKLDNN_THR=MKLDNN_THR_SEQ", - "OIDN_STATIC_LIB", - "__STDC_CONSTANT_MACROS", - "__STDC_LIMIT_MACROS", - "DISABLE_VERBOSE", - "MKLDNN_ENABLE_CONCURRENT_EXEC", - ] -) -env_oidn.AppendUnique(CPPDEFINES=["NDEBUG"]) # No assert() even in debug builds. - -env_thirdparty = env_oidn.Clone() -env_thirdparty.disable_warnings() - -if env["disable_exceptions"]: - # OIDN hard-requires exceptions, so we re-enable them here. - if env.msvc and ("_HAS_EXCEPTIONS", 0) in env_thirdparty["CPPDEFINES"]: - env_thirdparty["CPPDEFINES"].remove(("_HAS_EXCEPTIONS", 0)) - env_thirdparty.AppendUnique(CCFLAGS=["/EHsc"]) - elif not env.msvc and "-fno-exceptions" in env_thirdparty["CCFLAGS"]: - env_thirdparty["CCFLAGS"].remove("-fno-exceptions") - -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -env.modules_sources += thirdparty_obj - -weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza" -weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp" - -env_thirdparty.Depends(weights_out_path, weights_in_path) -env_thirdparty.CommandNoCache(weights_out_path, weights_in_path, resource_to_cpp.tza_to_cpp) - -# Godot source files - -module_obj = [] - -env_oidn.add_source_files(module_obj, "*.cpp") -env.modules_sources += module_obj - -# Needed to force rebuilding the module files when the thirdparty library is updated. -env.Depends(module_obj, thirdparty_obj) diff --git a/modules/denoise/config.py b/modules/denoise/config.py deleted file mode 100644 index 27d2ffbf86..0000000000 --- a/modules/denoise/config.py +++ /dev/null @@ -1,12 +0,0 @@ -def can_build(env, platform): - # Thirdparty dependency OpenImage Denoise includes oneDNN library - # and the version we use only supports x86_64. - # It's also only relevant for tools build and desktop platforms, - # as doing lightmap generation and denoising on Android or Web - # would be a bit far-fetched. - desktop_platforms = ["linuxbsd", "macos", "windows"] - return env.editor_build and platform in desktop_platforms and env["arch"] == "x86_64" - - -def configure(env): - pass diff --git a/modules/denoise/denoise_wrapper.h b/modules/denoise/denoise_wrapper.h deleted file mode 100644 index d4bf154a5d..0000000000 --- a/modules/denoise/denoise_wrapper.h +++ /dev/null @@ -1,38 +0,0 @@ -/**************************************************************************/ -/* denoise_wrapper.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 DENOISE_WRAPPER_H -#define DENOISE_WRAPPER_H - -void *oidn_denoiser_init(); -bool oidn_denoise(void *device, float *p_floats, int p_width, int p_height); -void oidn_denoiser_finish(void *device); - -#endif // DENOISE_WRAPPER_H diff --git a/modules/denoise/register_types.cpp b/modules/denoise/register_types.cpp deleted file mode 100644 index a4264b07c5..0000000000 --- a/modules/denoise/register_types.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/**************************************************************************/ -/* register_types.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 "register_types.h" - -#include "lightmap_denoiser.h" - -#include "core/config/engine.h" - -void initialize_denoise_module(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { - return; - } - - LightmapDenoiserOIDN::make_default_denoiser(); -} - -void uninitialize_denoise_module(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { - return; - } -} diff --git a/modules/denoise/register_types.h b/modules/denoise/register_types.h deleted file mode 100644 index 239877a5c7..0000000000 --- a/modules/denoise/register_types.h +++ /dev/null @@ -1,39 +0,0 @@ -/**************************************************************************/ -/* register_types.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 DENOISE_REGISTER_TYPES_H -#define DENOISE_REGISTER_TYPES_H - -#include "modules/register_module_types.h" - -void initialize_denoise_module(ModuleInitializationLevel p_level); -void uninitialize_denoise_module(ModuleInitializationLevel p_level); - -#endif // DENOISE_REGISTER_TYPES_H diff --git a/modules/denoise/resource_to_cpp.py b/modules/denoise/resource_to_cpp.py deleted file mode 100644 index a89eda9117..0000000000 --- a/modules/denoise/resource_to_cpp.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python - -## ======================================================================== ## -## Copyright 2009-2019 Intel Corporation ## -## ## -## Licensed under the Apache License, Version 2.0 (the "License"); ## -## you may not use this file except in compliance with the License. ## -## You may obtain a copy of the License at ## -## ## -## http://www.apache.org/licenses/LICENSE-2.0 ## -## ## -## Unless required by applicable law or agreed to in writing, software ## -## distributed under the License is distributed on an "AS IS" BASIS, ## -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## -## See the License for the specific language governing permissions and ## -## limitations under the License. ## -## ======================================================================== ## - -import os -from array import array - - -# Generates a C++ file from the specified binary resource file -def generate(in_path, out_path): - namespace = "oidn::weights" - scopes = namespace.split("::") - - file_name = os.path.basename(in_path) - var_name = os.path.splitext(file_name)[0] - - with open(in_path, "rb") as in_file, open(out_path, "w") as out_file: - # Header - out_file.write("// Generated from: %s\n" % file_name) - out_file.write("#include <cstddef>\n\n") - - # Open the namespaces - for s in scopes: - out_file.write("namespace %s {\n" % s) - if scopes: - out_file.write("\n") - - # Read the file - in_data = array("B", in_file.read()) - - # Write the size - out_file.write("//const size_t %s_size = %d;\n\n" % (var_name, len(in_data))) - - # Write the data - out_file.write("unsigned char %s[] = {" % var_name) - for i in range(len(in_data)): - c = in_data[i] - if i > 0: - out_file.write(",") - if (i + 1) % 20 == 1: - out_file.write("\n") - out_file.write("%d" % c) - out_file.write("\n};\n") - - # Close the namespaces - if scopes: - out_file.write("\n") - for scope in reversed(scopes): - out_file.write("} // namespace %s\n" % scope) - - -def tza_to_cpp(target, source, env): - for x in zip(source, target): - generate(str(x[0]), str(x[1])) diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp index 88aaa006b5..94473c76c0 100644 --- a/modules/enet/enet_connection.cpp +++ b/modules/enet/enet_connection.cpp @@ -37,7 +37,7 @@ #include "core/variant/typed_array.h" void ENetConnection::broadcast(enet_uint8 p_channel, ENetPacket *p_packet) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_MSG(p_channel >= host->channelLimit, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)host->channelLimit)); enet_host_broadcast(host, p_channel, p_packet); } @@ -71,7 +71,7 @@ Error ENetConnection::create_host(int p_max_peers, int p_max_channels, int p_in_ } void ENetConnection::destroy() { - ERR_FAIL_COND_MSG(!host, "Host already destroyed"); + ERR_FAIL_NULL_MSG(host, "Host already destroyed."); for (List<Ref<ENetPacketPeer>>::Element *E = peers.front(); E; E = E->next()) { E->get()->_on_disconnect(); } @@ -82,7 +82,7 @@ void ENetConnection::destroy() { Ref<ENetPacketPeer> ENetConnection::connect_to_host(const String &p_address, int p_port, int p_channels, int p_data) { Ref<ENetPacketPeer> out; - ERR_FAIL_COND_V_MSG(!host, out, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, out, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V_MSG(peers.size(), out, "The ENetConnection is already connected to a peer."); ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, out, "The remote port number must be between 1 and 65535 (inclusive)."); @@ -160,7 +160,7 @@ ENetConnection::EventType ENetConnection::_parse_event(const ENetEvent &p_event, } ENetConnection::EventType ENetConnection::service(int p_timeout, Event &r_event) { - ERR_FAIL_COND_V_MSG(!host, EVENT_ERROR, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, EVENT_ERROR, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V(r_event.peer.is_valid(), EVENT_ERROR); // Drop peers that have already been disconnected. @@ -186,7 +186,7 @@ ENetConnection::EventType ENetConnection::service(int p_timeout, Event &r_event) } int ENetConnection::check_events(EventType &r_type, Event &r_event) { - ERR_FAIL_COND_V_MSG(!host, -1, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, -1, "The ENetConnection instance isn't currently active."); ENetEvent event; int ret = enet_host_check_events(host, &event); if (ret < 0) { @@ -198,32 +198,32 @@ int ENetConnection::check_events(EventType &r_type, Event &r_event) { } void ENetConnection::flush() { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_flush(host); } void ENetConnection::bandwidth_limit(int p_in_bandwidth, int p_out_bandwidth) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_bandwidth_limit(host, p_in_bandwidth, p_out_bandwidth); } void ENetConnection::channel_limit(int p_max_channels) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_channel_limit(host, p_max_channels); } void ENetConnection::bandwidth_throttle() { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_bandwidth_throttle(host); } void ENetConnection::compress(CompressionMode p_mode) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); Compressor::setup(host, p_mode); } double ENetConnection::pop_statistic(HostStatistic p_stat) { - ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active."); uint32_t *ptr = nullptr; switch (p_stat) { case HOST_TOTAL_SENT_DATA: @@ -239,19 +239,19 @@ double ENetConnection::pop_statistic(HostStatistic p_stat) { ptr = &(host->totalReceivedPackets); break; } - ERR_FAIL_COND_V_MSG(ptr == nullptr, 0, "Invalid statistic: " + itos(p_stat)); + ERR_FAIL_NULL_V_MSG(ptr, 0, "Invalid statistic: " + itos(p_stat) + "."); uint32_t ret = *ptr; *ptr = 0; return ret; } int ENetConnection::get_max_channels() const { - ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active."); return host->channelLimit; } int ENetConnection::get_local_port() const { - ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V_MSG(!(host->socket), 0, "The ENetConnection instance isn't currently bound"); ENetAddress address; ERR_FAIL_COND_V_MSG(enet_socket_get_address(host->socket, &address), 0, "Unable to get socket address"); @@ -265,7 +265,7 @@ void ENetConnection::get_peers(List<Ref<ENetPacketPeer>> &r_peers) { } TypedArray<ENetPacketPeer> ENetConnection::_get_peers() { - ERR_FAIL_COND_V_MSG(!host, Array(), "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, Array(), "The ENetConnection instance isn't currently active."); TypedArray<ENetPacketPeer> out; for (const Ref<ENetPacketPeer> &I : peers) { out.push_back(I); @@ -275,7 +275,7 @@ TypedArray<ENetPacketPeer> ENetConnection::_get_peers() { Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) { #ifdef GODOT_ENET - ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); return enet_host_dtls_server_setup(host, const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK; #else @@ -285,7 +285,7 @@ Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) { void ENetConnection::refuse_new_connections(bool p_refuse) { #ifdef GODOT_ENET - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_refuse_new_connections(host, p_refuse); #else ERR_FAIL_MSG("ENet DTLS support not available in this build."); @@ -294,7 +294,7 @@ void ENetConnection::refuse_new_connections(bool p_refuse) { Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options) { #ifdef GODOT_ENET - ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER); return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK; #else @@ -315,7 +315,7 @@ Error ENetConnection::_create(ENetAddress *p_address, int p_max_peers, int p_max p_in_bandwidth /* limit incoming bandwidth if > 0 */, p_out_bandwidth /* limit outgoing bandwidth if > 0 */); - ERR_FAIL_COND_V_MSG(!host, ERR_CANT_CREATE, "Couldn't create an ENet host."); + ERR_FAIL_NULL_V_MSG(host, ERR_CANT_CREATE, "Couldn't create an ENet host."); return OK; } @@ -335,7 +335,7 @@ Array ENetConnection::_service(int p_timeout) { } void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_flags) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_MSG(p_channel < 0 || p_channel > (int)host->channelLimit, "Invalid channel"); ERR_FAIL_COND_MSG(p_flags & ~ENetPacketPeer::FLAG_ALLOWED, "Invalid flags"); ENetPacket *pkt = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags); @@ -343,7 +343,7 @@ void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_f } void ENetConnection::socket_send(const String &p_address, int p_port, const PackedByteArray &p_packet) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_MSG(!(host->socket), "The ENetConnection instance isn't currently bound"); ERR_FAIL_COND_MSG(p_port < 1 || p_port > 65535, "The remote port number must be between 1 and 65535 (inclusive)."); @@ -497,7 +497,7 @@ size_t ENetConnection::Compressor::enet_decompress(void *context, const enet_uin } void ENetConnection::Compressor::setup(ENetHost *p_host, CompressionMode p_mode) { - ERR_FAIL_COND(!p_host); + ERR_FAIL_NULL(p_host); switch (p_mode) { case COMPRESS_NONE: { enet_host_compress(p_host, nullptr); diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp index a131841a07..f2bf5337ee 100644 --- a/modules/enet/enet_packet_peer.cpp +++ b/modules/enet/enet_packet_peer.cpp @@ -31,51 +31,51 @@ #include "enet_packet_peer.h" void ENetPacketPeer::peer_disconnect(int p_data) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_disconnect(peer, p_data); } void ENetPacketPeer::peer_disconnect_later(int p_data) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_disconnect_later(peer, p_data); } void ENetPacketPeer::peer_disconnect_now(int p_data) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_disconnect_now(peer, p_data); _on_disconnect(); } void ENetPacketPeer::ping() { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_ping(peer); } void ENetPacketPeer::ping_interval(int p_interval) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_ping_interval(peer, p_interval); } int ENetPacketPeer::send(uint8_t p_channel, ENetPacket *p_packet) { - ERR_FAIL_COND_V(peer == nullptr, -1); - ERR_FAIL_COND_V(p_packet == nullptr, -1); + ERR_FAIL_NULL_V(peer, -1); + ERR_FAIL_NULL_V(p_packet, -1); ERR_FAIL_COND_V_MSG(p_channel >= peer->channelCount, -1, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)peer->channelCount)); return enet_peer_send(peer, p_channel, p_packet); } void ENetPacketPeer::reset() { - ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected"); + ERR_FAIL_NULL_MSG(peer, "Peer not connected."); enet_peer_reset(peer); _on_disconnect(); } void ENetPacketPeer::throttle_configure(int p_interval, int p_acceleration, int p_deceleration) { - ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected"); + ERR_FAIL_NULL_MSG(peer, "Peer not connected."); enet_peer_throttle_configure(peer, p_interval, p_acceleration, p_deceleration); } 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_NULL_MSG(peer, "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 than maximum timeout"); enet_peer_timeout(peer, p_timeout, p_timeout_min, p_timeout_max); } @@ -89,7 +89,7 @@ int ENetPacketPeer::get_available_packet_count() const { } Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - ERR_FAIL_COND_V(!peer, ERR_UNCONFIGURED); + ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED); ERR_FAIL_COND_V(!packet_queue.size(), ERR_UNAVAILABLE); if (last_packet) { enet_packet_destroy(last_packet); @@ -103,13 +103,13 @@ Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { } Error ENetPacketPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_FAIL_COND_V(!peer, ERR_UNCONFIGURED); + ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED); ENetPacket *packet = enet_packet_create(p_buffer, p_buffer_size, ENET_PACKET_FLAG_RELIABLE); return send(0, packet) < 0 ? FAILED : OK; } IPAddress ENetPacketPeer::get_remote_address() const { - ERR_FAIL_COND_V(!peer, IPAddress()); + ERR_FAIL_NULL_V(peer, IPAddress()); IPAddress out; #ifdef GODOT_ENET out.set_ipv6((uint8_t *)&(peer->address.host)); @@ -120,7 +120,7 @@ IPAddress ENetPacketPeer::get_remote_address() const { } int ENetPacketPeer::get_remote_port() const { - ERR_FAIL_COND_V(!peer, 0); + ERR_FAIL_NULL_V(peer, 0); return peer->address.port; } @@ -129,7 +129,7 @@ bool ENetPacketPeer::is_active() const { } double ENetPacketPeer::get_statistic(PeerStatistic p_stat) { - ERR_FAIL_COND_V(!peer, 0); + ERR_FAIL_NULL_V(peer, 0); switch (p_stat) { case PEER_PACKET_LOSS: return peer->packetLoss; @@ -171,7 +171,7 @@ ENetPacketPeer::PeerState ENetPacketPeer::get_state() const { } int ENetPacketPeer::get_channels() const { - ERR_FAIL_COND_V_MSG(!peer, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(peer, 0, "The ENetConnection instance isn't currently active."); return peer->channelCount; } @@ -183,12 +183,12 @@ void ENetPacketPeer::_on_disconnect() { } void ENetPacketPeer::_queue_packet(ENetPacket *p_packet) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); packet_queue.push_back(p_packet); } Error ENetPacketPeer::_send(int p_channel, PackedByteArray p_packet, int p_flags) { - ERR_FAIL_COND_V_MSG(peer == nullptr, ERR_UNCONFIGURED, "Peer not connected"); + ERR_FAIL_NULL_V_MSG(peer, ERR_UNCONFIGURED, "Peer not connected."); ERR_FAIL_COND_V_MSG(p_channel < 0 || p_channel > (int)peer->channelCount, ERR_INVALID_PARAMETER, "Invalid channel"); ERR_FAIL_COND_V_MSG(p_flags & ~FLAG_ALLOWED, ERR_INVALID_PARAMETER, "Invalid flags"); ENetPacket *packet = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 18045e323c..4fec745999 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -557,7 +557,7 @@ <description> Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget. - Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. + Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. See also [constant PROPERTY_HINT_RANGE]. [codeblock] @export_range(0, 20) var number @@ -567,7 +567,7 @@ @export_range(0, 100, 1, "or_greater") var power_percent @export_range(0, 100, 1, "or_greater", "or_less") var health_delta - @export_range(-3.14, 3.14, 0.001, "radians") var angle_radians + @export_range(-180, 180, 0.001, "radians_as_degrees") var angle_radians @export_range(0, 360, 1, "degrees") var angle_degrees @export_range(-8, 8, 2, "suffix:px") var target_offset [/codeblock] @@ -601,8 +601,8 @@ @icon("res://path/to/class/icon.svg") [/codeblock] [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported. - [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance. - [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported). + [b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported). </description> </annotation> <annotation name="@onready"> @@ -653,7 +653,7 @@ @tool extends Node [/codeblock] - [b]Note:[/b] As annotations describe their subject, the [code]@tool[/code] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] As annotations describe their subject, the [annotation @tool] annotation must be placed before the class definition and inheritance. </description> </annotation> <annotation name="@warning_ignore" qualifiers="vararg"> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index e488d6e266..2bfa191ff1 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -52,6 +52,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool in_keyword = false; bool in_word = false; bool in_number = false; + bool in_raw_string = false; bool in_node_path = false; bool in_node_ref = false; bool in_annotation = false; @@ -234,15 +235,33 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } if (str[from] == '\\') { - Dictionary escape_char_highlighter_info; - escape_char_highlighter_info["color"] = symbol_color; - color_map[from] = escape_char_highlighter_info; + if (!in_raw_string) { + Dictionary escape_char_highlighter_info; + escape_char_highlighter_info["color"] = symbol_color; + color_map[from] = escape_char_highlighter_info; + } from++; - Dictionary region_continue_highlighter_info; - region_continue_highlighter_info["color"] = region_color; - color_map[from + 1] = region_continue_highlighter_info; + if (!in_raw_string) { + int esc_len = 0; + if (str[from] == 'u') { + esc_len = 4; + } else if (str[from] == 'U') { + esc_len = 6; + } + for (int k = 0; k < esc_len && from < line_length - 1; k++) { + if (!is_hex_digit(str[from + 1])) { + break; + } + from++; + } + + Dictionary region_continue_highlighter_info; + region_continue_highlighter_info["color"] = region_color; + color_map[from + 1] = region_continue_highlighter_info; + } + continue; } @@ -423,7 +442,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (str[k] == '(') { in_function_name = true; - } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) { + } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR)) { in_variable_declaration = true; } @@ -461,7 +480,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_function_args = false; } - if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[') { + if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != '.') { expect_type = false; } @@ -489,6 +508,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_member_variable = false; } + if (!in_raw_string && in_region == -1 && str[j] == 'r' && j < line_length - 1 && (str[j + 1] == '"' || str[j + 1] == '\'')) { + in_raw_string = true; + } else if (in_raw_string && in_region == -1) { + in_raw_string = false; + } + // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand. if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) { if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) { @@ -520,7 +545,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_annotation = false; } - if (in_node_ref) { + if (in_raw_string) { + color = string_color; + } else if (in_node_ref) { next_type = NODE_REF; color = node_ref_color; } else if (in_annotation) { @@ -535,16 +562,11 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (in_keyword) { next_type = KEYWORD; color = keyword_color; - } else if (in_member_variable) { - next_type = MEMBER; - color = member_color; } else if (in_signal_declaration) { next_type = SIGNAL; - color = member_color; } else if (in_function_name) { next_type = FUNCTION; - if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { color = function_definition_color; } else { @@ -559,6 +581,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (expect_type) { next_type = TYPE; color = type_color; + } else if (in_member_variable) { + next_type = MEMBER; + color = member_color; } else { next_type = IDENTIFIER; } @@ -656,6 +681,12 @@ void GDScriptSyntaxHighlighter::_update_cache() { for (const String &E : core_types) { class_names[StringName(E)] = basetype_color; } + class_names[SNAME("Variant")] = basetype_color; + class_names[SNAME("void")] = basetype_color; + // `get_core_type_words()` doesn't return primitive types. + class_names[SNAME("bool")] = basetype_color; + class_names[SNAME("int")] = basetype_color; + class_names[SNAME("float")] = basetype_color; /* Reserved words. */ const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); @@ -670,6 +701,10 @@ void GDScriptSyntaxHighlighter::_update_cache() { } } + // Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls. + reserved_keywords[SNAME("set")] = function_color; + reserved_keywords[SNAME("get")] = function_color; + /* Global functions. */ List<StringName> global_function_list; GDScriptUtilityFunctions::get_function_list(&global_function_list); @@ -692,7 +727,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { } /* Strings */ - const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); List<String> strings; gdscript->get_string_delimiters(&strings); for (const String &string : strings) { diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index fe3b63d713..090857f397 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -78,6 +78,7 @@ private: Color built_in_type_color; Color number_color; Color member_color; + Color string_color; Color node_path_color; Color node_ref_color; Color annotation_color; diff --git a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd index b8fc8c75dc..28ab080dd2 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd @@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y += gravity * delta - # Handle Jump. + # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY diff --git a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd index 53bc606c9a..9b0e4be4ed 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd @@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y -= gravity * delta - # Handle Jump. + # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY diff --git a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd index b27b3e5655..547943b910 100644 --- a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd +++ b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd @@ -1,6 +1,7 @@ # meta-description: Basic plugin template + @tool -extends EditorPlugin +extends _BASE_ func _enter_tree() -> void: diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd index 556afe994b..6772ea4a26 100644 --- a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd +++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd @@ -1,6 +1,7 @@ # meta-description: Basic import script template + @tool -extends EditorScenePostImport +extends _BASE_ # Called by the editor when a scene has this script set as the import script in the import tab. diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd index 875afb4fc0..e8f907f43b 100644 --- a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd +++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd @@ -1,6 +1,7 @@ # meta-description: Basic import script template (no comments) + @tool -extends EditorScenePostImport +extends _BASE_ func _post_import(scene: Node) -> Object: diff --git a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd index fdb8550d43..fee7353f0d 100644 --- a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd +++ b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd @@ -1,6 +1,7 @@ # meta-description: Basic editor script template + @tool -extends EditorScript +extends _BASE_ # Called when the script is executed (using File -> Run in Script Editor). diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd index c79eeb91ec..c7a999ef24 100644 --- a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd +++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd @@ -1,15 +1,16 @@ # meta-description: Base template for rich text effects @tool -class_name _CLASS_ +# Having a class name is handy for picking the effect in the Inspector. +class_name RichText_CLASS_ extends _BASE_ # To use this effect: # - Enable BBCode on a RichTextLabel. # - Register this effect on the label. -# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text. -var bbcode := "_CLASS_" +# - Use [_CLASS_SNAKE_CASE_ param=2.0]hello[/_CLASS_SNAKE_CASE_] in text. +var bbcode := "_CLASS_SNAKE_CASE_" func _process_custom_fx(char_fx: CharFXTransform) -> bool: diff --git a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd index 283a95d3b4..458e22dae4 100644 --- a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd +++ b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd @@ -1,6 +1,7 @@ # meta-description: Visual shader's node plugin template @tool +# Having a class name is required for a custom node. class_name VisualShaderNode_CLASS_ extends _BASE_ @@ -17,7 +18,7 @@ func _get_description() -> String: return "" -func _get_return_icon_type() -> int: +func _get_return_icon_type() -> PortType: return PORT_TYPE_SCALAR @@ -29,7 +30,7 @@ func _get_input_port_name(port: int) -> String: return "" -func _get_input_port_type(port: int) -> int: +func _get_input_port_type(port: int) -> PortType: return PORT_TYPE_SCALAR @@ -41,10 +42,10 @@ func _get_output_port_name(port: int) -> String: return "result" -func _get_output_port_type(port: int) -> int: +func _get_output_port_type(port: int) -> PortType: return PORT_TYPE_SCALAR func _get_code(input_vars: Array[String], output_vars: Array[String], - mode: int, type: int) -> String: + mode: Shader.Mode, type: VisualShader.Type) -> String: return output_vars[0] + " = 0.0;" diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 62d2c14c17..f10ed0df29 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -83,7 +83,7 @@ void GDScriptNativeClass::_bind_methods() { Variant GDScriptNativeClass::_new() { Object *o = instantiate(); - ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); + ERR_FAIL_NULL_V_MSG(o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); RefCounted *rc = Object::cast_to<RefCounted>(o); if (rc) { @@ -215,7 +215,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr } else { owner = memnew(RefCounted); //by default, no base means use reference } - ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class."); + ERR_FAIL_NULL_V_MSG(owner, Variant(), "Can't inherit from a virtual class."); RefCounted *r = Object::cast_to<RefCounted>(owner); if (r) { @@ -306,6 +306,9 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl while (sptr) { Vector<_GDScriptMemberSort> msort; for (const KeyValue<StringName, MemberInfo> &E : sptr->member_indices) { + if (!sptr->members.has(E.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; ms.index = E.value.index; ms.name = E.key; @@ -1648,15 +1651,11 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { - const GDScript *sptr = script.ptr(); - while (sptr) { - if (sptr->member_indices.has(p_name)) { - if (r_is_valid) { - *r_is_valid = true; - } - return sptr->member_indices[p_name].property_info.type; + if (script->member_indices.has(p_name)) { + if (r_is_valid) { + *r_is_valid = true; } - sptr = sptr->_base; + return script->member_indices[p_name].property_info.type; } if (r_is_valid) { @@ -1732,6 +1731,9 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const Vector<_GDScriptMemberSort> msort; for (const KeyValue<StringName, GDScript::MemberInfo> &F : sptr->member_indices) { + if (!sptr->members.has(F.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; ms.index = F.value.index; ms.name = F.key; @@ -2366,61 +2368,60 @@ void GDScriptLanguage::frame() { /* EDITOR FUNCTIONS */ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { - // TODO: Add annotations here? + // Please keep alphabetical order within categories. static const char *_reserved_words[] = { - // operators + // Control flow. + "break", + "continue", + "elif", + "else", + "for", + "if", + "match", + "pass", + "return", + "when", + "while", + // Declarations. + "class", + "class_name", + "const", + "enum", + "extends", + "func", + "namespace", // Reserved for potential future use. + "signal", + "static", + "trait", // Reserved for potential future use. + "var", + // Other keywords. + "await", + "breakpoint", + "self", + "super", + "yield", // Reserved for potential future use. + // Operators. "and", + "as", "in", + "is", "not", "or", - // types and values + // Special values (tokenizer treats them as literals, not as tokens). "false", - "float", - "int", - "bool", "null", - "PI", - "TAU", + "true", + // Constants. "INF", "NAN", - "self", - "true", - "void", - // functions - "as", + "PI", + "TAU", + // Functions (highlighter uses global function color instead). "assert", - "await", - "breakpoint", - "class", - "class_name", - "extends", - "is", - "func", "preload", - "signal", - "super", - // var - "const", - "enum", - "static", - "var", - // control flow - "break", - "continue", - "if", - "elif", - "else", - "for", - "pass", - "return", - "match", - "while", - // These keywords are not implemented currently, but reserved for (potential) future use. - // We highlight them as keywords to make errors easier to understand. - "trait", - "namespace", - "yield", - nullptr + // Types (highlighter uses type color instead). + "void", + nullptr, }; const char **w = _reserved_words; @@ -2429,25 +2430,20 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { p_words->push_back(*w); w++; } - - List<StringName> functions; - GDScriptUtilityFunctions::get_function_list(&functions); - - for (const StringName &E : functions) { - p_words->push_back(String(E)); - } } bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const { + // Please keep alphabetical order. return p_keyword == "break" || p_keyword == "continue" || p_keyword == "elif" || p_keyword == "else" || - p_keyword == "if" || p_keyword == "for" || + p_keyword == "if" || p_keyword == "match" || p_keyword == "pass" || p_keyword == "return" || + p_keyword == "when" || p_keyword == "while"; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 2fd2ec236a..50ccfabcc1 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -94,12 +94,16 @@ class GDScript : public Script { GDScript *_base = nullptr; //fast pointer access GDScript *_owner = nullptr; //for subclasses - HashSet<StringName> members; //members are just indices to the instantiated script. - HashMap<StringName, Variant> constants; + // Members are just indices to the instantiated script. + HashMap<StringName, MemberInfo> member_indices; // Includes member info of all base GDScript classes. + HashSet<StringName> members; // Only members of the current class. + + // Only static variables of the current class. HashMap<StringName, MemberInfo> static_variables_indices; - Vector<Variant> static_variables; + Vector<Variant> static_variables; // Static variable values. + + HashMap<StringName, Variant> constants; HashMap<StringName, GDScriptFunction *> member_functions; - HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. HashMap<StringName, Ref<GDScript>> subclasses; HashMap<StringName, MethodInfo> _signals; Dictionary rpc_config; @@ -195,6 +199,7 @@ public: void clear(GDScript::ClearData *p_clear_data = nullptr); virtual bool is_valid() const override { return valid; } + virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes. bool inherits_script(const Ref<Script> &p_script) const override; @@ -501,7 +506,9 @@ public: virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override; virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override; virtual Script *create_script() const override; - virtual bool has_named_classes() const override; +#ifndef DISABLE_DEPRECATED + virtual bool has_named_classes() const override { return false; } +#endif virtual bool supports_builtin_mode() const override; virtual bool supports_documentation() const override; virtual bool can_inherit_from_file() const override { return true; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 4b8d527881..0d06597bc0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -248,7 +248,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me return ERR_PARSE_ERROR; } - if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) { + if (GDScriptParser::get_builtin_type(p_member_name) < Variant::VARIANT_MAX) { push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } @@ -459,6 +459,10 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } base = info_parser->get_parser()->head->get_datatype(); } else if (class_exists(name)) { + if (Engine::get_singleton()->has_singleton(name)) { + push_error(vformat(R"(Cannot inherit native class "%s" because it is an engine singleton.)", name), id); + return ERR_PARSE_ERROR; + } base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -669,11 +673,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return bad_type; } result.kind = GDScriptParser::DataType::VARIANT; - } else if (first == SNAME("Object")) { - // Object is treated like a native type, not a built-in. - result.kind = GDScriptParser::DataType::NATIVE; - result.builtin_type = Variant::OBJECT; - result.native_type = SNAME("Object"); } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. if (p_type->type_chain.size() > 1) { @@ -1415,7 +1414,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bo } void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) { - ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node."); + ERR_FAIL_NULL_MSG(p_node, "Trying to resolve type of a null node."); switch (p_node->type) { case GDScriptParser::Node::NONE: @@ -1672,15 +1671,42 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * StringName native_base; if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) { bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC); - valid = valid && parent_return_type == p_function->get_datatype(); + + if (p_function->return_type != nullptr) { + // Check return type covariance. + GDScriptParser::DataType return_type = p_function->get_datatype(); + if (return_type.is_variant()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow an explicitly specified `Variant` if the parent return type is narrower. + valid = valid && parent_return_type.is_variant(); + } else if (return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) { + // `is_type_compatible()` returns `true` if target is an `Object` and source is `null`. + // Don't allow `void` if the parent return type is a hard non-`void` type. + if (parent_return_type.is_hard_type() && !(parent_return_type.kind == GDScriptParser::DataType::BUILTIN && parent_return_type.builtin_type == Variant::NIL)) { + valid = false; + } + } else { + valid = valid && is_type_compatible(parent_return_type, return_type); + } + } int par_count_diff = p_function->parameters.size() - parameters_types.size(); valid = valid && par_count_diff >= 0; valid = valid && default_value_count >= default_par_count + par_count_diff; - int i = 0; - for (const GDScriptParser::DataType &par_type : parameters_types) { - valid = valid && par_type == p_function->parameters[i++]->get_datatype(); + if (valid) { + int i = 0; + for (const GDScriptParser::DataType &parent_par_type : parameters_types) { + // Check parameter type contravariance. + GDScriptParser::DataType current_par_type = p_function->parameters[i++]->get_datatype(); + if (parent_par_type.is_variant() && parent_par_type.is_hard_type()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow narrowing a hard `Variant`. + valid = valid && current_par_type.is_variant(); + } else { + valid = valid && is_type_compatible(current_par_type, parent_par_type); + } + } } if (!valid) { @@ -1704,7 +1730,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } parent_signature += ") -> "; - const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + const String return_type = parent_return_type.to_string_strict(); if (return_type == "null") { parent_signature += "void"; } else { @@ -1932,9 +1958,14 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } #ifdef DEBUG_ENABLED - if (!has_specified_type && !p_assignable->infer_datatype && !is_constant) { + if (!has_specified_type) { const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; - parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, is_parameter ? "Parameter" : "Variable", p_assignable->identifier->name); + const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable"); + if (p_assignable->infer_datatype || is_constant) { + parser->push_warning(p_assignable, GDScriptWarning::INFERRED_DECLARATION, declaration_type, p_assignable->identifier->name); + } else { + parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); + } } #endif @@ -2143,12 +2174,17 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } else if (!is_type_compatible(specified_type, variable_type)) { p_for->use_conversion_assign = true; } + if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + } } p_for->variable->set_datatype(specified_type); } else { p_for->variable->set_datatype(variable_type); #ifdef DEBUG_ENABLED - if (!variable_type.is_hard_type()) { + if (variable_type.is_hard_type()) { + parser->push_warning(p_for->variable, GDScriptWarning::INFERRED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); + } else { parser->push_warning(p_for->variable, GDScriptWarning::UNTYPED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); } #endif @@ -2208,6 +2244,10 @@ void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_m resolve_match_pattern(p_match_branch->patterns[i], p_match_test); } + if (p_match_branch->guard_body) { + resolve_suite(p_match_branch->guard_body); + } + resolve_suite(p_match_branch->block); decide_suite_type(p_match_branch, p_match_branch->block); @@ -2541,28 +2581,31 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr // When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed. // This function determines which type is that (if any). void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type) { + GDScriptParser::DataType expected_type = p_element_type; + expected_type.unset_container_element_type(); // Nested types (like `Array[Array[int]]`) are not currently supported. + for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element_node = p_array->elements[i]; if (element_node->is_constant) { - update_const_expression_builtin_type(element_node, p_element_type, "include"); + update_const_expression_builtin_type(element_node, expected_type, "include"); } - const GDScriptParser::DataType &element_type = element_node->get_datatype(); - if (element_type.has_no_type() || element_type.is_variant() || !element_type.is_hard_type()) { + const GDScriptParser::DataType &actual_type = element_node->get_datatype(); + if (actual_type.has_no_type() || actual_type.is_variant() || !actual_type.is_hard_type()) { mark_node_unsafe(element_node); continue; } - if (!is_type_compatible(p_element_type, element_type, true, p_array)) { - if (is_type_compatible(element_type, p_element_type)) { + if (!is_type_compatible(expected_type, actual_type, true, p_array)) { + if (is_type_compatible(actual_type, expected_type)) { mark_node_unsafe(element_node); continue; } - push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node); + push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", actual_type.to_string(), expected_type.to_string()), element_node); return; } } GDScriptParser::DataType array_type = p_array->get_datatype(); - array_type.set_container_element_type(p_element_type); + array_type.set_container_element_type(expected_type); p_array->set_datatype(array_type); } @@ -2609,7 +2652,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) { + if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type()) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type()); } @@ -2888,19 +2931,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) { // Call to name directly. StringName function_name = p_call->function_name; - Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); + if (function_name == SNAME("Object")) { + push_error(R"*(Invalid constructor "Object()", use "Object.new()" instead.)*", p_call); + p_call->set_datatype(call_type); + return; + } + + Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); if (builtin_type < Variant::VARIANT_MAX) { // Is a builtin constructor. call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; call_type.kind = GDScriptParser::DataType::BUILTIN; call_type.builtin_type = builtin_type; - if (builtin_type == Variant::OBJECT) { - call_type.kind = GDScriptParser::DataType::NATIVE; - call_type.native_type = function_name; // "Object". - } - bool safe_to_fold = true; switch (builtin_type) { // Those are stored by reference so not suited for compile-time construction. @@ -2936,7 +2980,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: - push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" constructor: argument %d should be "%s" but is "%s".)*", Variant::get_type_name(builtin_type), err.argument + 1, Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; @@ -2952,10 +2996,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: - push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too many arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: - push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too few arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: @@ -2966,21 +3010,41 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a break; } } else { - // TODO: Check constructors without constants. - // If there's one argument, try to use copy constructor (those aren't explicitly defined). if (p_call->arguments.size() == 1) { GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype(); - if (arg_type.is_variant()) { - mark_node_unsafe(p_call->arguments[0]); - } else { + if (arg_type.is_hard_type() && !arg_type.is_variant()) { if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) { // Okay. p_call->set_datatype(call_type); return; } + } else { +#ifdef DEBUG_ENABLED + mark_node_unsafe(p_call); + // Constructors support overloads. + Vector<String> types; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (i != builtin_type && Variant::can_convert_strict((Variant::Type)i, builtin_type)) { + types.push_back(Variant::get_type_name((Variant::Type)i)); + } + } + String expected_types = function_name; + if (types.size() == 1) { + expected_types += "\" or \"" + types[0]; + } else if (types.size() >= 2) { + for (int i = 0; i < types.size() - 1; i++) { + expected_types += "\", \"" + types[i]; + } + expected_types += "\", or \"" + types[types.size() - 1]; + } + parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", "constructor", function_name, expected_types, "Variant"); +#endif + p_call->set_datatype(call_type); + return; } } + List<MethodInfo> constructors; Variant::get_constructor_list(builtin_type, &constructors); bool match = false; @@ -2997,14 +3061,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); - - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (!is_type_compatible(par_type, arg_type, true)) { types_match = false; break; #ifdef DEBUG_ENABLED } else { - if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + if (par_type.builtin_type == Variant::INT && arg_type.builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { + parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, function_name); } #endif } @@ -3012,9 +3076,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (types_match) { for (int i = 0; i < p_call->arguments.size(); i++) { + GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); if (p_call->arguments[i]->is_constant) { - update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass"); + update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass"); } +#ifdef DEBUG_ENABLED + if (!(par_type.is_variant() && par_type.is_hard_type())) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (arg_type.is_variant() || !arg_type.is_hard_type()) { + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "constructor", function_name, par_type.to_string(), arg_type.to_string_strict()); + } + } +#endif } match = true; call_type = type_from_property(info.return_val); @@ -3203,11 +3277,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new"); + if (is_constructor && Engine::get_singleton()->has_singleton(base_type.native_type)) { + push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call); + p_call->set_datatype(call_type); + return; + } + if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) { - // If the function require typed arrays we must make literals be typed. + // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there. + // Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time. + if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) { + push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); + } + + // If the function requires typed arrays we must make literals be typed. for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { int index = E.key; - if (index < par_types.size() && par_types[index].has_container_element_type()) { + if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type()) { update_array_literal_element_type(E.value, par_types[index].get_container_element_type()); } } @@ -3308,8 +3394,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a #else push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); #endif // SUGGEST_GODOT4_RENAMES - } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { - push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); + } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.is_meta_type)) { + push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.to_string()), p_call); } } @@ -3797,6 +3883,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident #endif // Not a local, so check members. + if (!found_source) { reduce_identifier_from_base(p_identifier); if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { @@ -3849,10 +3936,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident StringName name = p_identifier->name; p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; - // Check globals. We make an exception for Variant::OBJECT because it's the base class for - // non-builtin types so we allow doing e.g. Object.new() + // Not a local or a member, so check globals. + Variant::Type builtin_type = GDScriptParser::get_builtin_type(name); - if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) { + if (builtin_type < Variant::VARIANT_MAX) { if (can_be_builtin) { p_identifier->set_datatype(make_builtin_meta_type(builtin_type)); return; @@ -4093,7 +4180,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); bool valid = false; // If the base is a metatype, use the analyzer instead. - if (p_subscript->base->is_constant && !base_type.is_meta_type) { + if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) { // Just try to get it. Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); if (valid) { @@ -4980,21 +5067,28 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); if (arg_type.is_variant() || !arg_type.is_hard_type()) { +#ifdef DEBUG_ENABLED // Argument can be anything, so this is unsafe (unless the parameter is a hard variant). if (!(par_type.is_hard_type() && par_type.is_variant())) { mark_node_unsafe(p_call->arguments[i]); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); } +#endif } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { - // Supertypes are acceptable for dynamic compliance, but it's unsafe. - mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), p_call->arguments[i]); +#ifdef DEBUG_ENABLED + } else { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); +#endif } #ifdef DEBUG_ENABLED } else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + parser->push_warning(p_call->arguments[i], GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); #endif } } @@ -5020,9 +5114,13 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier 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_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "native class"); + return; + } else if (ScriptServer::is_global_class(name)) { + String class_path = ScriptServer::get_global_class_path(name).get_file(); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, vformat(R"(global class defined in "%s")", class_path)); return; - } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) { + } else if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); return; } diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index d191bd0224..18609d0b80 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -59,7 +59,7 @@ GDScriptAnalyzer *GDScriptParserRef::get_analyzer() { } Error GDScriptParserRef::raise_status(Status p_new_status) { - ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); + ERR_FAIL_NULL_V(parser, ERR_INVALID_DATA); if (result != OK) { return result; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7f2c401afc..f417d323db 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -60,7 +60,7 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa scr = scr->_base; } - ERR_FAIL_COND_V(!nc, false); + ERR_FAIL_NULL_V(nc, false); return ClassDB::has_property(nc->get_name(), p_name); } @@ -104,13 +104,24 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D if (p_handle_metatype && p_datatype.is_meta_type) { result.kind = GDScriptDataType::NATIVE; result.builtin_type = Variant::OBJECT; - result.native_type = GDScriptNativeClass::get_class_static(); + // Fixes GH-82255. `GDScriptNativeClass` is obtainable in GDScript, + // but is not a registered and exposed class, so `GDScriptNativeClass` + // is missing from `GDScriptLanguage::get_singleton()->get_global_map()`. + //result.native_type = GDScriptNativeClass::get_class_static(); + result.native_type = Object::get_class_static(); break; } result.kind = GDScriptDataType::NATIVE; - result.native_type = p_datatype.native_type; result.builtin_type = p_datatype.builtin_type; + result.native_type = p_datatype.native_type; + +#ifdef DEBUG_ENABLED + if (unlikely(!GDScriptLanguage::get_singleton()->get_global_map().has(result.native_type))) { + ERR_PRINT(vformat(R"(GDScript bug: Native class "%s" not found.)", result.native_type)); + result.native_type = Object::get_class_static(); + } +#endif } break; case GDScriptParser::DataType::SCRIPT: { if (p_handle_metatype && p_datatype.is_meta_type) { @@ -601,11 +612,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code arguments.push_back(arg); } - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) { - // Construct a built-in type. - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - - gen->write_construct(result, vtype, arguments); + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + gen->write_construct(result, GDScriptParser::get_builtin_type(call->function_name), arguments); } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) { // Variant utility function. gen->write_call_utility(result, call->function_name, arguments); @@ -1917,6 +1925,26 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } + // If there's a guard, check its condition too. + if (branch->guard_body != nullptr) { + // Do this first so the guard does not run unless the pattern matched. + gen->write_and_left_operand(pattern_result); + + // Don't actually use the block for the guard. + // The binds are already in the locals and we don't want to clear the result of the guard condition before we check the actual match. + GDScriptCodeGenerator::Address guard_result = _parse_expression(codegen, err, static_cast<GDScriptParser::ExpressionNode *>(branch->guard_body->statements[0])); + if (err) { + return err; + } + + gen->write_and_right_operand(guard_result); + gen->write_end_and(pattern_result); + + if (guard_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } + // Check if pattern did match. gen->write_if(pattern_result); @@ -2783,7 +2811,7 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP minfo.property_info = prop_info; p_script->member_indices[name] = minfo; - p_script->members.insert(Variant()); + p_script->members.insert(name); } break; case GDScriptParser::ClassNode::Member::FUNCTION: { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index aec8f56516..2f26069281 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -59,6 +59,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); p_delimiters->push_back("''' '''"); + // NOTE: StringName, NodePath and r-strings are not listed here. } bool GDScriptLanguage::is_using_templates() { @@ -75,19 +76,25 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri #endif if (!type_hints) { processed_template = processed_template.replace(": int", "") + .replace(": Shader.Mode", "") + .replace(": VisualShader.Type", "") + .replace(": float", "") .replace(": String", "") .replace(": Array[String]", "") - .replace(": float", "") + .replace(": Node", "") .replace(": CharFXTransform", "") .replace(":=", "=") - .replace(" -> String", "") - .replace(" -> int", "") + .replace(" -> void", "") .replace(" -> bool", "") - .replace(" -> void", ""); + .replace(" -> int", "") + .replace(" -> PortType", "") + .replace(" -> String", "") + .replace(" -> Object", ""); } processed_template = processed_template.replace("_BASE_", p_base_class_name) - .replace("_CLASS_", p_class_name.to_pascal_case()) + .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_identifier()) + .replace("_CLASS_", p_class_name.to_pascal_case().validate_identifier()) .replace("_TS_", _get_indentation()); scr->set_source_code(processed_template); return scr; @@ -190,10 +197,6 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li return true; } -bool GDScriptLanguage::has_named_classes() const { - return false; -} - bool GDScriptLanguage::supports_builtin_mode() const { return true; } @@ -3393,6 +3396,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } + if ("Variant" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; + r_result.class_name = "Variant"; + return OK; + } + if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 4f5a65a709..372c212d2b 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -140,7 +140,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar if (p_argcount == 0) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; + r_error.expected = 1; return Variant(); } else if (p_argcount == 1) { //noooneee @@ -188,7 +188,7 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const { } Variant GDScriptFunctionState::resume(const Variant &p_arg) { - ERR_FAIL_COND_V(!function, Variant()); + ERR_FAIL_NULL_V(function, Variant()); { MutexLock lock(GDScriptLanguage::singleton->mutex); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 31da70f9ae..e984d97149 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -152,7 +152,7 @@ public: } GDScriptDataType get_container_element_type() const { - ERR_FAIL_COND_V(container_element_type == nullptr, GDScriptDataType()); + ERR_FAIL_NULL_V(container_element_type, GDScriptDataType()); return *container_element_type; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 52c1a5b141..9fb1030d12 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -52,11 +52,18 @@ #include "editor/editor_settings.h" #endif +// This function is used to determine that a type is "built-in" as opposed to native +// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded: +// `Variant::NIL` - `null` is literal, not a type. +// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type. static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.is_empty()) { - for (int i = 1; i < Variant::VARIANT_MAX; i++) { - builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + if (unlikely(builtin_types.is_empty())) { + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + Variant::Type type = (Variant::Type)i; + if (type != Variant::NIL && type != Variant::OBJECT) { + builtin_types[Variant::get_type_name(type)] = type; + } } } @@ -170,7 +177,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { #ifdef DEBUG_ENABLED void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { - ERR_FAIL_COND(p_source == nullptr); + ERR_FAIL_NULL(p_source); if (is_ignoring_warnings) { return; } @@ -383,8 +390,10 @@ GDScriptTokenizer::Token GDScriptParser::advance() { push_error(current.literal); current = tokenizer.scan(); } - for (Node *n : nodes_in_progress) { - update_extents(n); + if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line. + for (Node *n : nodes_in_progress) { + update_extents(n); + } } return previous; } @@ -579,13 +588,14 @@ void GDScriptParser::parse_program() { complete_extents(head); #ifdef TOOLS_ENABLED - for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) { - if (E.value.new_line && E.value.comment.begins_with("##")) { - class_doc_line = MIN(class_doc_line, E.key); + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + int line = MIN(max_script_doc_line, head->end_line); + while (line > 0) { + if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) { + head->doc_data = parse_class_doc_comment(line); + break; } - } - if (has_comment(class_doc_line, true)) { - head->doc_data = parse_class_doc_comment(class_doc_line, false); + line--; } #endif // TOOLS_ENABLED @@ -747,10 +757,6 @@ template <class T> void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) { advance(); -#ifdef TOOLS_ENABLED - int doc_comment_line = previous.start_line - 1; -#endif // TOOLS_ENABLED - // Consume annotations. List<AnnotationNode *> annotations; while (!annotation_stack.is_empty()) { @@ -762,11 +768,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind)); clear_unused_annotations(); } -#ifdef TOOLS_ENABLED - if (last_annotation->start_line == doc_comment_line) { - doc_comment_line--; - } -#endif // TOOLS_ENABLED } T *member = (this->*p_parse_function)(p_is_static); @@ -774,28 +775,40 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b return; } +#ifdef TOOLS_ENABLED + int doc_comment_line = member->start_line - 1; +#endif // TOOLS_ENABLED + for (AnnotationNode *&annotation : annotations) { member->annotations.push_back(annotation); +#ifdef TOOLS_ENABLED + if (annotation->start_line <= doc_comment_line) { + doc_comment_line = annotation->start_line - 1; + } +#endif // TOOLS_ENABLED } #ifdef TOOLS_ENABLED - // Consume doc comments. - class_doc_line = MIN(class_doc_line, doc_comment_line - 1); - - // Check whether current line has a doc comment - if (has_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); + if constexpr (std::is_same_v<T, ClassNode>) { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_class_doc_comment(member->start_line, true); + } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members. + // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice. + member->doc_data = parse_class_doc_comment(doc_comment_line); } - } else if (has_comment(doc_comment_line, true)) { - if constexpr (std::is_same_v<T, ClassNode>) { - member->doc_data = parse_class_doc_comment(doc_comment_line, true); - } else { + } else { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_doc_comment(member->start_line, true); + } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. member->doc_data = parse_doc_comment(doc_comment_line); } } + + min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment. #endif // TOOLS_ENABLED if (member->identifier != nullptr) { @@ -1263,6 +1276,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { push_multiline(true); consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); +#ifdef TOOLS_ENABLED + int min_enum_value_doc_line = previous.end_line + 1; +#endif HashMap<StringName, int> elements; @@ -1325,43 +1341,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { } } while (match(GDScriptTokenizer::Token::COMMA)); - pop_multiline(); - consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); - #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 (doc_comment_line == previous.start_line) { - break; - } - } else { - // If two values are same line. - if (doc_comment_line == enum_node->values[i + 1].line) { - continue; + int enum_value_line = enum_node->values[i].line; + int doc_comment_line = enum_value_line - 1; + + MemberDocData doc_data; + if (has_comment(enum_value_line, true)) { + // Inline doc comment. + if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) { + doc_data = parse_doc_comment(enum_value_line, true); } + } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. + doc_data = parse_doc_comment(doc_comment_line); } if (named) { - enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line); + enum_node->values.write[i].doc_data = doc_data; } else { - current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line)); + current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data); } + + min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment. } #endif // TOOLS_ENABLED + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); complete_extents(enum_node); end_statement("enum"); @@ -2034,7 +2042,37 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + bool has_guard = false; + if (match(GDScriptTokenizer::Token::WHEN)) { + // Pattern guard. + // Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope. + branch->guard_body = alloc_node<SuiteNode>(); + if (branch->patterns.size() > 0) { + for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { + SuiteNode::Local local(E.value, current_function); + local.type = SuiteNode::Local::PATTERN_BIND; + branch->guard_body->add_local(local); + } + } + + SuiteNode *parent_block = current_suite; + branch->guard_body->parent_block = parent_block; + current_suite = branch->guard_body; + + ExpressionNode *guard = parse_expression(false); + if (guard == nullptr) { + push_error(R"(Expected expression for pattern guard after "when".)"); + } else { + branch->guard_body->statements.append(guard); + } + current_suite = parent_block; + complete_extents(branch->guard_body); + + has_guard = true; + branch->has_wildcard = false; // If it has a guard, the wildcard might still not match. + } + + if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) { complete_extents(branch); return nullptr; } @@ -3454,31 +3492,21 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { } GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) { - MemberDocData result; + ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), result); - - if (p_single_line) { - if (comments[p_line].comment.begins_with("##")) { - result.description = comments[p_line].comment.trim_prefix("##").strip_edges(); - return result; - } - return result; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { + line--; } - line--; } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3488,11 +3516,10 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + MemberDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3513,35 +3540,22 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool return result; } -GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) { - ClassDocData result; +GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) { + ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - 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; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - bool is_in_brief = true; - if (p_inner_class) { - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; - } + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { line--; } } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3551,11 +3565,11 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + bool is_in_brief = true; + ClassDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3630,14 +3644,6 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, } } - if (current_class->members.size() > 0) { - const ClassNode::Member &m = current_class->members[0]; - int first_member_line = m.get_line(); - if (first_member_line == line) { - result = ClassDocData(); // Clear result. - } - } - return result; } #endif // TOOLS_ENABLED @@ -3705,6 +3711,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // PASS, { nullptr, nullptr, PREC_NONE }, // RETURN, { nullptr, nullptr, PREC_NONE }, // MATCH, + { nullptr, nullptr, PREC_NONE }, // WHEN, // Keywords { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS, { nullptr, nullptr, PREC_NONE }, // ASSERT, @@ -4095,25 +4102,29 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } break; case GDScriptParser::DataType::ENUM: { - variable->export_info.type = Variant::INT; - variable->export_info.hint = PROPERTY_HINT_ENUM; - - String enum_hint_string; - bool first = true; - for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { - if (!first) { - enum_hint_string += ","; - } else { - first = false; + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); } - enum_hint_string += E.key.operator String().capitalize().xml_escape(); - enum_hint_string += ":"; - enum_hint_string += String::num_int64(E.value).xml_escape(); - } - variable->export_info.hint_string = enum_hint_string; - variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; - variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } } break; default: push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 27d4d0fb47..2daadd7e6a 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -149,6 +149,7 @@ public: _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; + _FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; } PropertyInfo to_property_info(const String &p_name) const; _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) { @@ -156,7 +157,7 @@ public: } _FORCE_INLINE_ DataType get_container_element_type() const { - ERR_FAIL_COND_V(container_element_type == nullptr, DataType()); + ERR_FAIL_NULL_V(container_element_type, DataType()); return *container_element_type; } @@ -948,6 +949,7 @@ public: Vector<PatternNode *> patterns; SuiteNode *block = nullptr; bool has_wildcard = false; + SuiteNode *guard_body = nullptr; MatchBranchNode() { type = MATCH_BRANCH; @@ -1517,10 +1519,11 @@ private: TypeNode *parse_type(bool p_allow_void = false); #ifdef TOOLS_ENABLED - int class_doc_line = 0x7FFFFFFF; + int max_script_doc_line = INT_MAX; + int min_member_doc_line = 1; bool has_comment(int p_line, bool p_must_be_doc = false); 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); + ClassDocData parse_class_doc_comment(int p_line, bool p_single_line = false); #endif // TOOLS_ENABLED public: @@ -1529,7 +1532,7 @@ public: bool is_tool() const { return _is_tool; } ClassNode *find_class(const String &p_qualified_name) const; bool has_class(const GDScriptParser::ClassNode *p_class) const; - static Variant::Type get_builtin_type(const StringName &p_type); + static Variant::Type get_builtin_type(const StringName &p_type); // Excluding `Variant::NIL` and `Variant::OBJECT`. CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index 199ea81330..a6d2388a91 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -78,7 +78,7 @@ GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_m h = method.hash(); h = hash_murmur3_one_64(object->get_instance_id(), h); node = Object::cast_to<Node>(object); - ERR_FAIL_COND_MSG(!node, "RPC can only be defined on class that extends Node."); + ERR_FAIL_NULL_MSG(node, "RPC can only be defined on class that extends Node."); } Error GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const { diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 42b983ef45..98a3a1268f 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -99,6 +99,7 @@ static const char *token_names[] = { "pass", // PASS, "return", // RETURN, "match", // MATCH, + "when", // WHEN, // Keywords "as", // AS, "assert", // ASSERT, @@ -187,6 +188,7 @@ bool GDScriptTokenizer::Token::is_identifier() const { switch (type) { case IDENTIFIER: case MATCH: // Used in String.match(). + case WHEN: // New keyword, avoid breaking existing code. // Allow constants to be treated as regular identifiers. case CONST_PI: case CONST_INF: @@ -241,6 +243,7 @@ bool GDScriptTokenizer::Token::is_node_name() const { case VAR: case VOID: case WHILE: + case WHEN: case YIELD: return true; default: @@ -531,6 +534,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() { KEYWORD("void", Token::VOID) \ KEYWORD_GROUP('w') \ KEYWORD("while", Token::WHILE) \ + KEYWORD("when", Token::WHEN) \ KEYWORD_GROUP('y') \ KEYWORD("yield", Token::YIELD) \ KEYWORD_GROUP('I') \ @@ -857,10 +861,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { STRING_NODEPATH, }; + bool is_raw = false; bool is_multiline = false; StringType type = STRING_REGULAR; - if (_peek(-1) == '&') { + if (_peek(-1) == 'r') { + is_raw = true; + _advance(); + } else if (_peek(-1) == '&') { type = STRING_NAME; _advance(); } else if (_peek(-1) == '^') { @@ -890,7 +898,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { char32_t ch = _peek(); if (ch == 0x200E || ch == 0x200F || (ch >= 0x202A && ch <= 0x202E) || (ch >= 0x2066 && ch <= 0x2069)) { - Token error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion."); + Token error; + if (is_raw) { + error = make_error("Invisible text direction control character present in the string, use regular string literal instead of r-string."); + } else { + error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion."); + } error.start_column = column; error.leftmost_column = error.start_column; error.end_column = column + 1; @@ -905,144 +918,164 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - // Grab escape character. - char32_t code = _peek(); - _advance(); - if (_is_at_end()) { - return make_error("Unterminated string."); - } + if (is_raw) { + if (_peek() == quote_char) { + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } + result += '\\'; + result += quote_char; + } else if (_peek() == '\\') { // For `\\\"`. + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } + result += '\\'; + result += '\\'; + } else { + result += '\\'; + } + } else { + // Grab escape character. + char32_t code = _peek(); + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } - char32_t escaped = 0; - bool valid_escape = true; + char32_t escaped = 0; + bool valid_escape = true; - switch (code) { - case 'a': - escaped = '\a'; - break; - case 'b': - escaped = '\b'; - break; - case 'f': - escaped = '\f'; - break; - case 'n': - escaped = '\n'; - break; - case 'r': - escaped = '\r'; - break; - case 't': - escaped = '\t'; - break; - case 'v': - escaped = '\v'; - break; - case '\'': - escaped = '\''; - break; - case '\"': - escaped = '\"'; - break; - case '\\': - escaped = '\\'; - break; - case 'U': - case 'u': { - // Hexadecimal sequence. - int hex_len = (code == 'U') ? 6 : 4; - for (int j = 0; j < hex_len; j++) { - if (_is_at_end()) { - return make_error("Unterminated string."); + switch (code) { + case 'a': + escaped = '\a'; + break; + case 'b': + escaped = '\b'; + break; + case 'f': + escaped = '\f'; + break; + case 'n': + escaped = '\n'; + break; + case 'r': + escaped = '\r'; + break; + case 't': + escaped = '\t'; + break; + case 'v': + escaped = '\v'; + break; + case '\'': + escaped = '\''; + break; + case '\"': + escaped = '\"'; + break; + case '\\': + escaped = '\\'; + break; + case 'U': + case 'u': { + // Hexadecimal sequence. + int hex_len = (code == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { + if (_is_at_end()) { + return make_error("Unterminated string."); + } + + char32_t digit = _peek(); + char32_t value = 0; + if (is_digit(digit)) { + value = digit - '0'; + } else if (digit >= 'a' && digit <= 'f') { + value = digit - 'a'; + value += 10; + } else if (digit >= 'A' && digit <= 'F') { + value = digit - 'A'; + value += 10; + } else { + // Make error, but keep parsing the string. + Token error = make_error("Invalid hexadecimal digit in unicode escape sequence."); + error.start_column = column; + error.leftmost_column = error.start_column; + error.end_column = column + 1; + error.rightmost_column = error.end_column; + push_error(error); + valid_escape = false; + break; + } + + escaped <<= 4; + escaped |= value; + + _advance(); } - - char32_t digit = _peek(); - char32_t value = 0; - if (is_digit(digit)) { - value = digit - '0'; - } else if (digit >= 'a' && digit <= 'f') { - value = digit - 'a'; - value += 10; - } else if (digit >= 'A' && digit <= 'F') { - value = digit - 'A'; - value += 10; - } else { - // Make error, but keep parsing the string. - Token error = make_error("Invalid hexadecimal digit in unicode escape sequence."); - error.start_column = column; - error.leftmost_column = error.start_column; - error.end_column = column + 1; - error.rightmost_column = error.end_column; - push_error(error); - valid_escape = false; + } break; + case '\r': + if (_peek() != '\n') { + // Carriage return without newline in string. (???) + // Just add it to the string and keep going. + result += ch; + _advance(); break; } - - escaped <<= 4; - escaped |= value; - - _advance(); - } - } break; - case '\r': - if (_peek() != '\n') { - // Carriage return without newline in string. (???) - // Just add it to the string and keep going. - result += ch; - _advance(); + [[fallthrough]]; + case '\n': + // Escaping newline. + newline(false); + valid_escape = false; // Don't add to the string. break; - } - [[fallthrough]]; - case '\n': - // Escaping newline. - newline(false); - valid_escape = false; // Don't add to the string. - break; - default: - Token error = make_error("Invalid escape in string."); - error.start_column = column - 2; - error.leftmost_column = error.start_column; - push_error(error); - valid_escape = false; - break; - } - // Parse UTF-16 pair. - if (valid_escape) { - if ((escaped & 0xfffffc00) == 0xd800) { - if (prev == 0) { - prev = escaped; - prev_pos = column - 2; - continue; - } else { - Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + default: + Token error = make_error("Invalid escape in string."); error.start_column = column - 2; error.leftmost_column = error.start_column; push_error(error); valid_escape = false; - prev = 0; + break; + } + // Parse UTF-16 pair. + if (valid_escape) { + if ((escaped & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = escaped; + prev_pos = column - 2; + continue; + } else { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate."); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + prev = 0; + } + } else if ((escaped & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate."); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + } else { + escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } } - } else if ((escaped & 0xfffffc00) == 0xdc00) { - if (prev == 0) { - Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); - error.start_column = column - 2; + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate."); + error.start_column = prev_pos; error.leftmost_column = error.start_column; push_error(error); - valid_escape = false; - } else { - escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); prev = 0; } } - if (prev != 0) { - Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); - error.start_column = prev_pos; - error.leftmost_column = error.start_column; - push_error(error); - prev = 0; - } - } - if (valid_escape) { - result += escaped; + if (valid_escape) { + result += escaped; + } } } else if (ch == quote_char) { if (prev != 0) { @@ -1216,7 +1249,7 @@ void GDScriptTokenizer::check_indent() { if (line_continuation || multiline_mode) { // We cleared up all the whitespace at the beginning of the line. - // But if this is a continuation or multiline mode and we don't want any indentation change. + // If this is a line continuation or we're in multiline mode then we don't want any indentation changes. return; } @@ -1416,6 +1449,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (is_digit(c)) { return number(); + } else if (c == 'r' && (_peek() == '"' || _peek() == '\'')) { + // Raw string literals. + return string(); } else if (is_unicode_identifier_start(c)) { return potential_identifier(); } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 068393cee9..6dd8a98652 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -105,6 +105,7 @@ public: PASS, RETURN, MATCH, + WHEN, // Keywords AS, ASSERT, @@ -187,6 +188,8 @@ public: #ifdef TOOLS_ENABLED struct CommentData { String comment; + // true: Comment starts at beginning of line or after indentation. + // false: Inline comment (starts after some code). bool new_line = false; CommentData() {} CommentData(const String &p_comment, bool p_new_line) { diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 39cf9d79c8..69a0b42d89 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -45,14 +45,12 @@ #define VALIDATE_ARG_COUNT(m_count) \ if (p_arg_count < m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ } \ if (p_arg_count > m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ @@ -119,7 +117,6 @@ struct GDScriptUtilityFunctionsDefinitions { switch (p_arg_count) { case 0: { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; r_error.expected = 1; *r_ret = Variant(); } break; @@ -223,7 +220,6 @@ struct GDScriptUtilityFunctionsDefinitions { } break; default: { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 3; r_error.expected = 3; *r_ret = Variant(); @@ -251,6 +247,7 @@ struct GDScriptUtilityFunctionsDefinitions { } else if (p_args[0]->get_type() != Variant::OBJECT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; + r_error.expected = Variant::OBJECT; *r_ret = Variant(); } else { Object *obj = *p_args[0]; @@ -390,13 +387,13 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { if (p_arg_count < 3) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 3; + r_error.expected = 3; *r_ret = Variant(); return; } if (p_arg_count > 4) { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 4; + r_error.expected = 4; *r_ret = Variant(); return; } @@ -729,50 +726,50 @@ void GDScriptUtilityFunctions::unregister_functions() { GDScriptUtilityFunctions::FunctionPtr GDScriptUtilityFunctions::get_function(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, nullptr); + ERR_FAIL_NULL_V(info, nullptr); return info->function; } bool GDScriptUtilityFunctions::has_function_return_value(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return info->info.return_val.type != Variant::NIL || bool(info->info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); } Variant::Type GDScriptUtilityFunctions::get_function_return_type(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_NULL_V(info, Variant::NIL); return info->info.return_val.type; } StringName GDScriptUtilityFunctions::get_function_return_class(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, StringName()); + ERR_FAIL_NULL_V(info, StringName()); return info->info.return_val.class_name; } Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringName &p_function, int p_arg) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_NULL_V(info, Variant::NIL); ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL); return info->info.arguments[p_arg].type; } int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, 0); + ERR_FAIL_NULL_V(info, 0); return info->info.arguments.size(); } bool GDScriptUtilityFunctions::is_function_vararg(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return (bool)(info->info.flags & METHOD_FLAG_VARARG); } bool GDScriptUtilityFunctions::is_function_constant(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return info->is_constant; } @@ -788,6 +785,6 @@ void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions) MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, MethodInfo()); + ERR_FAIL_NULL_V(info, MethodInfo()); return info->info; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index c0644e089c..5ecae08f6c 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -398,7 +398,13 @@ void (*type_init_function_table[])(Variant *) = { #define OPCODES_END #define OPCODES_OUT #define DISPATCH_OPCODE continue +#ifdef _MSC_VER +#define OPCODE_SWITCH(m_test) \ + __assume(m_test <= OPCODE_END); \ + switch (m_test) +#else #define OPCODE_SWITCH(m_test) switch (m_test) +#endif #define OPCODE_BREAK break #define OPCODE_OUT break #endif @@ -513,7 +519,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (p_argcount > _argument_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; r_err.expected = _argument_count; - call_depth--; return _get_default_variant_for_data_type(return_type); } else if (p_argcount < _argument_count - _default_arg_count) { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ef2913926c..1fe9b0425c 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -94,6 +94,9 @@ String GDScriptWarning::get_message() const { return vformat(R"*(%s "%s()" has no static return type.)*", symbols[0], symbols[1]); } return vformat(R"(%s "%s" has no static type.)", symbols[0], symbols[1]); + case INFERRED_DECLARATION: + CHECK_SYMBOLS(2); + return vformat(R"(%s "%s" has an implicitly inferred static type.)", symbols[0], symbols[1]); case UNSAFE_PROPERTY_ACCESS: CHECK_SYMBOLS(2); return vformat(R"(The property "%s" is not present on the inferred type "%s" (but may be present on a subtype).)", symbols[0], symbols[1]); @@ -104,8 +107,8 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(1); return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); case UNSAFE_CALL_ARGUMENT: - CHECK_SYMBOLS(4); - return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); + CHECK_SYMBOLS(5); + return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]); case UNSAFE_VOID_RETURN: CHECK_SYMBOLS(2); return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]); @@ -207,6 +210,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "CONSTANT_USED_AS_FUNCTION", "FUNCTION_USED_AS_PROPERTY", "UNTYPED_DECLARATION", + "INFERRED_DECLARATION", "UNSAFE_PROPERTY_ACCESS", "UNSAFE_METHOD_ACCESS", "UNSAFE_CAST", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 2b17709338..1aef6fa81b 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -64,7 +64,8 @@ public: PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name. CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name. FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name. - UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or inferred (`:=`). + UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or implicitly inferred. + INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type. UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes). UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). UNSAFE_CAST, // Cast used in an unknown type. @@ -113,6 +114,7 @@ public: WARN, // CONSTANT_USED_AS_FUNCTION WARN, // FUNCTION_USED_AS_PROPERTY IGNORE, // UNTYPED_DECLARATION // Static typing is optional, we don't want to spam warnings. + IGNORE, // INFERRED_DECLARATION // Static typing is optional, we don't want to spam warnings. IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_CAST // Too common in untyped scenarios. diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 14fc21d7dc..8489fc08c1 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -290,7 +290,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_COND(peer == nullptr); + ERR_FAIL_NULL(peer); Dictionary message = make_notification(p_method, p_params); String msg = Variant(message).to_json_string(); @@ -311,7 +311,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_COND(peer == nullptr); + ERR_FAIL_NULL(peer); Dictionary message = make_request(p_method, p_params, next_server_id); next_server_id++; diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 8c44483288..053be7eec2 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -36,6 +36,8 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" +int GDScriptLanguageServer::port_override = -1; + GDScriptLanguageServer::GDScriptLanguageServer() { _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); @@ -62,7 +64,7 @@ void GDScriptLanguageServer::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { String remote_host = String(_EDITOR_GET("network/language_server/remote_host")); - int remote_port = (int)_EDITOR_GET("network/language_server/remote_port"); + int remote_port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); if (remote_host != host || remote_port != port || remote_use_thread != use_thread) { stop(); @@ -84,10 +86,10 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { host = String(_EDITOR_GET("network/language_server/remote_host")); - port = (int)_EDITOR_GET("network/language_server/remote_port"); + port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); if (protocol.start(port, IPAddress(host)) == OK) { - EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; thread.start(GDScriptLanguageServer::thread_main, this); diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 75f9403a74..e845d139bf 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -53,6 +53,7 @@ private: void _notification(int p_what); public: + static int port_override; GDScriptLanguageServer(); void start(); void stop(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 1e927f9f6e..d6779dc71c 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -346,6 +346,15 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } } + if (item.kind == lsp::CompletionItemKind::Method) { + bool is_trigger_character = params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter; + bool is_quote_character = params.context.triggerCharacter == "\"" || params.context.triggerCharacter == "'"; + + if (is_trigger_character && is_quote_character && item.insertText.is_quoted()) { + item.insertText = item.insertText.unquote(); + } + } + return item.to_json(true); } diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 1ac4267c7b..e09adb74bd 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -1429,6 +1429,17 @@ struct CompletionParams : public TextDocumentPositionParams { TextDocumentPositionParams::load(p_params); context.load(p_params["context"]); } + + Dictionary to_json() { + Dictionary ctx; + ctx["triggerCharacter"] = context.triggerCharacter; + ctx["triggerKind"] = context.triggerKind; + + Dictionary dict; + dict = TextDocumentPositionParams::to_json(); + dict["context"] = ctx; + return dict; + } }; /** diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 01772a2e38..f91dc83f2c 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -149,7 +149,7 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l // Set all warning levels to "Warn" in order to test them properly, even the ones that default to error. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { - if (i == GDScriptWarning::UNTYPED_DECLARATION) { + if (i == GDScriptWarning::UNTYPED_DECLARATION || i == GDScriptWarning::INFERRED_DECLARATION) { // TODO: Add ability for test scripts to specify which warnings to enable/disable for testing. continue; } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd new file mode 100644 index 0000000000..87d1b9ea18 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd @@ -0,0 +1,7 @@ +# GH-73283 + +class MyClass: + pass + +func test(): + MyClass.not_existing_method() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out new file mode 100644 index 0000000000..7340058dd4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Static function "not_existing_method()" not found in base "MyClass". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd new file mode 100644 index 0000000000..7e1efb8d1b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd @@ -0,0 +1,2 @@ +func test(): + Time.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out new file mode 100644 index 0000000000..bc4875d908 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot construct native class "Time" because it is an engine singleton. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd new file mode 100644 index 0000000000..a46b6d8658 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd @@ -0,0 +1,6 @@ +# GH-82081 + +extends Time + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out new file mode 100644 index 0000000000..7c26dea56e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot inherit native class "Time" because it is an engine singleton. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd new file mode 100644 index 0000000000..db3f3f4c72 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd @@ -0,0 +1,5 @@ +# GH-82021 + +func test(): + for x: String in [1, 2, 3]: + print(x) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out new file mode 100644 index 0000000000..0bb654e7e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "int" as "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd new file mode 100644 index 0000000000..fdf22f6843 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Object): + pass + +class B extends A: + func f(_p: Node): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out new file mode 100644 index 0000000000..c6a7e40e8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Object) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd new file mode 100644 index 0000000000..e4094f1d76 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Variant): + pass + +class B extends A: + func f(_p: Node): # No `is_type_compatible()` misuse. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out new file mode 100644 index 0000000000..52a6efc6fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Variant) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd new file mode 100644 index 0000000000..17663da4f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: int): + pass + +class B extends A: + func f(_p: float): # No implicit conversion. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out new file mode 100644 index 0000000000..7a6207fd45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(int) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd new file mode 100644 index 0000000000..6dfa75ecbc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Object: + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd new file mode 100644 index 0000000000..366494b94f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Variant: # No `is_type_compatible()` misuse. + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd new file mode 100644 index 0000000000..2cb4e7c616 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> void: # No `is_type_compatible()` misuse. + return + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd new file mode 100644 index 0000000000..2cabce46f5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd @@ -0,0 +1,10 @@ +class A: + func f() -> float: + return 0.0 + +class B extends A: + func f() -> int: # No implicit conversion. + return 0 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out new file mode 100644 index 0000000000..72f2c493d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> float". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd new file mode 100644 index 0000000000..1dcb9fc36a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd @@ -0,0 +1,4 @@ +func test(): + match 0: + _ when a == 0: + print("a does not exist") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out new file mode 100644 index 0000000000..c5f0a90d6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "a" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd new file mode 100644 index 0000000000..1600c3001f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd @@ -0,0 +1,4 @@ +# GH-73213 + +func test(): + print(Object()) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out new file mode 100644 index 0000000000..27668fcd48 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid constructor "Object()", use "Object.new()" instead. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd new file mode 100644 index 0000000000..57dfffdbee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd @@ -0,0 +1,5 @@ +func _init(): + super() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out new file mode 100644 index 0000000000..e68759223c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call the parent class' virtual function "_init()" because it hasn't been defined. diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd new file mode 100644 index 0000000000..dafd2ec0c8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd @@ -0,0 +1,17 @@ +class_name TestExportEnumAsDictionary + +enum MyEnum {A, B, C} + +const Utils = preload("../../utils.notest.gd") + +@export var x1 = MyEnum +@export var x2 = MyEnum.A +@export var x3 := MyEnum +@export var x4 := MyEnum.A +@export var x5: MyEnum + +func test(): + for property in get_property_list(): + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + print(Utils.get_property_signature(property)) + print(" ", Utils.get_property_additional_info(property)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out new file mode 100644 index 0000000000..f1a13f1045 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out @@ -0,0 +1,11 @@ +GDTEST_OK +@export var x1: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x2: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x3: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x4: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x5: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd new file mode 100644 index 0000000000..a43c233625 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd @@ -0,0 +1,20 @@ +class A: + func int_to_variant(_p: int): pass + func node_to_variant(_p: Node): pass + func node_2d_to_node(_p: Node2D): pass + + func variant_to_untyped(_p: Variant): pass + func int_to_untyped(_p: int): pass + func node_to_untyped(_p: Node): pass + +class B extends A: + func int_to_variant(_p: Variant): pass + func node_to_variant(_p: Variant): pass + func node_2d_to_node(_p: Node): pass + + func variant_to_untyped(_p): pass + func int_to_untyped(_p): pass + func node_to_untyped(_p): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd new file mode 100644 index 0000000000..4de50b6731 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd @@ -0,0 +1,32 @@ +class A: + func variant_to_int() -> Variant: return 0 + func variant_to_node() -> Variant: return null + func node_to_node_2d() -> Node: return null + + func untyped_to_void(): pass + func untyped_to_variant(): pass + func untyped_to_int(): pass + func untyped_to_node(): pass + + func void_to_untyped() -> void: pass + func variant_to_untyped() -> Variant: return null + func int_to_untyped() -> int: return 0 + func node_to_untyped() -> Node: return null + +class B extends A: + func variant_to_int() -> int: return 0 + func variant_to_node() -> Node: return null + func node_to_node_2d() -> Node2D: return null + + func untyped_to_void() -> void: pass + func untyped_to_variant() -> Variant: return null + func untyped_to_int() -> int: return 0 + func untyped_to_node() -> Node: return null + + func void_to_untyped(): pass + func variant_to_untyped(): pass + func int_to_untyped(): pass + func node_to_untyped(): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index b447180ea8..d0f895d784 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -23,6 +23,7 @@ func test() -> void: typed = variant() inferred = variant() + @warning_ignore("unsafe_call_argument") # TODO: Hard vs Weak vs Unknown. param_weak(typed) param_typed(typed) param_inferred(typed) diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd index 5a413e2015..08e7dc590e 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd @@ -6,10 +6,12 @@ var prop = null func check_arg(arg = null) -> void: if arg != null: + @warning_ignore("unsafe_call_argument") print(check(arg)) func check_recur() -> void: if recur != null: + @warning_ignore("unsafe_call_argument") print(check(recur)) else: recur = 1 @@ -22,11 +24,13 @@ func test() -> void: if prop == null: set('prop', 1) + @warning_ignore("unsafe_call_argument") print(check(prop)) set('prop', null) var loop = null while loop != 2: if loop != null: + @warning_ignore("unsafe_call_argument") print(check(loop)) loop = 1 if loop == null else 2 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd new file mode 100644 index 0000000000..e1a1f07e47 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd @@ -0,0 +1,22 @@ +var _typed_array: Array[int] + +func weak_param_func(weak_param = _typed_array): + weak_param = [11] # Don't treat the literal as typed! + return weak_param + +func hard_param_func(hard_param := _typed_array): + hard_param = [12] + return hard_param + +func test(): + var weak_var = _typed_array + print(weak_var.is_typed()) + weak_var = [21] # Don't treat the literal as typed! + print(weak_var.is_typed()) + print(weak_param_func().is_typed()) + + var hard_var := _typed_array + print(hard_var.is_typed()) + hard_var = [22] + print(hard_var.is_typed()) + print(hard_param_func().is_typed()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out new file mode 100644 index 0000000000..34b18dbe7c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +false +false +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd new file mode 100644 index 0000000000..a8641e4f3b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd @@ -0,0 +1,21 @@ +class BaseClass: + func _get_property_list(): + return {"property" : "definition"} + +class SuperClassMethodsRecognized extends BaseClass: + func _init(): + # Recognizes super class methods. + var _x = _get_property_list() + +class SuperMethodsRecognized extends BaseClass: + func _get_property_list(): + # Recognizes super method. + var result = super() + result["new"] = "new" + return result + +func test(): + var test1 = SuperClassMethodsRecognized.new() + print(test1._get_property_list()) # Calls base class's method. + var test2 = SuperMethodsRecognized.new() + print(test2._get_property_list()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out new file mode 100644 index 0000000000..ccff660117 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out @@ -0,0 +1,3 @@ +GDTEST_OK +{ "property": "definition" } +{ "property": "definition", "new": "new" } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd index 849df0921e..c1776fe1b4 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd @@ -14,4 +14,5 @@ func test(): func do_add_node(): var node = Node.new() node.name = "Node" + @warning_ignore("unsafe_call_argument") add_child(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd index 29239a19da..939e787ea5 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd @@ -1,3 +1,5 @@ +class_name ShadowedClass + var member: int = 0 var print_debug := 'print_debug' @@ -12,5 +14,6 @@ func test(): var sqrt := 'sqrt' var member := 'member' var reference := 'reference' + var ShadowedClass := 'ShadowedClass' print('warn') diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index accc791d8a..8297eed4b8 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -1,30 +1,34 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 5 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "print_debug" has the same name as a built-in function. >> WARNING ->> Line: 9 +>> Line: 11 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Array" has the same name as a built-in type. >> WARNING ->> Line: 10 +>> Line: 12 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable "Node" has the same name as a global class. +>> The variable "Node" has the same name as a native class. >> WARNING ->> Line: 11 +>> Line: 13 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "is_same" has the same name as a built-in function. >> WARNING ->> Line: 12 +>> Line: 14 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "sqrt" has the same name as a built-in function. >> WARNING ->> Line: 13 +>> Line: 15 >> SHADOWED_VARIABLE ->> The local variable "member" is shadowing an already-declared variable at line 1. +>> The local variable "member" is shadowing an already-declared variable at line 3. >> WARNING ->> Line: 14 +>> Line: 16 >> SHADOWED_VARIABLE_BASE_CLASS >> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted". +>> WARNING +>> Line: 17 +>> SHADOWED_GLOBAL_IDENTIFIER +>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd". warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd new file mode 100644 index 0000000000..c6d9b37485 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd @@ -0,0 +1,54 @@ +func variant_func(x: Variant) -> void: + print(x) + +func int_func(x: int) -> void: + print(x) + +func float_func(x: float) -> void: + print(x) + +func node_func(x: Node) -> void: + print(x) + +# We don't want to execute it because of errors, just analyze. +func no_exec_test(): + var variant: Variant = null + var untyped_int = 42 + var untyped_string = "abc" + var variant_int: Variant = 42 + var variant_string: Variant = "abc" + var typed_int: int = 42 + + variant_func(untyped_int) # No warning. + variant_func(untyped_string) # No warning. + variant_func(variant_int) # No warning. + variant_func(variant_string) # No warning. + variant_func(typed_int) # No warning. + + int_func(untyped_int) + int_func(untyped_string) + int_func(variant_int) + int_func(variant_string) + int_func(typed_int) # No warning. + + float_func(untyped_int) + float_func(untyped_string) + float_func(variant_int) + float_func(variant_string) + float_func(typed_int) # No warning. + + node_func(variant) + node_func(Object.new()) + node_func(Node.new()) # No warning. + node_func(Node2D.new()) # No warning. + + # GH-82529 + print(Callable(self, "test")) # No warning. + print(Callable(variant, "test")) + + print(Dictionary(variant)) + print(Vector2(variant)) + print(int(variant)) + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out new file mode 100644 index 0000000000..3084515233 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out @@ -0,0 +1,57 @@ +GDTEST_OK +>> WARNING +>> Line: 28 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 29 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 30 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 31 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 34 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 35 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 36 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 37 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 40 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Variant" was provided. +>> WARNING +>> Line: 41 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Object" was provided. +>> WARNING +>> Line: 47 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Callable()" requires the subtype "Object" but the supertype "Variant" was provided. +>> WARNING +>> Line: 49 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Dictionary()" requires the subtype "Dictionary" but the supertype "Variant" was provided. +>> WARNING +>> Line: 50 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Vector2()" requires the subtype "Vector2" or "Vector2i" but the supertype "Variant" was provided. +>> WARNING +>> Line: 51 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "int()" requires the subtype "int", "bool", or "float" but the supertype "Variant" was provided. diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd new file mode 100644 index 0000000000..e5eecbb819 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd @@ -0,0 +1,2 @@ +func test(): + print(r"\") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out new file mode 100644 index 0000000000..c8e843b0d7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unterminated string. diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd new file mode 100644 index 0000000000..9168b69f86 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd @@ -0,0 +1,2 @@ +func test(): + print(r"\\"") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out new file mode 100644 index 0000000000..c8e843b0d7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unterminated string. diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd new file mode 100644 index 0000000000..37dc910e5f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd @@ -0,0 +1,3 @@ +func test(): + # v + print(r"['"]*") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out new file mode 100644 index 0000000000..dcb5c2f289 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Closing "]" doesn't have an opening counterpart. diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd new file mode 100644 index 0000000000..d88b4a37c4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd @@ -0,0 +1,5 @@ +func test(): + var a = 0 + match a: + 0 when a = 1: + print("assignment not allowed on pattern guard") diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd index 7e1982597c..0c8a5d1367 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd @@ -14,3 +14,7 @@ func test(): var TAU = "TAU" print(TAU) + + # New keyword for pattern guards. + var when = "when" + print(when) diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out index aae2ae13d5..8ac8e92ef7 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out @@ -4,3 +4,4 @@ PI INF NAN TAU +when diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd index f04f4de08d..19f6e08285 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd @@ -3,27 +3,32 @@ extends Node func test(): var child = Node.new() child.name = "Child" + @warning_ignore("unsafe_call_argument") add_child(child) child.owner = self var hey = Node.new() hey.name = "Hey" + @warning_ignore("unsafe_call_argument") child.add_child(hey) hey.owner = self hey.unique_name_in_owner = true var fake_hey = Node.new() fake_hey.name = "Hey" + @warning_ignore("unsafe_call_argument") add_child(fake_hey) fake_hey.owner = self var sub_child = Node.new() sub_child.name = "SubChild" + @warning_ignore("unsafe_call_argument") hey.add_child(sub_child) sub_child.owner = self var howdy = Node.new() howdy.name = "Howdy" + @warning_ignore("unsafe_call_argument") sub_child.add_child(howdy) howdy.owner = self howdy.unique_name_in_owner = true diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd index 8ba558e91d..3d9404b20b 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd @@ -5,9 +5,11 @@ func test(): # Create the required node structure. var hello = Node.new() hello.name = "Hello" + @warning_ignore("unsafe_call_argument") add_child(hello) var world = Node.new() world.name = "World" + @warning_ignore("unsafe_call_argument") hello.add_child(world) # All the ways of writing node paths below with the `$` operator are valid. diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd index df6001c7e2..f16c768f7f 100644 --- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd +++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd @@ -26,6 +26,7 @@ func test(): if true: (v as Callable).call() print() + @warning_ignore("unsafe_call_argument") other(v) print() diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd index 59cdc7d6c2..31de73813f 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd @@ -2,4 +2,5 @@ func foo(x): return x + 1 func test(): + @warning_ignore("unsafe_call_argument") print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.gd b/modules/gdscript/tests/scripts/parser/features/r_strings.gd new file mode 100644 index 0000000000..6f546f28be --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/r_strings.gd @@ -0,0 +1,22 @@ +func test(): + print(r"test ' \' \" \\ \n \t \u2023 test") + print(r"\n\\[\t ]*(\w+)") + print(r"") + print(r"\"") + print(r"\\\"") + print(r"\\") + print(r"\" \\\" \\\\\"") + print(r"\ \\ \\\ \\\\ \\\\\ \\") + print(r'"') + print(r'"(?:\\.|[^"])*"') + print(r"""""") + print(r"""test \t "test"="" " \" \\\" \ \\ \\\ test""") + print(r'''r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""''') + print(r"\t + \t") + print(r"\t \ + \t") + print(r"""\t + \t""") + print(r"""\t \ + \t""") diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.out b/modules/gdscript/tests/scripts/parser/features/r_strings.out new file mode 100644 index 0000000000..114ef0a6c3 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/r_strings.out @@ -0,0 +1,22 @@ +GDTEST_OK +test ' \' \" \\ \n \t \u2023 test +\n\\[\t ]*(\w+) + +\" +\\\" +\\ +\" \\\" \\\\\" +\ \\ \\\ \\\\ \\\\\ \\ +" +"(?:\\.|[^"])*" + +test \t "test"="" " \" \\\" \ \\ \\\ test +r"""test \t "test"="" " \" \\\" \ \\ \\\ test""" +\t + \t +\t \ + \t +\t + \t +\t \ + \t diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd index f5ae2a74a7..33accd92a9 100644 --- a/modules/gdscript/tests/scripts/parser/features/super.gd +++ b/modules/gdscript/tests/scripts/parser/features/super.gd @@ -36,6 +36,7 @@ class SayNothing extends Say: print("howdy, see above") func say(name): + @warning_ignore("unsafe_call_argument") super(name + " super'd") print(prefix, " say nothing... or not? ", name) diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd index 523959a016..20cc0cee2e 100644 --- a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd @@ -29,6 +29,7 @@ func test(): const d = 1.1 _process(d) + @warning_ignore("unsafe_call_argument") print(is_equal_approx(ㄥ, PI + (d * PI))) func _process(Δ: float) -> void: diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd index 58b4df5a79..bc899a3a6f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd @@ -21,6 +21,12 @@ func test(): var elem := e prints(var_to_str(e), var_to_str(elem)) + # GH-82021 + print("Test implicitly typed array literal.") + for e: float in [100, 200, 300]: + var elem := e + prints(var_to_str(e), var_to_str(elem)) + print("Test String-keys dictionary.") var d1 := {a = 1, b = 2, c = 3} for k: StringName in d1: diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out index f67f7d89d5..eeebdc4be5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out @@ -15,6 +15,10 @@ Test typed int array. 10.0 10.0 20.0 20.0 30.0 30.0 +Test implicitly typed array literal. +100.0 100.0 +200.0 200.0 +300.0 300.0 Test String-keys dictionary. &"a" &"a" &"b" &"b" diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd new file mode 100644 index 0000000000..4cb51f8512 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd @@ -0,0 +1,71 @@ +var global := 0 + +func test(): + var a = 0 + var b = 1 + + match a: + 0 when b == 0: + print("does not run" if true else "") + 0 when b == 1: + print("guards work") + _: + print("does not run") + + match a: + var a_bind when b == 0: + prints("a is", a_bind, "and b is 0") + var a_bind when b == 1: + prints("a is", a_bind, "and b is 1") + _: + print("does not run") + + match a: + var a_bind when a_bind < 0: + print("a is less than zero") + var a_bind when a_bind == 0: + print("a is equal to zero") + _: + print("a is more than zero") + + match [1, 2, 3]: + [1, 2, var element] when element == 0: + print("does not run") + [1, 2, var element] when element == 3: + print("3rd element is 3") + + match a: + _ when b == 0: + print("does not run") + _ when b == 1: + print("works with wildcard too.") + _: + print("does not run") + + match a: + 0, 1 when b == 0: + print("does not run") + 0, 1 when b == 1: + print("guard with multiple patterns") + _: + print("does not run") + + match a: + 0 when b == 0: + print("does not run") + 0: + print("regular pattern after guard mismatch") + + match a: + 1 when side_effect(): + print("should not run the side effect call") + 0 when side_effect(): + print("will run the side effect call, but not this") + _: + assert(global == 1) + print("side effect only ran once") + +func side_effect(): + print("side effect") + global += 1 + return false diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out new file mode 100644 index 0000000000..452d1ff4bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out @@ -0,0 +1,10 @@ +GDTEST_OK +guards work +a is 0 and b is 1 +a is equal to zero +3rd element is 3 +works with wildcard too. +guard with multiple patterns +regular pattern after guard mismatch +side effect +side effect only ran once diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd new file mode 100644 index 0000000000..d0cbeeab85 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd @@ -0,0 +1,45 @@ +# GH-82169 + +const Utils = preload("../../utils.notest.gd") + +class A: + static var test_static_var_a1 + static var test_static_var_a2 + var test_var_a1 + var test_var_a2 + static func test_static_func_a1(): pass + static func test_static_func_a2(): pass + func test_func_a1(): pass + func test_func_a2(): pass + signal test_signal_a1() + signal test_signal_a2() + +class B extends A: + static var test_static_var_b1 + static var test_static_var_b2 + var test_var_b1 + var test_var_b2 + static func test_static_func_b1(): pass + static func test_static_func_b2(): pass + func test_func_b1(): pass + func test_func_b2(): pass + signal test_signal_b1() + signal test_signal_b2() + +func test(): + var b := B.new() + for property in (B as GDScript).get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property, true)) + print("---") + for property in b.get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + print("---") + for method in b.get_method_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method)) + print("---") + for method in b.get_signal_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method, true)) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out new file mode 100644 index 0000000000..f294b66ef9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out @@ -0,0 +1,24 @@ +GDTEST_OK +static var test_static_var_a1: Variant +static var test_static_var_a2: Variant +static var test_static_var_b1: Variant +static var test_static_var_b2: Variant +--- +var test_var_b1: Variant +var test_var_b2: Variant +var test_var_a1: Variant +var test_var_a2: Variant +--- +static func test_static_func_b1() -> void +static func test_static_func_b2() -> void +func test_func_b1() -> void +func test_func_b2() -> void +static func test_static_func_a1() -> void +static func test_static_func_a2() -> void +func test_func_a1() -> void +func test_func_a2() -> void +--- +signal test_signal_b1() +signal test_signal_b2() +signal test_signal_a1() +signal test_signal_a2() diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd new file mode 100644 index 0000000000..b875efef56 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd @@ -0,0 +1,6 @@ +# GH-73213 + +func test(): + var object := Object.new() # Not `Object()`. + print(object.get_class()) + object.free() diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.out b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out new file mode 100644 index 0000000000..3673881576 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out @@ -0,0 +1,2 @@ +GDTEST_OK +Object diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd index 2f55059334..fd1460a48f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd @@ -12,6 +12,7 @@ func test(): print("end") func test_construct(v, f): + @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. assert(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd index 8da8bb7e53..7fa76ca4b0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd @@ -44,6 +44,7 @@ func test(): @warning_ignore("unsafe_method_access") var path = get_script().get_path().get_base_dir() + @warning_ignore("unsafe_call_argument") var other = load(path + "/static_variables_load.gd") prints("load.perm:", other.perm) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd index fead2df854..0dbb252b0e 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd @@ -24,7 +24,7 @@ func test(): print(StringName("hello")) print(NodePath("hello/world")) var node := Node.new() - print(RID(node)) + print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented? print(node.get_name) print(node.property_list_changed) node.free() diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 50444e62a1..781843b8e2 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -17,7 +17,8 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String: TYPE_OBJECT: if not str(property.class_name).is_empty(): return property.class_name - return variant_get_type_name(property.type) + return type_string(property.type) + static func get_property_signature(property: Dictionary, is_static: bool = false) -> String: var result: String = "" @@ -30,6 +31,15 @@ static func get_property_signature(property: Dictionary, is_static: bool = false result += "var " + property.name + ": " + get_type(property) return result + +static func get_property_additional_info(property: Dictionary) -> String: + return 'hint=%s hint_string="%s" usage=%s' % [ + get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"), + str(property.hint_string).c_escape(), + get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""), + ] + + static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String: var result: String = "" if method.flags & METHOD_FLAG_STATIC: @@ -55,83 +65,135 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) -> result += " -> " + get_type(method.return, true) return result -static func variant_get_type_name(type: Variant.Type) -> String: - match type: - TYPE_NIL: - return "Nil" # `Nil` in core, `null` in GDScript. - TYPE_BOOL: - return "bool" - TYPE_INT: - return "int" - TYPE_FLOAT: - return "float" - TYPE_STRING: - return "String" - TYPE_VECTOR2: - return "Vector2" - TYPE_VECTOR2I: - return "Vector2i" - TYPE_RECT2: - return "Rect2" - TYPE_RECT2I: - return "Rect2i" - TYPE_VECTOR3: - return "Vector3" - TYPE_VECTOR3I: - return "Vector3i" - TYPE_TRANSFORM2D: - return "Transform2D" - TYPE_VECTOR4: - return "Vector4" - TYPE_VECTOR4I: - return "Vector4i" - TYPE_PLANE: - return "Plane" - TYPE_QUATERNION: - return "Quaternion" - TYPE_AABB: - return "AABB" - TYPE_BASIS: - return "Basis" - TYPE_TRANSFORM3D: - return "Transform3D" - TYPE_PROJECTION: - return "Projection" - TYPE_COLOR: - return "Color" - TYPE_STRING_NAME: - return "StringName" - TYPE_NODE_PATH: - return "NodePath" - TYPE_RID: - return "RID" - TYPE_OBJECT: - return "Object" - TYPE_CALLABLE: - return "Callable" - TYPE_SIGNAL: - return "Signal" - TYPE_DICTIONARY: - return "Dictionary" - TYPE_ARRAY: - return "Array" - TYPE_PACKED_BYTE_ARRAY: - return "PackedByteArray" - TYPE_PACKED_INT32_ARRAY: - return "PackedInt32Array" - TYPE_PACKED_INT64_ARRAY: - return "PackedInt64Array" - TYPE_PACKED_FLOAT32_ARRAY: - return "PackedFloat32Array" - TYPE_PACKED_FLOAT64_ARRAY: - return "PackedFloat64Array" - TYPE_PACKED_STRING_ARRAY: - return "PackedStringArray" - TYPE_PACKED_VECTOR2_ARRAY: - return "PackedVector2Array" - TYPE_PACKED_VECTOR3_ARRAY: - return "PackedVector3Array" - TYPE_PACKED_COLOR_ARRAY: - return "PackedColorArray" - push_error("Argument `type` is invalid. Use `TYPE_*` constants.") - return "<invalid type>" + +static func get_property_hint_name(hint: PropertyHint) -> String: + match hint: + PROPERTY_HINT_NONE: + return "PROPERTY_HINT_NONE" + PROPERTY_HINT_RANGE: + return "PROPERTY_HINT_RANGE" + PROPERTY_HINT_ENUM: + return "PROPERTY_HINT_ENUM" + PROPERTY_HINT_ENUM_SUGGESTION: + return "PROPERTY_HINT_ENUM_SUGGESTION" + PROPERTY_HINT_EXP_EASING: + return "PROPERTY_HINT_EXP_EASING" + PROPERTY_HINT_LINK: + return "PROPERTY_HINT_LINK" + PROPERTY_HINT_FLAGS: + return "PROPERTY_HINT_FLAGS" + PROPERTY_HINT_LAYERS_2D_RENDER: + return "PROPERTY_HINT_LAYERS_2D_RENDER" + PROPERTY_HINT_LAYERS_2D_PHYSICS: + return "PROPERTY_HINT_LAYERS_2D_PHYSICS" + PROPERTY_HINT_LAYERS_2D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_2D_NAVIGATION" + PROPERTY_HINT_LAYERS_3D_RENDER: + return "PROPERTY_HINT_LAYERS_3D_RENDER" + PROPERTY_HINT_LAYERS_3D_PHYSICS: + return "PROPERTY_HINT_LAYERS_3D_PHYSICS" + PROPERTY_HINT_LAYERS_3D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_3D_NAVIGATION" + PROPERTY_HINT_LAYERS_AVOIDANCE: + return "PROPERTY_HINT_LAYERS_AVOIDANCE" + PROPERTY_HINT_FILE: + return "PROPERTY_HINT_FILE" + PROPERTY_HINT_DIR: + return "PROPERTY_HINT_DIR" + PROPERTY_HINT_GLOBAL_FILE: + return "PROPERTY_HINT_GLOBAL_FILE" + PROPERTY_HINT_GLOBAL_DIR: + return "PROPERTY_HINT_GLOBAL_DIR" + PROPERTY_HINT_RESOURCE_TYPE: + return "PROPERTY_HINT_RESOURCE_TYPE" + PROPERTY_HINT_MULTILINE_TEXT: + return "PROPERTY_HINT_MULTILINE_TEXT" + PROPERTY_HINT_EXPRESSION: + return "PROPERTY_HINT_EXPRESSION" + PROPERTY_HINT_PLACEHOLDER_TEXT: + return "PROPERTY_HINT_PLACEHOLDER_TEXT" + PROPERTY_HINT_COLOR_NO_ALPHA: + return "PROPERTY_HINT_COLOR_NO_ALPHA" + PROPERTY_HINT_OBJECT_ID: + return "PROPERTY_HINT_OBJECT_ID" + PROPERTY_HINT_TYPE_STRING: + return "PROPERTY_HINT_TYPE_STRING" + PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: + return "PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" + PROPERTY_HINT_OBJECT_TOO_BIG: + return "PROPERTY_HINT_OBJECT_TOO_BIG" + PROPERTY_HINT_NODE_PATH_VALID_TYPES: + return "PROPERTY_HINT_NODE_PATH_VALID_TYPES" + PROPERTY_HINT_SAVE_FILE: + return "PROPERTY_HINT_SAVE_FILE" + PROPERTY_HINT_GLOBAL_SAVE_FILE: + return "PROPERTY_HINT_GLOBAL_SAVE_FILE" + PROPERTY_HINT_INT_IS_OBJECTID: + return "PROPERTY_HINT_INT_IS_OBJECTID" + PROPERTY_HINT_INT_IS_POINTER: + return "PROPERTY_HINT_INT_IS_POINTER" + PROPERTY_HINT_ARRAY_TYPE: + return "PROPERTY_HINT_ARRAY_TYPE" + PROPERTY_HINT_LOCALE_ID: + return "PROPERTY_HINT_LOCALE_ID" + PROPERTY_HINT_LOCALIZABLE_STRING: + return "PROPERTY_HINT_LOCALIZABLE_STRING" + PROPERTY_HINT_NODE_TYPE: + return "PROPERTY_HINT_NODE_TYPE" + PROPERTY_HINT_HIDE_QUATERNION_EDIT: + return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" + PROPERTY_HINT_PASSWORD: + return "PROPERTY_HINT_PASSWORD" + push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") + return "<invalid hint>" + + +static func get_property_usage_string(usage: int) -> String: + if usage == PROPERTY_USAGE_NONE: + return "PROPERTY_USAGE_NONE" + + const FLAGS: Array[Array] = [ + [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"], + [PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"], + [PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"], + [PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"], + [PROPERTY_USAGE_CHECKABLE, "PROPERTY_USAGE_CHECKABLE"], + [PROPERTY_USAGE_CHECKED, "PROPERTY_USAGE_CHECKED"], + [PROPERTY_USAGE_GROUP, "PROPERTY_USAGE_GROUP"], + [PROPERTY_USAGE_CATEGORY, "PROPERTY_USAGE_CATEGORY"], + [PROPERTY_USAGE_SUBGROUP, "PROPERTY_USAGE_SUBGROUP"], + [PROPERTY_USAGE_CLASS_IS_BITFIELD, "PROPERTY_USAGE_CLASS_IS_BITFIELD"], + [PROPERTY_USAGE_NO_INSTANCE_STATE, "PROPERTY_USAGE_NO_INSTANCE_STATE"], + [PROPERTY_USAGE_RESTART_IF_CHANGED, "PROPERTY_USAGE_RESTART_IF_CHANGED"], + [PROPERTY_USAGE_SCRIPT_VARIABLE, "PROPERTY_USAGE_SCRIPT_VARIABLE"], + [PROPERTY_USAGE_STORE_IF_NULL, "PROPERTY_USAGE_STORE_IF_NULL"], + [PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED"], + [PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE, "PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE"], + [PROPERTY_USAGE_CLASS_IS_ENUM, "PROPERTY_USAGE_CLASS_IS_ENUM"], + [PROPERTY_USAGE_NIL_IS_VARIANT, "PROPERTY_USAGE_NIL_IS_VARIANT"], + [PROPERTY_USAGE_ARRAY, "PROPERTY_USAGE_ARRAY"], + [PROPERTY_USAGE_ALWAYS_DUPLICATE, "PROPERTY_USAGE_ALWAYS_DUPLICATE"], + [PROPERTY_USAGE_NEVER_DUPLICATE, "PROPERTY_USAGE_NEVER_DUPLICATE"], + [PROPERTY_USAGE_HIGH_END_GFX, "PROPERTY_USAGE_HIGH_END_GFX"], + [PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT, "PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT"], + [PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT, "PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT"], + [PROPERTY_USAGE_KEYING_INCREMENTS, "PROPERTY_USAGE_KEYING_INCREMENTS"], + [PROPERTY_USAGE_DEFERRED_SET_RESOURCE, "PROPERTY_USAGE_DEFERRED_SET_RESOURCE"], + [PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT, "PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT"], + [PROPERTY_USAGE_EDITOR_BASIC_SETTING, "PROPERTY_USAGE_EDITOR_BASIC_SETTING"], + [PROPERTY_USAGE_READ_ONLY, "PROPERTY_USAGE_READ_ONLY"], + [PROPERTY_USAGE_SECRET, "PROPERTY_USAGE_SECRET"], + ] + + var result: String = "" + + for flag in FLAGS: + if usage & flag[0]: + result += flag[1] + "|" + usage &= ~flag[0] + + if usage != PROPERTY_USAGE_NONE: + push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") + return "<invalid usage flags>" + + return result.left(-1) diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index b86a8b3cb1..467bedc4b2 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -223,6 +223,16 @@ void test(TestType p_type) { // Initialize the language for the test routine. init_language(fa->get_path_absolute().get_base_dir()); + // Load global classes. + TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list(); + for (int i = 0; i < script_classes.size(); i++) { + Dictionary c = script_classes[i]; + if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { + continue; + } + ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]); + } + Vector<uint8_t> buf; uint64_t flen = fa->get_length(); buf.resize(flen + 1); diff --git a/modules/glslang/SCsub b/modules/glslang/SCsub index 22ef1b5ea9..a34a97d230 100644 --- a/modules/glslang/SCsub +++ b/modules/glslang/SCsub @@ -12,6 +12,8 @@ thirdparty_obj = [] if env["builtin_glslang"]: thirdparty_dir = "#thirdparty/glslang/" thirdparty_sources = [ + "glslang/GenericCodeGen/CodeGen.cpp", + "glslang/GenericCodeGen/Link.cpp", "glslang/MachineIndependent/attribute.cpp", "glslang/MachineIndependent/Constant.cpp", "glslang/MachineIndependent/glslang_tab.cpp", @@ -40,8 +42,7 @@ if env["builtin_glslang"]: "glslang/MachineIndependent/SpirvIntrinsics.cpp", "glslang/MachineIndependent/SymbolTable.cpp", "glslang/MachineIndependent/Versions.cpp", - "glslang/GenericCodeGen/CodeGen.cpp", - "glslang/GenericCodeGen/Link.cpp", + "glslang/ResourceLimits/ResourceLimits.cpp", "OGLCompilersDLL/InitializeDll.cpp", "SPIRV/disassemble.cpp", "SPIRV/doc.cpp", diff --git a/modules/glslang/glslang_resource_limits.h b/modules/glslang/glslang_resource_limits.h deleted file mode 100644 index 8340e63096..0000000000 --- a/modules/glslang/glslang_resource_limits.h +++ /dev/null @@ -1,156 +0,0 @@ -/**************************************************************************/ -/* glslang_resource_limits.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 GLSLANG_RESOURCE_LIMITS_H -#define GLSLANG_RESOURCE_LIMITS_H - -#include <glslang/Include/ResourceLimits.h> - -// Synchronized with upstream glslang/StandAlone/ResourceLimits.cpp which is not -// part of the public API. - -const TBuiltInResource DefaultTBuiltInResource = { - /* .MaxLights = */ 32, - /* .MaxClipPlanes = */ 6, - /* .MaxTextureUnits = */ 32, - /* .MaxTextureCoords = */ 32, - /* .MaxVertexAttribs = */ 64, - /* .MaxVertexUniformComponents = */ 4096, - /* .MaxVaryingFloats = */ 64, - /* .MaxVertexTextureImageUnits = */ 32, - /* .MaxCombinedTextureImageUnits = */ 80, - /* .MaxTextureImageUnits = */ 32, - /* .MaxFragmentUniformComponents = */ 4096, - /* .MaxDrawBuffers = */ 32, - /* .MaxVertexUniformVectors = */ 128, - /* .MaxVaryingVectors = */ 8, - /* .MaxFragmentUniformVectors = */ 16, - /* .MaxVertexOutputVectors = */ 16, - /* .MaxFragmentInputVectors = */ 15, - /* .MinProgramTexelOffset = */ -8, - /* .MaxProgramTexelOffset = */ 7, - /* .MaxClipDistances = */ 8, - /* .MaxComputeWorkGroupCountX = */ 65535, - /* .MaxComputeWorkGroupCountY = */ 65535, - /* .MaxComputeWorkGroupCountZ = */ 65535, - /* .MaxComputeWorkGroupSizeX = */ 1024, - /* .MaxComputeWorkGroupSizeY = */ 1024, - /* .MaxComputeWorkGroupSizeZ = */ 64, - /* .MaxComputeUniformComponents = */ 1024, - /* .MaxComputeTextureImageUnits = */ 16, - /* .MaxComputeImageUniforms = */ 8, - /* .MaxComputeAtomicCounters = */ 8, - /* .MaxComputeAtomicCounterBuffers = */ 1, - /* .MaxVaryingComponents = */ 60, - /* .MaxVertexOutputComponents = */ 64, - /* .MaxGeometryInputComponents = */ 64, - /* .MaxGeometryOutputComponents = */ 128, - /* .MaxFragmentInputComponents = */ 128, - /* .MaxImageUnits = */ 8, - /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8, - /* .MaxCombinedShaderOutputResources = */ 8, - /* .MaxImageSamples = */ 0, - /* .MaxVertexImageUniforms = */ 0, - /* .MaxTessControlImageUniforms = */ 0, - /* .MaxTessEvaluationImageUniforms = */ 0, - /* .MaxGeometryImageUniforms = */ 0, - /* .MaxFragmentImageUniforms = */ 8, - /* .MaxCombinedImageUniforms = */ 8, - /* .MaxGeometryTextureImageUnits = */ 16, - /* .MaxGeometryOutputVertices = */ 256, - /* .MaxGeometryTotalOutputComponents = */ 1024, - /* .MaxGeometryUniformComponents = */ 1024, - /* .MaxGeometryVaryingComponents = */ 64, - /* .MaxTessControlInputComponents = */ 128, - /* .MaxTessControlOutputComponents = */ 128, - /* .MaxTessControlTextureImageUnits = */ 16, - /* .MaxTessControlUniformComponents = */ 1024, - /* .MaxTessControlTotalOutputComponents = */ 4096, - /* .MaxTessEvaluationInputComponents = */ 128, - /* .MaxTessEvaluationOutputComponents = */ 128, - /* .MaxTessEvaluationTextureImageUnits = */ 16, - /* .MaxTessEvaluationUniformComponents = */ 1024, - /* .MaxTessPatchComponents = */ 120, - /* .MaxPatchVertices = */ 32, - /* .MaxTessGenLevel = */ 64, - /* .MaxViewports = */ 16, - /* .MaxVertexAtomicCounters = */ 0, - /* .MaxTessControlAtomicCounters = */ 0, - /* .MaxTessEvaluationAtomicCounters = */ 0, - /* .MaxGeometryAtomicCounters = */ 0, - /* .MaxFragmentAtomicCounters = */ 8, - /* .MaxCombinedAtomicCounters = */ 8, - /* .MaxAtomicCounterBindings = */ 1, - /* .MaxVertexAtomicCounterBuffers = */ 0, - /* .MaxTessControlAtomicCounterBuffers = */ 0, - /* .MaxTessEvaluationAtomicCounterBuffers = */ 0, - /* .MaxGeometryAtomicCounterBuffers = */ 0, - /* .MaxFragmentAtomicCounterBuffers = */ 1, - /* .MaxCombinedAtomicCounterBuffers = */ 1, - /* .MaxAtomicCounterBufferSize = */ 16384, - /* .MaxTransformFeedbackBuffers = */ 4, - /* .MaxTransformFeedbackInterleavedComponents = */ 64, - /* .MaxCullDistances = */ 8, - /* .MaxCombinedClipAndCullDistances = */ 8, - /* .MaxSamples = */ 4, - /* .maxMeshOutputVerticesNV = */ 256, - /* .maxMeshOutputPrimitivesNV = */ 512, - /* .maxMeshWorkGroupSizeX_NV = */ 32, - /* .maxMeshWorkGroupSizeY_NV = */ 1, - /* .maxMeshWorkGroupSizeZ_NV = */ 1, - /* .maxTaskWorkGroupSizeX_NV = */ 32, - /* .maxTaskWorkGroupSizeY_NV = */ 1, - /* .maxTaskWorkGroupSizeZ_NV = */ 1, - /* .maxMeshViewCountNV = */ 4, - /* .maxMeshOutputVerticesEXT = */ 256, - /* .maxMeshOutputPrimitivesEXT = */ 256, - /* .maxMeshWorkGroupSizeX_EXT = */ 128, - /* .maxMeshWorkGroupSizeY_EXT = */ 128, - /* .maxMeshWorkGroupSizeZ_EXT = */ 128, - /* .maxTaskWorkGroupSizeX_EXT = */ 128, - /* .maxTaskWorkGroupSizeY_EXT = */ 128, - /* .maxTaskWorkGroupSizeZ_EXT = */ 128, - /* .maxMeshViewCountEXT = */ 4, - /* .maxDualSourceDrawBuffersEXT = */ 1, - - /* .limits = */ { - /* .nonInductiveForLoops = */ 1, - /* .whileLoops = */ 1, - /* .doWhileLoops = */ 1, - /* .generalUniformIndexing = */ 1, - /* .generalAttributeMatrixVectorIndexing = */ 1, - /* .generalVaryingIndexing = */ 1, - /* .generalSamplerIndexing = */ 1, - /* .generalVariableIndexing = */ 1, - /* .generalConstantMatrixVectorIndexing = */ 1, - } -}; - -#endif // GLSLANG_RESOURCE_LIMITS_H diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp index 2b070c24b8..7fe3a57880 100644 --- a/modules/glslang/register_types.cpp +++ b/modules/glslang/register_types.cpp @@ -30,12 +30,11 @@ #include "register_types.h" -#include "glslang_resource_limits.h" - #include "core/config/engine.h" #include "servers/rendering/rendering_device.h" #include <glslang/Include/Types.h> +#include <glslang/Public/ResourceLimits.h> #include <glslang/Public/ShaderLang.h> #include <glslang/SPIRV/GlslangToSpv.h> @@ -133,7 +132,7 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage const int DefaultVersion = 100; //parse - if (!shader.parse(&DefaultTBuiltInResource, DefaultVersion, false, messages)) { + if (!shader.parse(GetDefaultResources(), DefaultVersion, false, messages)) { if (r_error) { (*r_error) = "Failed parse:\n"; (*r_error) += shader.getInfoLog(); diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 9b84397c7e..5c10b76e0a 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -95,5 +95,20 @@ <member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75"> If [member image_format] is a lossy image format, this determines the lossy quality of the image. On a range of [code]0.0[/code] to [code]1.0[/code], where [code]0.0[/code] is the lowest quality and [code]1.0[/code] is the highest quality. A lossy quality of [code]1.0[/code] is not the same as lossless. </member> + <member name="root_node_mode" type="int" setter="set_root_node_mode" getter="get_root_node_mode" enum="GLTFDocument.RootNodeMode" default="0"> + How to process the root node during export. See [enum RootNodeMode] for details. The default and recommended value is [constant ROOT_NODE_MODE_SINGLE_ROOT]. + [b]Note:[/b] Regardless of how the glTF file is exported, when importing, the root node type and name can be overridden in the scene import settings tab. + </member> </members> + <constants> + <constant name="ROOT_NODE_MODE_SINGLE_ROOT" value="0" enum="RootNodeMode"> + Treat the Godot scene's root node as the root node of the glTF file, and mark it as the single root node via the [code]GODOT_single_root[/code] glTF extension. This will be parsed the same as [constant ROOT_NODE_MODE_KEEP_ROOT] if the implementation does not support [code]GODOT_single_root[/code]. + </constant> + <constant name="ROOT_NODE_MODE_KEEP_ROOT" value="1" enum="RootNodeMode"> + Treat the Godot scene's root node as the root node of the glTF file, but do not mark it as anything special. An extra root node will be generated when importing into Godot. This uses only vanilla glTF features. This is equivalent to the behavior in Godot 4.1 and earlier. + </constant> + <constant name="ROOT_NODE_MODE_MULTI_ROOT" value="2" enum="RootNodeMode"> + Treat the Godot scene's root node as the name of the glTF scene, and add all of its children as root nodes of the glTF file. This uses only vanilla glTF features. This avoids an extra root node, but only the name of the Godot scene's root node will be preserved, as it will not be saved as a node. + </constant> + </constants> </class> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index dbfbf63da6..eee62845ca 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -65,6 +65,7 @@ <description> Part of the import process. This method is run after [method _import_post_parse] and before [method _import_node]. 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. + [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node. </description> </method> <method name="_get_image_file_extension" qualifiers="virtual"> diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 3787d0fe5e..cb45a6589e 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -49,6 +49,67 @@ #include <shlwapi.h> #endif +static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) { + String path = p_path; +#ifdef WINDOWS_ENABLED + path = path.path_join("blender.exe"); +#else + path = path.path_join("blender"); +#endif + +#if defined(MACOS_ENABLED) + if (!FileAccess::exists(path)) { + path = p_path.path_join("Blender"); + } +#endif + + if (!FileAccess::exists(path)) { + if (r_err) { + *r_err = TTR("Path does not contain a Blender installation."); + } + return false; + } + List<String> args; + args.push_back("--version"); + String pipe; + Error err = OS::get_singleton()->execute(path, args, &pipe); + if (err != OK) { + if (r_err) { + *r_err = TTR("Can't execute Blender binary."); + } + return false; + } + int bl = pipe.find("Blender "); + if (bl == -1) { + if (r_err) { + *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), path); + } + return false; + } + pipe = pipe.substr(bl); + pipe = pipe.replace_first("Blender ", ""); + int pp = pipe.find("."); + if (pp == -1) { + if (r_err) { + *r_err = TTR("Path supplied lacks a Blender binary."); + } + return false; + } + String v = pipe.substr(0, pp); + r_major = v.to_int(); + if (r_major < 3) { + if (r_err) { + *r_err = TTR("This Blender installation is too old for this importer (not 3.0+)."); + } + return false; + } + + int pp2 = pipe.find(".", pp + 1); + r_minor = pp2 > pp ? pipe.substr(pp + 1, pp2 - pp - 1).to_int() : 0; + + return true; +} + uint32_t EditorSceneFormatImporterBlend::get_import_flags() const { return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; } @@ -60,8 +121,13 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { - // Get global paths for source and sink. + String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); + if (blender_major_version == -1 || blender_minor_version == -1) { + _get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr); + } + + // Get global paths for source and sink. // Escape paths to be valid Python strings to embed in the script. const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape(); const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( @@ -153,9 +219,17 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ parameters_map["export_tangents"] = false; } if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) { - parameters_map["export_nla_strips"] = true; + if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) { + parameters_map["export_animation_mode"] = "ACTIONS"; + } else { + parameters_map["export_nla_strips"] = true; + } } else { - parameters_map["export_nla_strips"] = false; + if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) { + parameters_map["export_animation_mode"] = "ACTIVE_ACTIONS"; + } else { + parameters_map["export_nla_strips"] = false; + } } if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) { parameters_map["export_frame_range"] = true; @@ -269,67 +343,8 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li /////////////////////////// static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { - String path = p_path; -#ifdef WINDOWS_ENABLED - path = path.path_join("blender.exe"); -#else - path = path.path_join("blender"); -#endif - -#if defined(MACOS_ENABLED) - if (!FileAccess::exists(path)) { - path = path.path_join("Blender"); - } -#endif - - if (!FileAccess::exists(path)) { - if (r_err) { - *r_err = TTR("Path does not contain a Blender installation."); - } - return false; - } - List<String> args; - args.push_back("--version"); - String pipe; - Error err = OS::get_singleton()->execute(path, args, &pipe); - if (err != OK) { - if (r_err) { - *r_err = TTR("Can't execute Blender binary."); - } - return false; - } - int bl = pipe.find("Blender "); - if (bl == -1) { - if (r_err) { - *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s"), path); - } - return false; - } - pipe = pipe.substr(bl); - pipe = pipe.replace_first("Blender ", ""); - int pp = pipe.find("."); - if (pp == -1) { - if (r_err) { - *r_err = TTR("Path supplied lacks a Blender binary."); - } - return false; - } - String v = pipe.substr(0, pp); - int version = v.to_int(); - if (version < 3) { - if (r_err) { - *r_err = TTR("This Blender installation is too old for this importer (not 3.0+)."); - } - return false; - } - if (version > 3) { - if (r_err) { - *r_err = TTR("This Blender installation is too new for this importer (not 3.x)."); - } - return false; - } - - return true; + int major, minor; + return _get_blender_version(p_path, major, minor, r_err); } bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const { diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index c77a23f9f6..ec467db457 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -43,6 +43,9 @@ class ConfirmationDialog; class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterBlend, EditorSceneFormatImporter); + int blender_major_version = -1; + int blender_minor_version = -1; + public: enum { BLEND_VISIBLE_ALL, diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index e16e366692..582bcf466b 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -101,7 +101,6 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { ERR_FAIL_NULL_V(p_state, nullptr); ERR_FAIL_NULL_V(p_gltf_node, nullptr); - ERR_FAIL_NULL_V(p_scene_parent, nullptr); Node3D *ret_node = nullptr; GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node); return ret_node; diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index c60b522168..435a1260ba 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -111,7 +111,7 @@ void GLTFLight::set_outer_cone_angle(float p_outer_cone_angle) { Ref<GLTFLight> GLTFLight::from_node(const Light3D *p_light) { Ref<GLTFLight> l; l.instantiate(); - ERR_FAIL_COND_V_MSG(!p_light, l, "Tried to create a GLTFLight from a Light3D node, but the given node was null."); + ERR_FAIL_NULL_V_MSG(p_light, l, "Tried to create a GLTFLight from a Light3D node, but the given node was null."); l->color = p_light->get_color(); if (cast_to<DirectionalLight3D>(p_light)) { l->light_type = "directional"; diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp index 49a6edd2e3..b80f4348c2 100644 --- a/modules/gltf/extensions/physics/gltf_physics_body.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -112,7 +112,7 @@ void GLTFPhysicsBody::set_inertia_tensor(Basis p_inertia_tensor) { Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) { Ref<GLTFPhysicsBody> physics_body; physics_body.instantiate(); - ERR_FAIL_COND_V_MSG(!p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null."); + ERR_FAIL_NULL_V_MSG(p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null."); if (cast_to<CharacterBody3D>(p_body_node)) { physics_body->body_type = "character"; } else if (cast_to<AnimatableBody3D>(p_body_node)) { diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index e98b83359d..bac988630d 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -3051,6 +3051,8 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { 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); + ERR_FAIL_COND_V(da.is_null(), FAILED); + if (!da->dir_exists(full_texture_dir)) { da->make_dir(full_texture_dir); } @@ -3877,7 +3879,6 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } else { material->set_name(vformat("material_%s", itos(i))); } - material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); Dictionary material_extensions; if (material_dict.has("extensions")) { material_extensions = material_dict["extensions"]; @@ -5593,7 +5594,7 @@ void GLTFDocument::_create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_paren } void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { - ERR_FAIL_COND(!p_animation_player); + ERR_FAIL_NULL(p_animation_player); p_state->animation_players.push_back(p_animation_player); print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name()); } @@ -5612,7 +5613,7 @@ void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) { } void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) { - ERR_FAIL_COND(!camera); + ERR_FAIL_NULL(camera); GLTFCameraIndex camera_index = _convert_camera(p_state, camera); if (camera_index != -1) { p_gltf_node->camera = camera_index; @@ -5620,7 +5621,7 @@ void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_st } void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) { - ERR_FAIL_COND(!light); + ERR_FAIL_NULL(light); GLTFLightIndex light_index = _convert_light(p_state, light); if (light_index != -1) { p_gltf_node->light = light_index; @@ -5662,7 +5663,7 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf( GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) { - ERR_FAIL_COND(!p_multi_mesh_instance); + ERR_FAIL_NULL(p_multi_mesh_instance); Ref<MultiMesh> multi_mesh = p_multi_mesh_instance->get_multimesh(); if (multi_mesh.is_null()) { return; @@ -5882,14 +5883,18 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn if (!gltf_node_name.is_empty()) { current_node->set_name(gltf_node_name); } - // Add the node we generated and set the owner to the scene root. - p_scene_parent->add_child(current_node, true); - if (current_node != p_scene_root) { + // Note: p_scene_parent and p_scene_root must either both be null or both be valid. + if (p_scene_root == nullptr) { + // If the root node argument is null, this is the root node. + p_scene_root = current_node; + } else { + // Add the node we generated and set the owner to the scene root. + p_scene_parent->add_child(current_node, true); Array args; args.append(p_scene_root); current_node->propagate_call(StringName("set_owner"), args); + current_node->set_transform(gltf_node->xform); } - current_node->set_transform(gltf_node->xform); p_state->scene_nodes.insert(p_node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { @@ -6140,7 +6145,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key]; Node *root = p_animation_player->get_parent(); - ERR_FAIL_COND(root == nullptr); + ERR_FAIL_NULL(root); HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index); ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index)); node_path = root->get_path_to(node_element->value); @@ -6153,7 +6158,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ if (gltf_node->skeleton >= 0) { const Skeleton3D *sk = p_state->skeletons[gltf_node->skeleton]->godot_skeleton; - ERR_FAIL_COND(sk == nullptr); + ERR_FAIL_NULL(sk); const String path = p_animation_player->get_parent()->get_path_to(sk); const String bone = gltf_node->get_name(); @@ -7218,13 +7223,20 @@ void GLTFDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"), &GLTFDocument::write_to_filesystem); + BIND_ENUM_CONSTANT(ROOT_NODE_MODE_SINGLE_ROOT); + BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT); + BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT); + ClassDB::bind_method(D_METHOD("set_image_format", "image_format"), &GLTFDocument::set_image_format); ClassDB::bind_method(D_METHOD("get_image_format"), &GLTFDocument::get_image_format); ClassDB::bind_method(D_METHOD("set_lossy_quality", "lossy_quality"), &GLTFDocument::set_lossy_quality); ClassDB::bind_method(D_METHOD("get_lossy_quality"), &GLTFDocument::get_lossy_quality); + ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode); + ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode); ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_format"), "set_image_format", "get_image_format"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode"); ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"), &GLTFDocument::register_gltf_document_extension, DEFVAL(false)); @@ -7335,9 +7347,21 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_ } Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { - Node *single_root = memnew(Node3D); - for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { - _generate_scene_node(p_state, p_state->root_nodes[root_i], single_root, single_root); + // Generate the skeletons and skins (if any). + Error err = _create_skeletons(p_state); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skeletons."); + err = _create_skins(p_state); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skins."); + // Generate the node tree. + Node *single_root; + if (p_state->extensions_used.has("GODOT_single_root")) { + _generate_scene_node(p_state, 0, nullptr, nullptr); + single_root = p_state->scene_nodes[0]; + } else { + single_root = memnew(Node3D); + for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { + _generate_scene_node(p_state, p_state->root_nodes[root_i], single_root, single_root); + } } // Assign the scene name and single root name to each other // if one is missing, or do nothing if both are already set. @@ -7405,6 +7429,19 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint } } // Add the root node(s) and their descendants to the state. + if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_MULTI_ROOT) { + const int child_count = p_node->get_child_count(); + if (child_count > 0) { + for (int i = 0; i < child_count; i++) { + _convert_scene_node(p_state, p_node->get_child(i), -1, -1); + } + p_state->scene_name = p_node->get_name(); + return OK; + } + } + if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) { + p_state->extensions_used.append("GODOT_single_root"); + } _convert_scene_node(p_state, p_node, -1, -1); return OK; } @@ -7508,14 +7545,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se err = _determine_skeletons(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - /* CREATE SKELETONS */ - err = _create_skeletons(p_state); - ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - - /* CREATE SKINS */ - err = _create_skins(p_state); - ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - /* PARSE MESHES (we have enough info now) */ err = _parse_meshes(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); @@ -7597,3 +7626,11 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { } return ret; } + +void GLTFDocument::set_root_node_mode(GLTFDocument::RootNodeMode p_root_node_mode) { + _root_node_mode = p_root_node_mode; +} + +GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const { + return _root_node_mode; +} diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index febe04b55f..828d650cff 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -40,12 +40,6 @@ class GLTFDocument : public Resource { static Vector<Ref<GLTFDocumentExtension>> all_document_extensions; Vector<Ref<GLTFDocumentExtension>> document_extensions; -private: - const float BAKE_FPS = 30.0f; - String _image_format = "PNG"; - float _lossy_quality = 0.75f; - Ref<GLTFDocumentExtension> _image_save_extension; - public: const int32_t JOINT_GROUP_SIZE = 4; @@ -71,6 +65,18 @@ public: TEXTURE_TYPE_GENERIC = 0, TEXTURE_TYPE_NORMAL = 1, }; + enum RootNodeMode { + ROOT_NODE_MODE_SINGLE_ROOT, + ROOT_NODE_MODE_KEEP_ROOT, + ROOT_NODE_MODE_MULTI_ROOT, + }; + +private: + const float BAKE_FPS = 30.0f; + String _image_format = "PNG"; + float _lossy_quality = 0.75f; + Ref<GLTFDocumentExtension> _image_save_extension; + RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT; protected: static void _bind_methods(); @@ -84,6 +90,8 @@ public: String get_image_format() const; void set_lossy_quality(float p_lossy_quality); float get_lossy_quality() const; + void set_root_node_mode(RootNodeMode p_root_node_mode); + RootNodeMode get_root_node_mode() const; private: void _build_parent_hierachy(Ref<GLTFState> p_state); @@ -379,4 +387,6 @@ public: Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file); }; +VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode); + #endif // GLTF_DOCUMENT_H diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index 630b34c270..d56f67a092 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -60,7 +60,7 @@ void GLTFCamera::_bind_methods() { Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { Ref<GLTFCamera> c; c.instantiate(); - ERR_FAIL_COND_V_MSG(!p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null."); + ERR_FAIL_NULL_V_MSG(p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null."); c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. c->set_fov(Math::deg_to_rad(p_camera->get_fov())); diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 6a20768583..f7c01ff840 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -1291,14 +1291,14 @@ GridMapEditor::GridMapEditor() { search_box->connect("gui_input", callable_mp(this, &GridMapEditor::_sbox_input)); mode_thumbnail = memnew(Button); - mode_thumbnail->set_flat(true); + mode_thumbnail->set_theme_type_variation("FlatButton"); mode_thumbnail->set_toggle_mode(true); mode_thumbnail->set_pressed(true); hb->add_child(mode_thumbnail); mode_thumbnail->connect("pressed", callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_THUMBNAIL)); mode_list = memnew(Button); - mode_list->set_flat(true); + mode_list->set_theme_type_variation("FlatButton"); mode_list->set_toggle_mode(true); mode_list->set_pressed(false); hb->add_child(mode_list); @@ -1412,6 +1412,7 @@ GridMapEditor::GridMapEditor() { inner_mat.instantiate(); inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); d[RS::ARRAY_VERTEX] = triangles; @@ -1424,11 +1425,13 @@ GridMapEditor::GridMapEditor() { outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); selection_floor_mat.instantiate(); selection_floor_mat->set_albedo(Color(0.80, 0.80, 1.0, 1)); selection_floor_mat->set_on_top_of_alpha(); selection_floor_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_floor_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); d[RS::ARRAY_VERTEX] = lines; RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_LINES, d); @@ -1457,6 +1460,7 @@ GridMapEditor::GridMapEditor() { indicator_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + indicator_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); indicator_mat->set_albedo(Color(0.8, 0.5, 0.1)); } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 4ac143c7ff..6f493f48e3 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -267,6 +267,7 @@ void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) { } _recreate_octant_data(); + emit_signal(CoreStringNames::get_singleton()->changed); } Ref<MeshLibrary> GridMap::get_mesh_library() const { diff --git a/modules/lightmapper_rd/config.py b/modules/lightmapper_rd/config.py index d22f9454ed..ecc61c2d7e 100644 --- a/modules/lightmapper_rd/config.py +++ b/modules/lightmapper_rd/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return True + return env.editor_build and platform not in ["android", "ios"] def configure(env): diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 748e8ae50c..556b0b4374 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -589,8 +589,12 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int raster_push_constant.grid_size[0] = grid_size; raster_push_constant.grid_size[1] = grid_size; raster_push_constant.grid_size[2] = grid_size; - raster_push_constant.uv_offset[0] = 0; - raster_push_constant.uv_offset[1] = 0; + + // Half pixel offset is required so the rasterizer doesn't output face edges directly aligned into pixels. + // This fixes artifacts where the pixel would be traced from the edge of a face, causing half the rays to + // be outside of the boundaries of the geometry. See <https://github.com/godotengine/godot/issues/69126>. + raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x); + raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y); RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); //draw opaque @@ -610,24 +614,28 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int } } -LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) { +static Vector<RD::Uniform> dilate_or_denoise_common_uniforms(RID &p_source_light_tex, RID &p_dest_light_tex) { Vector<RD::Uniform> uniforms; { - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 0; - u.append_id(dest_light_tex); - uniforms.push_back(u); - } - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 1; - u.append_id(source_light_tex); - uniforms.push_back(u); - } + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 0; + u.append_id(p_dest_light_tex); + uniforms.push_back(u); } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + u.append_id(p_source_light_tex); + uniforms.push_back(u); + } + + return uniforms; +} + +LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) { + Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(source_light_tex, dest_light_tex); RID compute_shader_dilate = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("dilate")); ERR_FAIL_COND_V(compute_shader_dilate.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen @@ -663,7 +671,77 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade return BAKE_OK; } -LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { +LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function) { + RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams)); + DenoiseParams denoise_params; + denoise_params.spatial_bandwidth = 5.0f; + denoise_params.light_bandwidth = p_denoiser_strength; + denoise_params.albedo_bandwidth = 1.0f; + denoise_params.normal_bandwidth = 0.1f; + denoise_params.filter_strength = 10.0f; + p_rd->buffer_update(denoise_params_buffer, 0, sizeof(DenoiseParams), &denoise_params); + + Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(p_source_light_tex, p_dest_light_tex); + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 2; + u.append_id(p_source_normal_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; + u.binding = 3; + u.append_id(denoise_params_buffer); + uniforms.push_back(u); + } + + RID compute_shader_denoise = p_rd->shader_create_from_spirv(p_compute_shader->get_spirv_stages("denoise")); + ERR_FAIL_COND_V(compute_shader_denoise.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + + RID compute_shader_denoise_pipeline = p_rd->compute_pipeline_create(compute_shader_denoise); + RID denoise_uniform_set = p_rd->uniform_set_create(uniforms, compute_shader_denoise, 1); + + // We denoise in fixed size regions and synchronize execution to avoid GPU timeouts. + // We use a region with 1/4 the amount of pixels if we're denoising SH lightmaps, as + // all four of them are denoised in the shader in one dispatch. + const int max_region_size = p_bake_sh ? 512 : 1024; + int x_regions = (p_atlas_size.width - 1) / max_region_size + 1; + int y_regions = (p_atlas_size.height - 1) / max_region_size + 1; + for (int s = 0; s < p_atlas_slices; s++) { + p_push_constant.atlas_slice = s; + + for (int i = 0; i < x_regions; i++) { + for (int j = 0; j < y_regions; j++) { + int x = i * max_region_size; + int y = j * max_region_size; + int w = MIN((i + 1) * max_region_size, p_atlas_size.width) - x; + int h = MIN((j + 1) * max_region_size, p_atlas_size.height) - y; + p_push_constant.region_ofs[0] = x; + p_push_constant.region_ofs[1] = y; + + RD::ComputeListID compute_list = p_rd->compute_list_begin(); + p_rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_denoise_pipeline); + p_rd->compute_list_bind_uniform_set(compute_list, p_compute_base_uniform_set, 0); + p_rd->compute_list_bind_uniform_set(compute_list, denoise_uniform_set, 1); + p_rd->compute_list_set_push_constant(compute_list, &p_push_constant, sizeof(PushConstant)); + p_rd->compute_list_dispatch(compute_list, (w - 1) / 8 + 1, (h - 1) / 8 + 1, 1); + p_rd->compute_list_end(); + + p_rd->submit(); + p_rd->sync(); + } + } + } + + p_rd->free(compute_shader_denoise); + p_rd->free(denoise_params_buffer); + + return BAKE_OK; +} + +LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { if (p_step_function) { p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true); } @@ -1415,14 +1493,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } #endif - { - SWAP(light_accum_tex, light_accum_tex2); - BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1)); - if (unlikely(error != BAKE_OK)) { - return error; - } - } - /* DENOISE */ if (p_use_denoiser) { @@ -1430,39 +1500,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d p_step_function(0.8, RTR("Denoising"), p_bake_userdata, true); } - Ref<LightmapDenoiser> denoiser = LightmapDenoiser::create(); - if (denoiser.is_valid()) { - for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { - Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); - Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); - - Ref<Image> denoised = denoiser->denoise_image(img); - if (denoised != img) { - denoised->convert(Image::FORMAT_RGBAH); - Vector<uint8_t> ds = denoised->get_data(); - denoised.unref(); //avoid copy on write - { //restore alpha - uint32_t count = s.size() / 2; //uint16s - const uint16_t *src = (const uint16_t *)s.ptr(); - uint16_t *dst = (uint16_t *)ds.ptrw(); - for (uint32_t j = 0; j < count; j += 4) { - dst[j + 3] = src[j + 3]; - } - } - rd->texture_update(light_accum_tex, i, ds); - } - } - } - { SWAP(light_accum_tex, light_accum_tex2); - BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1)); + BakeError error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, atlas_size, atlas_slices, p_bake_sh, p_step_function); if (unlikely(error != BAKE_OK)) { return error; } } } + { + SWAP(light_accum_tex, light_accum_tex2); + BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1)); + if (unlikely(error != BAKE_OK)) { + return error; + } + } + #ifdef DEBUG_TEXTURES for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { @@ -1579,8 +1633,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d { seams_push_constant.base_index = seam_offset; rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline); - seams_push_constant.uv_offset[0] = uv_offsets[0].x / float(atlas_size.width); - seams_push_constant.uv_offset[1] = uv_offsets[0].y / float(atlas_size.height); + seams_push_constant.uv_offset[0] = (uv_offsets[0].x - 0.5f) / float(atlas_size.width); + seams_push_constant.uv_offset[1] = (uv_offsets[0].y - 0.5f) / float(atlas_size.height); seams_push_constant.blend = uv_offsets[0].z; rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant)); @@ -1603,8 +1657,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d for (int j = 1; j < uv_offset_count; j++) { seams_push_constant.base_index = seam_offset; - seams_push_constant.uv_offset[0] = uv_offsets[j].x / float(atlas_size.width); - seams_push_constant.uv_offset[1] = uv_offsets[j].y / float(atlas_size.height); + seams_push_constant.uv_offset[0] = (uv_offsets[j].x - 0.5f) / float(atlas_size.width); + seams_push_constant.uv_offset[1] = (uv_offsets[j].y - 0.5f) / float(atlas_size.height); seams_push_constant.blend = uv_offsets[0].z; rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant)); diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 061c9ba000..7120a21b84 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -229,11 +229,22 @@ class LightmapperRD : public Lightmapper { Vector<Ref<Image>> bake_textures; Vector<Color> probe_values; + struct DenoiseParams { + float spatial_bandwidth; + float light_bandwidth; + float albedo_bandwidth; + float normal_bandwidth; + + float filter_strength; + float pad[3]; + }; + BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); + BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function); public: virtual void add_mesh(const MeshData &p_mesh) override; @@ -241,7 +252,7 @@ public: virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override; virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override; virtual void add_probe(const Vector3 &p_position) override; - virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; int get_bake_texture_count() const override; Ref<Image> get_bake_texture(int p_index) const override; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index c2557dfed3..ce33f2ed1d 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -5,6 +5,7 @@ secondary = "#define MODE_BOUNCE_LIGHT"; dilate = "#define MODE_DILATE"; unocclude = "#define MODE_UNOCCLUDE"; light_probes = "#define MODE_LIGHT_PROBES"; +denoise = "#define MODE_DENOISE"; #[compute] @@ -65,11 +66,24 @@ layout(set = 1, binding = 6) uniform texture2D environment; layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic; #endif -#ifdef MODE_DILATE +#if defined(MODE_DILATE) || defined(MODE_DENOISE) layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light; layout(set = 1, binding = 1) uniform texture2DArray source_light; #endif +#ifdef MODE_DENOISE +layout(set = 1, binding = 2) uniform texture2DArray source_normal; +layout(set = 1, binding = 3) uniform DenoiseParams { + float spatial_bandwidth; + float light_bandwidth; + float albedo_bandwidth; + float normal_bandwidth; + + float filter_strength; +} +denoise_params; +#endif + layout(push_constant, std430) uniform Params { ivec2 atlas_size; // x used for light probe mode total probes uint ray_count; @@ -415,7 +429,7 @@ void main() { ); for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * (1.0 / 3.0); + sh_accum[j].rgb += light * c[j] * 8.0; } #endif @@ -735,4 +749,153 @@ void main() { imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), c); #endif + +#ifdef MODE_DENOISE + // Joint Non-local means (JNLM) denoiser. + // + // Based on YoctoImageDenoiser's JNLM implementation with corrections from "Nonlinearly Weighted First-order Regression for Denoising Monte Carlo Renderings". + // + // <https://github.com/ManuelPrandini/YoctoImageDenoiser/blob/06e19489dd64e47792acffde536393802ba48607/libs/yocto_extension/yocto_extension.cpp#L207> + // <https://benedikt-bitterli.me/nfor/nfor.pdf> + // + // MIT License + // + // Copyright (c) 2020 ManuelPrandini + // + // 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. + // + // Most of the constants below have been hand-picked to fit the common scenarios lightmaps + // are generated with, but they can be altered freely to experiment and achieve better results. + + // Half the size of the patch window around each pixel that is weighted to compute the denoised pixel. + // A value of 1 represents a 3x3 window, a value of 2 a 5x5 window, etc. + const int HALF_PATCH_WINDOW = 4; + + // Half the size of the search window around each pixel that is denoised and weighted to compute the denoised pixel. + const int HALF_SEARCH_WINDOW = 10; + + // For all of the following sigma values, smaller values will give less weight to pixels that have a bigger distance + // in the feature being evaluated. Therefore, smaller values are likely to cause more noise to appear, but will also + // cause less features to be erased in the process. + + // Controls how much the spatial distance of the pixels influences the denoising weight. + const float SIGMA_SPATIAL = denoise_params.spatial_bandwidth; + + // Controls how much the light color distance of the pixels influences the denoising weight. + const float SIGMA_LIGHT = denoise_params.light_bandwidth; + + // Controls how much the albedo color distance of the pixels influences the denoising weight. + const float SIGMA_ALBEDO = denoise_params.albedo_bandwidth; + + // Controls how much the normal vector distance of the pixels influences the denoising weight. + const float SIGMA_NORMAL = denoise_params.normal_bandwidth; + + // Strength of the filter. The original paper recommends values around 10 to 15 times the Sigma parameter. + const float FILTER_VALUE = denoise_params.filter_strength * SIGMA_LIGHT; + + // Formula constants. + const int PATCH_WINDOW_DIMENSION = (HALF_PATCH_WINDOW * 2 + 1); + const int PATCH_WINDOW_DIMENSION_SQUARE = (PATCH_WINDOW_DIMENSION * PATCH_WINDOW_DIMENSION); + const float TWO_SIGMA_SPATIAL_SQUARE = 2.0f * SIGMA_SPATIAL * SIGMA_SPATIAL; + const float TWO_SIGMA_LIGHT_SQUARE = 2.0f * SIGMA_LIGHT * SIGMA_LIGHT; + const float TWO_SIGMA_ALBEDO_SQUARE = 2.0f * SIGMA_ALBEDO * SIGMA_ALBEDO; + const float TWO_SIGMA_NORMAL_SQUARE = 2.0f * SIGMA_NORMAL * SIGMA_NORMAL; + const float FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE = FILTER_VALUE * FILTER_VALUE * TWO_SIGMA_LIGHT_SQUARE; + const float EPSILON = 1e-6f; + +#ifdef USE_SH_LIGHTMAPS + const uint slice_count = 4; + const uint slice_base = params.atlas_slice * slice_count; +#else + const uint slice_count = 1; + const uint slice_base = params.atlas_slice; +#endif + + for (uint i = 0; i < slice_count; i++) { + uint lightmap_slice = slice_base + i; + vec3 denoised_rgb = vec3(0.0f); + vec4 input_light = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, lightmap_slice), 0); + vec3 input_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb; + vec3 input_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + if (length(input_normal) > EPSILON) { + // Compute the denoised pixel if the normal is valid. + float sum_weights = 0.0f; + vec3 input_rgb = input_light.rgb; + for (int search_y = -HALF_SEARCH_WINDOW; search_y <= HALF_SEARCH_WINDOW; search_y++) { + for (int search_x = -HALF_SEARCH_WINDOW; search_x <= HALF_SEARCH_WINDOW; search_x++) { + ivec2 search_pos = atlas_pos + ivec2(search_x, search_y); + vec3 search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(search_pos, lightmap_slice), 0).rgb; + vec3 search_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).rgb; + vec3 search_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).xyz; + float patch_square_dist = 0.0f; + for (int offset_y = -HALF_PATCH_WINDOW; offset_y <= HALF_PATCH_WINDOW; offset_y++) { + for (int offset_x = -HALF_PATCH_WINDOW; offset_x <= HALF_PATCH_WINDOW; offset_x++) { + ivec2 offset_input_pos = atlas_pos + ivec2(offset_x, offset_y); + ivec2 offset_search_pos = search_pos + ivec2(offset_x, offset_y); + vec3 offset_input_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_input_pos, lightmap_slice), 0).rgb; + vec3 offset_search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_search_pos, lightmap_slice), 0).rgb; + vec3 offset_delta_rgb = offset_input_rgb - offset_search_rgb; + patch_square_dist += dot(offset_delta_rgb, offset_delta_rgb) - TWO_SIGMA_LIGHT_SQUARE; + } + } + + patch_square_dist = max(0.0f, patch_square_dist / (3.0f * PATCH_WINDOW_DIMENSION_SQUARE)); + + float weight = 1.0f; + + // Ignore weight if search position is out of bounds. + weight *= step(0, search_pos.x) * step(search_pos.x, params.atlas_size.x - 1); + weight *= step(0, search_pos.y) * step(search_pos.y, params.atlas_size.y - 1); + + // Ignore weight if normal is zero length. + weight *= step(EPSILON, length(search_normal)); + + // Weight with pixel distance. + vec2 pixel_delta = vec2(search_x, search_y); + float pixel_square_dist = dot(pixel_delta, pixel_delta); + weight *= exp(-pixel_square_dist / TWO_SIGMA_SPATIAL_SQUARE); + + // Weight with patch. + weight *= exp(-patch_square_dist / FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE); + + // Weight with albedo. + vec3 albedo_delta = input_albedo - search_albedo; + float albedo_square_dist = dot(albedo_delta, albedo_delta); + weight *= exp(-albedo_square_dist / TWO_SIGMA_ALBEDO_SQUARE); + + // Weight with normal. + vec3 normal_delta = input_normal - search_normal; + float normal_square_dist = dot(normal_delta, normal_delta); + weight *= exp(-normal_square_dist / TWO_SIGMA_NORMAL_SQUARE); + + denoised_rgb += weight * search_rgb; + sum_weights += weight; + } + } + + denoised_rgb /= sum_weights; + } else { + // Ignore pixels where the normal is empty, just copy the light color. + denoised_rgb = input_light.rgb; + } + + imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a)); + } +#endif } diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp index 7ec4a40766..984ce88316 100644 --- a/modules/lightmapper_rd/register_types.cpp +++ b/modules/lightmapper_rd/register_types.cpp @@ -58,7 +58,6 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) { GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512); GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048); GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64); - GLOBAL_DEF("rendering/lightmapping/primitive_meshes/texel_size", 0.2); #ifndef _3D_DISABLED GDREGISTER_CLASS(LightmapperRD); Lightmapper::create_gpu = create_lightmapper_rd; diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index 47c0dc9bb6..381ed42fe1 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -256,7 +256,7 @@ Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByte } Error HMACContextMbedTLS::update(PackedByteArray p_data) { - ERR_FAIL_COND_V_MSG(ctx == nullptr, ERR_INVALID_DATA, "Start must be called before update."); + ERR_FAIL_NULL_V_MSG(ctx, ERR_INVALID_DATA, "Start must be called before update."); ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_PARAMETER, "Src must not be empty."); @@ -265,7 +265,7 @@ Error HMACContextMbedTLS::update(PackedByteArray p_data) { } PackedByteArray HMACContextMbedTLS::finish() { - ERR_FAIL_COND_V_MSG(ctx == nullptr, PackedByteArray(), "Start must be called before finish."); + ERR_FAIL_NULL_V_MSG(ctx, PackedByteArray(), "Start must be called before finish."); ERR_FAIL_COND_V_MSG(hash_len == 0, PackedByteArray(), "Unsupported hash type."); PackedByteArray out; @@ -342,7 +342,7 @@ void CryptoMbedTLS::load_default_certificates(String p_path) { ERR_FAIL_COND(default_certs != nullptr); default_certs = memnew(X509CertificateMbedTLS); - ERR_FAIL_COND(default_certs == nullptr); + ERR_FAIL_NULL(default_certs); if (!p_path.is_empty()) { // Use certs defined in project settings. @@ -419,9 +419,19 @@ Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoK } PackedByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) { + ERR_FAIL_COND_V(p_bytes < 0, PackedByteArray()); PackedByteArray out; out.resize(p_bytes); - mbedtls_ctr_drbg_random(&ctr_drbg, out.ptrw(), p_bytes); + int left = p_bytes; + int pos = 0; + // Ensure we generate random in chunks of no more than MBEDTLS_CTR_DRBG_MAX_REQUEST bytes or mbedtls_ctr_drbg_random will fail. + while (left > 0) { + int to_read = MIN(left, MBEDTLS_CTR_DRBG_MAX_REQUEST); + int ret = mbedtls_ctr_drbg_random(&ctr_drbg, out.ptrw() + pos, to_read); + ERR_FAIL_COND_V_MSG(ret != 0, PackedByteArray(), vformat("Failed to generate %d random bytes(s). Error: %d.", p_bytes, ret)); + left -= to_read; + pos += to_read; + } return out; } diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index ed1a97cc2c..c7373481ca 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -40,7 +40,7 @@ int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); Error err = sp->base->put_packet((const uint8_t *)buf, len); if (err == ERR_BUSY) { @@ -58,7 +58,7 @@ int PacketPeerMbedDTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); int pc = sp->base->get_available_packet_count(); if (pc == 0) { diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index a9d187bd64..a359b42041 100644 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -40,7 +40,7 @@ int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); int sent; Error err = sp->base->put_partial_data((const uint8_t *)buf, len, sent); @@ -60,7 +60,7 @@ int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); int got; Error err = sp->base->get_partial_data((uint8_t *)buf, len, got); diff --git a/modules/minimp3/SCsub b/modules/minimp3/SCsub index 20e3165f38..09e84f71e9 100644 --- a/modules/minimp3/SCsub +++ b/modules/minimp3/SCsub @@ -13,5 +13,8 @@ if not env.msvc: else: env_minimp3.Prepend(CPPPATH=[thirdparty_dir]) +if not env["minimp3_extra_formats"]: + env_minimp3.Append(CPPDEFINES=["MINIMP3_ONLY_MP3"]) + # Godot source files env_minimp3.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 6af86a96dc..4efa4d329e 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -28,7 +28,6 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#define MINIMP3_ONLY_MP3 #define MINIMP3_FLOAT_OUTPUT #define MINIMP3_IMPLEMENTATION #define MINIMP3_NO_STDIO diff --git a/modules/minimp3/config.py b/modules/minimp3/config.py index e6bdcb2a83..115b376fc8 100644 --- a/modules/minimp3/config.py +++ b/modules/minimp3/config.py @@ -2,6 +2,14 @@ def can_build(env, platform): return True +def get_opts(platform): + from SCons.Variables import BoolVariable + + return [ + BoolVariable("minimp3_extra_formats", "Build minimp3 with MP1/MP2 decoding support", False), + ] + + def configure(env): pass diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp index 4e56120ec6..d60b979c3f 100644 --- a/modules/minimp3/resource_importer_mp3.cpp +++ b/modules/minimp3/resource_importer_mp3.cpp @@ -47,6 +47,10 @@ String ResourceImporterMP3::get_visible_name() const { } void ResourceImporterMP3::get_recognized_extensions(List<String> *p_extensions) const { +#ifndef MINIMP3_ONLY_MP3 + p_extensions->push_back("mp1"); + p_extensions->push_back("mp2"); +#endif p_extensions->push_back("mp3"); } diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp index 733f1dbe34..c4aba577db 100644 --- a/modules/mono/class_db_api_json.cpp +++ b/modules/mono/class_db_api_json.cpp @@ -47,7 +47,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { for (const StringName &E : class_list) { ClassDB::ClassInfo *t = ClassDB::classes.getptr(E); - ERR_FAIL_COND(!t); + ERR_FAIL_NULL(t); if (t->api != p_api || !t->exposed) { continue; } diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index b41f2155f8..95bf848cbf 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -392,10 +392,6 @@ Script *CSharpLanguage::create_script() const { return memnew(CSharpScript); } -bool CSharpLanguage::has_named_classes() const { - return false; -} - bool CSharpLanguage::supports_builtin_mode() const { return false; } @@ -554,13 +550,13 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const { String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type()); - if (!scr.is_valid() || !scr->valid || !scr->global_class) { - // Invalid script or the script is not a global class. - return String(); - } + // Always assign r_base_type and r_icon_path, even if the script + // is not a global one. In the case that it is not a global script, + // return an empty string AFTER assigning the return parameters. + // See GDScriptLanguage::get_global_class_name() in modules/gdscript/gdscript.cpp - String name = scr->class_name; - if (unlikely(name.is_empty())) { + if (!scr.is_valid() || !scr->valid) { + // Invalid script. return String(); } @@ -587,7 +583,8 @@ String CSharpLanguage::get_global_class_name(const String &p_path, String *r_bas *r_base_type = scr->get_instance_base_type(); } } - return name; + + return scr->global_class ? scr->class_name : String(); } String CSharpLanguage::debug_get_error() const { @@ -2300,6 +2297,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { bool tool = false; bool global_class = false; + bool abstract_class = false; // TODO: Use GDExtension godot_dictionary Array methods_array; @@ -2313,12 +2311,13 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { String icon_path; Ref<CSharpScript> base_script; GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo( - p_script.ptr(), &class_name, &tool, &global_class, &icon_path, + p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path, &methods_array, &rpc_functions_dict, &signals_dict, &base_script); p_script->class_name = class_name; p_script->tool = tool; p_script->global_class = global_class; + p_script->abstract_class = abstract_class; p_script->icon_path = icon_path; p_script->rpc_config.clear(); @@ -2353,6 +2352,8 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { mi.arguments.push_back(arg_info); } + mi.flags = (uint32_t)method_info_dict["flags"]; + p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi }); } @@ -2406,7 +2407,7 @@ bool CSharpScript::can_instantiate() const { ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive)."); } - return valid && extra_cond; + return valid && !abstract_class && extra_cond; } StringName CSharpScript::get_instance_base_type() const { @@ -2602,6 +2603,18 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { return MethodInfo(); } +Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + ERR_FAIL_COND_V(!valid, Variant()); + + Variant ret; + bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret); + if (ok) { + return ret; + } + + return Script::callp(p_method, p_args, p_argcount, r_error); +} + Error CSharpScript::reload(bool p_keep_state) { if (!reload_invalidated) { return OK; diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 33862016a4..2ab80c132d 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -63,6 +63,7 @@ class CSharpScript : public Script { bool tool = false; bool global_class = false; + bool abstract_class = false; bool valid = false; bool reload_invalidated = false; @@ -188,6 +189,9 @@ public: bool is_valid() const override { return valid; } + bool is_abstract() const override { + return abstract_class; + } bool inherits_script(const Ref<Script> &p_script) const override; @@ -199,6 +203,7 @@ public: void get_script_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; MethodInfo get_method_info(const StringName &p_method) const override; + Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; int get_member_line(const StringName &p_member) const override; @@ -424,7 +429,9 @@ public: } String validate_path(const String &p_path) const override; Script *create_script() const override; - bool has_named_classes() const override; +#ifndef DISABLE_DEPRECATED + virtual bool has_named_classes() const override { return false; } +#endif bool supports_builtin_mode() const override; /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 6677d77559..b35cec64f3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -4,8 +4,6 @@ <PropertyGroup> <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. --> <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk> - - <GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid> </PropertyGroup> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index 859ea52c93..4dcc96a1f6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -2,11 +2,6 @@ <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " /> <PropertyGroup> - <EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid> - <ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids> - </PropertyGroup> - - <PropertyGroup> <!-- Define constant to determine whether the real_t type in Godot is double precision or not. By default this is false, like the official Godot builds. If someone is using a custom diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs new file mode 100644 index 0000000000..0c374169b9 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs @@ -0,0 +1,10 @@ +namespace Godot.SourceGenerators.Sample +{ + public partial class AllReadOnly : GodotObject + { + public readonly string readonly_field = "foo"; + public string readonly_auto_property { get; } = "foo"; + public string readonly_property { get => "foo"; } + public string initonly_auto_property { get; init; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs new file mode 100644 index 0000000000..14a1802330 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs @@ -0,0 +1,10 @@ +using System; + +namespace Godot.SourceGenerators.Sample +{ + public partial class AllWriteOnly : GodotObject + { + bool writeonly_backing_field = false; + public bool writeonly_property { set => writeonly_backing_field = value; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs new file mode 100644 index 0000000000..f556bdc7e4 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs @@ -0,0 +1,13 @@ +namespace Godot.SourceGenerators.Sample +{ + public partial class MixedReadonlyWriteOnly : GodotObject + { + public readonly string readonly_field = "foo"; + public string readonly_auto_property { get; } = "foo"; + public string readonly_property { get => "foo"; } + public string initonly_auto_property { get; init; } + + bool writeonly_backing_field = false; + public bool writeonly_property { set => writeonly_backing_field = value; } + } +} 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 b6ea4b8e88..5866db5144 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -303,11 +303,6 @@ namespace Godot.SourceGenerators { foreach (var property in properties) { - // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. - // Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable. - if (property.IsWriteOnly || property.IsReadOnly || property.SetMethod!.IsInitOnly) - continue; - var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache); if (marshalType == null) @@ -325,10 +320,6 @@ namespace Godot.SourceGenerators foreach (var field in fields) { // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. - // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. - if (field.IsReadOnly) - continue; - var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache); if (marshalType == null) 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 5ea0ca53c3..7b643914bb 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -125,7 +125,7 @@ namespace Godot.SourceGenerators var members = symbol.GetMembers(); var methodSymbols = members - .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared) + .Where(s => s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared) .Cast<IMethodSymbol>() .Where(m => m.MethodKind == MethodKind.Ordinary); @@ -221,6 +221,29 @@ namespace Godot.SourceGenerators source.Append(" }\n"); } + // Generate InvokeGodotClassStaticMethod + + var godotClassStaticMethods = godotClassMethods.Where(m => m.Method.IsStatic).ToArray(); + + if (godotClassStaticMethods.Length > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static bool InvokeGodotClassStaticMethod(in godot_string_name method, "); + source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n"); + + foreach (var method in godotClassStaticMethods) + { + GenerateMethodInvoker(method, source); + } + + source.Append(" ret = default;\n"); + source.Append(" return false;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + // Generate HasGodotClassMethod if (distinctMethodNames.Length > 0) @@ -356,7 +379,14 @@ namespace Godot.SourceGenerators arguments = null; } - return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments, + MethodFlags flags = MethodFlags.Default; + + if (method.Method.IsStatic) + { + flags |= MethodFlags.Static; + } + + return new MethodInfo(method.Method.Name, returnVal, flags, arguments, defaultArguments: null); } 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 94d8696717..219ab7aa44 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -212,31 +212,37 @@ namespace Godot.SourceGenerators } // Generate GetGodotClassPropertyValue + bool allPropertiesAreWriteOnly = godotClassFields.Length == 0 && godotClassProperties.All(pi => pi.PropertySymbol.IsWriteOnly); - 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"); - - isFirstEntry = true; - foreach (var property in godotClassProperties) + if (!allPropertiesAreWriteOnly) { - GeneratePropertyGetter(property.PropertySymbol.Name, - property.PropertySymbol.Type, property.Type, source, isFirstEntry); - isFirstEntry = false; - } + 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"); - foreach (var field in godotClassFields) - { - GeneratePropertyGetter(field.FieldSymbol.Name, - field.FieldSymbol.Type, field.Type, source, isFirstEntry); - isFirstEntry = false; - } + isFirstEntry = true; + foreach (var property in godotClassProperties) + { + if (property.PropertySymbol.IsWriteOnly) + continue; - source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n"); + GeneratePropertyGetter(property.PropertySymbol.Name, + property.PropertySymbol.Type, property.Type, source, isFirstEntry); + isFirstEntry = false; + } - source.Append(" }\n"); + foreach (var field in godotClassFields) + { + GeneratePropertyGetter(field.FieldSymbol.Name, + field.FieldSymbol.Type, field.Type, source, isFirstEntry); + isFirstEntry = false; + } + + source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n"); + source.Append(" }\n"); + } // Generate GetGodotPropertyList const string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; 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 231a7be021..9de99414b6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -119,8 +119,14 @@ namespace Godot.SourceGenerators .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) .Cast<IFieldSymbol>(); - var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); - var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable. + var godotClassProperties = propertySymbols.Where(property => !(property.IsReadOnly || property.IsWriteOnly || property.SetMethod!.IsInitOnly)) + .WhereIsGodotCompatibleType(typeCache) + .ToArray(); + var godotClassFields = fieldSymbols.Where(property => !property.IsReadOnly) + .WhereIsGodotCompatibleType(typeCache) + .ToArray(); var signalDelegateSymbols = members .Where(s => s.Kind == SymbolKind.NamedType) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs new file mode 100644 index 0000000000..6e0c63dd43 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs @@ -0,0 +1,23 @@ +#nullable enable + +namespace GodotTools.Build +{ + public class BuildDiagnostic + { + public enum DiagnosticType + { + Hidden, + Info, + Warning, + Error, + } + + public DiagnosticType Type { get; set; } + public string? File { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string? Code { get; set; } + public string Message { get; set; } = ""; + public string? ProjectFile { get; set; } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 312c65e364..907511d140 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -40,9 +40,6 @@ namespace GodotTools.Build plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel); } - public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); - public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); - private static string GetLogFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); @@ -209,17 +206,19 @@ namespace GodotTools.Build if (!File.Exists(buildInfo.Project)) return true; // No project to build. - using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1); - - pr.Step("Building project", 0); + bool success; + using (var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1)) + { + pr.Step("Building project", 0); + success = Build(buildInfo); + } - if (!Build(buildInfo)) + if (!success) { ShowBuildErrorDialog("Failed to build project"); - return false; } - return true; + return success; } private static bool CleanProjectBlocking(BuildInfo buildInfo) @@ -227,32 +226,36 @@ namespace GodotTools.Build if (!File.Exists(buildInfo.Project)) return true; // No project to clean. - using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1); - - pr.Step("Cleaning project", 0); + bool success; + using (var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1)) + { + pr.Step("Cleaning project", 0); + success = Build(buildInfo); + } - if (!Build(buildInfo)) + if (!success) { ShowBuildErrorDialog("Failed to clean project"); - return false; } - return true; + return success; } private static bool PublishProjectBlocking(BuildInfo buildInfo) { - using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1); - - pr.Step("Running dotnet publish", 0); + bool success; + using (var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1)) + { + pr.Step("Running dotnet publish", 0); + success = Publish(buildInfo); + } - if (!Publish(buildInfo)) + if (!success) { ShowBuildErrorDialog("Failed to publish .NET project"); - return false; } - return true; + return success; } private static BuildInfo CreateBuildInfo( diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index 54f7ed02f5..f9e85c36e5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -1,425 +1,150 @@ using Godot; -using System; -using System.Diagnostics.CodeAnalysis; -using GodotTools.Internals; -using File = GodotTools.Utils.File; -using Path = System.IO.Path; +using static GodotTools.Internals.Globals; + +#nullable enable namespace GodotTools.Build { - public partial class BuildOutputView : VBoxContainer, ISerializationListener + public partial class BuildOutputView : HBoxContainer { - [Serializable] - private partial class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization - { - public bool Warning { get; set; } - public string File { get; set; } - public int Line { get; set; } - public int Column { get; set; } - public string Code { get; set; } - public string Message { get; set; } - public string ProjectFile { get; set; } - } - - [Signal] - public delegate void BuildStateChangedEventHandler(); - - public bool HasBuildExited { get; private set; } = false; +#nullable disable + private RichTextLabel _log; - public BuildResult? BuildResult { get; private set; } = null; + private Button _clearButton; + private Button _copyButton; +#nullable enable - public int ErrorCount { get; private set; } = 0; - - public int WarningCount { get; private set; } = 0; - - public bool ErrorsVisible { get; set; } = true; - public bool WarningsVisible { get; set; } = true; - - public Texture2D BuildStateIcon + public void Append(string text) { - get - { - if (!HasBuildExited) - return GetThemeIcon("Stop", "EditorIcons"); - - if (BuildResult == Build.BuildResult.Error) - return GetThemeIcon("Error", "EditorIcons"); - - if (WarningCount > 1) - return GetThemeIcon("Warning", "EditorIcons"); - - return null; - } + _log.AddText(text); } - public bool LogVisible + public void Clear() { - set => _buildLog.Visible = value; + _log.Clear(); } - // TODO Use List once we have proper serialization. - private Godot.Collections.Array<BuildIssue> _issues = new(); - private ItemList _issuesList; - private PopupMenu _issuesListContextMenu; - private TextEdit _buildLog; - private BuildInfo _buildInfo; - - private readonly object _pendingBuildLogTextLock = new object(); - [NotNull] private string _pendingBuildLogText = string.Empty; - - private void LoadIssuesFromFile(string csvFile) + private void CopyRequested() { - using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read); - - if (file == null) - return; + string text = _log.GetSelectedText(); - while (!file.EofReached()) - { - string[] csvColumns = file.GetCsvLine(); + if (string.IsNullOrEmpty(text)) + text = _log.GetParsedText(); - if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) - return; - - if (csvColumns.Length != 7) - { - GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); - continue; - } - - var issue = new BuildIssue - { - Warning = csvColumns[0] == "warning", - File = csvColumns[1], - Line = int.Parse(csvColumns[2]), - Column = int.Parse(csvColumns[3]), - Code = csvColumns[4], - Message = csvColumns[5], - ProjectFile = csvColumns[6] - }; - - if (issue.Warning) - WarningCount += 1; - else - ErrorCount += 1; - - _issues.Add(issue); - } + if (!string.IsNullOrEmpty(text)) + DisplayServer.ClipboardSet(text); } - private void IssueActivated(long idx) + public override void _Ready() { - if (idx < 0 || idx >= _issuesList.ItemCount) - throw new ArgumentOutOfRangeException(nameof(idx), "Item list index out of range."); - - // Get correct issue idx from issue list - int issueIndex = (int)_issuesList.GetItemMetadata((int)idx); - - if (issueIndex < 0 || issueIndex >= _issues.Count) - throw new InvalidOperationException("Issue index out of range."); - - BuildIssue issue = _issues[issueIndex]; + Name = "Output".TTR(); - if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) - return; - - string projectDir = !string.IsNullOrEmpty(issue.ProjectFile) ? - issue.ProjectFile.GetBaseDir() : - _buildInfo.Solution.GetBaseDir(); - - string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); - - if (!File.Exists(file)) - return; - - file = ProjectSettings.LocalizePath(file); - - if (file.StartsWith("res://")) + var vbLeft = new VBoxContainer { - var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); - - // 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(); - } - } - - public void UpdateIssuesList() - { - _issuesList.Clear(); + CustomMinimumSize = new Vector2(0, 180 * EditorScale), + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + AddChild(vbLeft); - using (var warningIcon = GetThemeIcon("Warning", "EditorIcons")) - using (var errorIcon = GetThemeIcon("Error", "EditorIcons")) + // Log - Rich Text Label. + _log = new RichTextLabel { - for (int i = 0; i < _issues.Count; i++) - { - BuildIssue issue = _issues[i]; - - if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) - continue; - - string tooltip = string.Empty; - tooltip += $"Message: {issue.Message}"; - - if (!string.IsNullOrEmpty(issue.Code)) - tooltip += $"\nCode: {issue.Code}"; - - tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; - - string text = string.Empty; - - if (!string.IsNullOrEmpty(issue.File)) - { - text += $"{issue.File}({issue.Line},{issue.Column}): "; - - tooltip += $"\nFile: {issue.File}"; - tooltip += $"\nLine: {issue.Line}"; - tooltip += $"\nColumn: {issue.Column}"; - } - - if (!string.IsNullOrEmpty(issue.ProjectFile)) - tooltip += $"\nProject: {issue.ProjectFile}"; - - text += issue.Message; - - int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); - string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); - _issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); - - int index = _issuesList.ItemCount - 1; - _issuesList.SetItemTooltip(index, tooltip); - _issuesList.SetItemMetadata(index, i); - } - } - } - - private void BuildLaunchFailed(BuildInfo buildInfo, string cause) - { - HasBuildExited = true; - BuildResult = Build.BuildResult.Error; - - _issuesList.Clear(); - - var issue = new BuildIssue { Message = cause, Warning = false }; - - ErrorCount += 1; - _issues.Add(issue); - - UpdateIssuesList(); - - EmitSignal(nameof(BuildStateChanged)); - } - - private void BuildStarted(BuildInfo buildInfo) - { - _buildInfo = buildInfo; - HasBuildExited = false; - - _issues.Clear(); - WarningCount = 0; - ErrorCount = 0; - _buildLog.Text = string.Empty; - - UpdateIssuesList(); - - EmitSignal(nameof(BuildStateChanged)); - } - - private void BuildFinished(BuildResult result) - { - HasBuildExited = true; - BuildResult = result; - - LoadIssuesFromFile(Path.Combine(_buildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + BbcodeEnabled = true, + ScrollFollowing = true, + SelectionEnabled = true, + ContextMenuEnabled = true, + FocusMode = FocusModeEnum.Click, + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + DeselectOnFocusLossEnabled = false, - UpdateIssuesList(); + }; + vbLeft.AddChild(_log); - EmitSignal(nameof(BuildStateChanged)); - } + var vbRight = new VBoxContainer(); + AddChild(vbRight); - private void UpdateBuildLogText() - { - lock (_pendingBuildLogTextLock) + // Tools grid + var hbTools = new HBoxContainer { - _buildLog.Text += _pendingBuildLogText; - _pendingBuildLogText = string.Empty; - ScrollToLastNonEmptyLogLine(); - } - } - - private void StdOutputReceived(string text) - { - lock (_pendingBuildLogTextLock) - { - if (_pendingBuildLogText.Length == 0) - CallDeferred(nameof(UpdateBuildLogText)); - _pendingBuildLogText += text + "\n"; - } - } + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + vbRight.AddChild(hbTools); - private void StdErrorReceived(string text) - { - lock (_pendingBuildLogTextLock) + // Clear. + _clearButton = new Button { - if (_pendingBuildLogText.Length == 0) - CallDeferred(nameof(UpdateBuildLogText)); - _pendingBuildLogText += text + "\n"; - } - } + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K), + }; + _clearButton.Pressed += Clear; + hbTools.AddChild(_clearButton); - private void ScrollToLastNonEmptyLogLine() - { - int line; - for (line = _buildLog.GetLineCount(); line > 0; line--) + // Copy. + _copyButton = new Button { - string lineText = _buildLog.GetLine(line); - - if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim())) - break; - } - - _buildLog.SetCaretLine(line); - } - - public void RestartBuild() - { - if (!HasBuildExited) - throw new InvalidOperationException("Build already started."); - - BuildManager.RestartBuild(this); - } - - public void StopBuild() - { - if (!HasBuildExited) - throw new InvalidOperationException("Build is not in progress."); + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C), + ShortcutContext = this, + }; + _copyButton.Pressed += CopyRequested; + hbTools.AddChild(_copyButton); - BuildManager.StopBuild(this); + UpdateTheme(); } - private enum IssuesContextMenuOption + public override void _Notification(int what) { - Copy - } + base._Notification(what); - private void IssuesListContextOptionPressed(long id) - { - switch ((IssuesContextMenuOption)id) + if (what == NotificationThemeChanged) { - case IssuesContextMenuOption.Copy: - { - // We don't allow multi-selection but just in case that changes later... - string text = null; - - foreach (int issueIndex in _issuesList.GetSelectedItems()) - { - if (text != null) - text += "\n"; - text += _issuesList.GetItemText(issueIndex); - } - - if (text != null) - DisplayServer.ClipboardSet(text); - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid issue context menu option"); + UpdateTheme(); } } - private void IssuesListClicked(long index, Vector2 atPosition, long mouseButtonIndex) + private void UpdateTheme() { - if (mouseButtonIndex != (long)MouseButton.Right) - { + // Nodes will be null until _Ready is called. + if (_log == null) return; - } - - _ = index; // Unused - - _issuesListContextMenu.Clear(); - _issuesListContextMenu.Size = new Vector2I(1, 1); - - if (_issuesList.IsAnythingSelected()) - { - // Add menu entries for the selected item - _issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), - label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy); - } - - if (_issuesListContextMenu.ItemCount > 0) - { - _issuesListContextMenu.Position = (Vector2I)(_issuesList.GlobalPosition + atPosition); - _issuesListContextMenu.Popup(); - } - } - - public override void _Ready() - { - base._Ready(); - SizeFlagsVertical = SizeFlags.ExpandFill; + var normalFont = GetThemeFont("output_source", "EditorFonts"); + if (normalFont != null) + _log.AddThemeFontOverride("normal_font", normalFont); - var hsc = new HSplitContainer - { - SizeFlagsHorizontal = SizeFlags.ExpandFill, - SizeFlagsVertical = SizeFlags.ExpandFill - }; - AddChild(hsc); + var boldFont = GetThemeFont("output_source_bold", "EditorFonts"); + if (boldFont != null) + _log.AddThemeFontOverride("bold_font", boldFont); - _issuesList = new ItemList - { - SizeFlagsVertical = SizeFlags.ExpandFill, - SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the build log - }; - _issuesList.ItemActivated += IssueActivated; - _issuesList.AllowRmbSelect = true; - _issuesList.ItemClicked += IssuesListClicked; - hsc.AddChild(_issuesList); + var italicsFont = GetThemeFont("output_source_italic", "EditorFonts"); + if (italicsFont != null) + _log.AddThemeFontOverride("italics_font", italicsFont); - _issuesListContextMenu = new PopupMenu(); - _issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; - _issuesList.AddChild(_issuesListContextMenu); + var boldItalicsFont = GetThemeFont("output_source_bold_italic", "EditorFonts"); + if (boldItalicsFont != null) + _log.AddThemeFontOverride("bold_italics_font", boldItalicsFont); - _buildLog = new TextEdit - { - Editable = false, - SizeFlagsVertical = SizeFlags.ExpandFill, - SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the issues list - }; - hsc.AddChild(_buildLog); + var monoFont = GetThemeFont("output_source_mono", "EditorFonts"); + if (monoFont != null) + _log.AddThemeFontOverride("mono_font", monoFont); - AddBuildEventListeners(); - } - - private void AddBuildEventListeners() - { - BuildManager.BuildLaunchFailed += BuildLaunchFailed; - BuildManager.BuildStarted += BuildStarted; - BuildManager.BuildFinished += BuildFinished; - // StdOutput/Error can be received from different threads, so we need to use CallDeferred - BuildManager.StdOutputReceived += StdOutputReceived; - BuildManager.StdErrorReceived += StdErrorReceived; - } + // Disable padding for highlighted background/foreground to prevent highlights from overlapping on close lines. + // This also better matches terminal output, which does not use any form of padding. + _log.AddThemeConstantOverride("text_highlight_h_padding", 0); + _log.AddThemeConstantOverride("text_highlight_v_padding", 0); - public void OnBeforeSerialize() - { - // In case it didn't update yet. We don't want to have to serialize any pending output. - UpdateBuildLogText(); - - // NOTE: - // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. - // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. - BuildManager.BuildLaunchFailed -= BuildLaunchFailed; - BuildManager.BuildStarted -= BuildStarted; - BuildManager.BuildFinished -= BuildFinished; - // StdOutput/Error can be received from different threads, so we need to use CallDeferred - BuildManager.StdOutputReceived -= StdOutputReceived; - BuildManager.StdErrorReceived -= StdErrorReceived; - } + int font_size = GetThemeFontSize("output_source_size", "EditorFonts"); + _log.AddThemeFontSizeOverride("normal_font_size", font_size); + _log.AddThemeFontSizeOverride("bold_font_size", font_size); + _log.AddThemeFontSizeOverride("italics_font_size", font_size); + _log.AddThemeFontSizeOverride("mono_font_size", font_size); - public void OnAfterDeserialize() - { - AddBuildEventListeners(); // Re-add them + _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons"); + _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons"); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs new file mode 100644 index 0000000000..9c165e5767 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs @@ -0,0 +1,40 @@ +using Godot; + +#nullable enable + +namespace GodotTools.Build +{ + public class BuildProblemsFilter + { + public BuildDiagnostic.DiagnosticType Type { get; } + + public Button ToggleButton { get; } + + private int _problemsCount; + + public int ProblemsCount + { + get => _problemsCount; + set + { + _problemsCount = value; + ToggleButton.Text = _problemsCount.ToString(); + } + } + + public bool IsActive => ToggleButton.ButtonPressed; + + public BuildProblemsFilter(BuildDiagnostic.DiagnosticType type) + { + Type = type; + ToggleButton = new Button + { + ToggleMode = true, + ButtonPressed = true, + Text = "0", + FocusMode = Control.FocusModeEnum.None, + ThemeTypeVariation = "EditorLogFilterButton", + }; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs new file mode 100644 index 0000000000..b23b3f42ef --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs @@ -0,0 +1,694 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Godot; +using GodotTools.Internals; +using static GodotTools.Internals.Globals; +using FileAccess = Godot.FileAccess; + +#nullable enable + +namespace GodotTools.Build +{ + public partial class BuildProblemsView : HBoxContainer + { +#nullable disable + private Button _clearButton; + private Button _copyButton; + + private Button _toggleLayoutButton; + + private Button _showSearchButton; + private LineEdit _searchBox; +#nullable enable + + private readonly Dictionary<BuildDiagnostic.DiagnosticType, BuildProblemsFilter> _filtersByType = new(); + +#nullable disable + private Tree _problemsTree; + private PopupMenu _problemsContextMenu; +#nullable enable + + public enum ProblemsLayout { List, Tree } + private ProblemsLayout _layout = ProblemsLayout.Tree; + + private readonly List<BuildDiagnostic> _diagnostics = new(); + + public int TotalDiagnosticCount => _diagnostics.Count; + + private readonly Dictionary<BuildDiagnostic.DiagnosticType, int> _problemCountByType = new(); + + public int WarningCount => + GetProblemCountForType(BuildDiagnostic.DiagnosticType.Warning); + + public int ErrorCount => + GetProblemCountForType(BuildDiagnostic.DiagnosticType.Error); + + private int GetProblemCountForType(BuildDiagnostic.DiagnosticType type) + { + if (!_problemCountByType.TryGetValue(type, out int count)) + { + count = _diagnostics.Count(d => d.Type == type); + _problemCountByType[type] = count; + } + + return count; + } + + private static IEnumerable<BuildDiagnostic> ReadDiagnosticsFromFile(string csvFile) + { + using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read); + + if (file == null) + yield break; + + while (!file.EofReached()) + { + string[] csvColumns = file.GetCsvLine(); + + if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) + yield break; + + if (csvColumns.Length != 7) + { + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; + } + + var diagnostic = new BuildDiagnostic + { + Type = csvColumns[0] switch + { + "warning" => BuildDiagnostic.DiagnosticType.Warning, + "error" or _ => BuildDiagnostic.DiagnosticType.Error, + }, + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6], + }; + + // If there's no ProjectFile but the File is a csproj, then use that. + if (string.IsNullOrEmpty(diagnostic.ProjectFile) && + !string.IsNullOrEmpty(diagnostic.File) && + diagnostic.File.EndsWith(".csproj")) + { + diagnostic.ProjectFile = diagnostic.File; + } + + yield return diagnostic; + } + } + + public void SetDiagnosticsFromFile(string csvFile) + { + var diagnostics = ReadDiagnosticsFromFile(csvFile); + SetDiagnostics(diagnostics); + } + + public void SetDiagnostics(IEnumerable<BuildDiagnostic> diagnostics) + { + _diagnostics.Clear(); + _problemCountByType.Clear(); + + _diagnostics.AddRange(diagnostics); + UpdateProblemsView(); + } + + public void Clear() + { + _problemsTree.Clear(); + _diagnostics.Clear(); + _problemCountByType.Clear(); + + UpdateProblemsView(); + } + + private void CopySelectedProblems() + { + var selectedItem = _problemsTree.GetNextSelected(null); + if (selectedItem == null) + return; + + var selectedIdxs = new List<int>(); + while (selectedItem != null) + { + int selectedIdx = (int)selectedItem.GetMetadata(0); + selectedIdxs.Add(selectedIdx); + + selectedItem = _problemsTree.GetNextSelected(selectedItem); + } + + if (selectedIdxs.Count == 0) + return; + + var selectedDiagnostics = selectedIdxs.Select(i => _diagnostics[i]); + + var sb = new StringBuilder(); + + foreach (var diagnostic in selectedDiagnostics) + { + if (!string.IsNullOrEmpty(diagnostic.Code)) + sb.Append($"{diagnostic.Code}: "); + + sb.AppendLine($"{diagnostic.Message} {diagnostic.File}({diagnostic.Line},{diagnostic.Column})"); + } + + string text = sb.ToString(); + + if (!string.IsNullOrEmpty(text)) + DisplayServer.ClipboardSet(text); + } + + private void ToggleLayout(bool pressed) + { + _layout = pressed ? ProblemsLayout.List : ProblemsLayout.Tree; + + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); + editorSettings.SetSetting(GodotSharpEditor.Settings.ProblemsLayout, Variant.From(_layout)); + + _toggleLayoutButton.Icon = GetToggleLayoutIcon(); + _toggleLayoutButton.TooltipText = GetToggleLayoutTooltipText(); + + UpdateProblemsView(); + } + + private bool GetToggleLayoutPressedState() + { + // If pressed: List layout. + // If not pressed: Tree layout. + return _layout == ProblemsLayout.List; + } + + private Texture2D? GetToggleLayoutIcon() + { + return _layout switch + { + ProblemsLayout.List => GetThemeIcon("FileList", "EditorIcons"), + ProblemsLayout.Tree or _ => GetThemeIcon("FileTree", "EditorIcons"), + }; + } + + private string GetToggleLayoutTooltipText() + { + return _layout switch + { + ProblemsLayout.List => "View as a Tree".TTR(), + ProblemsLayout.Tree or _ => "View as a List".TTR(), + }; + } + + private void ToggleSearchBoxVisibility(bool pressed) + { + _searchBox.Visible = pressed; + if (pressed) + { + _searchBox.GrabFocus(); + } + } + + private void SearchTextChanged(string text) + { + UpdateProblemsView(); + } + + private void ToggleFilter(bool pressed) + { + UpdateProblemsView(); + } + + private void GoToSelectedProblem() + { + var selectedItem = _problemsTree.GetSelected(); + if (selectedItem == null) + throw new InvalidOperationException("Item tree has no selected items."); + + // Get correct diagnostic index from problems tree. + int diagnosticIndex = (int)selectedItem.GetMetadata(0); + + if (diagnosticIndex < 0 || diagnosticIndex >= _diagnostics.Count) + throw new InvalidOperationException("Diagnostic index out of range."); + + var diagnostic = _diagnostics[diagnosticIndex]; + + if (string.IsNullOrEmpty(diagnostic.ProjectFile) && string.IsNullOrEmpty(diagnostic.File)) + return; + + string? projectDir = !string.IsNullOrEmpty(diagnostic.ProjectFile) ? + diagnostic.ProjectFile.GetBaseDir() : + GodotSharpEditor.Instance.MSBuildPanel.LastBuildInfo?.Solution.GetBaseDir(); + if (string.IsNullOrEmpty(projectDir)) + return; + + string file = Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath()); + + if (!File.Exists(file)) + return; + + file = ProjectSettings.LocalizePath(file); + + if (file.StartsWith("res://")) + { + var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); + + // Godot's ScriptEditor.Edit is 0-based but the diagnostic lines are 1-based. + if (script != null && Internal.ScriptEditorEdit(script, diagnostic.Line - 1, diagnostic.Column - 1)) + Internal.EditorNodeShowScriptScreen(); + } + } + + private void ShowProblemContextMenu(Vector2 position, long mouseButtonIndex) + { + if (mouseButtonIndex != (long)MouseButton.Right) + return; + + _problemsContextMenu.Clear(); + _problemsContextMenu.Size = new Vector2I(1, 1); + + var selectedItem = _problemsTree.GetSelected(); + if (selectedItem != null) + { + // Add menu entries for the selected item. + _problemsContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), + label: "Copy Error".TTR(), (int)ProblemContextMenuOption.Copy); + } + + if (_problemsContextMenu.ItemCount > 0) + { + _problemsContextMenu.Position = (Vector2I)(_problemsTree.GlobalPosition + position); + _problemsContextMenu.Popup(); + } + } + + private enum ProblemContextMenuOption + { + Copy, + } + + private void ProblemContextOptionPressed(long id) + { + switch ((ProblemContextMenuOption)id) + { + case ProblemContextMenuOption.Copy: + CopySelectedProblems(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid problem context menu option."); + } + } + + private bool ShouldDisplayDiagnostic(BuildDiagnostic diagnostic) + { + if (!_filtersByType[diagnostic.Type].IsActive) + return false; + + string searchText = _searchBox.Text; + if (!string.IsNullOrEmpty(searchText) && + (!diagnostic.Message.Contains(searchText, StringComparison.OrdinalIgnoreCase) || + !(diagnostic.File?.Contains(searchText, StringComparison.OrdinalIgnoreCase) ?? false))) + { + return false; + } + + return true; + } + + private Color? GetProblemItemColor(BuildDiagnostic diagnostic) + { + return diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Warning => GetThemeColor("warning_color", "Editor"), + BuildDiagnostic.DiagnosticType.Error => GetThemeColor("error_color", "Editor"), + _ => null, + }; + } + + public void UpdateProblemsView() + { + switch (_layout) + { + case ProblemsLayout.List: + UpdateProblemsList(); + break; + + case ProblemsLayout.Tree: + default: + UpdateProblemsTree(); + break; + } + + foreach (var (type, filter) in _filtersByType) + { + int count = _diagnostics.Count(d => d.Type == type); + filter.ProblemsCount = count; + } + + if (_diagnostics.Count == 0) + Name = "Problems".TTR(); + else + Name = $"{"Problems".TTR()} ({_diagnostics.Count})"; + } + + private void UpdateProblemsList() + { + _problemsTree.Clear(); + + var root = _problemsTree.CreateItem(); + + for (int i = 0; i < _diagnostics.Count; i++) + { + var diagnostic = _diagnostics[i]; + + if (!ShouldDisplayDiagnostic(diagnostic)) + continue; + + var item = CreateProblemItem(diagnostic, includeFileInText: true); + + var problemItem = _problemsTree.CreateItem(root); + problemItem.SetIcon(0, item.Icon); + problemItem.SetText(0, item.Text); + problemItem.SetTooltipText(0, item.TooltipText); + problemItem.SetMetadata(0, i); + + var color = GetProblemItemColor(diagnostic); + if (color.HasValue) + problemItem.SetCustomColor(0, color.Value); + } + } + + private void UpdateProblemsTree() + { + _problemsTree.Clear(); + + var root = _problemsTree.CreateItem(); + + var groupedDiagnostics = _diagnostics.Select((d, i) => (Diagnostic: d, Index: i)) + .Where(x => ShouldDisplayDiagnostic(x.Diagnostic)) + .GroupBy(x => x.Diagnostic.ProjectFile) + .Select(g => (ProjectFile: g.Key, Diagnostics: g.GroupBy(x => x.Diagnostic.File) + .Select(x => (File: x.Key, Diagnostics: x.ToArray())))) + .ToArray(); + + if (groupedDiagnostics.Length == 0) + return; + + foreach (var (projectFile, projectDiagnostics) in groupedDiagnostics) + { + TreeItem projectItem; + + if (groupedDiagnostics.Length == 1) + { + // Don't create a project item if there's only one project. + projectItem = root; + } + else + { + string projectFilePath = !string.IsNullOrEmpty(projectFile) + ? projectFile + : "Unknown project".TTR(); + projectItem = _problemsTree.CreateItem(root); + projectItem.SetText(0, projectFilePath); + projectItem.SetSelectable(0, false); + } + + foreach (var (file, fileDiagnostics) in projectDiagnostics) + { + if (fileDiagnostics.Length == 0) + continue; + + string? projectDir = Path.GetDirectoryName(projectFile); + string relativeFilePath = !string.IsNullOrEmpty(file) && !string.IsNullOrEmpty(projectDir) + ? Path.GetRelativePath(projectDir, file) + : "Unknown file".TTR(); + + string fileItemText = string.Format("{0} ({1} issues)".TTR(), relativeFilePath, fileDiagnostics.Length); + + var fileItem = _problemsTree.CreateItem(projectItem); + fileItem.SetText(0, fileItemText); + fileItem.SetSelectable(0, false); + + foreach (var (diagnostic, index) in fileDiagnostics) + { + var item = CreateProblemItem(diagnostic); + + var problemItem = _problemsTree.CreateItem(fileItem); + problemItem.SetIcon(0, item.Icon); + problemItem.SetText(0, item.Text); + problemItem.SetTooltipText(0, item.TooltipText); + problemItem.SetMetadata(0, index); + + var color = GetProblemItemColor(diagnostic); + if (color.HasValue) + problemItem.SetCustomColor(0, color.Value); + } + } + } + } + + private class ProblemItem + { + public string? Text { get; set; } + public string? TooltipText { get; set; } + public Texture2D? Icon { get; set; } + } + + private ProblemItem CreateProblemItem(BuildDiagnostic diagnostic, bool includeFileInText = false) + { + var text = new StringBuilder(); + var tooltip = new StringBuilder(); + + ReadOnlySpan<char> shortMessage = diagnostic.Message.AsSpan(); + int lineBreakIdx = shortMessage.IndexOf('\n'); + if (lineBreakIdx != -1) + shortMessage = shortMessage[..lineBreakIdx]; + text.Append(shortMessage); + + tooltip.Append($"Message: {diagnostic.Message}"); + + if (!string.IsNullOrEmpty(diagnostic.Code)) + tooltip.Append($"\nCode: {diagnostic.Code}"); + + string type = diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Hidden => "hidden", + BuildDiagnostic.DiagnosticType.Info => "info", + BuildDiagnostic.DiagnosticType.Warning => "warning", + BuildDiagnostic.DiagnosticType.Error => "error", + _ => "unknown", + }; + tooltip.Append($"\nType: {type}"); + + if (!string.IsNullOrEmpty(diagnostic.File)) + { + text.Append(' '); + if (includeFileInText) + { + text.Append(diagnostic.File); + } + + text.Append($"({diagnostic.Line},{diagnostic.Column})"); + + tooltip.Append($"\nFile: {diagnostic.File}"); + tooltip.Append($"\nLine: {diagnostic.Line}"); + tooltip.Append($"\nColumn: {diagnostic.Column}"); + } + + if (!string.IsNullOrEmpty(diagnostic.ProjectFile)) + tooltip.Append($"\nProject: {diagnostic.ProjectFile}"); + + return new ProblemItem() + { + Text = text.ToString(), + TooltipText = tooltip.ToString(), + Icon = diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("Warning", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("Error", "EditorIcons"), + _ => null, + }, + }; + } + + public override void _Ready() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); + + Name = "Problems".TTR(); + + var vbLeft = new VBoxContainer + { + CustomMinimumSize = new Vector2(0, 180 * EditorScale), + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + AddChild(vbLeft); + + // Problem Tree. + _problemsTree = new Tree + { + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + AllowRmbSelect = true, + HideRoot = true, + }; + _problemsTree.ItemActivated += GoToSelectedProblem; + _problemsTree.ItemMouseSelected += ShowProblemContextMenu; + vbLeft.AddChild(_problemsTree); + + // Problem context menu. + _problemsContextMenu = new PopupMenu(); + _problemsContextMenu.IdPressed += ProblemContextOptionPressed; + _problemsTree.AddChild(_problemsContextMenu); + + // Search box. + _searchBox = new LineEdit + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + PlaceholderText = "Filter Problems".TTR(), + ClearButtonEnabled = true, + }; + _searchBox.TextChanged += SearchTextChanged; + vbLeft.AddChild(_searchBox); + + var vbRight = new VBoxContainer(); + AddChild(vbRight); + + // Tools grid. + var hbTools = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + vbRight.AddChild(hbTools); + + // Clear. + _clearButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K), + ShortcutContext = this, + }; + _clearButton.Pressed += Clear; + hbTools.AddChild(_clearButton); + + // Copy. + _copyButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C), + ShortcutContext = this, + }; + _copyButton.Pressed += CopySelectedProblems; + hbTools.AddChild(_copyButton); + + // A second hbox to make a 2x2 grid of buttons. + var hbTools2 = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + }; + vbRight.AddChild(hbTools2); + + // Toggle List/Tree. + _toggleLayoutButton = new Button + { + Flat = true, + FocusMode = FocusModeEnum.None, + TooltipText = GetToggleLayoutTooltipText(), + ToggleMode = true, + ButtonPressed = GetToggleLayoutPressedState(), + }; + // Don't tint the icon even when in "pressed" state. + _toggleLayoutButton.AddThemeColorOverride("icon_pressed_color", Colors.White); + _toggleLayoutButton.Toggled += ToggleLayout; + hbTools2.AddChild(_toggleLayoutButton); + + // Show Search. + _showSearchButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + ToggleMode = true, + ButtonPressed = true, + Shortcut = EditorDefShortcut("editor/open_search", "Focus Search/Filter Bar".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.F), + ShortcutContext = this, + }; + _showSearchButton.Toggled += ToggleSearchBoxVisibility; + hbTools2.AddChild(_showSearchButton); + + // Diagnostic Type Filters. + vbRight.AddChild(new HSeparator()); + + var infoFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Info); + infoFilter.ToggleButton.TooltipText = "Toggle visibility of info diagnostics.".TTR(); + infoFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(infoFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Info] = infoFilter; + + var errorFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Error); + errorFilter.ToggleButton.TooltipText = "Toggle visibility of errors.".TTR(); + errorFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(errorFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Error] = errorFilter; + + var warningFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Warning); + warningFilter.ToggleButton.TooltipText = "Toggle visibility of warnings.".TTR(); + warningFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(warningFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Warning] = warningFilter; + + UpdateTheme(); + + UpdateProblemsView(); + } + + public override void _Notification(int what) + { + base._Notification(what); + + switch ((long)what) + { + case EditorSettings.NotificationEditorSettingsChanged: + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); + _toggleLayoutButton.ButtonPressed = GetToggleLayoutPressedState(); + UpdateProblemsView(); + break; + + case NotificationThemeChanged: + UpdateTheme(); + break; + } + } + + private void UpdateTheme() + { + // Nodes will be null until _Ready is called. + if (_clearButton == null) + return; + + foreach (var (type, filter) in _filtersByType) + { + filter.ToggleButton.Icon = type switch + { + BuildDiagnostic.DiagnosticType.Info => GetThemeIcon("Popup", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("StatusWarning", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("StatusError", "EditorIcons"), + _ => null, + }; + } + + _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons"); + _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons"); + _toggleLayoutButton.Icon = GetToggleLayoutIcon(); + _showSearchButton.Icon = GetThemeIcon("Search", "EditorIcons"); + _searchBox.RightIcon = GetThemeIcon("Search", "EditorIcons"); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index cc11132a55..bae87dd1dd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -5,28 +5,73 @@ using GodotTools.Internals; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; +#nullable enable + namespace GodotTools.Build { - public partial class MSBuildPanel : VBoxContainer + public partial class MSBuildPanel : MarginContainer, ISerializationListener { - public BuildOutputView BuildOutputView { get; private set; } + [Signal] + public delegate void BuildStateChangedEventHandler(); + +#nullable disable + private MenuButton _buildMenuButton; + private Button _openLogsFolderButton; + + private BuildProblemsView _problemsView; + private BuildOutputView _outputView; +#nullable enable + + public BuildInfo? LastBuildInfo { get; private set; } + public bool IsBuildingOngoing { get; private set; } + public BuildResult? BuildResult { get; private set; } - private MenuButton _buildMenuBtn; - private Button _errorsBtn; - private Button _warningsBtn; - private Button _viewLogBtn; - private Button _openLogsFolderBtn; + private readonly object _pendingBuildLogTextLock = new object(); + private string _pendingBuildLogText = string.Empty; - private void WarningsToggled(bool pressed) + public Texture2D? GetBuildStateIcon() { - BuildOutputView.WarningsVisible = pressed; - BuildOutputView.UpdateIssuesList(); + if (IsBuildingOngoing) + return GetThemeIcon("Stop", "EditorIcons"); + + if (_problemsView.WarningCount > 0 && _problemsView.ErrorCount > 0) + return GetThemeIcon("ErrorWarning", "EditorIcons"); + + if (_problemsView.WarningCount > 0) + return GetThemeIcon("Warning", "EditorIcons"); + + if (_problemsView.ErrorCount > 0) + return GetThemeIcon("Error", "EditorIcons"); + + return null; } - private void ErrorsToggled(bool pressed) + private enum BuildMenuOptions { - BuildOutputView.ErrorsVisible = pressed; - BuildOutputView.UpdateIssuesList(); + BuildProject, + RebuildProject, + CleanProject, + } + + private void BuildMenuOptionPressed(long id) + { + switch ((BuildMenuOptions)id) + { + case BuildMenuOptions.BuildProject: + BuildProject(); + break; + + case BuildMenuOptions.RebuildProject: + RebuildProject(); + break; + + case BuildMenuOptions.CleanProject: + CleanProject(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + } } public void BuildProject() @@ -73,108 +118,136 @@ namespace GodotTools.Build _ = BuildManager.CleanProjectBlocking("Debug"); } - private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed; - - private void OpenLogsFolderPressed() => OS.ShellOpen( + private void OpenLogsFolder() => OS.ShellOpen( $"file://{GodotSharpDirs.LogsDirPathFor("Debug")}" ); - private void BuildMenuOptionPressed(long id) + private void BuildLaunchFailed(BuildInfo buildInfo, string cause) { - switch ((BuildMenuOptions)id) + IsBuildingOngoing = false; + BuildResult = Build.BuildResult.Error; + + _problemsView.Clear(); + _outputView.Clear(); + + var diagnostic = new BuildDiagnostic { - case BuildMenuOptions.BuildProject: - BuildProject(); - break; - case BuildMenuOptions.RebuildProject: - RebuildProject(); - break; - case BuildMenuOptions.CleanProject: - CleanProject(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + Type = BuildDiagnostic.DiagnosticType.Error, + Message = cause, + }; + + _problemsView.SetDiagnostics(new[] { diagnostic }); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void BuildStarted(BuildInfo buildInfo) + { + LastBuildInfo = buildInfo; + IsBuildingOngoing = true; + BuildResult = null; + + _problemsView.Clear(); + _outputView.Clear(); + + _problemsView.UpdateProblemsView(); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void BuildFinished(BuildResult result) + { + IsBuildingOngoing = false; + BuildResult = result; + + string csvFile = Path.Combine(LastBuildInfo!.LogsDirPath, BuildManager.MsBuildIssuesFileName); + _problemsView.SetDiagnosticsFromFile(csvFile); + + _problemsView.UpdateProblemsView(); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void UpdateBuildLogText() + { + lock (_pendingBuildLogTextLock) + { + _outputView.Append(_pendingBuildLogText); + _pendingBuildLogText = string.Empty; } } - private enum BuildMenuOptions + private void StdOutputReceived(string text) { - BuildProject, - RebuildProject, - CleanProject + lock (_pendingBuildLogTextLock) + { + if (_pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + _pendingBuildLogText += text + "\n"; + } + } + + private void StdErrorReceived(string text) + { + lock (_pendingBuildLogTextLock) + { + if (_pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + _pendingBuildLogText += text + "\n"; + } } public override void _Ready() { base._Ready(); - CustomMinimumSize = new Vector2(0, 228 * EditorScale); - SizeFlagsVertical = SizeFlags.ExpandFill; + var bottomPanelStylebox = EditorInterface.Singleton.GetBaseControl().GetThemeStylebox("BottomPanel", "EditorStyles"); + AddThemeConstantOverride("margin_top", -(int)bottomPanelStylebox.ContentMarginTop); + AddThemeConstantOverride("margin_left", -(int)bottomPanelStylebox.ContentMarginLeft); + AddThemeConstantOverride("margin_right", -(int)bottomPanelStylebox.ContentMarginRight); + + var tabs = new TabContainer(); + AddChild(tabs); - var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; - AddChild(toolBarHBox); + var tabActions = new HBoxContainer + { + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + Alignment = BoxContainer.AlignmentMode.End, + }; + tabActions.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + tabs.GetTabBar().AddChild(tabActions); - _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("BuildCSharp", "EditorIcons") }; - toolBarHBox.AddChild(_buildMenuBtn); + _buildMenuButton = new MenuButton + { + TooltipText = "Build".TTR(), + Flat = true, + }; + tabActions.AddChild(_buildMenuButton); - var buildMenu = _buildMenuBtn.GetPopup(); + var buildMenu = _buildMenuButton.GetPopup(); buildMenu.AddItem("Build Project".TTR(), (int)BuildMenuOptions.BuildProject); buildMenu.AddItem("Rebuild Project".TTR(), (int)BuildMenuOptions.RebuildProject); buildMenu.AddItem("Clean Project".TTR(), (int)BuildMenuOptions.CleanProject); buildMenu.IdPressed += BuildMenuOptionPressed; - _errorsBtn = new Button + _openLogsFolderButton = new Button { - TooltipText = "Show Errors".TTR(), - Icon = GetThemeIcon("StatusError", "EditorIcons"), - ExpandIcon = false, - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None + TooltipText = "Show Logs in File Manager".TTR(), + Flat = true, }; - _errorsBtn.Toggled += ErrorsToggled; - toolBarHBox.AddChild(_errorsBtn); + _openLogsFolderButton.Pressed += OpenLogsFolder; + tabActions.AddChild(_openLogsFolderButton); - _warningsBtn = new Button - { - TooltipText = "Show Warnings".TTR(), - Icon = GetThemeIcon("NodeWarning", "EditorIcons"), - ExpandIcon = false, - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None - }; - _warningsBtn.Toggled += WarningsToggled; - toolBarHBox.AddChild(_warningsBtn); + _problemsView = new BuildProblemsView(); + tabs.AddChild(_problemsView); - _viewLogBtn = new Button - { - Text = "Show Output".TTR(), - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None - }; - _viewLogBtn.Toggled += ViewLogToggled; - toolBarHBox.AddChild(_viewLogBtn); - - // Horizontal spacer, push everything to the right. - toolBarHBox.AddChild(new Control - { - SizeFlagsHorizontal = SizeFlags.ExpandFill, - }); + _outputView = new BuildOutputView(); + tabs.AddChild(_outputView); - _openLogsFolderBtn = new Button - { - Text = "Show Logs in File Manager".TTR(), - Icon = GetThemeIcon("Filesystem", "EditorIcons"), - ExpandIcon = false, - FocusMode = FocusModeEnum.None, - }; - _openLogsFolderBtn.Pressed += OpenLogsFolderPressed; - toolBarHBox.AddChild(_openLogsFolderBtn); + UpdateTheme(); - BuildOutputView = new BuildOutputView(); - AddChild(BuildOutputView); + AddBuildEventListeners(); } public override void _Notification(int what) @@ -183,13 +256,49 @@ namespace GodotTools.Build if (what == NotificationThemeChanged) { - if (_buildMenuBtn != null) - _buildMenuBtn.Icon = GetThemeIcon("BuildCSharp", "EditorIcons"); - if (_errorsBtn != null) - _errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); - if (_warningsBtn != null) - _warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); + UpdateTheme(); } } + + private void UpdateTheme() + { + // Nodes will be null until _Ready is called. + if (_buildMenuButton == null) + return; + + _buildMenuButton.Icon = GetThemeIcon("BuildCSharp", "EditorIcons"); + _openLogsFolderButton.Icon = GetThemeIcon("Filesystem", "EditorIcons"); + } + + private void AddBuildEventListeners() + { + BuildManager.BuildLaunchFailed += BuildLaunchFailed; + BuildManager.BuildStarted += BuildStarted; + BuildManager.BuildFinished += BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred. + BuildManager.StdOutputReceived += StdOutputReceived; + BuildManager.StdErrorReceived += StdErrorReceived; + } + + public void OnBeforeSerialize() + { + // In case it didn't update yet. We don't want to have to serialize any pending output. + UpdateBuildLogText(); + + // NOTE: + // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. + // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. + BuildManager.BuildLaunchFailed -= BuildLaunchFailed; + BuildManager.BuildStarted -= BuildStarted; + BuildManager.BuildFinished -= BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred + BuildManager.StdOutputReceived -= StdOutputReceived; + BuildManager.StdErrorReceived -= StdErrorReceived; + } + + public void OnAfterDeserialize() + { + AddBuildEventListeners(); // Re-add them. + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 1c1185d91b..a00c812c79 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -30,6 +30,7 @@ namespace GodotTools public const string VerbosityLevel = "dotnet/build/verbosity_level"; public const string NoConsoleLogging = "dotnet/build/no_console_logging"; public const string CreateBinaryLog = "dotnet/build/create_binary_log"; + public const string ProblemsLayout = "dotnet/build/problems_layout"; } private EditorSettings _editorSettings; @@ -64,6 +65,7 @@ namespace GodotTools private bool CreateProjectSolution() { + string errorMessage = null; using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2)) { pr.Step("Generating C# project...".TTR()); @@ -95,22 +97,23 @@ namespace GodotTools } catch (IOException e) { - ShowErrorDialog("Failed to save solution. Exception message: ".TTR() + e.Message); - return false; + errorMessage = "Failed to save solution. Exception message: ".TTR() + e.Message; } - - pr.Step("Done".TTR()); - - // Here, after all calls to progress_task_step - CallDeferred(nameof(_ShowDotnetFeatures)); } else { - ShowErrorDialog("Failed to create C# project.".TTR()); + errorMessage = "Failed to create C# project.".TTR(); } + } - return true; + if (!string.IsNullOrEmpty(errorMessage)) + { + ShowErrorDialog(errorMessage); + return false; } + + _ShowDotnetFeatures(); + return true; } private void _ShowDotnetFeatures() @@ -160,14 +163,14 @@ namespace GodotTools { _errorDialog.Title = title; _errorDialog.DialogText = message; - _errorDialog.PopupCentered(); + EditorInterface.Singleton.PopupDialogCentered(_errorDialog); } public void ShowConfirmCreateSlnDialog() { _confirmCreateSlnDialog.Title = "C# solution already exists. This will override the existing C# project file, any manual changes will be lost.".TTR(); _confirmCreateSlnDialog.DialogText = "Create C# solution".TTR(); - _confirmCreateSlnDialog.PopupCentered(); + EditorInterface.Singleton.PopupDialogCentered(_confirmCreateSlnDialog); } private static string _vsCodePath = string.Empty; @@ -190,6 +193,9 @@ namespace GodotTools case ExternalEditorId.CustomEditor: { string file = ProjectSettings.GlobalizePath(script.ResourcePath); + string project = ProjectSettings.GlobalizePath("res://"); + // Since ProjectSettings.GlobalizePath replaces only "res:/", leaving a trailing slash, it is removed here. + project = project[..^1]; var execCommand = _editorSettings.GetSetting(Settings.CustomExecPath).As<string>(); var execArgs = _editorSettings.GetSetting(Settings.CustomExecPathArgs).As<string>(); var args = new List<string>(); @@ -226,6 +232,7 @@ namespace GodotTools hasFileFlag = true; } + arg = arg.ReplaceN("{project}", project); arg = arg.ReplaceN("{file}", file); args.Add(arg); @@ -433,7 +440,7 @@ namespace GodotTools private void BuildStateChanged() { if (_bottomPanelBtn != null) - _bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; + _bottomPanelBtn.Icon = MSBuildPanel.GetBuildStateIcon(); } public override void _EnablePlugin() @@ -478,15 +485,14 @@ namespace GodotTools _editorSettings = EditorInterface.Singleton.GetEditorSettings(); _errorDialog = new AcceptDialog(); - editorBaseControl.AddChild(_errorDialog); + _errorDialog.SetUnparentWhenInvisible(true); _confirmCreateSlnDialog = new ConfirmationDialog(); + _confirmCreateSlnDialog.SetUnparentWhenInvisible(true); _confirmCreateSlnDialog.Confirmed += () => CreateProjectSolution(); - editorBaseControl.AddChild(_confirmCreateSlnDialog); MSBuildPanel = new MSBuildPanel(); - MSBuildPanel.Ready += () => - MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; + MSBuildPanel.BuildStateChanged += BuildStateChanged; _bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" }); @@ -531,6 +537,7 @@ namespace GodotTools EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal)); EditorDef(Settings.NoConsoleLogging, false); EditorDef(Settings.CreateBinaryLog, false); + EditorDef(Settings.ProblemsLayout, Variant.From(BuildProblemsView.ProblemsLayout.Tree)); string settingsHintStr = "Disabled"; @@ -589,6 +596,14 @@ namespace GodotTools ["hint_string"] = string.Join(",", verbosityLevels), }); + _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = (int)Variant.Type.Int, + ["name"] = Settings.ProblemsLayout, + ["hint"] = (int)PropertyHint.Enum, + ["hint_string"] = "View as List,View as Tree", + }); + OnSettingsChanged(); _editorSettings.SettingsChanged += OnSettingsChanged; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 839846f963..36fdda4625 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -94,6 +94,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define ICALL_PREFIX "godot_icall_" #define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method" +#define ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "ClassDB_get_method_with_compatibility" #define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor" #define C_LOCAL_RET "ret" @@ -263,7 +264,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } else if (code_tag) { xml_output.append("["); pos = brk_pos + 1; - } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) { + } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) { const int tag_end = tag.find(" "); const String link_tag = tag.substr(0, tag_end); const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); @@ -297,6 +298,12 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf if (link_tag == "method") { _append_xml_method(xml_output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "constructor") { + // TODO: Support constructors? + _append_xml_undeclared(xml_output, link_target); + } else if (link_tag == "operator") { + // TODO: Support operators? + _append_xml_undeclared(xml_output, link_target); } else if (link_tag == "member") { _append_xml_member(xml_output, target_itype, target_cname, link_target, link_target_parts); } else if (link_tag == "signal") { @@ -400,29 +407,29 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf pos = brk_end + 1; tag_stack.push_front(tag); - } else if (tag == "code") { + } else if (tag == "code" || tag.begins_with("code ")) { xml_output.append("<c>"); code_tag = true; pos = brk_end + 1; - tag_stack.push_front(tag); - } else if (tag == "codeblock") { + tag_stack.push_front("code"); + } else if (tag == "codeblock" || tag.begins_with("codeblock ")) { xml_output.append("<code>"); code_tag = true; pos = brk_end + 1; - tag_stack.push_front(tag); + tag_stack.push_front("codeblock"); } else if (tag == "codeblocks") { line_del = true; pos = brk_end + 1; tag_stack.push_front(tag); - } else if (tag == "csharp") { + } else if (tag == "csharp" || tag.begins_with("csharp ")) { xml_output.append("<code>"); line_del = false; code_tag = true; pos = brk_end + 1; - tag_stack.push_front(tag); + tag_stack.push_front("csharp"); } else if (tag == "kbd") { // keyboard combinations are not supported in xml comments pos = brk_end + 1; @@ -1398,6 +1405,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); output.append("using System;\n"); // IntPtr + output.append("using System.ComponentModel;\n"); // EditorBrowsable output.append("using System.Diagnostics;\n"); // DebuggerBrowsable output.append("using Godot.NativeInterop;\n"); @@ -1865,7 +1873,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output << "\n" << INDENT1 "{\n"; + HashMap<String, StringName> method_names; for (const MethodInterface &imethod : itype.methods) { + if (method_names.has(imethod.proxy_name)) { + ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value."); + continue; + } + method_names[imethod.proxy_name] = imethod.cname; output << INDENT2 "/// <summary>\n" << INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n" << INDENT2 "/// </summary>\n" @@ -2114,7 +2128,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf arguments_sig += iarg.name; - if (iarg.default_argument.size()) { + if (!p_imethod.is_compat && iarg.default_argument.size()) { if (iarg.def_param_mode != ArgumentInterface::CONSTANT) { arguments_sig += " = null"; } else { @@ -2202,8 +2216,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output << "GodotObject."; } - p_output << ICALL_CLASSDB_GET_METHOD "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." - << p_imethod.proxy_name + p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." + << p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul" << ");\n"; } @@ -2242,6 +2256,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("\")]"); } + if (p_imethod.is_compat) { + p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); + } + p_output.append(MEMBER_BEGIN); p_output.append(p_imethod.is_internal ? "internal " : "public "); @@ -2958,6 +2976,12 @@ bool method_has_ptr_parameter(MethodInfo p_method_info) { return false; } +struct SortMethodWithHashes { + _FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const { + return p_a.first < p_b.first; + } +}; + bool BindingsGenerator::_populate_object_type_interfaces() { obj_types.clear(); @@ -3085,11 +3109,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { List<MethodInfo> virtual_method_list; ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true); - List<MethodInfo> method_list; - ClassDB::get_method_list(type_cname, &method_list, true); - method_list.sort(); + List<Pair<MethodInfo, uint32_t>> method_list_with_hashes; + ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true); + method_list_with_hashes.sort_custom_inplace<SortMethodWithHashes>(); + + List<MethodInterface> compat_methods; + for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) { + const MethodInfo &method_info = E.first; + const uint32_t hash = E.second; - for (const MethodInfo &method_info : method_list) { int argc = method_info.arguments.size(); if (method_info.name.is_empty()) { @@ -3111,6 +3139,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { MethodInterface imethod; imethod.name = method_info.name; imethod.cname = cname; + imethod.hash = hash; if (method_info.flags & METHOD_FLAG_STATIC) { imethod.is_static = true; @@ -3123,7 +3152,17 @@ bool BindingsGenerator::_populate_object_type_interfaces() { PropertyInfo return_info = method_info.return_val; - MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name); + MethodBind *m = nullptr; + + if (!imethod.is_virtual) { + bool method_exists = false; + m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat); + + if (unlikely(!method_exists)) { + ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false, + "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'."); + } + } imethod.is_vararg = m && m->is_vararg(); @@ -3244,6 +3283,14 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false, "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'."); + // Compat methods aren't added to the type yet, they need to be checked for conflicts + // after all the non-compat methods have been added. The compat methods are added in + // reverse so the most recently added ones take precedence over older compat methods. + if (imethod.is_compat) { + compat_methods.push_front(imethod); + continue; + } + // Methods starting with an underscore are ignored unless they're used as a property setter or getter if (!imethod.is_virtual && imethod.name[0] == '_') { for (const PropertyInterface &iprop : itype.properties) { @@ -3258,6 +3305,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + // Add compat methods that don't conflict with other methods in the type. + for (const MethodInterface &imethod : compat_methods) { + if (_method_has_conflicting_signature(imethod, itype)) { + WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored."); + continue; + } + itype.methods.push_back(imethod); + } + // Populate signals const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; @@ -4039,6 +4095,50 @@ void BindingsGenerator::_populate_global_constants() { } } +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) { + // Compare p_imethod with all the methods already registered in p_itype. + for (const MethodInterface &method : p_itype.methods) { + if (method.proxy_name == p_imethod.proxy_name) { + if (_method_has_conflicting_signature(p_imethod, method)) { + return true; + } + } + } + + return false; +} + +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) { + // Check if a method already exists in p_itype with a method signature that would conflict with p_imethod. + // The return type is ignored because only changing the return type is not enough to avoid conflicts. + // The const keyword is also ignored since it doesn't generate different C# code. + + if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) { + // Different argument count, so no conflict. + return false; + } + + for (int i = 0; i < p_imethod_left.arguments.size(); i++) { + const ArgumentInterface &iarg_left = p_imethod_left.arguments[i]; + const ArgumentInterface &iarg_right = p_imethod_right.arguments[i]; + + if (iarg_left.type.cname != iarg_right.type.cname) { + // Different types for arguments in the same position, so no conflict. + return false; + } + + if (iarg_left.def_param_mode != iarg_right.def_param_mode) { + // If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T' + // and will not create a conflict. + if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + return false; + } + } + } + + return true; +} + void BindingsGenerator::_initialize_blacklisted_methods() { blacklisted_methods["Object"].push_back("to_string"); // there is already ToString blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 6118576bb6..aa4e5ea093 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -134,6 +134,11 @@ class BindingsGenerator { String proxy_name; /** + * Hash of the ClassDB method + */ + uint64_t hash = 0; + + /** * [TypeInterface::name] of the return type */ TypeReference return_type; @@ -168,6 +173,12 @@ class BindingsGenerator { */ bool is_internal = false; + /** + * Determines if the method is a compatibility method added to avoid breaking binary compatibility. + * These methods will be generated but hidden and are considered deprecated. + */ + bool is_compat = false; + List<ArgumentInterface> arguments; const DocData::MethodDoc *method_doc = nullptr; @@ -787,6 +798,9 @@ class BindingsGenerator { void _populate_global_constants(); + bool _method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype); + bool _method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right); + Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file); Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output); diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig index d0c660de4c..df4a6c2d0d 100644 --- a/modules/mono/glue/GodotSharp/.editorconfig +++ b/modules/mono/glue/GodotSharp/.editorconfig @@ -1,5 +1,5 @@ [**/Generated/**.cs] -# Validate parameter is non-null before using it +# CA1062: Validate parameter is non-null before using it # Useful for generated code, as it disables nullable dotnet_diagnostic.CA1062.severity = error # CA1069: Enums should not have duplicate values @@ -10,3 +10,8 @@ dotnet_diagnostic.CA1708.severity = none dotnet_diagnostic.CS1591.severity = none # CS1573: Parameter has no matching param tag in the XML comment dotnet_diagnostic.CS1573.severity = none + +[GodotSharp/Core/**.cs] +# CS1591: Missing XML comment for publicly visible type or member +# TODO: Temporary change to not pollute the warnings, but we need to document public APIs +dotnet_diagnostic.CS1591.severity = suggestion diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs index 48b47b166a..bf8b2f10dc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs @@ -7,6 +7,8 @@ using System.ComponentModel; namespace Godot; +#pragma warning disable CS1734 // XML comment on 'X' has a paramref tag for 'Y', but there is no parameter by that name. + partial class AnimationNode { /// <inheritdoc cref="BlendInput(int, double, bool, bool, float, FilterAction, bool, bool)"/> @@ -24,6 +26,44 @@ partial class AnimationNode } } +partial class AnimationPlayer +{ + /// <inheritdoc cref="CallbackModeMethod"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public AnimationMethodCallMode MethodCallMode + { + get => (AnimationMethodCallMode)CallbackModeMethod; + set => CallbackModeMethod = (AnimationCallbackModeMethod)value; + } + + /// <inheritdoc cref="Active"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public bool PlaybackActive + { + get => Active; + set => Active = value; + } + + /// <inheritdoc cref="CallbackModeProcess"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public AnimationProcessCallback PlaybackProcessMode + { + get => (AnimationProcessCallback)CallbackModeProcess; + set => CallbackModeProcess = (AnimationCallbackModeProcess)value; + } +} + +partial class AnimationTree +{ + /// <inheritdoc cref="CallbackModeProcess"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public AnimationProcessCallback ProcessCallback + { + get => (AnimationProcessCallback)CallbackModeProcess; + set => CallbackModeProcess = (AnimationCallbackModeProcess)value; + } +} + partial class CodeEdit { /// <inheritdoc cref="AddCodeCompletionOption(CodeCompletionKind, string, string, Nullable{Color}, Resource, Nullable{Variant}, int)"/> @@ -36,7 +76,7 @@ partial class CodeEdit partial class Geometry3D { - /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Collections.Array{Plane})"/> + /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Godot.Collections.Array{Plane})"/> [EditorBrowsable(EditorBrowsableState.Never)] public static Vector3[] SegmentIntersectsConvex(Vector3 from, Vector3 to, Godot.Collections.Array planes) { @@ -44,6 +84,51 @@ partial class Geometry3D } } +partial class GraphEdit +{ + /// <inheritdoc cref="ShowArrangeButton"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ArrangeNodesButtonHidden + { + get => !ShowArrangeButton; + set => ShowArrangeButton = !value; + } + + /// <inheritdoc cref="GetMenuHBox()"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public HBoxContainer GetZoomHBox() + { + return GetMenuHBox(); + } + + /// <inheritdoc cref="SnappingDistance"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public int SnapDistance + { + get => SnappingDistance; + set => SnappingDistance = value; + } + + /// <inheritdoc cref="SnappingEnabled"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public bool UseSnap + { + get => SnappingEnabled; + set => SnappingEnabled = value; + } +} + +partial class GraphNode +{ + /// <inheritdoc cref="DeleteRequest"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public event Action CloseRequest + { + add => DeleteRequest += value; + remove => DeleteRequest -= value; + } +} + partial class MeshInstance3D { /// <inheritdoc cref="CreateMultipleConvexCollisions(MeshConvexDecompositionSettings)"/> @@ -108,6 +193,19 @@ partial class SurfaceTool } } +partial class TileMap +{ + /// <summary> + /// The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Never)] + public int CellQuadrantSize + { + get => RenderingQuadrantSize; + set => RenderingQuadrantSize = value; + } +} + partial class Tree { /// <inheritdoc cref="EditSelected(bool)"/> @@ -127,3 +225,5 @@ partial class UndoRedo CreateAction(name, mergeMode, backwardUndoOps: false); } } + +#pragma warning restore CS1734 diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index d25944dceb..cc99225a33 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -723,7 +723,7 @@ namespace Godot /// <returns>A hash code for this AABB.</returns> public override readonly int GetHashCode() { - return _position.GetHashCode() ^ _size.GetHashCode(); + return HashCode.Combine(_position, _size); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index d53dd9a9af..a7712db737 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -1123,7 +1123,7 @@ namespace Godot /// <returns>A hash code for this basis.</returns> public override readonly int GetHashCode() { - return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode(); + return HashCode.Combine(Row0, Row1, Row2); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs index 109643c2d4..a78cb0bba9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -25,10 +25,11 @@ namespace Godot.Bridge public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath; public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge; public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass; - public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo; + public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo; public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType; public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList; public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> ScriptManagerBridge_CallStatic; public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> CSharpInstanceBridge_Call; public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Set; public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get; @@ -70,6 +71,7 @@ namespace Godot.Bridge ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType, ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList, ScriptManagerBridge_GetPropertyDefaultValues = &ScriptManagerBridge.GetPropertyDefaultValues, + ScriptManagerBridge_CallStatic = &ScriptManagerBridge.CallStatic, CSharpInstanceBridge_Call = &CSharpInstanceBridge.Call, CSharpInstanceBridge_Set = &CSharpInstanceBridge.Set, CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 0fe4bcdfce..9a7e19024b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -90,7 +91,7 @@ namespace Godot.Bridge internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName, IntPtr godotObject) { - // TODO: Optimize with source generators and delegate pointers + // TODO: Optimize with source generators and delegate pointers. try { @@ -124,13 +125,15 @@ namespace Godot.Bridge IntPtr godotObject, godot_variant** args, int argCount) { - // TODO: Optimize with source generators and delegate pointers + // TODO: Optimize with source generators and delegate pointers. try { // Performance is not critical here as this will be replaced with source generators. Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + Debug.Assert(!scriptType.IsAbstract, $"Cannot create script instance. The class '{scriptType.FullName}' is abstract."); + var ctor = scriptType .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(c => c.GetParameters().Length == argCount) @@ -146,7 +149,7 @@ namespace Godot.Bridge else { throw new MissingMemberException( - $"The class '{scriptType.FullName}' does not define a constructor that takes x parameters."); + $"The class '{scriptType.FullName}' does not define a constructor that takes {argCount} parameters."); } } @@ -597,7 +600,7 @@ namespace Godot.Bridge [UnmanagedCallersOnly] internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_string* outClassName, - godot_bool* outTool, godot_bool* outGlobal, godot_string* outIconPath, + godot_bool* outTool, godot_bool* outGlobal, godot_bool* outAbstract, godot_string* outIconPath, godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript) { @@ -631,9 +634,10 @@ namespace Godot.Bridge var iconAttr = scriptType.GetCustomAttributes(inherit: false) .OfType<IconAttribute>() .FirstOrDefault(); - *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path); + *outAbstract = scriptType.IsAbstract.ToGodotBool(); + // Methods // Performance is not critical here as this will be replaced with source generators. @@ -677,6 +681,8 @@ namespace Godot.Bridge methodInfo.Add("params", methodParams); + methodInfo.Add("flags", (int)method.Flags); + methods.Add(methodInfo); } } @@ -797,6 +803,7 @@ namespace Godot.Bridge *outClassName = default; *outTool = godot_bool.False; *outGlobal = godot_bool.False; + *outAbstract = godot_bool.False; *outIconPath = default; *outMethodsDest = NativeFuncs.godotsharp_array_new(); *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new(); @@ -958,6 +965,54 @@ namespace Godot.Bridge public godot_variant Value; // Not owned } + private delegate bool InvokeGodotClassStaticMethodDelegate(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret); + + [UnmanagedCallersOnly] + internal static unsafe godot_bool CallStatic(IntPtr scriptPtr, godot_string_name* method, + godot_variant** args, int argCount, godot_variant_call_error* refCallError, godot_variant* ret) + { + // TODO: Optimize with source generators and delegate pointers. + + try + { + Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + + Type? top = scriptType; + Type native = GodotObject.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var invokeGodotClassStaticMethod = top.GetMethod( + "InvokeGodotClassStaticMethod", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (invokeGodotClassStaticMethod != null) + { + var invoked = invokeGodotClassStaticMethod.CreateDelegate<InvokeGodotClassStaticMethodDelegate>()( + CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args, argCount), out godot_variant retValue); + if (invoked) + { + *ret = retValue; + return godot_bool.True; + } + } + + top = top.BaseType; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *ret = default; + return godot_bool.False; + } + + *ret = default; + (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD; + return godot_bool.False; + } + [UnmanagedCallersOnly] internal static unsafe void GetPropertyDefaultValues(IntPtr scriptPtr, delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index 5dddb38055..293e680067 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -1308,7 +1308,7 @@ namespace Godot /// <returns>A hash code for this color.</returns> public override readonly int GetHashCode() { - return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); + return HashCode.Combine(R, G, B, A); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index c6337e56ef..43598ca84d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -247,6 +247,18 @@ namespace Godot return methodBind; } + internal static IntPtr ClassDB_get_method_with_compatibility(StringName type, StringName method, ulong hash) + { + var typeSelf = (godot_string_name)type.NativeValue; + var methodSelf = (godot_string_name)method.NativeValue; + IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method_with_compatibility(typeSelf, methodSelf, hash); + + if (methodBind == IntPtr.Zero) + throw new NativeMethodBindNotFoundException(type + "." + method); + + return methodBind; + } + internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) { // for some reason the '??' operator doesn't support 'delegate*' diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index 81b2ffef34..09269508b7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -134,6 +134,38 @@ namespace Godot } /// <summary> + /// Returns the difference between the two angles, + /// in range of -<see cref="Pi"/>, <see cref="Pi"/>. + /// When <paramref name="from"/> and <paramref name="to"/> are opposite, + /// returns -<see cref="Pi"/> if <paramref name="from"/> is smaller than <paramref name="to"/>, + /// or <see cref="Pi"/> otherwise. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The destination angle.</param> + /// <returns>The difference between the two angles.</returns> + public static float AngleDifference(float from, float to) + { + float difference = (to - from) % MathF.Tau; + return ((2.0f * difference) % MathF.Tau) - difference; + } + + /// <summary> + /// Returns the difference between the two angles, + /// in range of -<see cref="Pi"/>, <see cref="Pi"/>. + /// When <paramref name="from"/> and <paramref name="to"/> are opposite, + /// returns -<see cref="Pi"/> if <paramref name="from"/> is smaller than <paramref name="to"/>, + /// or <see cref="Pi"/> otherwise. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The destination angle.</param> + /// <returns>The difference between the two angles.</returns> + public static double AngleDifference(double from, double to) + { + double difference = (to - from) % Math.Tau; + return ((2.0 * difference) % Math.Tau) - difference; + } + + /// <summary> /// Returns the arc sine of <paramref name="s"/> in radians. /// Use to get the angle of sine <paramref name="s"/>. /// </summary> @@ -1093,9 +1125,7 @@ namespace Godot /// <returns>The resulting angle of the interpolation.</returns> public static float LerpAngle(float from, float to, float weight) { - float difference = (to - from) % MathF.Tau; - float distance = ((2 * difference) % MathF.Tau) - difference; - return from + (distance * weight); + return from + AngleDifference(from, to) * weight; } /// <summary> @@ -1110,9 +1140,7 @@ namespace Godot /// <returns>The resulting angle of the interpolation.</returns> public static double LerpAngle(double from, double to, double weight) { - double difference = (to - from) % Math.Tau; - double distance = ((2 * difference) % Math.Tau) - difference; - return from + (distance * weight); + return from + AngleDifference(from, to) * weight; } /// <summary> @@ -1429,6 +1457,38 @@ namespace Godot } /// <summary> + /// Rotates <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> amount. Will not go past <paramref name="to"/>. + /// Similar to <see cref="MoveToward(float, float, float)"/> but interpolates correctly when the angles wrap around <see cref="Tau"/>. + /// If <paramref name="delta"/> is negative, this function will rotate away from <paramref name="to"/>, toward the opposite angle, and will not go past the opposite angle. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The angle to move towards.</param> + /// <param name="delta">The amount to move by.</param> + /// <returns>The angle after moving.</returns> + public static float RotateToward(float from, float to, float delta) + { + float difference = AngleDifference(from, to); + float absDifference = Math.Abs(difference); + return from + Math.Clamp(delta, absDifference - MathF.PI, absDifference) * (difference >= 0.0f ? 1.0f : -1.0f); + } + + /// <summary> + /// Rotates <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> amount. Will not go past <paramref name="to"/>. + /// Similar to <see cref="MoveToward(double, double, double)"/> but interpolates correctly when the angles wrap around <see cref="Tau"/>. + /// If <paramref name="delta"/> is negative, this function will rotate away from <paramref name="to"/>, toward the opposite angle, and will not go past the opposite angle. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The angle to move towards.</param> + /// <param name="delta">The amount to move by.</param> + /// <returns>The angle after moving.</returns> + public static double RotateToward(double from, double to, double delta) + { + double difference = AngleDifference(from, to); + double absDifference = Math.Abs(difference); + return from + Math.Clamp(delta, absDifference - Math.PI, absDifference) * (difference >= 0.0 ? 1.0 : -1.0); + } + + /// <summary> /// Rounds <paramref name="s"/> to the nearest whole number, /// with halfway cases rounded towards the nearest multiple of two. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index a656c5de90..dc53e48bd0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -206,7 +206,7 @@ namespace Godot.NativeInterop } 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."; + return $"Invalid call to {where}. Expected {error.Expected} 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: diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index d42ee15657..d181bf2c0f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -42,6 +42,9 @@ namespace Godot.NativeInterop public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname, in godot_string_name p_methodname); + public static partial IntPtr godotsharp_method_bind_get_method_with_compatibility( + in godot_string_name p_classname, in godot_string_name p_methodname, ulong p_hash); + public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor( in godot_string_name p_classname); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 3c7455a76c..85b2b02c45 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -414,7 +414,7 @@ namespace Godot /// <returns>A hash code for this plane.</returns> public override readonly int GetHashCode() { - return _normal.GetHashCode() ^ _d.GetHashCode(); + return HashCode.Combine(_normal, _d); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index 998a2786a7..a80d202ef2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -1001,7 +1001,7 @@ namespace Godot /// <returns>A hash code for this projection.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 2e282447bd..39e1b7e4b8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -800,7 +800,7 @@ namespace Godot /// <returns>A hash code for this quaternion.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 458802f95d..babb26960b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -459,7 +459,7 @@ namespace Godot /// <returns>A hash code for this rect.</returns> public override readonly int GetHashCode() { - return _position.GetHashCode() ^ _size.GetHashCode(); + return HashCode.Combine(_position, _size); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs index 2099d0abca..49fba02b54 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs @@ -419,7 +419,7 @@ namespace Godot /// <returns>A hash code for this rect.</returns> public override readonly int GetHashCode() { - return _position.GetHashCode() ^ _size.GetHashCode(); + return HashCode.Combine(_position, _size); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 618c892681..0e3e54a0c2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -626,7 +626,7 @@ namespace Godot /// <returns>A hash code for this transform.</returns> public override readonly int GetHashCode() { - return X.GetHashCode() ^ Y.GetHashCode() ^ Origin.GetHashCode(); + return HashCode.Combine(X, Y, Origin); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index b16e6e592e..7b27071df1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -653,7 +653,7 @@ namespace Godot /// <returns>A hash code for this transform.</returns> public override readonly int GetHashCode() { - return Basis.GetHashCode() ^ Origin.GetHashCode(); + return HashCode.Combine(Basis, Origin); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 642ef231f3..4842dbc9af 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -1000,7 +1000,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode(); + return HashCode.Combine(X, Y); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs index 231e791904..4ee452455e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs @@ -182,6 +182,9 @@ namespace Godot } // Constants + private static readonly Vector2I _min = new Vector2I(int.MinValue, int.MinValue); + private static readonly Vector2I _max = new Vector2I(int.MaxValue, int.MaxValue); + private static readonly Vector2I _zero = new Vector2I(0, 0); private static readonly Vector2I _one = new Vector2I(1, 1); @@ -191,6 +194,17 @@ namespace Godot private static readonly Vector2I _left = new Vector2I(-1, 0); /// <summary> + /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector2.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector2I(int.MinValue, int.MinValue)</c>.</value> + public static Vector2I Min { get { return _min; } } + /// <summary> + /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector2.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector2I(int.MaxValue, int.MaxValue)</c>.</value> + public static Vector2I Max { get { return _max; } } + + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> /// <value>Equivalent to <c>new Vector2I(0, 0)</c>.</value> @@ -542,7 +556,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode(); + return HashCode.Combine(X, Y); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 7d548f1d10..d26d4662a0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -265,7 +265,7 @@ namespace Godot return new Vector3( Mathf.BezierDerivative(X, control1.X, control2.X, end.X, t), Mathf.BezierDerivative(Y, control1.Y, control2.Y, end.Y, t), - Mathf.BezierDerivative(Z, control1.Z, control2.Z, end.Y, t) + Mathf.BezierDerivative(Z, control1.Z, control2.Z, end.Z, t) ); } @@ -1102,7 +1102,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode(); + return HashCode.Combine(X, Y, Z); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs index 8543052f56..db8ceb30e9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs @@ -193,6 +193,9 @@ namespace Godot } // Constants + private static readonly Vector3I _min = new Vector3I(int.MinValue, int.MinValue, int.MinValue); + private static readonly Vector3I _max = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue); + private static readonly Vector3I _zero = new Vector3I(0, 0, 0); private static readonly Vector3I _one = new Vector3I(1, 1, 1); @@ -204,6 +207,17 @@ namespace Godot private static readonly Vector3I _back = new Vector3I(0, 0, 1); /// <summary> + /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector3.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector3I(int.MinValue, int.MinValue, int.MinValue)</c>.</value> + public static Vector3I Min { get { return _min; } } + /// <summary> + /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector3.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value> + public static Vector3I Max { get { return _max; } } + + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> /// <value>Equivalent to <c>new Vector3I(0, 0, 0)</c>.</value> @@ -597,7 +611,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode(); + return HashCode.Combine(X, Y, Z); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 10a0b14162..eeaef5e46e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -884,7 +884,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs index f813903177..e75e996b04 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs @@ -228,10 +228,24 @@ namespace Godot } // Constants + private static readonly Vector4I _min = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue); + private static readonly Vector4I _max = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue); + private static readonly Vector4I _zero = new Vector4I(0, 0, 0, 0); private static readonly Vector4I _one = new Vector4I(1, 1, 1, 1); /// <summary> + /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector4.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue)</c>.</value> + public static Vector4I Min { get { return _min; } } + /// <summary> + /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector4.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value> + public static Vector4I Max { get { return _max; } } + + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> /// <value>Equivalent to <c>new Vector4I(0, 0, 0, 0)</c>.</value> @@ -618,7 +632,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 8a36b3e514..a55b8d693b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -11,9 +11,6 @@ <LangVersion>10</LangVersion> <AnalysisMode>Recommended</AnalysisMode> - - <!-- Disabled temporarily as it pollutes the warnings, but we need to document public APIs. --> - <NoWarn>CS1591</NoWarn> </PropertyGroup> <PropertyGroup> <Description>Godot C# Core API.</Description> diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 24a9d4030a..3518507f8c 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -68,6 +68,10 @@ MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, con return ClassDB::get_method(*p_classname, *p_methodname); } +MethodBind *godotsharp_method_bind_get_method_with_compatibility(const StringName *p_classname, const StringName *p_methodname, uint64_t p_hash) { + return ClassDB::get_method_with_compatibility(*p_classname, *p_methodname, p_hash); +} + godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) { ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(*p_classname); if (class_info) { @@ -1416,6 +1420,7 @@ void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) { static const void *unmanaged_callbacks[]{ (void *)godotsharp_dotnet_module_is_initialized, (void *)godotsharp_method_bind_get_method, + (void *)godotsharp_method_bind_get_method_with_compatibility, (void *)godotsharp_get_class_constructor, (void *)godotsharp_engine_get_singleton, (void *)godotsharp_stack_info_vector_resize, diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 8fdf163b26..ef4e32e4a7 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -42,7 +42,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { #define CHECK_CALLBACK_NOT_NULL_IMPL(m_var, m_class, m_method) \ { \ - ERR_FAIL_COND_MSG(m_var == nullptr, \ + ERR_FAIL_NULL_MSG(m_var, \ "Mono Cache: Managed callback for '" #m_class "_" #m_method "' is null."); \ checked_count += 1; \ } @@ -70,6 +70,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyDefaultValues); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CallStatic); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Call); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Set); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get); diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index f604e4d681..dac8cdcaef 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -91,10 +91,11 @@ struct ManagedCallbacks { using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *); using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *); using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *); - using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *); + using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *); using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool); using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add); using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add); + using FuncScriptManagerBridge_CallStatic = bool(GD_CLR_STDCALL *)(const CSharpScript *, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *); using FuncCSharpInstanceBridge_Call = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *); using FuncCSharpInstanceBridge_Set = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant *); using FuncCSharpInstanceBridge_Get = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, Variant *); @@ -130,6 +131,7 @@ struct ManagedCallbacks { FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType; FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList; FuncScriptManagerBridge_GetPropertyDefaultValues ScriptManagerBridge_GetPropertyDefaultValues; + FuncScriptManagerBridge_CallStatic ScriptManagerBridge_CallStatic; FuncCSharpInstanceBridge_Call CSharpInstanceBridge_Call; FuncCSharpInstanceBridge_Set CSharpInstanceBridge_Set; FuncCSharpInstanceBridge_Get CSharpInstanceBridge_Get; diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index 1c7546436b..eab1f5d51d 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -112,9 +112,9 @@ void ReplicationEditor::_pick_node_filter_input(const Ref<InputEvent> &p_ie) { void ReplicationEditor::_pick_node_selected(NodePath p_path) { Node *root = current->get_node(current->get_root_path()); - ERR_FAIL_COND(!root); + ERR_FAIL_NULL(root); Node *node = get_node(p_path); - ERR_FAIL_COND(!node); + ERR_FAIL_NULL(node); NodePath path_to = root->get_path_to(node); adding_node_path = path_to; prop_selector->select_property_from_instance(node); @@ -245,24 +245,29 @@ ReplicationEditor::ReplicationEditor() { add_pick_button->connect("pressed", callable_mp(this, &ReplicationEditor::_pick_new_property)); add_pick_button->set_text(TTR("Add property to sync...")); hb->add_child(add_pick_button); + VSeparator *vs = memnew(VSeparator); vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0)); hb->add_child(vs); hb->add_child(memnew(Label(TTR("Path:")))); + np_line_edit = memnew(LineEdit); np_line_edit->set_placeholder(":property"); np_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); np_line_edit->connect("text_submitted", callable_mp(this, &ReplicationEditor::_np_text_submitted)); hb->add_child(np_line_edit); + add_from_path_button = memnew(Button); add_from_path_button->connect("pressed", callable_mp(this, &ReplicationEditor::_add_pressed)); add_from_path_button->set_text(TTR("Add from path")); hb->add_child(add_from_path_button); + vs = memnew(VSeparator); vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0)); hb->add_child(vs); + pin = memnew(Button); - pin->set_flat(true); + pin->set_theme_type_variation("FlatButton"); pin->set_toggle_mode(true); hb->add_child(pin); diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp index ea52741601..9b05fa884b 100644 --- a/modules/multiplayer/multiplayer_debugger.cpp +++ b/modules/multiplayer/multiplayer_debugger.cpp @@ -237,7 +237,7 @@ void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_proces // ReplicationProfiler MultiplayerDebugger::SyncInfo::SyncInfo(MultiplayerSynchronizer *p_sync) { - ERR_FAIL_COND(!p_sync); + ERR_FAIL_NULL(p_sync); synchronizer = p_sync->get_instance_id(); if (p_sync->get_replication_config().is_valid()) { config = p_sync->get_replication_config()->get_instance_id(); @@ -305,7 +305,7 @@ void MultiplayerDebugger::ReplicationProfiler::add(const Array &p_data) { const ObjectID id = p_data[1]; const uint64_t size = p_data[2]; MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(id)); - ERR_FAIL_COND(!sync); + ERR_FAIL_NULL(sync); if (!sync_data.has(id)) { sync_data[id] = SyncInfo(sync); } diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index 6c6aa28344..264a2e9c8e 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -268,7 +268,7 @@ void MultiplayerSpawner::_spawn_notify(ObjectID p_id) { void MultiplayerSpawner::_node_exit(ObjectID p_id) { Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); - ERR_FAIL_COND(!node); + ERR_FAIL_NULL(node); if (tracked_nodes.has(p_id)) { tracked_nodes.erase(p_id); get_multiplayer()->object_configuration_remove(node, this); @@ -323,10 +323,10 @@ Node *MultiplayerSpawner::spawn(const Variant &p_data) { ERR_FAIL_COND_V_MSG(!spawn_function.is_valid(), nullptr, "Custom spawn requires the 'spawn_function' property to be a valid callable."); Node *parent = get_spawn_node(); - ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node."); + ERR_FAIL_NULL_V_MSG(parent, nullptr, "Cannot find spawn node."); Node *node = instantiate_custom(p_data); - ERR_FAIL_COND_V_MSG(!node, nullptr, "The 'spawn_function' callable must return a valid node."); + ERR_FAIL_NULL_V_MSG(node, nullptr, "The 'spawn_function' callable must return a valid node."); _track(node, p_data); parent->add_child(node, true); diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index 9c2d281f72..233f15c3a4 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -149,14 +149,14 @@ PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const { } Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) { - ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_obj, ERR_INVALID_PARAMETER); r_variant.resize(p_properties.size()); r_variant_ptrs.resize(r_variant.size()); int i = 0; for (const NodePath &prop : p_properties) { bool valid = false; const Object *obj = _get_prop_target(p_obj, prop); - ERR_FAIL_COND_V(!obj, FAILED); + ERR_FAIL_NULL_V(obj, FAILED); 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)); @@ -166,11 +166,11 @@ Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Obj } Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state) { - ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_obj, ERR_INVALID_PARAMETER); int i = 0; for (const NodePath &prop : p_properties) { Object *obj = _get_prop_target(p_obj, prop); - ERR_FAIL_COND_V(!obj, FAILED); + ERR_FAIL_NULL_V(obj, FAILED); obj->set_indexed(prop.get_subnames(), p_state[i]); i += 1; } @@ -374,7 +374,7 @@ Error MultiplayerSynchronizer::_watch_changes(uint64_t p_usec) { return OK; } Node *node = get_root_node(); - ERR_FAIL_COND_V(!node, FAILED); + ERR_FAIL_NULL_V(node, FAILED); int idx = -1; Watcher *ptr = watchers.ptrw(); for (const NodePath &prop : props) { diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index 90e6ac7c2c..8d102ca981 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -53,7 +53,7 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) { void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_COND(!root_node); + ERR_FAIL_NULL(root_node); ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); int ofs = 1; @@ -74,7 +74,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac } Node *node = root_node->get_node(path); - ERR_FAIL_COND(node == nullptr); + ERR_FAIL_NULL(node); const bool valid_rpc_checksum = multiplayer->get_rpc_md5(node) == methods_md5; if (valid_rpc_checksum == false) { ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); @@ -119,7 +119,7 @@ void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_pack } PathSentCache *psc = path_send_cache.getptr(path); - ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); + ERR_FAIL_NULL_MSG(psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); HashMap<int, bool>::Iterator E = psc->confirmed_peers.find(p_from); ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); @@ -165,7 +165,7 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { const PathSentCache *psc = path_send_cache.getptr(p_path); - ERR_FAIL_COND_V(!psc, false); + ERR_FAIL_NULL_V(psc, false); HashMap<int, bool>::ConstIterator F = psc->confirmed_peers.find(p_peer); ERR_FAIL_COND_V(!F, false); // Should never happen. return F->value; @@ -173,7 +173,7 @@ bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { int SceneCacheInterface::make_object_cache(Object *p_obj) { Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, -1); + ERR_FAIL_NULL_V(node, -1); NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path()); // See if the path is cached. PathSentCache *psc = path_send_cache.getptr(for_path); @@ -188,7 +188,7 @@ int SceneCacheInterface::make_object_cache(Object *p_obj) { bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) { Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, false); + ERR_FAIL_NULL_V(node, false); r_id = make_object_cache(p_obj); ERR_FAIL_COND_V(r_id < 0, false); @@ -233,7 +233,7 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) { Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_COND_V(!root_node, nullptr); + ERR_FAIL_NULL_V(root_node, nullptr); HashMap<int, PathGetCache>::Iterator E = path_get_cache.find(p_from); ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 7f12fea4bf..e350f2f68b 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -155,7 +155,7 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(spawner, ERR_INVALID_PARAMETER); // Track node. const ObjectID oid = node->get_instance_id(); TrackedNode &tobj = _track(oid); @@ -226,7 +226,7 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(sync, ERR_INVALID_PARAMETER); // Add to synchronizer list. TrackedNode &tobj = _track(p_obj->get_instance_id()); @@ -270,7 +270,7 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(sync, ERR_INVALID_PARAMETER); sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed)); // Untrack synchronizer. const ObjectID oid = node->get_instance_id(); @@ -291,9 +291,9 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid) { MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(p_sid); - ERR_FAIL_COND(!sync); // Bug. + ERR_FAIL_NULL(sync); // Bug. Node *node = sync->get_root_node(); - ERR_FAIL_COND(!node); // Bug. + ERR_FAIL_NULL(node); // Bug. const ObjectID oid = node->get_instance_id(); if (spawned_nodes.has(oid) && p_peer != multiplayer->get_unique_id()) { _update_spawn_visibility(p_peer, oid); @@ -341,7 +341,7 @@ bool SceneReplicationInterface::is_rpc_visible(const ObjectID &p_oid, int p_peer } Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) { - ERR_FAIL_COND_V(!p_sync, ERR_BUG); + ERR_FAIL_NULL_V(p_sync, ERR_BUG); if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority() || p_peer == multiplayer->get_unique_id()) { return OK; } @@ -380,7 +380,7 @@ Error SceneReplicationInterface::_update_sync_visibility(int p_peer, Multiplayer Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) { const TrackedNode *tnode = tracked_nodes.getptr(p_oid); - ERR_FAIL_COND_V(!tnode, ERR_BUG); + ERR_FAIL_NULL_V(tnode, ERR_BUG); MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tnode->spawner); Node *node = get_id_as<Node>(p_oid); ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG); @@ -467,7 +467,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa const ObjectID oid = p_node->get_instance_id(); const TrackedNode *tnode = tracked_nodes.getptr(oid); - ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER); uint32_t nid = tnode->net_id; ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED); @@ -549,7 +549,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) { const ObjectID oid = p_node->get_instance_id(); const TrackedNode *tnode = tracked_nodes.getptr(oid); - ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER); MAKE_ROOM(5); uint8_t *ptr = packet_cache.ptrw(); ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_DESPAWN; @@ -568,7 +568,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b uint32_t node_target = decode_uint32(&p_buffer[ofs]); ofs += 4; MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_path_cache()->get_cached_object(p_from, node_target)); - ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST); + ERR_FAIL_NULL_V(spawner, ERR_DOES_NOT_EXIST); ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED); uint32_t net_id = decode_uint32(&p_buffer[ofs]); @@ -592,7 +592,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b // Check that we can spawn. Node *parent = spawner->get_node_or_null(spawner->get_spawn_path()); - ERR_FAIL_COND_V(!parent, ERR_UNCONFIGURED); + ERR_FAIL_NULL_V(parent, ERR_UNCONFIGURED); ERR_FAIL_COND_V(parent->has_node(name), ERR_INVALID_DATA); Node *node = nullptr; @@ -611,7 +611,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b // Scene based spawn. node = spawner->instantiate_scene(scene_id); } - ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED); + ERR_FAIL_NULL_V(node, ERR_UNAUTHORIZED); node->set_name(name); // Add and track remote @@ -656,13 +656,13 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p PeerInfo &pinfo = peers_info[p_from]; ERR_FAIL_COND_V(!pinfo.recv_nodes.has(net_id), ERR_UNAUTHORIZED); Node *node = get_id_as<Node>(pinfo.recv_nodes[net_id]); - ERR_FAIL_COND_V(!node, ERR_BUG); + ERR_FAIL_NULL_V(node, ERR_BUG); pinfo.recv_nodes.erase(net_id); const ObjectID oid = node->get_instance_id(); ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_BUG); MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tracked_nodes[oid].spawner); - ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST); + ERR_FAIL_NULL_V(spawner, ERR_DOES_NOT_EXIST); ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED); if (node->get_parent() != nullptr) { diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index da1a044c9d..48e1d13f9c 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -134,7 +134,7 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i String SceneRPCInterface::get_rpc_md5(const Object *p_obj) { const Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, ""); + ERR_FAIL_NULL_V(node, ""); const RPCConfigCache cache = _get_node_config(node); String rpc_list; for (const KeyValue<uint16_t, RPCConfig> &config : cache.configs) { @@ -145,7 +145,7 @@ String SceneRPCInterface::get_rpc_md5(const Object *p_obj) { Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_COND_V(!root_node, nullptr); + ERR_FAIL_NULL_V(root_node, nullptr); Node *node = nullptr; if (p_node_target & 0x80000000) { @@ -225,7 +225,7 @@ void SceneRPCInterface::process_rpc(int p_from, const uint8_t *p_packet, int p_p } Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); - ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); + ERR_FAIL_NULL_MSG(node, "Invalid packet received. Requested node was not found."); uint16_t name_id = 0; switch (name_id_compression) { diff --git a/modules/navigation/config.py b/modules/navigation/config.py index d22f9454ed..a42f27fbe1 100644 --- a/modules/navigation/config.py +++ b/modules/navigation/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return True + return not env["disable_3d"] def configure(env): diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp index 85948e7547..18d66c7b69 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp @@ -64,7 +64,7 @@ void NavigationMeshEditor::_notification(int p_what) { void NavigationMeshEditor::_bake_pressed() { button_bake->set_pressed(false); - ERR_FAIL_COND(!node); + ERR_FAIL_NULL(node); Ref<NavigationMesh> navmesh = node->get_navigation_mesh(); if (!navmesh.is_valid()) { err_dialog->set_text(TTR("A NavigationMesh resource must be set or created for this node to work.")); @@ -134,7 +134,7 @@ NavigationMeshEditor::NavigationMeshEditor() { bake_hbox = memnew(HBoxContainer); button_bake = memnew(Button); - button_bake->set_flat(true); + button_bake->set_theme_type_variation("FlatButton"); bake_hbox->add_child(button_bake); button_bake->set_toggle_mode(true); button_bake->set_text(TTR("Bake NavigationMesh")); @@ -142,7 +142,7 @@ NavigationMeshEditor::NavigationMeshEditor() { button_bake->connect("pressed", callable_mp(this, &NavigationMeshEditor::_bake_pressed)); button_reset = memnew(Button); - button_reset->set_flat(true); + button_reset->set_theme_type_variation("FlatButton"); bake_hbox->add_child(button_reset); button_reset->set_text(TTR("Clear NavigationMesh")); button_reset->set_tooltip_text(TTR("Clears the internal NavigationMesh vertices and polygons.")); diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 9162fcf171..6a3bf6793e 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -116,7 +116,7 @@ RID GodotNavigationServer::map_create() { COMMAND_2(map_set_active, RID, p_map, bool, p_active) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); if (p_active) { if (!map_is_active(p_map)) { @@ -133,126 +133,126 @@ COMMAND_2(map_set_active, RID, p_map, bool, p_active) { bool GodotNavigationServer::map_is_active(RID p_map) const { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, false); + ERR_FAIL_NULL_V(map, false); return active_maps.find(map) >= 0; } COMMAND_2(map_set_up, RID, p_map, Vector3, p_up) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_up(p_up); } Vector3 GodotNavigationServer::map_get_up(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_up(); } COMMAND_2(map_set_cell_size, RID, p_map, real_t, p_cell_size) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_cell_size(p_cell_size); } real_t GodotNavigationServer::map_get_cell_size(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_cell_size(); } COMMAND_2(map_set_cell_height, RID, p_map, real_t, p_cell_height) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_cell_height(p_cell_height); } real_t GodotNavigationServer::map_get_cell_height(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_cell_height(); } COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_use_edge_connections(p_enabled); } bool GodotNavigationServer::map_get_use_edge_connections(RID p_map) const { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, false); + ERR_FAIL_NULL_V(map, false); return map->get_use_edge_connections(); } COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_edge_connection_margin(p_connection_margin); } real_t GodotNavigationServer::map_get_edge_connection_margin(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_edge_connection_margin(); } COMMAND_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_link_connection_radius(p_connection_radius); } real_t GodotNavigationServer::map_get_link_connection_radius(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_link_connection_radius(); } Vector<Vector3> GodotNavigationServer::map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector<Vector3>()); + ERR_FAIL_NULL_V(map, Vector<Vector3>()); return map->get_path(p_origin, p_destination, p_optimize, p_navigation_layers, nullptr, nullptr, nullptr); } Vector3 GodotNavigationServer::map_get_closest_point_to_segment(RID p_map, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_closest_point_to_segment(p_from, p_to, p_use_collision); } Vector3 GodotNavigationServer::map_get_closest_point(RID p_map, const Vector3 &p_point) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_closest_point(p_point); } Vector3 GodotNavigationServer::map_get_closest_point_normal(RID p_map, const Vector3 &p_point) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_closest_point_normal(p_point); } RID GodotNavigationServer::map_get_closest_point_owner(RID p_map, const Vector3 &p_point) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, RID()); + ERR_FAIL_NULL_V(map, RID()); return map->get_closest_point_owner(p_point); } @@ -260,7 +260,7 @@ RID GodotNavigationServer::map_get_closest_point_owner(RID p_map, const Vector3 TypedArray<RID> GodotNavigationServer::map_get_links(RID p_map) const { TypedArray<RID> link_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, link_rids); + ERR_FAIL_NULL_V(map, link_rids); const LocalVector<NavLink *> &links = map->get_links(); link_rids.resize(links.size()); @@ -274,7 +274,7 @@ TypedArray<RID> GodotNavigationServer::map_get_links(RID p_map) const { TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const { TypedArray<RID> regions_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, regions_rids); + ERR_FAIL_NULL_V(map, regions_rids); const LocalVector<NavRegion *> ®ions = map->get_regions(); regions_rids.resize(regions.size()); @@ -288,7 +288,7 @@ TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const { TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const { TypedArray<RID> agents_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, agents_rids); + ERR_FAIL_NULL_V(map, agents_rids); const LocalVector<NavAgent *> &agents = map->get_agents(); agents_rids.resize(agents.size()); @@ -302,7 +302,7 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const { TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const { TypedArray<RID> obstacles_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, obstacles_rids); + ERR_FAIL_NULL_V(map, obstacles_rids); const LocalVector<NavObstacle *> obstacles = map->get_obstacles(); obstacles_rids.resize(obstacles.size()); for (uint32_t i = 0; i < obstacles.size(); i++) { @@ -313,7 +313,7 @@ TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const { RID GodotNavigationServer::region_get_map(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, RID()); + ERR_FAIL_NULL_V(region, RID()); if (region->get_map()) { return region->get_map()->get_self(); @@ -323,7 +323,7 @@ RID GodotNavigationServer::region_get_map(RID p_region) const { RID GodotNavigationServer::agent_get_map(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, RID()); + ERR_FAIL_NULL_V(agent, RID()); if (agent->get_map()) { return agent->get_map()->get_self(); @@ -342,35 +342,35 @@ RID GodotNavigationServer::region_create() { 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); + ERR_FAIL_NULL(region); 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); + ERR_FAIL_NULL_V(region, 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); + ERR_FAIL_NULL(region); region->set_use_edge_connections(p_enabled); } bool GodotNavigationServer::region_get_use_edge_connections(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, false); + ERR_FAIL_NULL_V(region, false); return region->get_use_edge_connections(); } COMMAND_2(region_set_map, RID, p_region, RID, p_map) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); NavMap *map = map_owner.get_or_null(p_map); @@ -379,14 +379,14 @@ COMMAND_2(region_set_map, RID, p_region, RID, p_map) { COMMAND_2(region_set_transform, RID, p_region, Transform3D, p_transform) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_transform(p_transform); } COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); ERR_FAIL_COND(p_enter_cost < 0.0); region->set_enter_cost(p_enter_cost); @@ -394,14 +394,14 @@ COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost) { real_t GodotNavigationServer::region_get_enter_cost(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_enter_cost(); } COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); ERR_FAIL_COND(p_travel_cost < 0.0); region->set_travel_cost(p_travel_cost); @@ -409,28 +409,28 @@ COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost) { real_t GodotNavigationServer::region_get_travel_cost(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_travel_cost(); } COMMAND_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_owner_id(p_owner_id); } ObjectID GodotNavigationServer::region_get_owner_id(RID p_region) const { const NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, ObjectID()); + ERR_FAIL_NULL_V(region, ObjectID()); return region->get_owner_id(); } bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_point) const { const NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, false); + ERR_FAIL_NULL_V(region, false); if (region->get_map()) { RID closest_point_owner = map_get_closest_point_owner(region->get_map()->get_self(), p_point); @@ -441,21 +441,21 @@ bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_poi COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_navigation_layers(p_navigation_layers); } uint32_t GodotNavigationServer::region_get_navigation_layers(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_navigation_layers(); } COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navigation_mesh) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_mesh(p_navigation_mesh); } @@ -463,7 +463,7 @@ COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navi #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); + ERR_FAIL_NULL(p_root_node); 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.."); @@ -479,21 +479,21 @@ void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_na int GodotNavigationServer::region_get_connections_count(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(!region, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_connections_count(); } Vector3 GodotNavigationServer::region_get_connection_pathway_start(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(!region, Vector3()); + ERR_FAIL_NULL_V(region, Vector3()); return region->get_connection_pathway_start(p_connection_id); } Vector3 GodotNavigationServer::region_get_connection_pathway_end(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(!region, Vector3()); + ERR_FAIL_NULL_V(region, Vector3()); return region->get_connection_pathway_end(p_connection_id); } @@ -509,7 +509,7 @@ RID GodotNavigationServer::link_create() { COMMAND_2(link_set_map, RID, p_link, RID, p_map) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); NavMap *map = map_owner.get_or_null(p_map); @@ -518,7 +518,7 @@ COMMAND_2(link_set_map, RID, p_link, RID, p_map) { RID GodotNavigationServer::link_get_map(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, RID()); + ERR_FAIL_NULL_V(link, RID()); if (link->get_map()) { return link->get_map()->get_self(); @@ -528,112 +528,112 @@ RID GodotNavigationServer::link_get_map(const RID p_link) const { 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); + ERR_FAIL_NULL(link); 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); + ERR_FAIL_NULL_V(link, 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); + ERR_FAIL_NULL(link); link->set_bidirectional(p_bidirectional); } bool GodotNavigationServer::link_is_bidirectional(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, false); + ERR_FAIL_NULL_V(link, false); return link->is_bidirectional(); } COMMAND_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_navigation_layers(p_navigation_layers); } uint32_t GodotNavigationServer::link_get_navigation_layers(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, 0); + ERR_FAIL_NULL_V(link, 0); return link->get_navigation_layers(); } COMMAND_2(link_set_start_position, RID, p_link, Vector3, p_position) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_start_position(p_position); } Vector3 GodotNavigationServer::link_get_start_position(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, Vector3()); + ERR_FAIL_NULL_V(link, Vector3()); return link->get_start_position(); } COMMAND_2(link_set_end_position, RID, p_link, Vector3, p_position) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_end_position(p_position); } Vector3 GodotNavigationServer::link_get_end_position(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, Vector3()); + ERR_FAIL_NULL_V(link, Vector3()); return link->get_end_position(); } COMMAND_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_enter_cost(p_enter_cost); } real_t GodotNavigationServer::link_get_enter_cost(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, 0); + ERR_FAIL_NULL_V(link, 0); return link->get_enter_cost(); } COMMAND_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_travel_cost(p_travel_cost); } real_t GodotNavigationServer::link_get_travel_cost(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, 0); + ERR_FAIL_NULL_V(link, 0); return link->get_travel_cost(); } COMMAND_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_owner_id(p_owner_id); } ObjectID GodotNavigationServer::link_get_owner_id(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, ObjectID()); + ERR_FAIL_NULL_V(link, ObjectID()); return link->get_owner_id(); } @@ -649,35 +649,35 @@ RID GodotNavigationServer::agent_create() { COMMAND_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_enabled(p_enabled); } bool GodotNavigationServer::agent_get_avoidance_enabled(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->is_avoidance_enabled(); } COMMAND_2(agent_set_use_3d_avoidance, RID, p_agent, bool, p_enabled) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_use_3d_avoidance(p_enabled); } bool GodotNavigationServer::agent_get_use_3d_avoidance(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->get_use_3d_avoidance(); } COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); NavMap *map = map_owner.get_or_null(p_map); @@ -686,28 +686,28 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { COMMAND_2(agent_set_paused, RID, p_agent, bool, p_paused) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_paused(p_paused); } bool GodotNavigationServer::agent_get_paused(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->get_paused(); } COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_neighbor_distance(p_distance); } COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_max_neighbors(p_count); } @@ -715,7 +715,7 @@ COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) { COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) { ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_time_horizon_agents(p_time_horizon); } @@ -723,7 +723,7 @@ COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) { COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon) { ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_time_horizon_obstacles(p_time_horizon); } @@ -731,7 +731,7 @@ COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) { ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_radius(p_radius); } @@ -739,7 +739,7 @@ COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) { COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) { ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_height(p_height); } @@ -747,42 +747,42 @@ COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) { COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) { ERR_FAIL_COND_MSG(p_max_speed < 0.0, "Max speed must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_max_speed(p_max_speed); } COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_velocity(p_velocity); } COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_velocity_forced(p_velocity); } COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_position(p_position); } bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->is_map_changed(); } COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_callback(p_callback); @@ -797,13 +797,13 @@ COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) { COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_layers(p_layers); } COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_mask(p_mask); } @@ -811,7 +811,7 @@ COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority) { ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_priority(p_priority); } @@ -833,35 +833,35 @@ RID GodotNavigationServer::obstacle_create() { COMMAND_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_avoidance_enabled(p_enabled); } bool GodotNavigationServer::obstacle_get_avoidance_enabled(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, false); + ERR_FAIL_NULL_V(obstacle, false); return obstacle->is_avoidance_enabled(); } COMMAND_2(obstacle_set_use_3d_avoidance, RID, p_obstacle, bool, p_enabled) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_use_3d_avoidance(p_enabled); } bool GodotNavigationServer::obstacle_get_use_3d_avoidance(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, false); + ERR_FAIL_NULL_V(obstacle, false); return obstacle->get_use_3d_avoidance(); } COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); NavMap *map = map_owner.get_or_null(p_map); @@ -870,7 +870,7 @@ COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) { RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, RID()); + ERR_FAIL_NULL_V(obstacle, RID()); if (obstacle->get_map()) { return obstacle->get_map()->get_self(); } @@ -879,14 +879,14 @@ RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const { COMMAND_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_paused(p_paused); } bool GodotNavigationServer::obstacle_get_paused(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, false); + ERR_FAIL_NULL_V(obstacle, false); return obstacle->get_paused(); } @@ -894,39 +894,39 @@ bool GodotNavigationServer::obstacle_get_paused(RID p_obstacle) const { COMMAND_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius) { ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_radius(p_radius); } COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_height(p_height); } COMMAND_2(obstacle_set_velocity, RID, p_obstacle, Vector3, p_velocity) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_velocity(p_velocity); } COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_position(p_position); } void GodotNavigationServer::obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_vertices(p_vertices); } COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_avoidance_layers(p_layers); } @@ -934,7 +934,7 @@ void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> #ifndef _3D_DISABLED ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); - ERR_FAIL_COND_MSG(p_root_node == nullptr, "No parsing root node specified."); + ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified."); ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree."); ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton()); @@ -1079,13 +1079,21 @@ void GodotNavigationServer::flush_queries() { void GodotNavigationServer::map_force_update(RID p_map) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); flush_queries(); map->sync(); } +void GodotNavigationServer::sync() { +#ifndef _3D_DISABLED + if (navmesh_generator_3d) { + navmesh_generator_3d->sync(); + } +#endif // _3D_DISABLED +} + void GodotNavigationServer::process(real_t p_delta_time) { flush_queries(); @@ -1093,16 +1101,6 @@ void GodotNavigationServer::process(real_t p_delta_time) { return; } -#ifndef _3D_DISABLED - // Sync finished navmesh bakes before doing NavMap updates. - if (navmesh_generator_3d) { - navmesh_generator_3d->sync(); - // Finished bakes emit callbacks and users might have reacted to those. - // Flush queue again so users do not have to wait for the next sync. - flush_queries(); - } -#endif // _3D_DISABLED - int _new_pm_region_count = 0; int _new_pm_agent_count = 0; int _new_pm_link_count = 0; @@ -1168,7 +1166,7 @@ PathQueryResult GodotNavigationServer::_query_path(const PathQueryParameters &p_ PathQueryResult r_query_result; const NavMap *map = map_owner.get_or_null(p_parameters.map); - ERR_FAIL_COND_V(map == nullptr, r_query_result); + ERR_FAIL_NULL_V(map, r_query_result); // run the pathfinding diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index c12605bc7a..4ead4fc398 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -243,6 +243,7 @@ public: void flush_queries(); virtual void process(real_t p_delta_time) override; virtual void init() override; + virtual void sync() override; virtual void finish() override; virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override; diff --git a/modules/navigation/godot_navigation_server_2d.cpp b/modules/navigation/godot_navigation_server_2d.cpp new file mode 100644 index 0000000000..b54729e06f --- /dev/null +++ b/modules/navigation/godot_navigation_server_2d.cpp @@ -0,0 +1,369 @@ +/**************************************************************************/ +/* godot_navigation_server_2d.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 "godot_navigation_server_2d.h" + +#ifdef CLIPPER2_ENABLED +#include "nav_mesh_generator_2d.h" +#endif // CLIPPER2_ENABLED + +#include "servers/navigation_server_3d.h" + +#define FORWARD_0(FUNC_NAME) \ + GodotNavigationServer2D::FUNC_NAME() { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(); \ + } + +#define FORWARD_0_C(FUNC_NAME) \ + GodotNavigationServer2D::FUNC_NAME() \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(); \ + } + +#define FORWARD_1(FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ + } + +#define FORWARD_1_C(FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ + } + +#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \ + } + +#define FORWARD_2(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ + } + +#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ + } + +#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \ + } + +#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \ + } + +static RID rid_to_rid(const RID d) { + return d; +} + +static bool bool_to_bool(const bool d) { + return d; +} + +static int int_to_int(const int d) { + return d; +} + +static uint32_t uint32_to_uint32(const uint32_t d) { + return d; +} + +static real_t real_to_real(const real_t d) { + return d; +} + +static Vector3 v2_to_v3(const Vector2 d) { + return Vector3(d.x, 0.0, d.y); +} + +static Vector2 v3_to_v2(const Vector3 &d) { + return Vector2(d.x, d.z); +} + +static Vector<Vector3> vector_v2_to_v3(const Vector<Vector2> &d) { + Vector<Vector3> nd; + nd.resize(d.size()); + for (int i(0); i < nd.size(); i++) { + nd.write[i] = v2_to_v3(d[i]); + } + return nd; +} + +static Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) { + Vector<Vector2> nd; + nd.resize(d.size()); + for (int i(0); i < nd.size(); i++) { + nd.write[i] = v3_to_v2(d[i]); + } + return nd; +} + +static Transform3D trf2_to_trf3(const Transform2D &d) { + Vector3 o(v2_to_v3(d.get_origin())); + Basis b; + b.rotate(Vector3(0, -1, 0), d.get_rotation()); + b.scale(v2_to_v3(d.get_scale())); + return Transform3D(b, o); +} + +static ObjectID id_to_id(const ObjectID &id) { + return id; +} + +static Callable callable_to_callable(const Callable &c) { + return c; +} + +static Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) { + if (d.is_valid()) { + return d->get_navigation_mesh(); + } else { + return Ref<NavigationMesh>(); + } +} + +void GodotNavigationServer2D::init() { +#ifdef CLIPPER2_ENABLED + navmesh_generator_2d = memnew(NavMeshGenerator2D); + ERR_FAIL_NULL_MSG(navmesh_generator_2d, "Failed to init NavMeshGenerator2D."); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::sync() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->sync(); + } +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::finish() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->finish(); + memdelete(navmesh_generator_2d); + navmesh_generator_2d = nullptr; + } +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon."); + ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified."); + ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon."); + ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); + ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback); +#endif // CLIPPER2_ENABLED +} + +GodotNavigationServer2D::GodotNavigationServer2D() {} + +GodotNavigationServer2D::~GodotNavigationServer2D() {} + +TypedArray<RID> FORWARD_0_C(get_maps); + +TypedArray<RID> FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid); + +TypedArray<RID> FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid); + +TypedArray<RID> FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid); + +TypedArray<RID> FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid); + +RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid); + +RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid); + +RID FORWARD_0(map_create); + +void FORWARD_2(map_set_active, RID, p_map, bool, p_active, rid_to_rid, bool_to_bool); + +bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid); + +void GodotNavigationServer2D::map_force_update(RID p_map) { + NavigationServer3D::get_singleton()->map_force_update(p_map); +} + +void FORWARD_2(map_set_cell_size, RID, p_map, real_t, p_cell_size, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(map_get_use_edge_connections, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid); + +Vector<Vector2> FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32); + +Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); +RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); + +RID FORWARD_0(region_create); + +void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid); + +void FORWARD_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(region_get_enter_cost, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(region_get_travel_cost, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id, rid_to_rid, id_to_id); +ObjectID FORWARD_1_C(region_get_owner_id, RID, p_region, rid_to_rid); +bool FORWARD_2_C(region_owns_point, RID, p_region, const Vector2 &, p_point, rid_to_rid, v2_to_v3); + +void FORWARD_2(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid); +void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); +uint32_t FORWARD_1_C(region_get_navigation_layers, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3); + +void GodotNavigationServer2D::region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) { + NavigationServer3D::get_singleton()->region_set_navigation_mesh(p_region, poly_to_mesh(p_navigation_polygon)); +} + +int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid); +Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); +Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); + +RID FORWARD_0(link_create); + +void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); +uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_start_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_position, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_end_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_position, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id, rid_to_rid, id_to_id); +ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid); + +RID GodotNavigationServer2D::agent_create() { + RID agent = NavigationServer3D::get_singleton()->agent_create(); + return agent; +} + +void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid); +void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid); +void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int); +void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3); + +bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid); +void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid); + +void FORWARD_1(free, RID, p_object, rid_to_rid); +void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); + +void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real); + +RID GodotNavigationServer2D::obstacle_create() { + RID obstacle = NavigationServer3D::get_singleton()->obstacle_create(); + return obstacle; +} +void FORWARD_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(obstacle_get_avoidance_enabled, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(obstacle_get_paused, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius, rid_to_rid, real_to_real); +void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3); +void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); + +void GodotNavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) { + NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices)); +} + +void GodotNavigationServer2D::query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const { + ERR_FAIL_COND(!p_query_parameters.is_valid()); + ERR_FAIL_COND(!p_query_result.is_valid()); + + const NavigationUtilities::PathQueryResult _query_result = NavigationServer3D::get_singleton()->_query_path(p_query_parameters->get_parameters()); + + p_query_result->set_path(vector_v3_to_v2(_query_result.path)); + p_query_result->set_path_types(_query_result.path_types); + p_query_result->set_path_rids(_query_result.path_rids); + p_query_result->set_path_owner_ids(_query_result.path_owner_ids); +} diff --git a/modules/navigation/godot_navigation_server_2d.h b/modules/navigation/godot_navigation_server_2d.h new file mode 100644 index 0000000000..337f5f40d8 --- /dev/null +++ b/modules/navigation/godot_navigation_server_2d.h @@ -0,0 +1,234 @@ +/**************************************************************************/ +/* godot_navigation_server_2d.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 GODOT_NAVIGATION_SERVER_2D_H +#define GODOT_NAVIGATION_SERVER_2D_H + +#include "nav_agent.h" +#include "nav_link.h" +#include "nav_map.h" +#include "nav_obstacle.h" +#include "nav_region.h" + +#include "servers/navigation_server_2d.h" + +#ifdef CLIPPER2_ENABLED +class NavMeshGenerator2D; +#endif // CLIPPER2_ENABLED + +// This server exposes the `NavigationServer3D` features in the 2D world. +class GodotNavigationServer2D : public NavigationServer2D { + GDCLASS(GodotNavigationServer2D, NavigationServer2D); + +#ifdef CLIPPER2_ENABLED + NavMeshGenerator2D *navmesh_generator_2d = nullptr; +#endif // CLIPPER2_ENABLED + +public: + GodotNavigationServer2D(); + virtual ~GodotNavigationServer2D(); + + virtual TypedArray<RID> get_maps() const override; + + virtual RID map_create() override; + virtual void map_set_active(RID p_map, bool p_active) override; + virtual bool map_is_active(RID p_map) const override; + virtual void map_set_cell_size(RID p_map, real_t p_cell_size) override; + virtual real_t map_get_cell_size(RID p_map) const override; + virtual void map_set_use_edge_connections(RID p_map, bool p_enabled) override; + virtual bool map_get_use_edge_connections(RID p_map) const override; + virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) override; + virtual real_t map_get_edge_connection_margin(RID p_map) const override; + virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) override; + virtual real_t map_get_link_connection_radius(RID p_map) const override; + virtual Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override; + virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const override; + virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const override; + virtual TypedArray<RID> map_get_links(RID p_map) const override; + virtual TypedArray<RID> map_get_regions(RID p_map) const override; + virtual TypedArray<RID> map_get_agents(RID p_map) const override; + virtual TypedArray<RID> map_get_obstacles(RID p_map) const override; + virtual void map_force_update(RID p_map) override; + + virtual RID region_create() override; + virtual void region_set_enabled(RID p_region, bool p_enabled) override; + virtual bool region_get_enabled(RID p_region) const override; + virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) override; + virtual bool region_get_use_edge_connections(RID p_region) const override; + virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) override; + virtual real_t region_get_enter_cost(RID p_region) const override; + virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) override; + virtual real_t region_get_travel_cost(RID p_region) const override; + virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) override; + virtual ObjectID region_get_owner_id(RID p_region) const override; + virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const override; + virtual void region_set_map(RID p_region, RID p_map) override; + virtual RID region_get_map(RID p_region) const override; + virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) override; + virtual uint32_t region_get_navigation_layers(RID p_region) const override; + virtual void region_set_transform(RID p_region, Transform2D p_transform) override; + virtual void region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) override; + virtual int region_get_connections_count(RID p_region) const override; + virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override; + virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; + + virtual RID link_create() override; + + /// Set the map of this link. + virtual void link_set_map(RID p_link, RID p_map) override; + virtual RID link_get_map(RID p_link) const override; + + virtual void link_set_enabled(RID p_link, bool p_enabled) override; + virtual bool link_get_enabled(RID p_link) const override; + + /// Set whether this link travels in both directions. + virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) override; + virtual bool link_is_bidirectional(RID p_link) const override; + + /// Set the link's layers. + virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override; + virtual uint32_t link_get_navigation_layers(RID p_link) const override; + + /// Set the start position of the link. + virtual void link_set_start_position(RID p_link, Vector2 p_position) override; + virtual Vector2 link_get_start_position(RID p_link) const override; + + /// Set the end position of the link. + virtual void link_set_end_position(RID p_link, Vector2 p_position) override; + virtual Vector2 link_get_end_position(RID p_link) const override; + + /// Set the enter cost of the link. + virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) override; + virtual real_t link_get_enter_cost(RID p_link) const override; + + /// Set the travel cost of the link. + virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) override; + virtual real_t link_get_travel_cost(RID p_link) const override; + + /// Set the node which manages this link. + virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) override; + virtual ObjectID link_get_owner_id(RID p_link) const override; + + /// Creates the agent. + virtual RID agent_create() override; + + /// Put the agent in the map. + virtual void agent_set_map(RID p_agent, RID p_map) override; + virtual RID agent_get_map(RID p_agent) const override; + + virtual void agent_set_paused(RID p_agent, bool p_paused) override; + virtual bool agent_get_paused(RID p_agent) const override; + + virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override; + virtual bool agent_get_avoidance_enabled(RID p_agent) const override; + + /// The maximum distance (center point to + /// center point) to other agents this agent + /// takes into account in the navigation. The + /// larger this number, the longer the running + /// time of the simulation. If the number is too + /// low, the simulation will not be safe. + /// Must be non-negative. + virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override; + + /// The maximum number of other agents this + /// agent takes into account in the navigation. + /// The larger this number, the longer the + /// running time of the simulation. If the + /// number is too low, the simulation will not + /// be safe. + virtual void agent_set_max_neighbors(RID p_agent, int p_count) override; + + /// The minimal amount of time for which this + /// agent's velocities that are computed by the + /// simulation are safe with respect to other + /// agents. The larger this number, the sooner + /// this agent will respond to the presence of + /// other agents, but the less freedom this + /// agent has in choosing its velocities. + /// Must be positive. + + virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override; + virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override; + + /// The radius of this agent. + /// Must be non-negative. + virtual void agent_set_radius(RID p_agent, real_t p_radius) override; + + /// The maximum speed of this agent. + /// Must be non-negative. + virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) override; + + /// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly + virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) override; + + /// The wanted velocity for the agent as a "suggestion" to the avoidance simulation. + /// The simulation will try to fulfill this velocity wish if possible but may change the velocity depending on other agent's and obstacles'. + virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity) override; + + /// Position of the agent in world space. + virtual void agent_set_position(RID p_agent, Vector2 p_position) override; + + /// Returns true if the map got changed the previous frame. + virtual bool agent_is_map_changed(RID p_agent) const override; + + /// Callback called at the end of the RVO process + virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override; + + virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override; + virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override; + virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override; + + virtual RID obstacle_create() override; + virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override; + virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const override; + virtual void obstacle_set_map(RID p_obstacle, RID p_map) override; + virtual RID obstacle_get_map(RID p_obstacle) const override; + virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) override; + virtual bool obstacle_get_paused(RID p_obstacle) const override; + virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) override; + virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override; + virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) override; + virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) override; + virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override; + + virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const override; + + virtual void init() override; + virtual void sync() override; + virtual void finish() override; + virtual void free(RID p_object) override; + + virtual void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override; + virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; + virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; +}; + +#endif // GODOT_NAVIGATION_SERVER_2D_H diff --git a/modules/navigation/nav_mesh_generator_2d.cpp b/modules/navigation/nav_mesh_generator_2d.cpp new file mode 100644 index 0000000000..089744c8bd --- /dev/null +++ b/modules/navigation/nav_mesh_generator_2d.cpp @@ -0,0 +1,830 @@ +/**************************************************************************/ +/* nav_mesh_generator_2d.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 "nav_mesh_generator_2d.h" + +#include "core/config/project_settings.h" +#include "scene/2d/mesh_instance_2d.h" +#include "scene/2d/multimesh_instance_2d.h" +#include "scene/2d/physics_body_2d.h" +#include "scene/2d/polygon_2d.h" +#include "scene/2d/tile_map.h" +#include "scene/resources/capsule_shape_2d.h" +#include "scene/resources/circle_shape_2d.h" +#include "scene/resources/concave_polygon_shape_2d.h" +#include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/navigation_polygon.h" +#include "scene/resources/rectangle_shape_2d.h" + +#include "thirdparty/clipper2/include/clipper2/clipper.h" +#include "thirdparty/misc/polypartition.h" + +NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr; +Mutex NavMeshGenerator2D::baking_navmesh_mutex; +Mutex NavMeshGenerator2D::generator_task_mutex; +bool NavMeshGenerator2D::use_threads = true; +bool NavMeshGenerator2D::baking_use_multiple_threads = true; +bool NavMeshGenerator2D::baking_use_high_priority_threads = true; +HashSet<Ref<NavigationPolygon>> NavMeshGenerator2D::baking_navmeshes; +HashMap<WorkerThreadPool::TaskID, NavMeshGenerator2D::NavMeshGeneratorTask2D *> NavMeshGenerator2D::generator_tasks; + +NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() { + return singleton; +} + +NavMeshGenerator2D::NavMeshGenerator2D() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; + + baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads"); + baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads"); + + // Using threads might cause problems on certain exports or with the Editor on certain devices. + // This is the main switch to turn threaded navmesh baking off should the need arise. + use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint(); +} + +NavMeshGenerator2D::~NavMeshGenerator2D() { + cleanup(); +} + +void NavMeshGenerator2D::sync() { + if (generator_tasks.size() == 0) { + return; + } + + baking_navmesh_mutex.lock(); + generator_task_mutex.lock(); + + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); + + NavMeshGeneratorTask2D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); + + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); + } + } + + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } + + generator_task_mutex.unlock(); + baking_navmesh_mutex.unlock(); +} + +void NavMeshGenerator2D::cleanup() { + baking_navmesh_mutex.lock(); + generator_task_mutex.lock(); + + baking_navmeshes.clear(); + + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask2D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); + + generator_task_mutex.unlock(); + baking_navmesh_mutex.unlock(); +} + +void NavMeshGenerator2D::finish() { + cleanup(); +} + +void NavMeshGenerator2D::parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { + ERR_FAIL_COND(!Thread::is_main_thread()); + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_NULL(p_root_node); + ERR_FAIL_COND(!p_root_node->is_inside_tree()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + generator_parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node); + + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } +} + +void NavMeshGenerator2D::bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + p_navigation_mesh->clear(); + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } + return; + } + + baking_navmesh_mutex.lock(); + if (baking_navmeshes.has(p_navigation_mesh)) { + baking_navmesh_mutex.unlock(); + ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish."); + } + baking_navmeshes.insert(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data); + + baking_navmesh_mutex.lock(); + baking_navmeshes.erase(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } +} + +void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + p_navigation_mesh->clear(); + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } + return; + } + + if (!use_threads) { + bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); + return; + } + + baking_navmesh_mutex.lock(); + if (baking_navmeshes.has(p_navigation_mesh)) { + baking_navmesh_mutex.unlock(); + ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish."); + } + baking_navmeshes.insert(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + generator_task_mutex.lock(); + NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D); + generator_task->navigation_mesh = p_navigation_mesh; + generator_task->source_geometry_data = p_source_geometry_data; + generator_task->callback = p_callback; + generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; + generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D"); + generator_tasks.insert(generator_task->thread_task_id, generator_task); + generator_task_mutex.unlock(); +} + +void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { + NavMeshGeneratorTask2D *generator_task = static_cast<NavMeshGeneratorTask2D *>(p_arg); + + generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data); + + generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED; +} + +void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children) { + generator_parse_meshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_multimeshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node); + + if (p_recurse_children) { + for (int i = 0; i < p_node->get_child_count(); i++) { + generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children); + } + } +} + +void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + MeshInstance2D *mesh_instance = Object::cast_to<MeshInstance2D>(p_node); + + if (mesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref<Mesh> mesh = mesh_instance->get_mesh(); + if (!mesh.is_valid()) { + return; + } + + const Transform2D mesh_instance_xform = p_source_geometry_data->root_node_transform * mesh_instance->get_global_transform(); + + using namespace Clipper2Lib; + + Paths64 subject_paths, dummy_clip_paths; + + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + Path64 subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + subject_paths.push_back(subject_path); + } + + Paths64 path_solution; + + path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + Vector<Vector<Vector2>> polypaths; + + for (const Path64 &scaled_path : path_solution) { + Vector<Vector2> shape_outline; + for (const Point64 &scaled_point : scaled_path) { + shape_outline.push_back(Point2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y))); + } + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = mesh_instance_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + +void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + MultiMeshInstance2D *multimesh_instance = Object::cast_to<MultiMeshInstance2D>(p_node); + + if (multimesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh(); + if (!(multimesh.is_valid() && multimesh->get_transform_format() == MultiMesh::TRANSFORM_2D)) { + return; + } + + Ref<Mesh> mesh = multimesh->get_mesh(); + if (!mesh.is_valid()) { + return; + } + + using namespace Clipper2Lib; + + Paths64 mesh_subject_paths, dummy_clip_paths; + + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + Path64 subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + mesh_subject_paths.push_back(subject_path); + } + + Paths64 mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + int multimesh_instance_count = multimesh->get_visible_instance_count(); + if (multimesh_instance_count == -1) { + multimesh_instance_count = multimesh->get_instance_count(); + } + + const Transform2D multimesh_instance_xform = p_source_geometry_data->root_node_transform * multimesh_instance->get_global_transform(); + + for (int i = 0; i < multimesh_instance_count; i++) { + const Transform2D multimesh_instance_mesh_instance_xform = multimesh_instance_xform * multimesh->get_instance_transform_2d(i); + + for (const Path64 &mesh_path : mesh_path_solution) { + Vector<Vector2> shape_outline; + + for (const Point64 &mesh_path_point : mesh_path) { + shape_outline.push_back(Point2(static_cast<real_t>(mesh_path_point.x), static_cast<real_t>(mesh_path_point.y))); + } + + for (int j = 0; j < shape_outline.size(); j++) { + shape_outline.write[j] = multimesh_instance_mesh_instance_xform.xform(shape_outline[j]); + } + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } +} + +void NavMeshGenerator2D::generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + Polygon2D *polygon_2d = Object::cast_to<Polygon2D>(p_node); + + if (polygon_2d == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) { + const Transform2D polygon_2d_xform = p_source_geometry_data->root_node_transform * polygon_2d->get_global_transform(); + + Vector<Vector2> shape_outline = polygon_2d->get_polygon(); + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = polygon_2d_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + +void NavMeshGenerator2D::generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + StaticBody2D *static_body = Object::cast_to<StaticBody2D>(p_node); + + if (static_body == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + if (!(static_body->get_collision_layer() & parsed_collision_mask)) { + return; + } + + List<uint32_t> shape_owners; + static_body->get_shape_owners(&shape_owners); + + for (uint32_t shape_owner : shape_owners) { + if (static_body->is_shape_owner_disabled(shape_owner)) { + continue; + } + + const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); + + for (int shape_index = 0; shape_index < shape_count; shape_index++) { + Ref<Shape2D> s = static_body->shape_owner_get_shape(shape_owner, shape_index); + + if (s.is_null()) { + continue; + } + + const Transform2D static_body_xform = p_source_geometry_data->root_node_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner); + + RectangleShape2D *rectangle_shape = Object::cast_to<RectangleShape2D>(*s); + if (rectangle_shape) { + Vector<Vector2> shape_outline; + + const Vector2 &rectangle_size = rectangle_shape->get_size(); + + shape_outline.resize(5); + shape_outline.write[0] = static_body_xform.xform(-rectangle_size * 0.5); + shape_outline.write[1] = static_body_xform.xform(Vector2(rectangle_size.x, -rectangle_size.y) * 0.5); + shape_outline.write[2] = static_body_xform.xform(rectangle_size * 0.5); + shape_outline.write[3] = static_body_xform.xform(Vector2(-rectangle_size.x, rectangle_size.y) * 0.5); + shape_outline.write[4] = static_body_xform.xform(-rectangle_size * 0.5); + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CapsuleShape2D *capsule_shape = Object::cast_to<CapsuleShape2D>(*s); + if (capsule_shape) { + const real_t capsule_height = capsule_shape->get_height(); + const real_t capsule_radius = capsule_shape->get_radius(); + + Vector<Vector2> shape_outline; + const real_t turn_step = Math_TAU / 12.0; + shape_outline.resize(14); + int shape_outline_inx = 0; + for (int i = 0; i < 12; i++) { + Vector2 ofs = Vector2(0, (i > 3 && i <= 9) ? -capsule_height * 0.5 + capsule_radius : capsule_height * 0.5 - capsule_radius); + + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius + ofs); + shape_outline_inx += 1; + if (i == 3 || i == 9) { + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius - ofs); + shape_outline_inx += 1; + } + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CircleShape2D *circle_shape = Object::cast_to<CircleShape2D>(*s); + if (circle_shape) { + const real_t circle_radius = circle_shape->get_radius(); + + Vector<Vector2> shape_outline; + int circle_edge_count = 12; + shape_outline.resize(circle_edge_count); + + const real_t turn_step = Math_TAU / real_t(circle_edge_count); + for (int i = 0; i < circle_edge_count; i++) { + shape_outline.write[i] = static_body_xform.xform(Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * circle_radius); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConcavePolygonShape2D *concave_polygon_shape = Object::cast_to<ConcavePolygonShape2D>(*s); + if (concave_polygon_shape) { + Vector<Vector2> shape_outline = concave_polygon_shape->get_segments(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConvexPolygonShape2D *convex_polygon_shape = Object::cast_to<ConvexPolygonShape2D>(*s); + if (convex_polygon_shape) { + Vector<Vector2> shape_outline = convex_polygon_shape->get_points(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } + } +} + +void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + TileMap *tilemap = Object::cast_to<TileMap>(p_node); + + if (tilemap == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + + if (tilemap->get_layers_count() <= 0) { + return; + } + + int tilemap_layer = 0; // only main tile map layer is supported + + Ref<TileSet> tile_set = tilemap->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int physics_layers_count = tile_set->get_physics_layers_count(); + int navigation_layers_count = tile_set->get_navigation_layers_count(); + + if (physics_layers_count <= 0 && navigation_layers_count <= 0) { + return; + } + + const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tilemap->get_global_transform(); + TypedArray<Vector2i> used_cells = tilemap->get_used_cells(tilemap_layer); + + for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) { + const Vector2i &cell = used_cells[used_cell_index]; + + const TileData *tile_data = tilemap->get_cell_tile_data(tilemap_layer, cell, false); + + Transform2D tile_transform; + tile_transform.set_origin(tilemap->map_to_local(cell)); + + const Transform2D tile_transform_offset = tilemap_xform * tile_transform; + + if (navigation_layers_count > 0) { + Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(tilemap_layer); + if (navigation_polygon.is_valid()) { + for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) { + Vector<Vector2> traversable_outline = navigation_polygon->get_outline(outline_index); + + for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) { + traversable_outline.write[traversable_outline_index] = tile_transform_offset.xform(traversable_outline[traversable_outline_index]); + } + + p_source_geometry_data->_add_traversable_outline(traversable_outline); + } + } + } + + if (physics_layers_count > 0 && (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && (tile_set->get_physics_layer_collision_layer(tilemap_layer) & parsed_collision_mask)) { + for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(tilemap_layer); collision_polygon_index++) { + Vector<Vector2> obstruction_outline = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index); + + for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) { + obstruction_outline.write[obstruction_outline_index] = tile_transform_offset.xform(obstruction_outline[obstruction_outline_index]); + } + + p_source_geometry_data->_add_obstruction_outline(obstruction_outline); + } + } + } +} + +void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node) { + List<Node *> parse_nodes; + + if (p_navigation_mesh->get_source_geometry_mode() == NavigationPolygon::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) { + parse_nodes.push_back(p_root_node); + } else { + p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_geometry_group_name(), &parse_nodes); + } + + Transform2D root_node_transform = Transform2D(); + if (Object::cast_to<Node2D>(p_root_node)) { + root_node_transform = Object::cast_to<Node2D>(p_root_node)->get_global_transform().affine_inverse(); + } + + p_source_geometry_data->clear(); + p_source_geometry_data->root_node_transform = root_node_transform; + + bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationPolygon::SOURCE_GEOMETRY_GROUPS_EXPLICIT; + + for (Node *E : parse_nodes) { + generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, E, recurse_children); + } +}; + +static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPath64 *p_polypath_item) { + using namespace Clipper2Lib; + + Vector<Vector2> polygon_vertices; + + for (const Point64 &polypath_point : p_polypath_item->Polygon()) { + polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y))); + } + + TPPLPoly tp; + tp.Init(polygon_vertices.size()); + for (int j = 0; j < polygon_vertices.size(); j++) { + tp[j] = polygon_vertices[j]; + } + + if (p_polypath_item->IsHole()) { + tp.SetOrientation(TPPL_ORIENTATION_CW); + tp.SetHole(true); + } else { + tp.SetOrientation(TPPL_ORIENTATION_CCW); + } + p_tppl_in_polygon.push_back(tp); + + for (size_t i = 0; i < p_polypath_item->Count(); i++) { + const PolyPath64 *polypath_item = p_polypath_item->Child(i); + generator_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item); + } +} + +bool NavMeshGenerator2D::generator_emit_callback(const Callable &p_callback) { + ERR_FAIL_COND_V(!p_callback.is_valid(), false); + + Callable::CallError ce; + Variant result; + p_callback.callp(nullptr, 0, result, ce); + + return ce.error == Callable::CallError::CALL_OK; +} + +void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data) { + if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) { + return; + } + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + return; + } + + int outline_count = p_navigation_mesh->get_outline_count(); + const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines(); + const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->_get_obstruction_outlines(); + + if (outline_count == 0 && traversable_outlines.size() == 0) { + return; + } + + using namespace Clipper2Lib; + + Paths64 traversable_polygon_paths; + Paths64 obstruction_polygon_paths; + + for (int i = 0; i < outline_count; i++) { + const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i); + Path64 subject_path; + for (const Vector2 &traversable_point : traversable_outline) { + const Point64 &point = Point64(traversable_point.x, traversable_point.y); + subject_path.push_back(point); + } + traversable_polygon_paths.push_back(subject_path); + } + + for (const Vector<Vector2> &traversable_outline : traversable_outlines) { + Path64 subject_path; + for (const Vector2 &traversable_point : traversable_outline) { + const Point64 &point = Point64(traversable_point.x, traversable_point.y); + subject_path.push_back(point); + } + traversable_polygon_paths.push_back(subject_path); + } + + for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) { + Path64 clip_path; + for (const Vector2 &obstruction_point : obstruction_outline) { + const Point64 &point = Point64(obstruction_point.x, obstruction_point.y); + clip_path.push_back(point); + } + obstruction_polygon_paths.push_back(clip_path); + } + + Paths64 path_solution; + + // first merge all traversable polygons according to user specified fill rule + Paths64 dummy_clip_path; + traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero); + // merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry + obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero); + + path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero); + + real_t agent_radius_offset = p_navigation_mesh->get_agent_radius(); + if (agent_radius_offset > 0.0) { + path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon); + } + //path_solution = RamerDouglasPeucker(path_solution, 0.025); // + + Vector<Vector<Vector2>> new_baked_outlines; + + for (const Path64 &scaled_path : path_solution) { + Vector<Vector2> polypath; + for (const Point64 &scaled_point : scaled_path) { + polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y))); + } + new_baked_outlines.push_back(polypath); + } + + if (new_baked_outlines.size() == 0) { + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + + Paths64 polygon_paths; + + for (const Vector<Vector2> &baked_outline : new_baked_outlines) { + Path64 polygon_path; + for (const Vector2 &baked_outline_point : baked_outline) { + const Point64 &point = Point64(baked_outline_point.x, baked_outline_point.y); + polygon_path.push_back(point); + } + polygon_paths.push_back(polygon_path); + } + + ClipType clipper_cliptype = ClipType::Union; + + List<TPPLPoly> tppl_in_polygon, tppl_out_polygon; + + PolyTree64 polytree; + Clipper64 clipper_64; + + clipper_64.AddSubject(polygon_paths); + clipper_64.Execute(clipper_cliptype, FillRule::NonZero, polytree); + + for (size_t i = 0; i < polytree.Count(); i++) { + const PolyPath64 *polypath_item = polytree[i]; + generator_recursive_process_polytree_items(tppl_in_polygon, polypath_item); + } + + TPPLPartition tpart; + if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed! + ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + + Vector<Vector2> new_vertices; + Vector<Vector<int>> new_polygons; + + HashMap<Vector2, int> points; + for (List<TPPLPoly>::Element *I = tppl_out_polygon.front(); I; I = I->next()) { + TPPLPoly &tp = I->get(); + + Vector<int> new_polygon; + + for (int64_t i = 0; i < tp.GetNumPoints(); i++) { + HashMap<Vector2, int>::Iterator E = points.find(tp[i]); + if (!E) { + E = points.insert(tp[i], new_vertices.size()); + new_vertices.push_back(tp[i]); + } + new_polygon.push_back(E->value); + } + + new_polygons.push_back(new_polygon); + } + + p_navigation_mesh->set_vertices(new_vertices); + p_navigation_mesh->clear_polygons(); + for (int i = 0; i < new_polygons.size(); i++) { + p_navigation_mesh->add_polygon(new_polygons[i]); + } +} diff --git a/modules/navigation/nav_mesh_generator_2d.h b/modules/navigation/nav_mesh_generator_2d.h new file mode 100644 index 0000000000..763ad24636 --- /dev/null +++ b/modules/navigation/nav_mesh_generator_2d.h @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* nav_mesh_generator_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef NAV_MESH_GENERATOR_2D_H +#define NAV_MESH_GENERATOR_2D_H + +#include "core/object/class_db.h" +#include "core/object/worker_thread_pool.h" + +class Node; +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + +class NavMeshGenerator2D : public Object { + static NavMeshGenerator2D *singleton; + + static Mutex baking_navmesh_mutex; + static Mutex generator_task_mutex; + + static bool use_threads; + static bool baking_use_multiple_threads; + static bool baking_use_high_priority_threads; + + struct NavMeshGeneratorTask2D { + enum TaskStatus { + BAKING_STARTED, + BAKING_FINISHED, + BAKING_FAILED, + CALLBACK_DISPATCHED, + CALLBACK_FAILED, + }; + + Ref<NavigationPolygon> navigation_mesh; + Ref<NavigationMeshSourceGeometryData2D> source_geometry_data; + Callable callback; + WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID; + NavMeshGeneratorTask2D::TaskStatus status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; + }; + + static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> generator_tasks; + + static void generator_thread_bake(void *p_arg); + + static HashSet<Ref<NavigationPolygon>> baking_navmeshes; + + static void generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children); + static void generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node); + static void generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data); + + static void generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + + static bool generator_emit_callback(const Callable &p_callback); + +public: + static NavMeshGenerator2D *get_singleton(); + + static void sync(); + static void cleanup(); + static void finish(); + + static void parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()); + static void bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable()); + static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable()); + + NavMeshGenerator2D(); + ~NavMeshGenerator2D(); +}; + +#endif // NAV_MESH_GENERATOR_2D_H diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp index 7c27417e5f..5de1c4cba9 100644 --- a/modules/navigation/nav_mesh_generator_3d.cpp +++ b/modules/navigation/nav_mesh_generator_3d.cpp @@ -149,7 +149,7 @@ void NavMeshGenerator3D::finish() { void NavMeshGenerator3D::parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { ERR_FAIL_COND(!Thread::is_main_thread()); ERR_FAIL_COND(!p_navigation_mesh.is_valid()); - ERR_FAIL_COND(p_root_node == nullptr); + ERR_FAIL_NULL(p_root_node); ERR_FAIL_COND(!p_root_node->is_inside_tree()); ERR_FAIL_COND(!p_source_geometry_data.is_valid()); @@ -714,7 +714,7 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation bake_state = "Creating heightfield..."; // step #3 hf = rcAllocHeightfield(); - ERR_FAIL_COND(!hf); + ERR_FAIL_NULL(hf); ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)); bake_state = "Marking walkable triangles..."; // step #4 @@ -744,7 +744,7 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation chf = rcAllocCompactHeightfield(); - ERR_FAIL_COND(!chf); + ERR_FAIL_NULL(chf); ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf)); rcFreeHeightField(hf); @@ -769,17 +769,17 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation cset = rcAllocContourSet(); - ERR_FAIL_COND(!cset); + ERR_FAIL_NULL(cset); ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset)); bake_state = "Creating polymesh..."; // step #9 poly_mesh = rcAllocPolyMesh(); - ERR_FAIL_COND(!poly_mesh); + ERR_FAIL_NULL(poly_mesh); ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh)); detail_mesh = rcAllocPolyMeshDetail(); - ERR_FAIL_COND(!detail_mesh); + ERR_FAIL_NULL(detail_mesh); ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh)); rcFreeCompactHeightfield(chf); diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index 4e7964ed76..3675aae518 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -89,13 +89,13 @@ int NavRegion::get_connections_count() const { } Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const { - ERR_FAIL_COND_V(!map, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); return connections[p_connection_id].pathway_start; } Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const { - ERR_FAIL_COND_V(!map, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); return connections[p_connection_id].pathway_end; } diff --git a/modules/navigation/register_types.cpp b/modules/navigation/register_types.cpp index 1548ff4b9c..525fe71134 100644 --- a/modules/navigation/register_types.cpp +++ b/modules/navigation/register_types.cpp @@ -31,6 +31,7 @@ #include "register_types.h" #include "godot_navigation_server.h" +#include "godot_navigation_server_2d.h" #ifndef DISABLE_DEPRECATED #ifndef _3D_DISABLED @@ -43,6 +44,7 @@ #endif #include "core/config/engine.h" +#include "servers/navigation_server_2d.h" #include "servers/navigation_server_3d.h" #ifndef DISABLE_DEPRECATED @@ -55,9 +57,14 @@ NavigationServer3D *new_server() { return memnew(GodotNavigationServer); } +NavigationServer2D *new_navigation_server_2d() { + return memnew(GodotNavigationServer2D); +} + void initialize_navigation_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) { NavigationServer3DManager::set_default_server(new_server); + NavigationServer2DManager::set_default_server(new_navigation_server_2d); #ifndef DISABLE_DEPRECATED #ifndef _3D_DISABLED diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 1bd10f1009..73a3723ea4 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -106,10 +106,13 @@ if env["opengl3"] and env["platform"] != "macos": env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_fb_foveation_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_fb_update_swapchain_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_pico_controller_extension.cpp") diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index 6d79e33de8..72866f1cf7 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -206,7 +206,8 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/waist," "/user/vive_tracker_htcx/role/chest," "/user/vive_tracker_htcx/role/camera," - "/user/vive_tracker_htcx/role/keyboard"); + "/user/vive_tracker_htcx/role/keyboard," + "/user/eyes_ext"); Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> palm_pose = action_set->add_new_action("palm_pose", "Palm pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); @@ -503,6 +504,11 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/camera/output/haptic," "/user/vive_tracker_htcx/role/keyboard/output/haptic"); add_interaction_profile(profile); + + // Create our eye gaze interaction profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/eye_gaze_interaction"); + profile->add_new_binding(default_pose, "/user/eyes_ext/input/gaze_ext/pose"); + add_interaction_profile(profile); } void OpenXRActionMap::create_editor_action_sets() { diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 8d8cbf1a29..c7c666dc2f 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -77,6 +77,20 @@ Returns [code]true[/code] if the given action set is active. </description> </method> + <method name="is_eye_gaze_interaction_supported"> + <return type="bool" /> + <description> + Returns the capabilities of the eye gaze interaction extension. + [b]Note:[/b] This only returns a valid value after OpenXR has been initialized. + </description> + </method> + <method name="is_foveation_supported" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if OpenXRs foveation extension is supported, the interface must be initialised before this returns a valid value. + [b]Note:[/b] This feature is only available on the compatibility renderer and currently only available on some stand alone headsets. For Vulkan set [member Viewport.vrs_mode] to [code]VRS_XR[/code] on desktop. + </description> + </method> <method name="set_action_set_active"> <return type="void" /> <param index="0" name="name" type="String" /> @@ -98,6 +112,12 @@ <member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0"> The display refresh rate for the current HMD. Only functional if this feature is supported by the OpenXR runtime and after the interface has been initialized. </member> + <member name="foveation_dynamic" type="bool" setter="set_foveation_dynamic" getter="get_foveation_dynamic" default="false"> + Enable dynamic foveation adjustment, the interface must be initialised before this is accessible. If enabled foveation will automatically adjusted between low and [member foveation_level]. + </member> + <member name="foveation_level" type="int" setter="set_foveation_level" getter="get_foveation_level" default="0"> + Set foveation level from 0 (off) to 3 (high), the interface must be initialised before this is accessible. + </member> <member name="render_target_size_multiplier" type="float" setter="set_render_target_size_multiplier" getter="get_render_target_size_multiplier" default="1.0"> The render size multiplier for the current HMD. Must be set before the interface has been initialized. </member> diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp new file mode 100644 index 0000000000..59bdec5c8e --- /dev/null +++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* openxr_eye_gaze_interaction.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_eye_gaze_interaction.h" + +#include "core/os/os.h" + +#include "../action_map/openxr_interaction_profile_metadata.h" + +OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::singleton = nullptr; + +OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::get_singleton() { + ERR_FAIL_NULL_V(singleton, nullptr); + return singleton; +} + +OpenXREyeGazeInteractionExtension::OpenXREyeGazeInteractionExtension() { + singleton = this; +} + +OpenXREyeGazeInteractionExtension::~OpenXREyeGazeInteractionExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXREyeGazeInteractionExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME] = &available; + + return request_extensions; +} + +void *OpenXREyeGazeInteractionExtension::set_system_properties_and_get_next_pointer(void *p_next_pointer) { + if (!available) { + return p_next_pointer; + } + + properties.type = XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT; + properties.next = p_next_pointer; + properties.supportsEyeGazeInteraction = false; + + return &properties; +} + +bool OpenXREyeGazeInteractionExtension::is_available() { + return available; +} + +bool OpenXREyeGazeInteractionExtension::supports_eye_gaze_interaction() { + // The extension being available only means that the OpenXR Runtime supports the extension. + // The `supportsEyeGazeInteraction` is set to true if the device also supports this. + // Thus both need to be true. + // In addition, on mobile runtimes, the proper permission needs to be granted. + if (available && properties.supportsEyeGazeInteraction) { + return !OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature("PERMISSION_XR_EXT_eye_gaze_interaction"); + } + + return false; +} + +void OpenXREyeGazeInteractionExtension::on_register_metadata() { + OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton(); + ERR_FAIL_NULL(metadata); + + // Eyes top path + metadata->register_top_level_path("Eye gaze tracker", "/user/eyes_ext", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME); + + // Eye gaze interaction + metadata->register_interaction_profile("Eye gaze", "/interaction_profiles/ext/eye_gaze_interaction", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME); + metadata->register_io_path("/interaction_profiles/ext/eye_gaze_interaction", "Gaze pose", "/user/eyes_ext", "/user/eyes_ext/input/gaze_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE); +} diff --git a/modules/denoise/lightmap_denoiser.h b/modules/openxr/extensions/openxr_eye_gaze_interaction.h index 8f658ab096..704940ad26 100644 --- a/modules/denoise/lightmap_denoiser.h +++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* lightmap_denoiser.h */ +/* openxr_eye_gaze_interaction.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,31 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef LIGHTMAP_DENOISER_H -#define LIGHTMAP_DENOISER_H +#ifndef OPENXR_EYE_GAZE_INTERACTION_H +#define OPENXR_EYE_GAZE_INTERACTION_H -#include "core/object/class_db.h" -#include "scene/3d/lightmapper.h" +#include "openxr_extension_wrapper.h" -struct OIDNDeviceImpl; +class OpenXREyeGazeInteractionExtension : public OpenXRExtensionWrapper { +public: + static OpenXREyeGazeInteractionExtension *get_singleton(); -class LightmapDenoiserOIDN : public LightmapDenoiser { - GDCLASS(LightmapDenoiserOIDN, LightmapDenoiser); + OpenXREyeGazeInteractionExtension(); + ~OpenXREyeGazeInteractionExtension(); -protected: - void *device = nullptr; + virtual HashMap<String, bool *> get_requested_extensions() override; + virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) override; -public: - static LightmapDenoiser *create_oidn_denoiser(); + bool is_available(); + bool supports_eye_gaze_interaction(); - Ref<Image> denoise_image(const Ref<Image> &p_image) override; + virtual void on_register_metadata() override; - static void make_default_denoiser(); +private: + static OpenXREyeGazeInteractionExtension *singleton; - LightmapDenoiserOIDN(); - ~LightmapDenoiserOIDN(); + bool available = false; + XrSystemEyeGazeInteractionPropertiesEXT properties; }; -#endif // LIGHTMAP_DENOISER_H +#endif // OPENXR_EYE_GAZE_INTERACTION_H diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.cpp b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp new file mode 100644 index 0000000000..7277f85b12 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp @@ -0,0 +1,168 @@ +/**************************************************************************/ +/* openxr_fb_foveation_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_fb_foveation_extension.h" +#include "core/config/project_settings.h" + +OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::singleton = nullptr; + +OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::get_singleton() { + return singleton; +} + +OpenXRFBFoveationExtension::OpenXRFBFoveationExtension(const String &p_rendering_driver) { + singleton = this; + rendering_driver = p_rendering_driver; + swapchain_update_state_ext = OpenXRFBUpdateSwapchainExtension::get_singleton(); + int fov_level = GLOBAL_GET("xr/openxr/foveation_level"); + if (fov_level >= 0 && fov_level < 4) { + foveation_level = XrFoveationLevelFB(fov_level); + } + bool fov_dyn = GLOBAL_GET("xr/openxr/foveation_dynamic"); + foveation_dynamic = fov_dyn ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB; + + swapchain_create_info_foveation_fb.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB; + swapchain_create_info_foveation_fb.next = nullptr; + swapchain_create_info_foveation_fb.flags = 0; +} + +OpenXRFBFoveationExtension::~OpenXRFBFoveationExtension() { + singleton = nullptr; + swapchain_update_state_ext = nullptr; +} + +HashMap<String, bool *> OpenXRFBFoveationExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + if (rendering_driver == "vulkan") { + // This is currently only supported on OpenGL, but we may add Vulkan support in the future... + + } else if (rendering_driver == "opengl3") { + request_extensions[XR_FB_FOVEATION_EXTENSION_NAME] = &fb_foveation_ext; + request_extensions[XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME] = &fb_foveation_configuration_ext; + } + + return request_extensions; +} + +void OpenXRFBFoveationExtension::on_instance_created(const XrInstance p_instance) { + if (fb_foveation_ext) { + EXT_INIT_XR_FUNC(xrCreateFoveationProfileFB); + EXT_INIT_XR_FUNC(xrDestroyFoveationProfileFB); + } + + if (fb_foveation_configuration_ext) { + // nothing to register here... + } +} + +void OpenXRFBFoveationExtension::on_instance_destroyed() { + fb_foveation_ext = false; + fb_foveation_configuration_ext = false; +} + +bool OpenXRFBFoveationExtension::is_enabled() const { + return swapchain_update_state_ext != nullptr && swapchain_update_state_ext->is_enabled() && fb_foveation_ext && fb_foveation_configuration_ext; +} + +void *OpenXRFBFoveationExtension::set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { + if (is_enabled()) { + swapchain_create_info_foveation_fb.next = p_next_pointer; + return &swapchain_create_info_foveation_fb; + } else { + return p_next_pointer; + } +} + +void OpenXRFBFoveationExtension::on_state_ready() { + update_profile(); +} + +XrFoveationLevelFB OpenXRFBFoveationExtension::get_foveation_level() const { + return foveation_level; +} + +void OpenXRFBFoveationExtension::set_foveation_level(XrFoveationLevelFB p_foveation_level) { + foveation_level = p_foveation_level; + + // Update profile will do nothing if we're not yet initialised + update_profile(); +} + +XrFoveationDynamicFB OpenXRFBFoveationExtension::get_foveation_dynamic() const { + return foveation_dynamic; +} + +void OpenXRFBFoveationExtension::set_foveation_dynamic(XrFoveationDynamicFB p_foveation_dynamic) { + foveation_dynamic = p_foveation_dynamic; + + // Update profile will do nothing if we're not yet initialised + update_profile(); +} + +void OpenXRFBFoveationExtension::update_profile() { + if (!is_enabled()) { + return; + } + + XrFoveationLevelProfileCreateInfoFB level_profile_create_info; + level_profile_create_info.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB; + level_profile_create_info.next = nullptr; + level_profile_create_info.level = foveation_level; + level_profile_create_info.verticalOffset = 0.0f; + level_profile_create_info.dynamic = foveation_dynamic; + + XrFoveationProfileCreateInfoFB profile_create_info; + profile_create_info.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB; + profile_create_info.next = &level_profile_create_info; + + XrFoveationProfileFB foveation_profile; + XrResult result = xrCreateFoveationProfileFB(OpenXRAPI::get_singleton()->get_session(), &profile_create_info, &foveation_profile); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to create the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + return; + } + + XrSwapchainStateFoveationFB foveation_update_state; + foveation_update_state.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB; + foveation_update_state.profile = foveation_profile; + + result = swapchain_update_state_ext->xrUpdateSwapchainFB(OpenXRAPI::get_singleton()->get_color_swapchain(), (XrSwapchainStateBaseHeaderFB *)&foveation_update_state); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to update the swapchain [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + + // We still want to destroy our profile so keep going... + } + + result = xrDestroyFoveationProfileFB(foveation_profile); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to destroy the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + } +} diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.h b/modules/openxr/extensions/openxr_fb_foveation_extension.h new file mode 100644 index 0000000000..1c5e722731 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.h @@ -0,0 +1,96 @@ +/**************************************************************************/ +/* openxr_fb_foveation_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_FB_FOVEATION_EXTENSION_H +#define OPENXR_FB_FOVEATION_EXTENSION_H + +// This extension implements the FB Foveation extension. +// This is an extension Meta added due to VRS being unavailable on Android. +// Other Android based devices are implementing this as well, see: +// https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_FB_foveation + +// Note: Currently we only support this for OpenGL. +// This extension works on enabling foveated rendering on the swapchain. +// Vulkan does not render 3D content directly to the swapchain image +// hence this extension can't be used. + +#include "../openxr_api.h" +#include "../util.h" +#include "openxr_extension_wrapper.h" +#include "openxr_fb_update_swapchain_extension.h" + +class OpenXRFBFoveationExtension : public OpenXRExtensionWrapper { +public: + static OpenXRFBFoveationExtension *get_singleton(); + + OpenXRFBFoveationExtension(const String &p_rendering_driver); + virtual ~OpenXRFBFoveationExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override; + + virtual void on_state_ready() override; + + bool is_enabled() const; + + XrFoveationLevelFB get_foveation_level() const; + void set_foveation_level(XrFoveationLevelFB p_foveation_level); + + XrFoveationDynamicFB get_foveation_dynamic() const; + void set_foveation_dynamic(XrFoveationDynamicFB p_foveation_dynamic); + +private: + static OpenXRFBFoveationExtension *singleton; + + // Setup + String rendering_driver; + bool fb_foveation_ext = false; + bool fb_foveation_configuration_ext = false; + + // Configuration + XrFoveationLevelFB foveation_level = XR_FOVEATION_LEVEL_NONE_FB; + XrFoveationDynamicFB foveation_dynamic = XR_FOVEATION_DYNAMIC_DISABLED_FB; + + void update_profile(); + + // Enable foveation on this swapchain + XrSwapchainCreateInfoFoveationFB swapchain_create_info_foveation_fb; + OpenXRFBUpdateSwapchainExtension *swapchain_update_state_ext = nullptr; + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC3(xrCreateFoveationProfileFB, (XrSession), session, (const XrFoveationProfileCreateInfoFB *), create_info, (XrFoveationProfileFB *), profile); + EXT_PROTO_XRRESULT_FUNC1(xrDestroyFoveationProfileFB, (XrFoveationProfileFB), profile); +}; + +#endif // OPENXR_FB_FOVEATION_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp new file mode 100644 index 0000000000..1289183ea4 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp @@ -0,0 +1,102 @@ +/**************************************************************************/ +/* openxr_fb_update_swapchain_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_fb_update_swapchain_extension.h" + +// always include this as late as possible +#include "../openxr_platform_inc.h" + +OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::singleton = nullptr; + +OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::get_singleton() { + return singleton; +} + +OpenXRFBUpdateSwapchainExtension::OpenXRFBUpdateSwapchainExtension(const String &p_rendering_driver) { + singleton = this; + rendering_driver = p_rendering_driver; +} + +OpenXRFBUpdateSwapchainExtension::~OpenXRFBUpdateSwapchainExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRFBUpdateSwapchainExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME] = &fb_swapchain_update_state_ext; + + if (rendering_driver == "vulkan") { +#ifdef XR_USE_GRAPHICS_API_VULKAN + request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME] = &fb_swapchain_update_state_vulkan_ext; +#endif + } else if (rendering_driver == "opengl3") { +#ifdef XR_USE_GRAPHICS_API_OPENGL_ES + request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME] = &fb_swapchain_update_state_opengles_ext; +#endif + } + + return request_extensions; +} + +void OpenXRFBUpdateSwapchainExtension::on_instance_created(const XrInstance p_instance) { + if (fb_swapchain_update_state_ext) { + EXT_INIT_XR_FUNC(xrUpdateSwapchainFB); + EXT_INIT_XR_FUNC(xrGetSwapchainStateFB); + } + + if (fb_swapchain_update_state_vulkan_ext) { + // nothing to register here... + } + + if (fb_swapchain_update_state_opengles_ext) { + // nothing to register here... + } +} + +void OpenXRFBUpdateSwapchainExtension::on_instance_destroyed() { + fb_swapchain_update_state_ext = false; + fb_swapchain_update_state_vulkan_ext = false; + fb_swapchain_update_state_opengles_ext = false; +} + +bool OpenXRFBUpdateSwapchainExtension::is_enabled() const { + if (rendering_driver == "vulkan") { + return fb_swapchain_update_state_ext && fb_swapchain_update_state_vulkan_ext; + } else if (rendering_driver == "opengl3") { +#ifdef XR_USE_GRAPHICS_API_OPENGL_ES + return fb_swapchain_update_state_ext && fb_swapchain_update_state_opengles_ext; +#else + return fb_swapchain_update_state_ext; +#endif + } + + return false; +} diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.h index 87f02cb4c6..a02b550e58 100644 --- a/modules/denoise/denoise_wrapper.cpp +++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* denoise_wrapper.cpp */ +/* openxr_fb_update_swapchain_extension.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,39 +28,46 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "denoise_wrapper.h" +#ifndef OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H +#define OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H -#include <OpenImageDenoise/oidn.h> +// This extension implements the FB update swapchain extension. +// This is an extension Meta added to further configure the swapchain. +// Other Android based devices are implementing this as well, see: +// https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_FB_swapchain_update_state -#include <stdio.h> +#include "../openxr_api.h" +#include "../util.h" +#include "openxr_extension_wrapper.h" -void *oidn_denoiser_init() { - OIDNDeviceImpl *device = oidnNewDevice(OIDN_DEVICE_TYPE_CPU); - oidnCommitDevice(device); - return device; -} +class OpenXRFBUpdateSwapchainExtension : public OpenXRExtensionWrapper { + friend class OpenXRFBFoveationExtension; -bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) { - OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr; - OIDNFilter filter = oidnNewFilter(device, "RTLightmap"); - oidnSetSharedFilterImage(filter, "color", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); - oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); - oidnSetFilter1b(filter, "hdr", true); - //oidnSetFilter1f(filter, "hdrScale", 1.0f); - oidnCommitFilter(filter); - oidnExecuteFilter(filter); +public: + static OpenXRFBUpdateSwapchainExtension *get_singleton(); - const char *msg; - bool success = true; - if (oidnGetDeviceError(device, &msg) != OIDN_ERROR_NONE) { - printf("LightmapDenoiser: %s\n", msg); - success = false; - } + OpenXRFBUpdateSwapchainExtension(const String &p_rendering_driver); + virtual ~OpenXRFBUpdateSwapchainExtension() override; - oidnReleaseFilter(filter); - return success; -} + virtual HashMap<String, bool *> get_requested_extensions() override; -void oidn_denoiser_finish(void *device) { - oidnReleaseDevice((OIDNDeviceImpl *)device); -} + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + bool is_enabled() const; + +private: + static OpenXRFBUpdateSwapchainExtension *singleton; + + // Setup + String rendering_driver; + bool fb_swapchain_update_state_ext = false; + bool fb_swapchain_update_state_vulkan_ext = false; + bool fb_swapchain_update_state_opengles_ext = false; + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC2(xrUpdateSwapchainFB, (XrSwapchain), swapchain, (const XrSwapchainStateBaseHeaderFB *), state); + EXT_PROTO_XRRESULT_FUNC2(xrGetSwapchainStateFB, (XrSwapchain), swapchain, (XrSwapchainStateBaseHeaderFB *), state); +}; + +#endif // OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index c92b2b08d0..caf97ca2e0 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -32,6 +32,7 @@ #include "../openxr_api.h" +#include "core/config/project_settings.h" #include "core/string/print_string.h" #include "servers/xr_server.h" @@ -59,7 +60,6 @@ HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions() request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext; request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext; - request_extensions[XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME] = &hand_tracking_aim_state_ext; return request_extensions; } @@ -106,17 +106,11 @@ void OpenXRHandTrackingExtension::on_state_ready() { } // Setup our hands and reset data - for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) { // we'll do this later hand_trackers[i].is_initialized = false; hand_trackers[i].hand_tracker = XR_NULL_HANDLE; - hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }; - hand_trackers[i].aimState.pinchStrengthIndex = 0.0; - hand_trackers[i].aimState.pinchStrengthMiddle = 0.0; - hand_trackers[i].aimState.pinchStrengthRing = 0.0; - hand_trackers[i].aimState.pinchStrengthLittle = 0.0; - hand_trackers[i].locations.isActive = false; for (int j = 0; j < XR_HAND_JOINT_COUNT_EXT; j++) { @@ -141,7 +135,7 @@ void OpenXRHandTrackingExtension::on_process() { XrResult result; - for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) { if (hand_trackers[i].hand_tracker == XR_NULL_HANDLE) { XrHandTrackerCreateInfoEXT createInfo = { XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, // type @@ -157,18 +151,6 @@ void OpenXRHandTrackingExtension::on_process() { hand_trackers[i].is_initialized = false; } else { void *next_pointer = nullptr; - if (hand_tracking_aim_state_ext) { - hand_trackers[i].aimState.type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB; - hand_trackers[i].aimState.next = next_pointer; - hand_trackers[i].aimState.status = 0; - hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }; - hand_trackers[i].aimState.pinchStrengthIndex = 0.0; - hand_trackers[i].aimState.pinchStrengthMiddle = 0.0; - hand_trackers[i].aimState.pinchStrengthRing = 0.0; - hand_trackers[i].aimState.pinchStrengthLittle = 0.0; - - next_pointer = &hand_trackers[i].aimState; - } hand_trackers[i].velocities.type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT; hand_trackers[i].velocities.next = next_pointer; @@ -219,20 +201,6 @@ void OpenXRHandTrackingExtension::on_process() { !hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive } - - /* TODO change this to managing the controller from openxr_interface - if (hand_tracking_aim_state_ext && hand_trackers[i].locations.isActive && check_bit(XR_HAND_TRACKING_AIM_VALID_BIT_FB, hand_trackers[i].aimState.status)) { - // Controllers are updated based on the aim state's pose and pinches' strength - if (hand_trackers[i].aim_state_godot_controller == -1) { - hand_trackers[i].aim_state_godot_controller = - arvr_api->godot_arvr_add_controller( - const_cast<char *>(hand_controller_names[i]), - i + HAND_CONTROLLER_ID_OFFSET, - true, - true); - } - } - */ } } } @@ -246,7 +214,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); - for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) { if (hand_trackers[i].hand_tracker != XR_NULL_HANDLE) { xrDestroyHandTrackerEXT(hand_trackers[i].hand_tracker); @@ -260,25 +228,25 @@ bool OpenXRHandTrackingExtension::get_active() { return handTrackingSystemProperties.supportsHandTracking; } -const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(uint32_t p_hand) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, nullptr); +const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(HandTrackedHands p_hand) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, nullptr); return &hand_trackers[p_hand]; } -XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(uint32_t p_hand) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT); +XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTrackedHands p_hand) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT); return hand_trackers[p_hand].motion_range; } -void OpenXRHandTrackingExtension::set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range) { - ERR_FAIL_UNSIGNED_INDEX(p_hand, MAX_OPENXR_TRACKED_HANDS); +void OpenXRHandTrackingExtension::set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range) { + ERR_FAIL_UNSIGNED_INDEX(p_hand, OPENXR_MAX_TRACKED_HANDS); hand_trackers[p_hand].motion_range = p_motion_range; } -Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Quaternion()); +Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Quaternion()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Quaternion()); if (!hand_trackers[p_hand].is_initialized) { @@ -289,8 +257,8 @@ Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand, return Quaternion(location.pose.orientation.x, location.pose.orientation.y, location.pose.orientation.z, location.pose.orientation.w); } -Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3()); +Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3()); if (!hand_trackers[p_hand].is_initialized) { @@ -301,8 +269,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, Xr return Vector3(location.pose.position.x, location.pose.position.y, location.pose.position.z); } -float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, 0.0); +float OpenXRHandTrackingExtension::get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, 0.0); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, 0.0); if (!hand_trackers[p_hand].is_initialized) { @@ -312,8 +280,8 @@ float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHand return hand_trackers[p_hand].joint_locations[p_joint].radius; } -Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3()); +Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3()); if (!hand_trackers[p_hand].is_initialized) { @@ -324,8 +292,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_h return Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z); } -Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3()); +Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3()); if (!hand_trackers[p_hand].is_initialized) { diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h index 99d315c525..5ca0ff60d3 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.h +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h @@ -35,10 +35,14 @@ #include "core/math/quaternion.h" #include "openxr_extension_wrapper.h" -#define MAX_OPENXR_TRACKED_HANDS 2 - class OpenXRHandTrackingExtension : public OpenXRExtensionWrapper { public: + enum HandTrackedHands { + OPENXR_TRACKED_LEFT_HAND, + OPENXR_TRACKED_RIGHT_HAND, + OPENXR_MAX_TRACKED_HANDS + }; + struct HandTracker { bool is_initialized = false; XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; @@ -47,7 +51,6 @@ public: XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT]; XrHandJointVelocityEXT joint_velocities[XR_HAND_JOINT_COUNT_EXT]; - XrHandTrackingAimStateFB aimState; XrHandJointVelocitiesEXT velocities; XrHandJointLocationsEXT locations; }; @@ -69,29 +72,28 @@ public: virtual void on_state_stopping() override; bool get_active(); - const HandTracker *get_hand_tracker(uint32_t p_hand) const; + const HandTracker *get_hand_tracker(HandTrackedHands p_hand) const; - XrHandJointsMotionRangeEXT get_motion_range(uint32_t p_hand) const; - void set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range); + XrHandJointsMotionRangeEXT get_motion_range(HandTrackedHands p_hand) const; + void set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range); - Quaternion get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const; - Vector3 get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const; - float get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const; + Quaternion get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + Vector3 get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + float get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; - Vector3 get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const; - Vector3 get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const; + Vector3 get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + Vector3 get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; private: static OpenXRHandTrackingExtension *singleton; // state XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties; - HandTracker hand_trackers[MAX_OPENXR_TRACKED_HANDS]; // Fixed for left and right hand + HandTracker hand_trackers[OPENXR_MAX_TRACKED_HANDS]; // Fixed for left and right hand // related extensions bool hand_tracking_ext = false; bool hand_motion_range_ext = false; - bool hand_tracking_aim_state_ext = false; // functions void cleanup_hand_tracking(); diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h index 598d3415ad..3b0aa0bce9 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -39,39 +39,8 @@ #include "core/templates/vector.h" -#ifdef ANDROID_ENABLED -#define XR_USE_GRAPHICS_API_OPENGL_ES -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES3/gl3.h> -#include <GLES3/gl3ext.h> -#else -#define XR_USE_GRAPHICS_API_OPENGL -#endif - -#ifdef WINDOWS_ENABLED -// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform -// however due to the way the openxr headers are put together, we have no choice. -#include <windows.h> -#endif - -#ifdef X11_ENABLED -#include OPENGL_INCLUDE_H -#define GL_GLEXT_PROTOTYPES 1 -#define GL3_PROTOTYPES 1 -#include "thirdparty/glad/glad/gl.h" -#include "thirdparty/glad/glad/glx.h" - -#include <X11/Xlib.h> -#endif - -#ifdef ANDROID_ENABLED -// The jobject type from jni.h is used by openxr_platform.h on Android. -#include <jni.h> -#endif - -// Include platform dependent structs. -#include <openxr/openxr_platform.h> +// always include this as late as possible +#include "../openxr_platform_inc.h" class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper { public: diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h index 4add6f6fa2..f31621fda0 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -36,24 +36,9 @@ #include "openxr_extension_wrapper.h" #include "core/templates/vector.h" -#include "drivers/vulkan/vulkan_context.h" -// Need to include Vulkan so we know of type definitions. -#define XR_USE_GRAPHICS_API_VULKAN - -#ifdef WINDOWS_ENABLED -// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform -// however due to the way the openxr headers are put together, we have no choice. -#include <windows.h> -#endif - -#ifdef ANDROID_ENABLED -// The jobject type from jni.h is used by openxr_platform.h on Android. -#include <jni.h> -#endif - -// Include platform dependent structs. -#include <openxr/openxr_platform.h> +// always include this as late as possible +#include "../openxr_platform_inc.h" class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { public: diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 1f1c7e4ee0..b1c7ab1615 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -43,31 +43,7 @@ #include "editor/editor_settings.h" #endif -// We need to have all the graphics API defines before the Vulkan or OpenGL -// extensions are included, otherwise we'll only get one graphics API. -#ifdef VULKAN_ENABLED -#define XR_USE_GRAPHICS_API_VULKAN -#endif -#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) -#ifdef ANDROID_ENABLED -#define XR_USE_GRAPHICS_API_OPENGL_ES -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES3/gl3.h> -#include <GLES3/gl3ext.h> -#else -#define XR_USE_GRAPHICS_API_OPENGL -#endif // ANDROID_ENABLED -#ifdef X11_ENABLED -#include OPENGL_INCLUDE_H -#define GL_GLEXT_PROTOTYPES 1 -#define GL3_PROTOTYPES 1 -#include "thirdparty/glad/glad/gl.h" -#include "thirdparty/glad/glad/glx.h" - -#include <X11/Xlib.h> -#endif // X11_ENABLED -#endif // GLES_ENABLED +#include "openxr_platform_inc.h" #ifdef VULKAN_ENABLED #include "extensions/openxr_vulkan_extension.h" @@ -79,7 +55,9 @@ #include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" +#include "extensions/openxr_fb_foveation_extension.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" +#include "extensions/openxr_fb_update_swapchain_extension.h" #ifdef ANDROID_ENABLED #define OPENXR_LOADER_NAME "libopenxr_loader.so" @@ -1341,6 +1319,10 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device."); } + // Also register our rendering extensions + register_extension_wrapper(memnew(OpenXRFBUpdateSwapchainExtension(p_rendering_driver))); + register_extension_wrapper(memnew(OpenXRFBFoveationExtension(p_rendering_driver))); + // initialize for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_before_instance_created(); @@ -1860,6 +1842,10 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { return true; } +XrSwapchain OpenXRAPI::get_color_swapchain() { + return swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain; +} + RID OpenXRAPI::get_color_texture() { if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index); @@ -2011,6 +1997,55 @@ void OpenXRAPI::set_render_target_size_multiplier(double multiplier) { render_target_size_multiplier = multiplier; } +bool OpenXRAPI::is_foveation_supported() const { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + return fov_ext != nullptr && fov_ext->is_enabled(); +} + +int OpenXRAPI::get_foveation_level() const { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + switch (fov_ext->get_foveation_level()) { + case XR_FOVEATION_LEVEL_NONE_FB: + return 0; + case XR_FOVEATION_LEVEL_LOW_FB: + return 1; + case XR_FOVEATION_LEVEL_MEDIUM_FB: + return 2; + case XR_FOVEATION_LEVEL_HIGH_FB: + return 3; + default: + return 0; + } + } + + return 0; +} + +void OpenXRAPI::set_foveation_level(int p_foveation_level) { + ERR_FAIL_UNSIGNED_INDEX(p_foveation_level, 4); + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + XrFoveationLevelFB levels[] = { XR_FOVEATION_LEVEL_NONE_FB, XR_FOVEATION_LEVEL_LOW_FB, XR_FOVEATION_LEVEL_MEDIUM_FB, XR_FOVEATION_LEVEL_HIGH_FB }; + fov_ext->set_foveation_level(levels[p_foveation_level]); + } +} + +bool OpenXRAPI::get_foveation_dynamic() const { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + return fov_ext->get_foveation_dynamic() == XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB; + } + return false; +} + +void OpenXRAPI::set_foveation_dynamic(bool p_foveation_dynamic) { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + fov_ext->set_foveation_dynamic(p_foveation_dynamic ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB); + } +} + OpenXRAPI::OpenXRAPI() { // OpenXRAPI is only constructed if OpenXR is enabled. singleton = this; diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 26de535153..89f8f3cbec 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -54,7 +54,6 @@ // Godot is currently restricted to C++17 which doesn't allow this notation. Make sure critical fields are set. // forward declarations, we don't want to include these fully -class OpenXRVulkanExtension; class OpenXRInterface; class OpenXRAPI { @@ -356,6 +355,7 @@ public: void pre_render(); bool pre_draw_viewport(RID p_render_target); + XrSwapchain get_color_swapchain(); RID get_color_texture(); RID get_depth_texture(); void post_draw_viewport(RID p_render_target); @@ -370,6 +370,15 @@ public: double get_render_target_size_multiplier() const; void set_render_target_size_multiplier(double multiplier); + // Foveation settings + bool is_foveation_supported() const; + + int get_foveation_level() const; + void set_foveation_level(int p_foveation_level); + + bool get_foveation_dynamic() const; + void set_foveation_dynamic(bool p_foveation_dynamic); + // action map String get_default_action_map_resource_name(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index cf8d1654b1..d0b01c5771 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -34,7 +34,7 @@ #include "core/io/resource_saver.h" #include "servers/rendering/rendering_server_globals.h" -#include "extensions/openxr_hand_tracking_extension.h" +#include "extensions/openxr_eye_gaze_interaction.h" void OpenXRInterface::_bind_methods() { // lifecycle signals @@ -54,10 +54,23 @@ void OpenXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_render_target_size_multiplier", "multiplier"), &OpenXRInterface::set_render_target_size_multiplier); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "render_target_size_multiplier"), "set_render_target_size_multiplier", "get_render_target_size_multiplier"); + // Foveation level + ClassDB::bind_method(D_METHOD("is_foveation_supported"), &OpenXRInterface::is_foveation_supported); + + ClassDB::bind_method(D_METHOD("get_foveation_level"), &OpenXRInterface::get_foveation_level); + ClassDB::bind_method(D_METHOD("set_foveation_level", "foveation_level"), &OpenXRInterface::set_foveation_level); + ADD_PROPERTY(PropertyInfo(Variant::INT, "foveation_level"), "set_foveation_level", "get_foveation_level"); + + ClassDB::bind_method(D_METHOD("get_foveation_dynamic"), &OpenXRInterface::get_foveation_dynamic); + ClassDB::bind_method(D_METHOD("set_foveation_dynamic", "foveation_dynamic"), &OpenXRInterface::set_foveation_dynamic); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "foveation_dynamic"), "set_foveation_dynamic", "get_foveation_dynamic"); + + // Action sets ClassDB::bind_method(D_METHOD("is_action_set_active", "name"), &OpenXRInterface::is_action_set_active); ClassDB::bind_method(D_METHOD("set_action_set_active", "name", "active"), &OpenXRInterface::set_action_set_active); ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRInterface::get_action_sets); + // Refresh rates ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); // Hand tracking. @@ -106,6 +119,8 @@ void OpenXRInterface::_bind_methods() { BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_DISTAL); BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP); BIND_ENUM_CONSTANT(HAND_JOINT_MAX); + + ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported); } StringName OpenXRInterface::get_name() const { @@ -139,7 +154,9 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { "/user/vive_tracker_htcx/role/waist", "/user/vive_tracker_htcx/role/chest", "/user/vive_tracker_htcx/role/camera", - "/user/vive_tracker_htcx/role/keyboard" + "/user/vive_tracker_htcx/role/keyboard", + + "/user/eyes_ext", }; return arr; @@ -692,6 +709,21 @@ Array OpenXRInterface::get_available_display_refresh_rates() const { } } +bool OpenXRInterface::is_eye_gaze_interaction_supported() { + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else { + OpenXREyeGazeInteractionExtension *eye_gaze_ext = OpenXREyeGazeInteractionExtension::get_singleton(); + if (eye_gaze_ext == nullptr) { + return false; + } else { + return eye_gaze_ext->supports_eye_gaze_interaction(); + } + } +} + bool OpenXRInterface::is_action_set_active(const String &p_action_set) const { for (ActionSet *action_set : action_sets) { if (action_set->action_set_name == p_action_set) { @@ -740,6 +772,46 @@ void OpenXRInterface::set_render_target_size_multiplier(double multiplier) { } } +bool OpenXRInterface::is_foveation_supported() const { + if (openxr_api == nullptr) { + return false; + } else { + return openxr_api->is_foveation_supported(); + } +} + +int OpenXRInterface::get_foveation_level() const { + if (openxr_api == nullptr) { + return 0; + } else { + return openxr_api->get_foveation_level(); + } +} + +void OpenXRInterface::set_foveation_level(int p_foveation_level) { + if (openxr_api == nullptr) { + return; + } else { + openxr_api->set_foveation_level(p_foveation_level); + } +} + +bool OpenXRInterface::get_foveation_dynamic() const { + if (openxr_api == nullptr) { + return false; + } else { + return openxr_api->get_foveation_dynamic(); + } +} + +void OpenXRInterface::set_foveation_dynamic(bool p_foveation_dynamic) { + if (openxr_api == nullptr) { + return; + } else { + openxr_api->set_foveation_dynamic(p_foveation_dynamic); + } +} + Size2 OpenXRInterface::get_render_target_size() { if (openxr_api == nullptr) { return Size2(); @@ -835,6 +907,24 @@ RID OpenXRInterface::get_depth_texture() { } } +void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand) { + OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); + if (hand_tracking_ext && hand_tracking_ext->get_active()) { + OpenXRInterface::Tracker *tracker = find_tracker(p_path); + if (tracker && tracker->positional_tracker.is_valid()) { + // TODO add in confidence! Requires PR #82715 + + Transform3D transform; + transform.basis = Basis(hand_tracking_ext->get_hand_joint_rotation(p_hand, XR_HAND_JOINT_PALM_EXT)); + transform.origin = hand_tracking_ext->get_hand_joint_position(p_hand, XR_HAND_JOINT_PALM_EXT); + Vector3 linear_velocity = hand_tracking_ext->get_hand_joint_linear_velocity(p_hand, XR_HAND_JOINT_PALM_EXT); + Vector3 angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT); + + tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, XRPose::XR_TRACKING_CONFIDENCE_HIGH); + } + } +} + void OpenXRInterface::process() { if (openxr_api) { // do our normal process @@ -842,8 +932,8 @@ void OpenXRInterface::process() { Transform3D t; Vector3 linear_velocity; Vector3 angular_velocity; - XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); - if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + head_confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); + if (head_confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { // Only update our transform if we have one to update it with // note that poses are stored without world scale and reference frame applied! head_transform = t; @@ -865,14 +955,14 @@ void OpenXRInterface::process() { handle_tracker(trackers[i]); } } + + // Handle hand tracking + handle_hand_tracking("/user/hand/left", OpenXRHandTrackingExtension::OPENXR_TRACKED_LEFT_HAND); + handle_hand_tracking("/user/hand/right", OpenXRHandTrackingExtension::OPENXR_TRACKED_RIGHT_HAND); } if (head.is_valid()) { - // TODO figure out how to get our velocities - - head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity); - - // TODO set confidence on pose once we support tracking this.. + head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity, head_confidence); } } @@ -1069,7 +1159,7 @@ void OpenXRInterface::set_motion_range(const Hand p_hand, const HandMotionRange break; } - hand_tracking_ext->set_motion_range(uint32_t(p_hand), xr_motion_range); + hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), xr_motion_range); } } @@ -1078,7 +1168,7 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(uint32_t(p_hand)); + XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand)); switch (xr_motion_range) { case XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT: @@ -1096,7 +1186,7 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_ Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_rotation(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_rotation(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Quaternion(); @@ -1105,7 +1195,7 @@ Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_jo Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_position(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_position(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Vector3(); @@ -1114,7 +1204,7 @@ Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_radius(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_radius(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return 0.0; @@ -1123,7 +1213,7 @@ float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) co Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_linear_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_linear_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Vector3(); @@ -1132,7 +1222,7 @@ Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints Vector3 OpenXRInterface::get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_angular_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_angular_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Vector3(); diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 81efbd6777..8e24c8dce9 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -33,6 +33,7 @@ #include "action_map/openxr_action_map.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" +#include "extensions/openxr_hand_tracking_extension.h" #include "openxr_api.h" #include "servers/xr/xr_interface.h" @@ -55,6 +56,7 @@ private: Transform3D head_transform; Vector3 head_linear_velocity; Vector3 head_angular_velocity; + XRPose::TrackingConfidence head_confidence; Transform3D transform_for_view[2]; // We currently assume 2, but could be 4 for VARJO which we do not support yet void _load_action_map(); @@ -97,6 +99,8 @@ private: void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye); + void handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand); + protected: static void _bind_methods(); @@ -107,6 +111,8 @@ public: virtual PackedStringArray get_suggested_tracker_names() const override; virtual TrackingStatus get_tracking_status() const override; + bool is_eye_gaze_interaction_supported(); + bool initialize_on_startup() const; virtual bool is_initialized() const override; virtual bool initialize() override; @@ -130,6 +136,14 @@ public: double get_render_target_size_multiplier() const; void set_render_target_size_multiplier(double multiplier); + bool is_foveation_supported() const; + + int get_foveation_level() const; + void set_foveation_level(int p_foveation_level); + + bool get_foveation_dynamic() const; + void set_foveation_dynamic(bool p_foveation_dynamic); + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; diff --git a/modules/denoise/lightmap_denoiser.cpp b/modules/openxr/openxr_platform_inc.h index 72764036e1..6288d1e380 100644 --- a/modules/denoise/lightmap_denoiser.cpp +++ b/modules/openxr/openxr_platform_inc.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* lightmap_denoiser.cpp */ +/* openxr_platform_inc.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,38 +28,51 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "lightmap_denoiser.h" +#ifndef OPENXR_PLATFORM_INC_H +#define OPENXR_PLATFORM_INC_H -#include "denoise_wrapper.h" +// In various places we need to include platform definitions but we can't +// include these in our normal header files as we'll end up with issues. -#include "core/io/image.h" +#ifdef VULKAN_ENABLED +#define XR_USE_GRAPHICS_API_VULKAN +#include "drivers/vulkan/vulkan_context.h" +#endif // VULKAN_ENABLED -LightmapDenoiser *LightmapDenoiserOIDN::create_oidn_denoiser() { - return memnew(LightmapDenoiserOIDN); -} +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) +#ifdef ANDROID_ENABLED +#define XR_USE_GRAPHICS_API_OPENGL_ES +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl3ext.h> +#else +#define XR_USE_GRAPHICS_API_OPENGL +#endif // ANDROID_ENABLED +#ifdef X11_ENABLED +#define GL_GLEXT_PROTOTYPES 1 +#define GL3_PROTOTYPES 1 +#include "thirdparty/glad/glad/gl.h" +#include "thirdparty/glad/glad/glx.h" +#endif // X11_ENABLED +#endif // defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) -void LightmapDenoiserOIDN::make_default_denoiser() { - create_function = create_oidn_denoiser; -} +#ifdef X11_ENABLED +#include <X11/Xlib.h> +#endif // X11_ENABLED -Ref<Image> LightmapDenoiserOIDN::denoise_image(const Ref<Image> &p_image) { - Ref<Image> img = p_image->duplicate(); +#ifdef WINDOWS_ENABLED +// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform +// however due to the way the openxr headers are put together, we have no choice. +#include <windows.h> +#endif // WINDOWS_ENABLED - img->convert(Image::FORMAT_RGBF); +#ifdef ANDROID_ENABLED +// The jobject type from jni.h is used by openxr_platform.h on Android. +#include <jni.h> +#endif // ANDROID_ENABLED - Vector<uint8_t> data = img->get_data(); - if (!oidn_denoise(device, (float *)data.ptrw(), img->get_width(), img->get_height())) { - return p_image; - } +// Include platform dependent structs. +#include <openxr/openxr_platform.h> - img->set_data(img->get_width(), img->get_height(), false, img->get_format(), data); - return img; -} - -LightmapDenoiserOIDN::LightmapDenoiserOIDN() { - device = oidn_denoiser_init(); -} - -LightmapDenoiserOIDN::~LightmapDenoiserOIDN() { - oidn_denoiser_finish(device); -} +#endif // OPENXR_PLATFORM_INC_H diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index d69c803502..544932bdeb 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -42,6 +42,7 @@ #include "scene/openxr_hand.h" #include "extensions/openxr_composition_layer_depth_extension.h" +#include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" @@ -110,11 +111,18 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension)); - OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRWMRControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension)); + + // register gated extensions + if (GLOBAL_GET("xr/openxr/extensions/eye_gaze_interaction") && (!OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension)); + } + if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension)); + } } if (OpenXRAPI::openxr_is_enabled()) { diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp index bedc8874d6..c48fac8055 100644 --- a/modules/openxr/scene/openxr_hand.cpp +++ b/modules/openxr/scene/openxr_hand.cpp @@ -113,7 +113,7 @@ void OpenXRHand::_set_motion_range() { break; } - hand_tracking_ext->set_motion_range(hand, xr_motion_range); + hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(hand), xr_motion_range); } Skeleton3D *OpenXRHand::get_skeleton() { @@ -204,7 +204,7 @@ void OpenXRHand::_update_skeleton() { Quaternion inv_quaternions[XR_HAND_JOINT_COUNT_EXT]; Vector3 positions[XR_HAND_JOINT_COUNT_EXT]; - const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(hand); + const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(OpenXRHandTrackingExtension::HandTrackedHands(hand)); const float ws = XRServer::get_singleton()->get_world_scale(); if (hand_tracker->is_initialized && hand_tracker->locations.isActive) { @@ -243,26 +243,27 @@ void OpenXRHand::_update_skeleton() { // Get our target quaternion Quaternion q = quaternions[i]; + // Get our target position + Vector3 p = positions[i]; + // get local translation, parent should already be processed if (parent == -1) { // use our palm location here, that is what we are tracking q = inv_quaternions[XR_HAND_JOINT_PALM_EXT] * q; + p = inv_quaternions[XR_HAND_JOINT_PALM_EXT].xform(p - positions[XR_HAND_JOINT_PALM_EXT]); } else { int found = false; for (int b = 0; b < XR_HAND_JOINT_COUNT_EXT && !found; b++) { if (bones[b] == parent) { q = inv_quaternions[b] * q; + p = inv_quaternions[b].xform(p - positions[b]); found = true; } } } - // And get the movement from our rest position - // Transform3D rest = skeleton->get_bone_rest(bones[i]); - // q = rest.basis.get_quaternion().inverse() * q; - // and set our pose - // skeleton->set_bone_pose_position(bones[i], v); + skeleton->set_bone_pose_position(bones[i], p); skeleton->set_bone_pose_rotation(bones[i], q); } } diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index 69fbf87483..5005000eae 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -221,7 +221,7 @@ void RaycastOcclusionCull::occluder_initialize(RID p_occluder) { void RaycastOcclusionCull::occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices) { Occluder *occluder = occluder_owner.get_or_null(p_occluder); - ERR_FAIL_COND(!occluder); + ERR_FAIL_NULL(occluder); occluder->vertices = p_vertices; occluder->indices = p_indices; @@ -242,7 +242,7 @@ void RaycastOcclusionCull::occluder_set_mesh(RID p_occluder, const PackedVector3 void RaycastOcclusionCull::free_occluder(RID p_occluder) { Occluder *occluder = occluder_owner.get_or_null(p_occluder); - ERR_FAIL_COND(!occluder); + ERR_FAIL_NULL(occluder); memdelete(occluder); occluder_owner.free(p_occluder); } @@ -250,17 +250,15 @@ void RaycastOcclusionCull::free_occluder(RID p_occluder) { //////////////////////////////////////////////////////// void RaycastOcclusionCull::add_scenario(RID p_scenario) { - if (scenarios.has(p_scenario)) { - scenarios[p_scenario].removed = false; - } else { - scenarios[p_scenario] = Scenario(); - } + ERR_FAIL_COND(scenarios.has(p_scenario)); + scenarios[p_scenario] = Scenario(); } void RaycastOcclusionCull::remove_scenario(RID p_scenario) { - ERR_FAIL_COND(!scenarios.has(p_scenario)); - Scenario &scenario = scenarios[p_scenario]; - scenario.removed = true; + Scenario *scenario = scenarios.getptr(p_scenario); + ERR_FAIL_NULL(scenario); + scenario->free(); + scenarios.erase(p_scenario); } void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, RID p_occluder, const Transform3D &p_xform, bool p_enabled) { @@ -291,7 +289,7 @@ void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, if (p_occluder.is_valid()) { Occluder *occluder = occluder_owner.get_or_null(p_occluder); - ERR_FAIL_COND(!occluder); + ERR_FAIL_NULL(occluder); occluder->users.insert(InstanceID(p_scenario, p_instance)); } changed = true; @@ -390,6 +388,23 @@ void RaycastOcclusionCull::Scenario::_transform_vertices_range(const Vector3 *p_ } } +void RaycastOcclusionCull::Scenario::free() { + if (commit_thread) { + if (commit_thread->is_started()) { + commit_thread->wait_to_finish(); + } + memdelete(commit_thread); + commit_thread = nullptr; + } + + for (int i = 0; i < 2; i++) { + if (ebr_scene[i]) { + rtcReleaseScene(ebr_scene[i]); + ebr_scene[i] = nullptr; + } + } +} + void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) { Scenario *scenario = (Scenario *)p_ud; int commit_idx = 1 - (scenario->current_scene_idx); @@ -397,8 +412,8 @@ void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) { scenario->commit_done = true; } -bool RaycastOcclusionCull::Scenario::update() { - ERR_FAIL_COND_V(singleton == nullptr, false); +void RaycastOcclusionCull::Scenario::update() { + ERR_FAIL_NULL(singleton); if (commit_thread == nullptr) { commit_thread = memnew(Thread); @@ -409,22 +424,12 @@ bool RaycastOcclusionCull::Scenario::update() { commit_thread->wait_to_finish(); current_scene_idx = 1 - current_scene_idx; } else { - return false; - } - } - - if (removed) { - if (ebr_scene[0]) { - rtcReleaseScene(ebr_scene[0]); + return; } - if (ebr_scene[1]) { - rtcReleaseScene(ebr_scene[1]); - } - return true; } if (!dirty && removed_instances.is_empty() && dirty_instances_array.is_empty()) { - return false; + return; } for (const RID &scenario : removed_instances) { @@ -480,7 +485,6 @@ bool RaycastOcclusionCull::Scenario::update() { dirty = false; commit_done = false; commit_thread->start(&Scenario::_commit_scene, this); - return false; } void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThreadData *p_raycast_data) const { @@ -492,7 +496,7 @@ void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThrea } void RaycastOcclusionCull::Scenario::raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count) const { - ERR_FAIL_COND(singleton == nullptr); + ERR_FAIL_NULL(singleton); if (raycast_singleton->ebr_device == nullptr) { return; // Embree is initialized on demand when there is some scenario with occluders in it. } @@ -544,13 +548,7 @@ void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform3D &p_cam_ } Scenario &scenario = scenarios[buffer.scenario_rid]; - - bool removed = scenario.update(); - - if (removed) { - scenarios.erase(buffer.scenario_rid); - return; - } + scenario.update(); buffer.update_camera_rays(p_cam_transform, p_cam_projection, p_cam_orthogonal); @@ -603,19 +601,7 @@ RaycastOcclusionCull::RaycastOcclusionCull() { RaycastOcclusionCull::~RaycastOcclusionCull() { for (KeyValue<RID, Scenario> &K : scenarios) { - Scenario &scenario = K.value; - if (scenario.commit_thread) { - if (scenario.commit_thread->is_started()) { - scenario.commit_thread->wait_to_finish(); - } - memdelete(scenario.commit_thread); - } - - for (int i = 0; i < 2; i++) { - if (scenario.ebr_scene[i]) { - rtcReleaseScene(scenario.ebr_scene[i]); - } - } + K.value.free(); } if (ebr_device != nullptr) { diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h index c4e733b664..ab5eb4eaf0 100644 --- a/modules/raycast/raycast_occlusion_cull.h +++ b/modules/raycast/raycast_occlusion_cull.h @@ -132,7 +132,6 @@ private: Thread *commit_thread = nullptr; bool commit_done = true; bool dirty = false; - bool removed = false; RTCScene ebr_scene[2] = { nullptr, nullptr }; int current_scene_idx = 0; @@ -147,7 +146,8 @@ private: void _transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data); void _transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform3D &p_xform, int p_from, int p_to); static void _commit_scene(void *p_ud); - bool update(); + void free(); + void update(); void _raycast(uint32_t p_thread, const RaycastThreadData *p_raycast_data) const; void raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count) const; diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml index 5770e7155e..ab74fce3a9 100644 --- a/modules/regex/doc_classes/RegEx.xml +++ b/modules/regex/doc_classes/RegEx.xml @@ -10,7 +10,7 @@ var regex = RegEx.new() regex.compile("\\w-(\\d+)") [/codeblock] - The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code]. + The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code]. In GDScript, you can also use raw string literals (r-strings). For example, [code]compile(r'"(?:\\.|[^"])*"')[/code] would be read the same. Using [method search], you can find the pattern within the given text. If a pattern is found, [RegExMatch] is returned and you can retrieve details of the results using methods such as [method RegExMatch.get_string] and [method RegExMatch.get_start]. [codeblock] var regex = RegEx.new() diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h index 6515d5d130..3e4d769377 100644 --- a/modules/regex/tests/test_regex.h +++ b/modules/regex/tests/test_regex.h @@ -83,9 +83,16 @@ TEST_CASE("[RegEx] Searching") { REQUIRE(match != nullptr); CHECK(match->get_string(0) == "ea"); + match = re.search(s, 1, 2); + REQUIRE(match != nullptr); + CHECK(match->get_string(0) == "e"); match = re.search(s, 2, 4); REQUIRE(match != nullptr); CHECK(match->get_string(0) == "a"); + match = re.search(s, 3, 5); + CHECK(match == nullptr); + match = re.search(s, 6, 2); + CHECK(match == nullptr); const Array all_results = re.search_all(s); CHECK(all_results.size() == 2); @@ -103,11 +110,45 @@ TEST_CASE("[RegEx] Searching") { } TEST_CASE("[RegEx] Substitution") { - String s = "Double all the vowels."; + const String s1 = "Double all the vowels."; - RegEx re("(?<vowel>[aeiou])"); - REQUIRE(re.is_valid()); - CHECK(re.sub(s, "$0$vowel", true) == "Doouublee aall thee vooweels."); + RegEx re1("(?<vowel>[aeiou])"); + REQUIRE(re1.is_valid()); + CHECK(re1.sub(s1, "$0$vowel", true) == "Doouublee aall thee vooweels."); + + const String s2 = "Substitution with group."; + + RegEx re2("Substitution (.+)"); + REQUIRE(re2.is_valid()); + CHECK(re2.sub(s2, "Test ${1}") == "Test with group."); + + const String s3 = "Useless substitution"; + + RegEx re3("Anything"); + REQUIRE(re3.is_valid()); + CHECK(re3.sub(s3, "Something") == "Useless substitution"); + + const String s4 = "acacac"; + + RegEx re4("(a)(b){0}(c)"); + REQUIRE(re4.is_valid()); + CHECK(re4.sub(s4, "${1}.${3}.", true) == "a.c.a.c.a.c."); +} + +TEST_CASE("[RegEx] Substitution with empty input and/or replacement") { + const String s1 = ""; + const String s2 = "gogogo"; + + RegEx re1(""); + REQUIRE(re1.is_valid()); + CHECK(re1.sub(s1, "") == ""); + CHECK(re1.sub(s1, "a") == "a"); + CHECK(re1.sub(s2, "") == "gogogo"); + + RegEx re2("go"); + REQUIRE(re2.is_valid()); + CHECK(re2.sub(s2, "") == "gogo"); + CHECK(re2.sub(s2, "", true) == ""); } TEST_CASE("[RegEx] Uninitialized use") { @@ -150,6 +191,37 @@ TEST_CASE("[RegEx] Invalid end position") { CHECK(re.sub(s, "", true, 0, 10) == "Gdt"); } + +TEST_CASE("[RegEx] Get match string list") { + const String s = "Godot Engine"; + + RegEx re("(Go)(dot)"); + Ref<RegExMatch> match = re.search(s); + REQUIRE(match != nullptr); + PackedStringArray result; + result.append("Godot"); + result.append("Go"); + result.append("dot"); + CHECK(match->get_strings() == result); +} + +TEST_CASE("[RegEx] Match start and end positions") { + const String s = "Whole pattern"; + + RegEx re1("pattern"); + REQUIRE(re1.is_valid()); + Ref<RegExMatch> match = re1.search(s); + REQUIRE(match != nullptr); + CHECK(match->get_start(0) == 6); + CHECK(match->get_end(0) == 13); + + RegEx re2("(?<vowel>[aeiou])"); + REQUIRE(re2.is_valid()); + match = re2.search(s); + REQUIRE(match != nullptr); + CHECK(match->get_start("vowel") == 2); + CHECK(match->get_end("vowel") == 3); +} } // namespace TestRegEx #endif // TEST_REGEX_H diff --git a/modules/svg/SCsub b/modules/svg/SCsub index c4d7671fb3..a99bc8df60 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -11,40 +11,45 @@ thirdparty_obj = [] thirdparty_dir = "#thirdparty/thorvg/" thirdparty_sources = [ - "src/lib/sw_engine/tvgSwFill.cpp", - "src/lib/sw_engine/tvgSwImage.cpp", - "src/lib/sw_engine/tvgSwMath.cpp", - "src/lib/sw_engine/tvgSwMemPool.cpp", - "src/lib/sw_engine/tvgSwRaster.cpp", - "src/lib/sw_engine/tvgSwRenderer.cpp", - "src/lib/sw_engine/tvgSwRle.cpp", - "src/lib/sw_engine/tvgSwShape.cpp", - "src/lib/sw_engine/tvgSwStroke.cpp", - "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", - "src/lib/tvgCanvas.cpp", - "src/lib/tvgFill.cpp", - "src/lib/tvgGlCanvas.cpp", - "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", - "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", - "src/lib/tvgPaint.cpp", - "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", - "src/lib/tvgRender.cpp", - "src/lib/tvgSaver.cpp", - "src/lib/tvgScene.cpp", - "src/lib/tvgShape.cpp", - "src/lib/tvgSwCanvas.cpp", - "src/lib/tvgTaskScheduler.cpp", - "src/loaders/raw/tvgRawLoader.cpp", + # common + "src/common/tvgBezier.cpp", + "src/common/tvgCompressor.cpp", + "src/common/tvgMath.cpp", + "src/common/tvgStr.cpp", + # SVG parser "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", "src/loaders/svg/tvgSvgPath.cpp", "src/loaders/svg/tvgSvgSceneBuilder.cpp", "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + # renderer common + "src/renderer/tvgAccessor.cpp", + # "src/renderer/tvgAnimation.cpp", + "src/renderer/tvgCanvas.cpp", + "src/renderer/tvgFill.cpp", + # "src/renderer/tvgGlCanvas.cpp", + "src/renderer/tvgInitializer.cpp", + "src/renderer/tvgLoader.cpp", + "src/renderer/tvgPaint.cpp", + "src/renderer/tvgPicture.cpp", + "src/renderer/tvgRender.cpp", + # "src/renderer/tvgSaver.cpp", + "src/renderer/tvgScene.cpp", + "src/renderer/tvgShape.cpp", + "src/renderer/tvgSwCanvas.cpp", + "src/renderer/tvgTaskScheduler.cpp", + # renderer sw_engine + "src/renderer/sw_engine/tvgSwFill.cpp", + "src/renderer/sw_engine/tvgSwImage.cpp", + "src/renderer/sw_engine/tvgSwMath.cpp", + "src/renderer/sw_engine/tvgSwMemPool.cpp", + "src/renderer/sw_engine/tvgSwRaster.cpp", + "src/renderer/sw_engine/tvgSwRenderer.cpp", + "src/renderer/sw_engine/tvgSwRle.cpp", + "src/renderer/sw_engine/tvgSwShape.cpp", + "src/renderer/sw_engine/tvgSwStroke.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] @@ -58,15 +63,13 @@ env_thirdparty = env_svg.Clone() env_thirdparty.disable_warnings() env_thirdparty.Prepend( CPPPATH=[ - thirdparty_dir + "src/lib", - thirdparty_dir + "src/lib/sw_engine", - thirdparty_dir + "src/loaders/raw", + thirdparty_dir + "src/common", thirdparty_dir + "src/loaders/svg", + thirdparty_dir + "src/renderer", + thirdparty_dir + "src/renderer/sw_engine", + thirdparty_dir + "src/loaders/raw", ] ) -# Also requires libpng headers -if env["builtin_libpng"]: - env_thirdparty.Prepend(CPPPATH=["#thirdparty/libpng"]) env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) env.modules_sources += thirdparty_obj diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 360741363a..3c468e61d7 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -39,7 +39,13 @@ freetype_enabled = "freetype" in env.module_list msdfgen_enabled = "msdfgen" in env.module_list if "svg" in env.module_list: - env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + env_text_server_adv.Prepend( + CPPPATH=[ + "#thirdparty/thorvg/inc", + "#thirdparty/thorvg/src/common", + "#thirdparty/thorvg/src/renderer", + ] + ) # Enable ThorVG static object linking. env_text_server_adv.Append(CPPDEFINES=["TVG_STATIC"]) diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 38fd5f6403..b95c35f80d 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -42,58 +42,69 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: thirdparty_tvg_dir = "../../../thirdparty/thorvg/" thirdparty_tvg_sources = [ - "src/lib/sw_engine/tvgSwFill.cpp", - "src/lib/sw_engine/tvgSwImage.cpp", - "src/lib/sw_engine/tvgSwMath.cpp", - "src/lib/sw_engine/tvgSwMemPool.cpp", - "src/lib/sw_engine/tvgSwRaster.cpp", - "src/lib/sw_engine/tvgSwRenderer.cpp", - "src/lib/sw_engine/tvgSwRle.cpp", - "src/lib/sw_engine/tvgSwShape.cpp", - "src/lib/sw_engine/tvgSwStroke.cpp", - "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", - "src/lib/tvgCanvas.cpp", - "src/lib/tvgFill.cpp", - "src/lib/tvgGlCanvas.cpp", - "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", - "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", - "src/lib/tvgPaint.cpp", - "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", - "src/lib/tvgRender.cpp", - "src/lib/tvgSaver.cpp", - "src/lib/tvgScene.cpp", - "src/lib/tvgShape.cpp", - "src/lib/tvgSwCanvas.cpp", - "src/lib/tvgTaskScheduler.cpp", - "src/loaders/raw/tvgRawLoader.cpp", + # common + "src/common/tvgBezier.cpp", + "src/common/tvgCompressor.cpp", + "src/common/tvgMath.cpp", + "src/common/tvgStr.cpp", + # SVG parser "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", "src/loaders/svg/tvgSvgPath.cpp", "src/loaders/svg/tvgSvgSceneBuilder.cpp", "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + # renderer common + "src/renderer/tvgAccessor.cpp", + # "src/renderer/tvgAnimation.cpp", + "src/renderer/tvgCanvas.cpp", + "src/renderer/tvgFill.cpp", + # "src/renderer/tvgGlCanvas.cpp", + "src/renderer/tvgInitializer.cpp", + "src/renderer/tvgLoader.cpp", + "src/renderer/tvgPaint.cpp", + "src/renderer/tvgPicture.cpp", + "src/renderer/tvgRender.cpp", + # "src/renderer/tvgSaver.cpp", + "src/renderer/tvgScene.cpp", + "src/renderer/tvgShape.cpp", + "src/renderer/tvgSwCanvas.cpp", + "src/renderer/tvgTaskScheduler.cpp", + # renderer sw_engine + "src/renderer/sw_engine/tvgSwFill.cpp", + "src/renderer/sw_engine/tvgSwImage.cpp", + "src/renderer/sw_engine/tvgSwMath.cpp", + "src/renderer/sw_engine/tvgSwMemPool.cpp", + "src/renderer/sw_engine/tvgSwRaster.cpp", + "src/renderer/sw_engine/tvgSwRenderer.cpp", + "src/renderer/sw_engine/tvgSwRle.cpp", + "src/renderer/sw_engine/tvgSwShape.cpp", + "src/renderer/sw_engine/tvgSwStroke.cpp", ] thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] env_tvg.Append( CPPPATH=[ "../../../thirdparty/thorvg/inc", - "../../../thirdparty/thorvg/src/lib", - "../../../thirdparty/thorvg/src/lib/sw_engine", - "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/common", "../../../thirdparty/thorvg/src/loaders/svg", - "../../../thirdparty/libpng", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/renderer", + "../../../thirdparty/thorvg/src/renderer/sw_engine", ] ) # Enable ThorVG static object linking. env_tvg.Append(CPPDEFINES=["TVG_STATIC"]) - env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/common", + "../../../thirdparty/thorvg/src/renderer", + ] + ) env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) lib = env_tvg.Library( diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 3cbc300946..b605b29f84 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -397,6 +397,14 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) { font_owner.free(p_rid); } memdelete(fd); + } else if (font_var_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_rid); + { + font_var_owner.free(p_rid); + } + memdelete(fdv); } else if (shaped_owner.owns(p_rid)) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_rid); { @@ -409,7 +417,7 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) { bool TextServerAdvanced::_has(const RID &p_rid) { _THREAD_SAFE_METHOD_ - return font_owner.owns(p_rid) || shaped_owner.owns(p_rid); + return font_owner.owns(p_rid) || font_var_owner.owns(p_rid) || shaped_owner.owns(p_rid); } bool TextServerAdvanced::_load_support_data(const String &p_filename) { @@ -1809,8 +1817,8 @@ _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_d } hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, nullptr); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, nullptr); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1828,9 +1836,25 @@ RID TextServerAdvanced::_create_font() { return font_owner.make_rid(fd); } +RID TextServerAdvanced::_create_font_linked_variation(const RID &p_font_rid) { + _THREAD_SAFE_METHOD_ + + RID rid = p_font_rid; + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + ERR_FAIL_COND_V(!font_owner.owns(rid), RID()); + + FontAdvancedLinkedVariation *new_fdv = memnew(FontAdvancedLinkedVariation); + new_fdv->base_font = rid; + + return font_var_owner.make_rid(new_fdv); +} + void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -1840,8 +1864,8 @@ void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteA } void TextServerAdvanced::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -1854,8 +1878,8 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f ERR_FAIL_COND(p_face_index < 0); ERR_FAIL_COND(p_face_index >= 0x7FFF); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->face_index != p_face_index) { @@ -1865,16 +1889,16 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f } int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->face_index; } int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); int face_count = 0; @@ -1919,8 +1943,8 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1929,8 +1953,8 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty } BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1939,8 +1963,8 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p } void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const String &p_name) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1949,8 +1973,8 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin } String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1959,8 +1983,8 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1969,8 +1993,8 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh } int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 400); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 400); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1979,8 +2003,8 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1989,8 +2013,8 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre } int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 100); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 100); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1999,8 +2023,8 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_name) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2009,8 +2033,8 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n } String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2019,8 +2043,8 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const { } Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2132,8 +2156,8 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) } void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->antialiasing != p_antialiasing) { @@ -2143,16 +2167,16 @@ void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServe } TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TextServer::FONT_ANTIALIASING_NONE); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE); MutexLock lock(fd->mutex); return fd->antialiasing; } void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->mipmaps != p_generate_mipmaps) { @@ -2167,16 +2191,16 @@ void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool } bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->mipmaps; } void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf != p_msdf) { @@ -2186,16 +2210,16 @@ void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID } bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf; } void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_range != p_msdf_pixel_range) { @@ -2205,16 +2229,16 @@ void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64 } int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf_range; } void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_source_size != p_msdf_size) { @@ -2224,48 +2248,48 @@ void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms } int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf_source_size; } void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->fixed_size = p_fixed_size; } int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->fixed_size; } void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->allow_system_fallback = p_allow_system_fallback; } bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->allow_system_fallback; } void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->force_autohinter != p_force_autohinter) { @@ -2275,16 +2299,16 @@ void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool } bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->force_autohinter; } void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->hinting != p_hinting) { @@ -2294,32 +2318,32 @@ void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hi } TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, HINTING_NONE); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, HINTING_NONE); MutexLock lock(fd->mutex); return fd->hinting; } void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->subpixel_positioning = p_subpixel; } TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioning(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED); MutexLock lock(fd->mutex); return fd->subpixel_positioning; } void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_strength) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->embolden != p_strength) { @@ -2329,8 +2353,8 @@ void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_stre } double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->embolden; @@ -2338,30 +2362,39 @@ double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const { void TextServerAdvanced::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + if (fdv->extra_spacing[p_spacing] != p_value) { + fdv->extra_spacing[p_spacing] = p_value; + } + } else { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL(fd); - MutexLock lock(fd->mutex); - if (fd->extra_spacing[p_spacing] != p_value) { - _font_clear_cache(fd); - fd->extra_spacing[p_spacing] = p_value; + MutexLock lock(fd->mutex); + if (fd->extra_spacing[p_spacing] != p_value) { + fd->extra_spacing[p_spacing] = p_value; + } } } int64_t TextServerAdvanced::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const { ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + return fdv->extra_spacing[p_spacing]; + } else { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); - - MutexLock lock(fd->mutex); - - return fd->extra_spacing[p_spacing]; + MutexLock lock(fd->mutex); + return fd->extra_spacing[p_spacing]; + } } void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->transform != p_transform) { @@ -2371,16 +2404,16 @@ void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transf } Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Transform2D()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Transform2D()); MutexLock lock(fd->mutex); return fd->transform; } void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (!fd->variation_coordinates.recursive_equal(p_variation_coordinates, 1)) { @@ -2390,16 +2423,16 @@ void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid, } Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->variation_coordinates; } void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->oversampling != p_oversampling) { @@ -2409,16 +2442,16 @@ void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_ } double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->oversampling; } TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); TypedArray<Vector2i> ret; @@ -2429,8 +2462,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_ } void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -2441,8 +2474,8 @@ void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) { } void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -2453,8 +2486,8 @@ void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Ve } void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2464,8 +2497,8 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, } double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2480,8 +2513,8 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz } void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); Vector2i size = _get_size(fd, p_size); @@ -2490,8 +2523,8 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size } double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2506,8 +2539,8 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si } void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2517,8 +2550,8 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int } double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2533,8 +2566,8 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i } void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2544,8 +2577,8 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in } double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2560,8 +2593,8 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, } void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2576,8 +2609,8 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, } double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2592,8 +2625,8 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size } int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2604,8 +2637,8 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const } void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2614,8 +2647,8 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto } void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2626,8 +2659,8 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto } void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND(p_image.is_null()); MutexLock lock(fd->mutex); @@ -2655,8 +2688,8 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve } Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Ref<Image>()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Ref<Image>()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2669,8 +2702,8 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) { ERR_FAIL_COND(p_offsets.size() % 4 != 0); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2688,8 +2721,8 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const } PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2713,8 +2746,8 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font } PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2729,8 +2762,8 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, } void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2740,8 +2773,8 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2 } void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2751,8 +2784,8 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2 } double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const { - const FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + const FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_font_size); @@ -2765,8 +2798,8 @@ double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) c } Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2803,8 +2836,8 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64 } void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2818,8 +2851,8 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t } Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2848,8 +2881,8 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const } void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2863,8 +2896,8 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec } Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2893,8 +2926,8 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve } void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2908,8 +2941,8 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto } Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Rect2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Rect2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2933,8 +2966,8 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V } void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2948,8 +2981,8 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve } int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, -1); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, -1); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2973,8 +3006,8 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c } void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2988,8 +3021,8 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons } RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, RID()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, RID()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -3034,8 +3067,8 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const } Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Size2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Size2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -3080,8 +3113,8 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co } Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3130,8 +3163,8 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i } TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3146,8 +3179,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon } void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3157,8 +3190,8 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t } void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3168,8 +3201,8 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s } void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3179,8 +3212,8 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size } Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3212,8 +3245,8 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s } int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_char, int64_t p_variation_selector) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + "."); ERR_FAIL_COND_V_MSG((p_variation_selector >= 0xd800 && p_variation_selector <= 0xdfff) || (p_variation_selector > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_variation_selector, 16) + "."); @@ -3237,8 +3270,8 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t } int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_glyph_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3268,7 +3301,7 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri } bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + FontAdvanced *fd = _get_font_data(p_font_rid); ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + "."); if (!fd) { return false; @@ -3289,8 +3322,8 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c } String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); if (fd->cache.is_empty()) { @@ -3322,8 +3355,8 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons } void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + "."); ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + "."); @@ -3357,8 +3390,8 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2 } void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -3388,8 +3421,8 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2 } void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3480,8 +3513,8 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); @@ -3572,8 +3605,8 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->language_support_overrides.has(p_language)) { @@ -3584,32 +3617,32 @@ bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, cons } void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides[p_language] = p_supported; } bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->language_support_overrides[p_language]; } void TextServerAdvanced::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides.erase(p_language); } PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const RID &p_font_rid) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -3620,8 +3653,8 @@ PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const } bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->script_support_overrides.has(p_script)) { @@ -3634,32 +3667,32 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const } void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->script_support_overrides[p_script] = p_supported; } bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->script_support_overrides[p_script]; } void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->script_support_overrides.erase(p_script); } PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const RID &p_font_rid) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -3670,8 +3703,8 @@ PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const R } void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -3680,16 +3713,16 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_ } Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->feature_overrides; } Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -3698,8 +3731,8 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri } Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -3843,7 +3876,7 @@ RID TextServerAdvanced::_create_shaped_text(TextServer::Direction p_direction, T void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); sd->parent = RID(); @@ -3859,7 +3892,7 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) { void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction."); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->direction != p_direction) { @@ -3873,7 +3906,7 @@ void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextSer TextServer::Direction TextServerAdvanced::_shaped_text_get_direction(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR); + ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR); MutexLock lock(sd->mutex); return sd->direction; @@ -3881,7 +3914,7 @@ TextServer::Direction TextServerAdvanced::_shaped_text_get_direction(const RID & TextServer::Direction TextServerAdvanced::_shaped_text_get_inferred_direction(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR); + ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR); MutexLock lock(sd->mutex); return sd->para_direction; @@ -3890,7 +3923,7 @@ TextServer::Direction TextServerAdvanced::_shaped_text_get_inferred_direction(co void TextServerAdvanced::_shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); if (sd->custom_punct != p_punct) { if (sd->parent != RID()) { @@ -3904,13 +3937,13 @@ void TextServerAdvanced::_shaped_text_set_custom_punctuation(const RID &p_shaped String TextServerAdvanced::_shaped_text_get_custom_punctuation(const RID &p_shaped) const { _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, String()); + ERR_FAIL_NULL_V(sd, String()); return sd->custom_punct; } void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, const Array &p_override) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->parent != RID()) { @@ -3931,7 +3964,7 @@ void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, con void TextServerAdvanced::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->orientation != p_orientation) { @@ -3945,7 +3978,7 @@ void TextServerAdvanced::_shaped_text_set_orientation(const RID &p_shaped, TextS void TextServerAdvanced::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool p_enabled) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); ERR_FAIL_COND(sd->parent != RID()); @@ -3957,7 +3990,7 @@ void TextServerAdvanced::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool TextServerAdvanced::_shaped_text_get_preserve_invalid(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_invalid; @@ -3965,7 +3998,7 @@ bool TextServerAdvanced::_shaped_text_get_preserve_invalid(const RID &p_shaped) void TextServerAdvanced::_shaped_text_set_preserve_control(const RID &p_shaped, bool p_enabled) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->preserve_control != p_enabled) { @@ -3979,7 +4012,7 @@ void TextServerAdvanced::_shaped_text_set_preserve_control(const RID &p_shaped, bool TextServerAdvanced::_shaped_text_get_preserve_control(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_control; @@ -3988,7 +4021,7 @@ bool TextServerAdvanced::_shaped_text_get_preserve_control(const RID &p_shaped) void TextServerAdvanced::_shaped_text_set_spacing(const RID &p_shaped, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->extra_spacing[p_spacing] != p_value) { @@ -4004,7 +4037,7 @@ int64_t TextServerAdvanced::_shaped_text_get_spacing(const RID &p_shaped, Spacin ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); return sd->extra_spacing[p_spacing]; @@ -4012,7 +4045,7 @@ int64_t TextServerAdvanced::_shaped_text_get_spacing(const RID &p_shaped, Spacin TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); + ERR_FAIL_NULL_V(sd, TextServer::ORIENTATION_HORIZONTAL); MutexLock lock(sd->mutex); return sd->orientation; @@ -4020,20 +4053,20 @@ TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const R int64_t TextServerAdvanced::_shaped_get_span_count(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); return sd->spans.size(); } Variant TextServerAdvanced::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_NULL_V(sd, Variant()); ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); return sd->spans[p_index].meta; } void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); ERR_FAIL_INDEX(p_index, sd->spans.size()); ShapedTextDataAdvanced::Span &span = sd->spans.ptrw()[p_index]; @@ -4046,12 +4079,12 @@ void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64 bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); ERR_FAIL_COND_V(p_size <= 0, false); MutexLock lock(sd->mutex); for (int i = 0; i < p_fonts.size(); i++) { - ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false); + ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false); } if (p_text.is_empty()) { @@ -4082,7 +4115,7 @@ bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const Stri bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); ERR_FAIL_COND_V(p_key == Variant(), false); ERR_FAIL_COND_V(sd->objects.has(p_key), false); @@ -4112,7 +4145,7 @@ bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Vari bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), false); @@ -4255,7 +4288,7 @@ void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const { RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const { _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); if (sd->parent != RID()) { @@ -4439,7 +4472,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S RID TextServerAdvanced::_shaped_text_get_parent(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); return sd->parent; @@ -4447,7 +4480,7 @@ RID TextServerAdvanced::_shaped_text_get_parent(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double p_width, BitField<TextServer::JustificationFlag> p_jst_flags) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4604,7 +4637,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const PackedFloat32Array &p_tab_stops) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4660,7 +4693,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped_line); - ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4827,7 +4860,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ int64_t TextServerAdvanced::_shaped_text_get_trim_pos(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.trim_pos; @@ -4835,7 +4868,7 @@ int64_t TextServerAdvanced::_shaped_text_get_trim_pos(const RID &p_shaped) const int64_t TextServerAdvanced::_shaped_text_get_ellipsis_pos(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_pos; @@ -4843,7 +4876,7 @@ int64_t TextServerAdvanced::_shaped_text_get_ellipsis_pos(const RID &p_shaped) c const Glyph *TextServerAdvanced::_shaped_text_get_ellipsis_glyphs(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, nullptr, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.ptr(); @@ -4851,7 +4884,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_ellipsis_glyphs(const RID &p_s int64_t TextServerAdvanced::_shaped_text_get_ellipsis_glyph_count(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, 0, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.size(); @@ -4914,7 +4947,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const { PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, PackedInt32Array()); + ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4928,7 +4961,7 @@ PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -5193,7 +5226,7 @@ _FORCE_INLINE_ int64_t _generate_kashida_justification_opportunies(const String bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -5347,7 +5380,7 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size); double scale = _font_get_scale(p_font, p_font_size); bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); - ERR_FAIL_COND_V(hb_font == nullptr, Glyph()); + ERR_FAIL_NULL_V(hb_font, Glyph()); hb_buffer_clear_contents(p_sd->hb_buffer); hb_buffer_set_direction(p_sd->hb_buffer, p_direction); @@ -5633,8 +5666,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star return; } - FontAdvanced *fd = font_owner.get_or_null(f); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(f); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i fss = _get_size(fd, fs); @@ -5645,7 +5678,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star bool last_run = (p_sd->end == p_end); double ea = _get_extra_advance(f, fs); bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_AUTO && fs <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); - ERR_FAIL_COND(hb_font == nullptr); + ERR_FAIL_NULL(hb_font); hb_buffer_clear_contents(p_sd->hb_buffer); hb_buffer_set_direction(p_sd->hb_buffer, p_direction); @@ -5836,7 +5869,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (sd->valid) { @@ -6052,7 +6085,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->valid; @@ -6060,7 +6093,7 @@ bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const { const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6071,7 +6104,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6082,7 +6115,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6100,7 +6133,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped) Vector2i TextServerAdvanced::_shaped_text_get_range(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Vector2i()); + ERR_FAIL_NULL_V(sd, Vector2i()); MutexLock lock(sd->mutex); return Vector2(sd->start, sd->end); @@ -6109,7 +6142,7 @@ Vector2i TextServerAdvanced::_shaped_text_get_range(const RID &p_shaped) const { Array TextServerAdvanced::_shaped_text_get_objects(const RID &p_shaped) const { Array ret; const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, ret); + ERR_FAIL_NULL_V(sd, ret); MutexLock lock(sd->mutex); for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { @@ -6121,7 +6154,7 @@ Array TextServerAdvanced::_shaped_text_get_objects(const RID &p_shaped) const { Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, const Variant &p_key) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Rect2()); + ERR_FAIL_NULL_V(sd, Rect2()); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); @@ -6133,7 +6166,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Size2()); + ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6148,7 +6181,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6159,7 +6192,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6170,7 +6203,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6181,7 +6214,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6193,7 +6226,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 7445becfae..57cca819be 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -293,6 +293,11 @@ class TextServerAdvanced : public TextServerExtension { } }; + struct FontAdvancedLinkedVariation { + RID base_font; + int extra_spacing[4] = { 0, 0, 0, 0 }; + }; + struct FontAdvanced { Mutex mutex; @@ -534,9 +539,19 @@ class TextServerAdvanced : public TextServerExtension { // Common data. double oversampling = 1.0; + mutable RID_PtrOwner<FontAdvancedLinkedVariation> font_var_owner; mutable RID_PtrOwner<FontAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; + _FORCE_INLINE_ FontAdvanced *_get_font_data(const RID &p_font_rid) const { + RID rid = p_font_rid; + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + return font_owner.get_or_null(rid); + } + struct SystemFontKey { String font_name; TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; @@ -704,6 +719,7 @@ public: /* Font interface */ MODBIND0R(RID, create_font); + MODBIND1R(RID, create_font_linked_variation, const RID &); MODBIND2(font_set_data, const RID &, const PackedByteArray &); MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t); diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index 0da2a54bc2..e808864512 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -9,7 +9,9 @@ msdfgen_enabled = "msdfgen" in env.module_list env_text_server_fb = env_modules.Clone() if "svg" in env.module_list: - env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + env_text_server_fb.Prepend( + CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/common", "#thirdparty/thorvg/src/renderer"] + ) # Enable ThorVG static object linking. env_text_server_fb.Append(CPPDEFINES=["TVG_STATIC"]) diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 20e1afa2e5..846ac02cf1 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -37,58 +37,69 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: thirdparty_tvg_dir = "../../../thirdparty/thorvg/" thirdparty_tvg_sources = [ - "src/lib/sw_engine/tvgSwFill.cpp", - "src/lib/sw_engine/tvgSwImage.cpp", - "src/lib/sw_engine/tvgSwMath.cpp", - "src/lib/sw_engine/tvgSwMemPool.cpp", - "src/lib/sw_engine/tvgSwRaster.cpp", - "src/lib/sw_engine/tvgSwRenderer.cpp", - "src/lib/sw_engine/tvgSwRle.cpp", - "src/lib/sw_engine/tvgSwShape.cpp", - "src/lib/sw_engine/tvgSwStroke.cpp", - "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", - "src/lib/tvgCanvas.cpp", - "src/lib/tvgFill.cpp", - "src/lib/tvgGlCanvas.cpp", - "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", - "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", - "src/lib/tvgPaint.cpp", - "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", - "src/lib/tvgRender.cpp", - "src/lib/tvgSaver.cpp", - "src/lib/tvgScene.cpp", - "src/lib/tvgShape.cpp", - "src/lib/tvgSwCanvas.cpp", - "src/lib/tvgTaskScheduler.cpp", - "src/loaders/raw/tvgRawLoader.cpp", + # common + "src/common/tvgBezier.cpp", + "src/common/tvgCompressor.cpp", + "src/common/tvgMath.cpp", + "src/common/tvgStr.cpp", + # SVG parser "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", "src/loaders/svg/tvgSvgPath.cpp", "src/loaders/svg/tvgSvgSceneBuilder.cpp", "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + # renderer common + "src/renderer/tvgAccessor.cpp", + # "src/renderer/tvgAnimation.cpp", + "src/renderer/tvgCanvas.cpp", + "src/renderer/tvgFill.cpp", + # "src/renderer/tvgGlCanvas.cpp", + "src/renderer/tvgInitializer.cpp", + "src/renderer/tvgLoader.cpp", + "src/renderer/tvgPaint.cpp", + "src/renderer/tvgPicture.cpp", + "src/renderer/tvgRender.cpp", + # "src/renderer/tvgSaver.cpp", + "src/renderer/tvgScene.cpp", + "src/renderer/tvgShape.cpp", + "src/renderer/tvgSwCanvas.cpp", + "src/renderer/tvgTaskScheduler.cpp", + # renderer sw_engine + "src/renderer/sw_engine/tvgSwFill.cpp", + "src/renderer/sw_engine/tvgSwImage.cpp", + "src/renderer/sw_engine/tvgSwMath.cpp", + "src/renderer/sw_engine/tvgSwMemPool.cpp", + "src/renderer/sw_engine/tvgSwRaster.cpp", + "src/renderer/sw_engine/tvgSwRenderer.cpp", + "src/renderer/sw_engine/tvgSwRle.cpp", + "src/renderer/sw_engine/tvgSwShape.cpp", + "src/renderer/sw_engine/tvgSwStroke.cpp", ] thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] env_tvg.Append( CPPPATH=[ "../../../thirdparty/thorvg/inc", - "../../../thirdparty/thorvg/src/lib", - "../../../thirdparty/thorvg/src/lib/sw_engine", - "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/common", "../../../thirdparty/thorvg/src/loaders/svg", - "../../../thirdparty/libpng", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/renderer", + "../../../thirdparty/thorvg/src/renderer/sw_engine", ] ) # Enable ThorVG static object linking. env_tvg.Append(CPPDEFINES=["TVG_STATIC"]) - env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/common", + "../../../thirdparty/thorvg/src/renderer", + ] + ) env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) lib = env_tvg.Library( diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 26d16a1ec9..0cfbf7f530 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -123,6 +123,14 @@ void TextServerFallback::_free_rid(const RID &p_rid) { font_owner.free(p_rid); } memdelete(fd); + } else if (font_var_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_rid); + { + font_var_owner.free(p_rid); + } + memdelete(fdv); } else if (shaped_owner.owns(p_rid)) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid); { @@ -935,9 +943,25 @@ RID TextServerFallback::_create_font() { return font_owner.make_rid(fd); } +RID TextServerFallback::_create_font_linked_variation(const RID &p_font_rid) { + _THREAD_SAFE_METHOD_ + + RID rid = p_font_rid; + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + ERR_FAIL_COND_V(!font_owner.owns(rid), RID()); + + FontFallbackLinkedVariation *new_fdv = memnew(FontFallbackLinkedVariation); + new_fdv->base_font = rid; + + return font_var_owner.make_rid(new_fdv); +} + void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -947,8 +971,8 @@ void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteA } void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -958,8 +982,8 @@ void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t } void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -971,8 +995,8 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f ERR_FAIL_COND(p_face_index < 0); ERR_FAIL_COND(p_face_index >= 0x7FFF); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->face_index != p_face_index) { @@ -982,16 +1006,16 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f } int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->face_index; } int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); int face_count = 0; @@ -1036,8 +1060,8 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { } BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1046,8 +1070,8 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p } void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const String &p_name) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1056,8 +1080,8 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin } String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1066,8 +1090,8 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { } void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1076,8 +1100,8 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh } int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 400); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 400); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1086,8 +1110,8 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { } void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1096,8 +1120,8 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre } int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 100); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 100); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1106,8 +1130,8 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { } void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1116,8 +1140,8 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n } String TextServerFallback::_font_get_name(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1126,8 +1150,8 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const { } void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->antialiasing != p_antialiasing) { @@ -1137,16 +1161,16 @@ void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServe } TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TextServer::FONT_ANTIALIASING_NONE); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE); MutexLock lock(fd->mutex); return fd->antialiasing; } void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->mipmaps != p_generate_mipmaps) { @@ -1161,16 +1185,16 @@ void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool } bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->mipmaps; } void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf != p_msdf) { @@ -1180,16 +1204,16 @@ void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID } bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf; } void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_range != p_msdf_pixel_range) { @@ -1199,16 +1223,16 @@ void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64 } int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf_range; } void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_source_size != p_msdf_size) { @@ -1218,48 +1242,48 @@ void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms } int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf_source_size; } void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->fixed_size = p_fixed_size; } int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->fixed_size; } void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->allow_system_fallback = p_allow_system_fallback; } bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->allow_system_fallback; } void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->force_autohinter != p_force_autohinter) { @@ -1269,16 +1293,16 @@ void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool } bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->force_autohinter; } void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->hinting != p_hinting) { @@ -1288,32 +1312,32 @@ void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hi } TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, HINTING_NONE); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, HINTING_NONE); MutexLock lock(fd->mutex); return fd->hinting; } void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->subpixel_positioning = p_subpixel; } TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioning(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED); MutexLock lock(fd->mutex); return fd->subpixel_positioning; } void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_strength) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->embolden != p_strength) { @@ -1323,8 +1347,8 @@ void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_stre } double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->embolden; @@ -1332,29 +1356,40 @@ double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const { void TextServerFallback::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + if (fdv->extra_spacing[p_spacing] != p_value) { + fdv->extra_spacing[p_spacing] = p_value; + } + } else { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL(fd); - MutexLock lock(fd->mutex); - if (fd->extra_spacing[p_spacing] != p_value) { - _font_clear_cache(fd); - fd->extra_spacing[p_spacing] = p_value; + MutexLock lock(fd->mutex); + if (fd->extra_spacing[p_spacing] != p_value) { + _font_clear_cache(fd); + fd->extra_spacing[p_spacing] = p_value; + } } } int64_t TextServerFallback::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const { ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + return fdv->extra_spacing[p_spacing]; + } else { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); - - MutexLock lock(fd->mutex); - return fd->extra_spacing[p_spacing]; + MutexLock lock(fd->mutex); + return fd->extra_spacing[p_spacing]; + } } void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->transform != p_transform) { @@ -1364,16 +1399,16 @@ void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transf } Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Transform2D()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Transform2D()); MutexLock lock(fd->mutex); return fd->transform; } void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (!fd->variation_coordinates.recursive_equal(p_variation_coordinates, 1)) { @@ -1383,16 +1418,16 @@ void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid, } Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->variation_coordinates; } void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->oversampling != p_oversampling) { @@ -1402,16 +1437,16 @@ void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_ } double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->oversampling; } TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); TypedArray<Vector2i> ret; @@ -1422,8 +1457,8 @@ TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_ } void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -1434,8 +1469,8 @@ void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) { } void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -1446,8 +1481,8 @@ void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Ve } void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1457,8 +1492,8 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, } double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1473,8 +1508,8 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz } void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); Vector2i size = _get_size(fd, p_size); @@ -1483,8 +1518,8 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size } double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1499,8 +1534,8 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si } void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1510,8 +1545,8 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int } double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1526,8 +1561,8 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i } void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1537,8 +1572,8 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in } double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1553,8 +1588,8 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, } void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1569,8 +1604,8 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, } double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1585,8 +1620,8 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size } int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1597,8 +1632,8 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const } void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1607,8 +1642,8 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto } void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1619,8 +1654,8 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto } void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND(p_image.is_null()); MutexLock lock(fd->mutex); @@ -1648,8 +1683,8 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve } Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Ref<Image>()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Ref<Image>()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1662,8 +1697,8 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) { ERR_FAIL_COND(p_offsets.size() % 4 != 0); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1681,8 +1716,8 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const } PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1706,8 +1741,8 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font } PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1722,8 +1757,8 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, } void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1733,8 +1768,8 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2 } void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1744,8 +1779,8 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2 } Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1782,8 +1817,8 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64 } void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1797,8 +1832,8 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t } Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1827,8 +1862,8 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const } void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1842,8 +1877,8 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec } Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1872,8 +1907,8 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve } void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1887,8 +1922,8 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto } Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Rect2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Rect2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1912,8 +1947,8 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V } void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1927,8 +1962,8 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve } int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, -1); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, -1); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1952,8 +1987,8 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c } void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1967,8 +2002,8 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons } RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, RID()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, RID()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2013,8 +2048,8 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const } Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Size2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Size2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2059,8 +2094,8 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co } Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2109,8 +2144,8 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i } TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2125,8 +2160,8 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon } void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2136,8 +2171,8 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t } void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2147,8 +2182,8 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s } void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2158,8 +2193,8 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size } Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2202,7 +2237,7 @@ int64_t TextServerFallback::_font_get_char_from_glyph_index(const RID &p_font_ri } bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); + FontFallback *fd = _get_font_data(p_font_rid); ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + "."); if (!fd) { return false; @@ -2223,8 +2258,8 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c } String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); if (fd->cache.is_empty()) { @@ -2256,8 +2291,8 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons } void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + "."); ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + "."); @@ -2291,8 +2326,8 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2 } void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2322,8 +2357,8 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2 } void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2414,8 +2449,8 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); @@ -2506,8 +2541,8 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->language_support_overrides.has(p_language)) { @@ -2518,32 +2553,32 @@ bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, cons } void TextServerFallback::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides[p_language] = p_supported; } bool TextServerFallback::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->language_support_overrides[p_language]; } void TextServerFallback::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides.erase(p_language); } PackedStringArray TextServerFallback::_font_get_language_support_overrides(const RID &p_font_rid) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -2554,8 +2589,8 @@ PackedStringArray TextServerFallback::_font_get_language_support_overrides(const } bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->script_support_overrides.has(p_script)) { @@ -2566,24 +2601,24 @@ bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const } void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->script_support_overrides[p_script] = p_supported; } bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->script_support_overrides[p_script]; } void TextServerFallback::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2592,8 +2627,8 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_ } PackedStringArray TextServerFallback::_font_get_script_support_overrides(const RID &p_font_rid) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -2604,8 +2639,8 @@ PackedStringArray TextServerFallback::_font_get_script_support_overrides(const R } void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2614,8 +2649,8 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_ } Dictionary TextServerFallback::_font_get_opentype_feature_overrides(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->feature_overrides; @@ -2626,8 +2661,8 @@ Dictionary TextServerFallback::_font_supported_feature_list(const RID &p_font_ri } Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2716,7 +2751,7 @@ RID TextServerFallback::_create_shaped_text(TextServer::Direction p_direction, T void TextServerFallback::_shaped_text_clear(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); sd->parent = RID(); @@ -2746,7 +2781,7 @@ TextServer::Direction TextServerFallback::_shaped_text_get_inferred_direction(co void TextServerFallback::_shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) { _THREAD_SAFE_METHOD_ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); if (sd->custom_punct != p_punct) { if (sd->parent != RID()) { @@ -2760,13 +2795,13 @@ void TextServerFallback::_shaped_text_set_custom_punctuation(const RID &p_shaped String TextServerFallback::_shaped_text_get_custom_punctuation(const RID &p_shaped) const { _THREAD_SAFE_METHOD_ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, String()); + ERR_FAIL_NULL_V(sd, String()); return sd->custom_punct; } void TextServerFallback::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->orientation != p_orientation) { @@ -2784,7 +2819,7 @@ void TextServerFallback::_shaped_text_set_bidi_override(const RID &p_shaped, con TextServer::Orientation TextServerFallback::_shaped_text_get_orientation(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); + ERR_FAIL_NULL_V(sd, TextServer::ORIENTATION_HORIZONTAL); MutexLock lock(sd->mutex); return sd->orientation; @@ -2794,7 +2829,7 @@ void TextServerFallback::_shaped_text_set_preserve_invalid(const RID &p_shaped, ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); MutexLock lock(sd->mutex); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); if (sd->preserve_invalid != p_enabled) { if (sd->parent != RID()) { full_copy(sd); @@ -2806,7 +2841,7 @@ void TextServerFallback::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool TextServerFallback::_shaped_text_get_preserve_invalid(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_invalid; @@ -2814,7 +2849,7 @@ bool TextServerFallback::_shaped_text_get_preserve_invalid(const RID &p_shaped) void TextServerFallback::_shaped_text_set_preserve_control(const RID &p_shaped, bool p_enabled) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->preserve_control != p_enabled) { @@ -2828,7 +2863,7 @@ void TextServerFallback::_shaped_text_set_preserve_control(const RID &p_shaped, bool TextServerFallback::_shaped_text_get_preserve_control(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_control; @@ -2837,7 +2872,7 @@ bool TextServerFallback::_shaped_text_get_preserve_control(const RID &p_shaped) void TextServerFallback::_shaped_text_set_spacing(const RID &p_shaped, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->extra_spacing[p_spacing] != p_value) { @@ -2853,7 +2888,7 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); return sd->extra_spacing[p_spacing]; @@ -2861,20 +2896,20 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin int64_t TextServerFallback::_shaped_get_span_count(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); return sd->spans.size(); } Variant TextServerFallback::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_NULL_V(sd, Variant()); ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); return sd->spans[p_index].meta; } void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); ERR_FAIL_INDEX(p_index, sd->spans.size()); ShapedTextDataFallback::Span &span = sd->spans.ptrw()[p_index]; @@ -2898,13 +2933,13 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64 bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(p_size <= 0, false); for (int i = 0; i < p_fonts.size(); i++) { - ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false); + ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false); } if (p_text.is_empty()) { @@ -2949,7 +2984,7 @@ bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const Stri bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(p_key == Variant(), false); @@ -2981,7 +3016,7 @@ bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Vari bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), false); @@ -3125,7 +3160,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start _THREAD_SAFE_METHOD_ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); if (sd->parent != RID()) { @@ -3217,7 +3252,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start RID TextServerFallback::_shaped_text_get_parent(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); return sd->parent; @@ -3225,7 +3260,7 @@ RID TextServerFallback::_shaped_text_get_parent(const RID &p_shaped) const { double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double p_width, BitField<JustificationFlag> p_jst_flags) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3334,7 +3369,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const PackedFloat32Array &p_tab_stops) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3390,7 +3425,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3446,7 +3481,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) { bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3462,7 +3497,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line); - ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3619,7 +3654,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ int64_t TextServerFallback::_shaped_text_get_trim_pos(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.trim_pos; @@ -3627,7 +3662,7 @@ int64_t TextServerFallback::_shaped_text_get_trim_pos(const RID &p_shaped) const int64_t TextServerFallback::_shaped_text_get_ellipsis_pos(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_pos; @@ -3635,7 +3670,7 @@ int64_t TextServerFallback::_shaped_text_get_ellipsis_pos(const RID &p_shaped) c const Glyph *TextServerFallback::_shaped_text_get_ellipsis_glyphs(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, nullptr, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.ptr(); @@ -3643,7 +3678,7 @@ const Glyph *TextServerFallback::_shaped_text_get_ellipsis_glyphs(const RID &p_s int64_t TextServerFallback::_shaped_text_get_ellipsis_glyph_count(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, 0, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.size(); @@ -3651,7 +3686,7 @@ int64_t TextServerFallback::_shaped_text_get_ellipsis_glyph_count(const RID &p_s bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (sd->valid) { @@ -3960,7 +3995,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->valid; @@ -3968,7 +4003,7 @@ bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const { const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3979,7 +4014,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3990,7 +4025,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4002,7 +4037,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) Vector2i TextServerFallback::_shaped_text_get_range(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Vector2i()); + ERR_FAIL_NULL_V(sd, Vector2i()); MutexLock lock(sd->mutex); return Vector2(sd->start, sd->end); @@ -4011,7 +4046,7 @@ Vector2i TextServerFallback::_shaped_text_get_range(const RID &p_shaped) const { Array TextServerFallback::_shaped_text_get_objects(const RID &p_shaped) const { Array ret; const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, ret); + ERR_FAIL_NULL_V(sd, ret); MutexLock lock(sd->mutex); for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { @@ -4023,7 +4058,7 @@ Array TextServerFallback::_shaped_text_get_objects(const RID &p_shaped) const { Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, const Variant &p_key) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Rect2()); + ERR_FAIL_NULL_V(sd, Rect2()); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); @@ -4035,7 +4070,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Size2()); + ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4050,7 +4085,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4061,7 +4096,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4072,7 +4107,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4083,7 +4118,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4095,7 +4130,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4107,7 +4142,7 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, PackedInt32Array()); + ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); if (!sd->valid) { diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index c44b45fc27..3b0b10ec35 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -245,6 +245,11 @@ class TextServerFallback : public TextServerExtension { } }; + struct FontFallbackLinkedVariation { + RID base_font; + int extra_spacing[4] = { 0, 0, 0, 0 }; + }; + struct FontFallback { Mutex mutex; @@ -451,9 +456,19 @@ class TextServerFallback : public TextServerExtension { // Common data. double oversampling = 1.0; + mutable RID_PtrOwner<FontFallbackLinkedVariation> font_var_owner; mutable RID_PtrOwner<FontFallback> font_owner; mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner; + _FORCE_INLINE_ FontFallback *_get_font_data(const RID &p_font_rid) const { + RID rid = p_font_rid; + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + return font_owner.get_or_null(rid); + } + struct SystemFontKey { String font_name; TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; @@ -569,6 +584,7 @@ public: /* Font interface */ MODBIND0R(RID, create_font); + MODBIND1R(RID, create_font_linked_variation, const RID &); MODBIND2(font_set_data, const RID &, const PackedByteArray &); MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t); diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp index df7672754b..aef4f394b2 100644 --- a/modules/upnp/upnp.cpp +++ b/modules/upnp/upnp.cpp @@ -242,14 +242,14 @@ Ref<UPNPDevice> UPNP::get_device(int index) const { } void UPNP::add_device(Ref<UPNPDevice> device) { - ERR_FAIL_COND(device == nullptr); + ERR_FAIL_NULL(device); devices.push_back(device); } void UPNP::set_device(int index, Ref<UPNPDevice> device) { ERR_FAIL_INDEX(index, devices.size()); - ERR_FAIL_COND(device == nullptr); + ERR_FAIL_NULL(device); devices.set(index, device); } diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index a127a6b75a..38cb614847 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -600,7 +600,7 @@ ssize_t WSLPeer::_wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t * } int WSLPeer::_wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) { - ERR_FAIL_COND_V(!_static_rng, WSLAY_ERR_CALLBACK_FAILURE); + ERR_FAIL_NULL_V(_static_rng, WSLAY_ERR_CALLBACK_FAILURE); Error err = _static_rng->get_random_bytes(buf, len); ERR_FAIL_COND_V(err != OK, WSLAY_ERR_CALLBACK_FAILURE); return 0; @@ -676,7 +676,7 @@ void WSLPeer::poll() { } if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) { - ERR_FAIL_COND(!wsl_ctx); + ERR_FAIL_NULL(wsl_ctx); int err = 0; if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) { // Error close. |
