summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/assimp/import_utils.h42
-rw-r--r--modules/bullet/hinge_joint_bullet.cpp3
-rw-r--r--modules/bullet/shape_bullet.cpp3
-rw-r--r--modules/bullet/slider_joint_bullet.cpp157
-rw-r--r--modules/bullet/space_bullet.h3
-rw-r--r--modules/csg/csg.cpp45
-rw-r--r--modules/csg/csg_gizmos.cpp45
-rw-r--r--modules/csg/csg_shape.cpp72
-rw-r--r--modules/denoise/SCsub119
-rw-r--r--modules/denoise/config.py6
-rw-r--r--modules/denoise/denoise_wrapper.cpp34
-rw-r--r--modules/denoise/denoise_wrapper.h8
-rw-r--r--modules/denoise/lightmap_denoiser.cpp63
-rw-r--r--modules/denoise/lightmap_denoiser.h57
-rw-r--r--modules/denoise/register_types.cpp41
-rw-r--r--modules/denoise/register_types.h32
-rw-r--r--modules/denoise/resource_to_cpp.py70
-rw-r--r--modules/gdnative/gdnative/gdnative.cpp3
-rw-r--r--modules/gdscript/gdscript.cpp64
-rw-r--r--modules/gdscript/gdscript.h8
-rw-r--r--modules/gdscript/gdscript_compiler.cpp103
-rw-r--r--modules/gdscript/gdscript_compiler.h6
-rw-r--r--modules/gdscript/gdscript_editor.cpp40
-rw-r--r--modules/gdscript/gdscript_function.cpp83
-rw-r--r--modules/gdscript/gdscript_function.h14
-rw-r--r--modules/gdscript/gdscript_functions.cpp5
-rw-r--r--modules/gdscript/gdscript_parser.cpp417
-rw-r--r--modules/gdscript/gdscript_parser.h9
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp50
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp3
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp3
-rw-r--r--modules/gdscript/language_server/lsp.hpp33
-rw-r--r--modules/jsonrpc/jsonrpc.cpp3
-rw-r--r--modules/lightmapper_rd/SCsub12
-rw-r--r--modules/lightmapper_rd/config.py6
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp1754
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.h229
-rw-r--r--modules/lightmapper_rd/lm_blendseams.glsl117
-rw-r--r--modules/lightmapper_rd/lm_common_inc.glsl92
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl657
-rw-r--r--modules/lightmapper_rd/lm_raster.glsl170
-rw-r--r--modules/lightmapper_rd/register_types.cpp64
-rw-r--r--modules/lightmapper_rd/register_types.h37
-rw-r--r--[-rwxr-xr-x]modules/mbedtls/packet_peer_mbed_dtls.cpp6
-rw-r--r--[-rwxr-xr-x]modules/mbedtls/stream_peer_mbedtls.cpp6
-rw-r--r--modules/mobile_vr/mobile_vr_interface.cpp20
-rw-r--r--modules/mono/build_scripts/godot_tools_build.py8
-rw-r--r--modules/mono/build_scripts/solution_builder.py156
-rw-r--r--modules/mono/config.py1
-rw-r--r--modules/mono/csharp_script.cpp174
-rw-r--r--modules/mono/csharp_script.h3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj58
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj39
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs33
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs94
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs219
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs207
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj53
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs46
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs88
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs57
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj17
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs218
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs332
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs44
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs (renamed from modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs)6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs (renamed from modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs)2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs9
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs100
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs302
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs116
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs23
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs (renamed from modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs)4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs32
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj60
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe0
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs11
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs33
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs27
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.sln2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs91
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs65
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildTab.cs12
-rwxr-xr-xmodules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj128
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs163
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs212
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs360
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs12
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs7
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs19
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs51
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/packages.config5
-rw-r--r--modules/mono/editor/bindings_generator.cpp15
-rw-r--r--modules/mono/editor/bindings_generator.h3
-rw-r--r--modules/mono/editor/code_completion.cpp249
-rw-r--r--modules/mono/editor/code_completion.h56
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp8
-rw-r--r--modules/mono/editor/godotsharp_export.cpp79
-rw-r--r--modules/mono/editor/script_class_parser.cpp20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs18
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj1
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj1
-rw-r--r--modules/mono/managed_callable.cpp1
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp35
-rw-r--r--modules/mono/mono_gd/gd_mono.h1
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.cpp71
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_class.cpp18
-rw-r--r--modules/mono/mono_gd/gd_mono_class.h7
-rw-r--r--modules/mono/mono_gd/gd_mono_field.cpp3
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp7
-rw-r--r--modules/mono/utils/macros.h2
-rw-r--r--modules/opensimplex/noise_texture.cpp15
-rw-r--r--modules/opensimplex/open_simplex_noise.cpp12
-rw-r--r--modules/pvr/texture_loader_pvr.cpp8
-rw-r--r--modules/theora/video_stream_theora.cpp6
-rw-r--r--modules/tinyexr/image_saver_tinyexr.cpp16
-rw-r--r--modules/visual_script/visual_script_editor.cpp272
-rw-r--r--modules/visual_script/visual_script_expression.cpp180
-rw-r--r--modules/visual_script/visual_script_yield_nodes.cpp28
-rw-r--r--modules/webrtc/webrtc_data_channel_js.cpp6
-rw-r--r--modules/webrtc/webrtc_multiplayer.cpp3
-rw-r--r--modules/websocket/emws_client.cpp6
-rw-r--r--modules/xatlas_unwrap/register_types.cpp1
147 files changed, 7708 insertions, 2445 deletions
diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h
index f78931add3..e3510c2cb3 100644
--- a/modules/assimp/import_utils.h
+++ b/modules/assimp/import_utils.h
@@ -202,20 +202,34 @@ public:
*/
static float get_fbx_fps(int32_t time_mode, const aiScene *p_scene) {
switch (time_mode) {
- case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack
- case AssetImportFbx::TIME_MODE_120: return 120;
- case AssetImportFbx::TIME_MODE_100: return 100;
- case AssetImportFbx::TIME_MODE_60: return 60;
- case AssetImportFbx::TIME_MODE_50: return 50;
- case AssetImportFbx::TIME_MODE_48: return 48;
- case AssetImportFbx::TIME_MODE_30: return 30;
- case AssetImportFbx::TIME_MODE_30_DROP: return 30;
- case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: return 29.9700262f;
- case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: return 29.9700262f;
- case AssetImportFbx::TIME_MODE_PAL: return 25;
- case AssetImportFbx::TIME_MODE_CINEMA: return 24;
- case AssetImportFbx::TIME_MODE_1000: return 1000;
- case AssetImportFbx::TIME_MODE_CINEMA_ND: return 23.976f;
+ case AssetImportFbx::TIME_MODE_DEFAULT:
+ return 24; //hack
+ case AssetImportFbx::TIME_MODE_120:
+ return 120;
+ case AssetImportFbx::TIME_MODE_100:
+ return 100;
+ case AssetImportFbx::TIME_MODE_60:
+ return 60;
+ case AssetImportFbx::TIME_MODE_50:
+ return 50;
+ case AssetImportFbx::TIME_MODE_48:
+ return 48;
+ case AssetImportFbx::TIME_MODE_30:
+ return 30;
+ case AssetImportFbx::TIME_MODE_30_DROP:
+ return 30;
+ case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME:
+ return 29.9700262f;
+ case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME:
+ return 29.9700262f;
+ case AssetImportFbx::TIME_MODE_PAL:
+ return 25;
+ case AssetImportFbx::TIME_MODE_CINEMA:
+ return 24;
+ case AssetImportFbx::TIME_MODE_1000:
+ return 1000;
+ case AssetImportFbx::TIME_MODE_CINEMA_ND:
+ return 23.976f;
case AssetImportFbx::TIME_MODE_CUSTOM:
int32_t frame_rate = -1;
p_scene->mMetaData->Get("FrameRate", frame_rate);
diff --git a/modules/bullet/hinge_joint_bullet.cpp b/modules/bullet/hinge_joint_bullet.cpp
index 4bea9f87c0..e7f3d75c10 100644
--- a/modules/bullet/hinge_joint_bullet.cpp
+++ b/modules/bullet/hinge_joint_bullet.cpp
@@ -162,7 +162,8 @@ void HingeJointBullet::set_flag(PhysicsServer3D::HingeJointFlag p_flag, bool p_v
case PhysicsServer3D::HINGE_JOINT_FLAG_ENABLE_MOTOR:
hingeConstraint->enableMotor(p_value);
break;
- case PhysicsServer3D::HINGE_JOINT_FLAG_MAX: break; // Can't happen, but silences warning
+ case PhysicsServer3D::HINGE_JOINT_FLAG_MAX:
+ break; // Can't happen, but silences warning
}
}
diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp
index 8ac26a0fdb..5bcb4743c9 100644
--- a/modules/bullet/shape_bullet.cpp
+++ b/modules/bullet/shape_bullet.cpp
@@ -81,7 +81,8 @@ void ShapeBullet::add_owner(ShapeOwnerBullet *p_owner) {
void ShapeBullet::remove_owner(ShapeOwnerBullet *p_owner, bool p_permanentlyFromThisBody) {
Map<ShapeOwnerBullet *, int>::Element *E = owners.find(p_owner);
- if (!E) return;
+ if (!E)
+ return;
E->get()--;
if (p_permanentlyFromThisBody || 0 >= E->get()) {
owners.erase(E);
diff --git a/modules/bullet/slider_joint_bullet.cpp b/modules/bullet/slider_joint_bullet.cpp
index f193daef39..de47c91e5d 100644
--- a/modules/bullet/slider_joint_bullet.cpp
+++ b/modules/bullet/slider_joint_bullet.cpp
@@ -344,56 +344,123 @@ real_t SliderJointBullet::getLinearPos() {
void SliderJointBullet::set_param(PhysicsServer3D::SliderJointParam p_param, real_t p_value) {
switch (p_param) {
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: setUpperLinLimit(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: setLowerLinLimit(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: setSoftnessLimLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: setRestitutionLimLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: setDampingLimLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: setSoftnessDirLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: setRestitutionDirLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: setDampingDirLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: setSoftnessOrthoLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: setRestitutionOrthoLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: setDampingOrthoLin(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: setUpperAngLimit(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: setLowerAngLimit(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: setSoftnessLimAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: setRestitutionLimAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: setDampingLimAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: setSoftnessDirAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: setRestitutionDirAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: setDampingDirAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: setSoftnessOrthoAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: setRestitutionOrthoAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: setDampingOrthoAng(p_value); break;
- case PhysicsServer3D::SLIDER_JOINT_MAX: break; // Can't happen, but silences warning
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER:
+ setUpperLinLimit(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER:
+ setLowerLinLimit(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS:
+ setSoftnessLimLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION:
+ setRestitutionLimLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING:
+ setDampingLimLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS:
+ setSoftnessDirLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION:
+ setRestitutionDirLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING:
+ setDampingDirLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS:
+ setSoftnessOrthoLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION:
+ setRestitutionOrthoLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING:
+ setDampingOrthoLin(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER:
+ setUpperAngLimit(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER:
+ setLowerAngLimit(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS:
+ setSoftnessLimAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION:
+ setRestitutionLimAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING:
+ setDampingLimAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS:
+ setSoftnessDirAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION:
+ setRestitutionDirAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING:
+ setDampingDirAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS:
+ setSoftnessOrthoAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION:
+ setRestitutionOrthoAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING:
+ setDampingOrthoAng(p_value);
+ break;
+ case PhysicsServer3D::SLIDER_JOINT_MAX:
+ break; // Can't happen, but silences warning
}
}
real_t SliderJointBullet::get_param(PhysicsServer3D::SliderJointParam p_param) const {
switch (p_param) {
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER: return getUpperLinLimit();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER: return getLowerLinLimit();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS: return getSoftnessLimLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION: return getRestitutionLimLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING: return getDampingLimLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS: return getSoftnessDirLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION: return getRestitutionDirLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING: return getDampingDirLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS: return getSoftnessOrthoLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION: return getRestitutionOrthoLin();
- case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING: return getDampingOrthoLin();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER: return getUpperAngLimit();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER: return getLowerAngLimit();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS: return getSoftnessLimAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION: return getRestitutionLimAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING: return getDampingLimAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS: return getSoftnessDirAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION: return getRestitutionDirAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING: return getDampingDirAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS: return getSoftnessOrthoAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION: return getRestitutionOrthoAng();
- case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING: return getDampingOrthoAng();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_UPPER:
+ return getUpperLinLimit();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_LOWER:
+ return getLowerLinLimit();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS:
+ return getSoftnessLimLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION:
+ return getRestitutionLimLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_LIMIT_DAMPING:
+ return getDampingLimLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_SOFTNESS:
+ return getSoftnessDirLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_RESTITUTION:
+ return getRestitutionDirLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_MOTION_DAMPING:
+ return getDampingDirLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_SOFTNESS:
+ return getSoftnessOrthoLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_RESTITUTION:
+ return getRestitutionOrthoLin();
+ case PhysicsServer3D::SLIDER_JOINT_LINEAR_ORTHOGONAL_DAMPING:
+ return getDampingOrthoLin();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_UPPER:
+ return getUpperAngLimit();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_LOWER:
+ return getLowerAngLimit();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS:
+ return getSoftnessLimAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_RESTITUTION:
+ return getRestitutionLimAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING:
+ return getDampingLimAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_SOFTNESS:
+ return getSoftnessDirAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_RESTITUTION:
+ return getRestitutionDirAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_MOTION_DAMPING:
+ return getDampingDirAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_SOFTNESS:
+ return getSoftnessOrthoAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_RESTITUTION:
+ return getRestitutionOrthoAng();
+ case PhysicsServer3D::SLIDER_JOINT_ANGULAR_ORTHOGONAL_DAMPING:
+ return getDampingOrthoAng();
default:
return 0;
}
diff --git a/modules/bullet/space_bullet.h b/modules/bullet/space_bullet.h
index 3d4a2aeceb..0173c5a1c7 100644
--- a/modules/bullet/space_bullet.h
+++ b/modules/bullet/space_bullet.h
@@ -170,7 +170,8 @@ public:
contactDebugCount = 0;
}
_FORCE_INLINE_ void add_debug_contact(const Vector3 &p_contact) {
- if (contactDebugCount < contactDebug.size()) contactDebug.write[contactDebugCount++] = p_contact;
+ if (contactDebugCount < contactDebug.size())
+ contactDebug.write[contactDebugCount++] = p_contact;
}
_FORCE_INLINE_ Vector<Vector3> get_debug_contacts() { return contactDebug; }
_FORCE_INLINE_ int get_debug_contact_count() { return contactDebugCount; }
diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp
index 6714db76bb..a6951a9320 100644
--- a/modules/csg/csg.cpp
+++ b/modules/csg/csg.cpp
@@ -138,10 +138,12 @@ inline bool is_point_in_triangle(const Vector3 &p_point, const Vector3 p_vertice
lambda[2] = p_vertices[0].cross(p_vertices[1]).dot(p_point) / det;
// Point is in the plane if all lambdas sum to 1.
- if (!Math::is_equal_approx(lambda[0] + lambda[1] + lambda[2], 1)) return false;
+ if (!Math::is_equal_approx(lambda[0] + lambda[1] + lambda[2], 1))
+ return false;
// Point is inside the triangle if all lambdas are positive.
- if (lambda[0] < 0 || lambda[1] < 0 || lambda[2] < 0) return false;
+ if (lambda[0] < 0 || lambda[1] < 0 || lambda[2] < 0)
+ return false;
return true;
}
@@ -524,7 +526,8 @@ void CSGBrushOperation::MeshMerge::_add_distance(List<real_t> &r_intersectionsA,
// Check if distance exists.
for (const List<real_t>::Element *E = intersections.front(); E; E = E->next())
- if (Math::abs(**E - p_distance) < vertex_snap) return;
+ if (Math::abs(**E - p_distance) < vertex_snap)
+ return;
intersections.push_back(p_distance);
}
@@ -790,7 +793,8 @@ int CSGBrushOperation::Build2DFaces::_add_vertex(const Vertex2D &p_vertex) {
// Check if vertex exists.
int vertex_id = _get_point_idx(p_vertex.point);
- if (vertex_id != -1) return vertex_id;
+ if (vertex_id != -1)
+ return vertex_id;
vertices.push_back(p_vertex);
return vertices.size() - 1;
@@ -816,7 +820,8 @@ void CSGBrushOperation::Build2DFaces::_add_vertex_idx_sorted(Vector<int> &r_vert
// Sort along the axis with the greatest difference.
int axis = 0;
- if (Math::abs(new_point.x - first_point.x) < Math::abs(new_point.y - first_point.y)) axis = 1;
+ if (Math::abs(new_point.x - first_point.x) < Math::abs(new_point.y - first_point.y))
+ axis = 1;
// Add it to the beginning or the end appropriately.
if (new_point[axis] < first_point[axis])
@@ -834,7 +839,8 @@ void CSGBrushOperation::Build2DFaces::_add_vertex_idx_sorted(Vector<int> &r_vert
// Determine axis being sorted against i.e. the axis with the greatest difference.
int axis = 0;
- if (Math::abs(last_point.x - first_point.x) < Math::abs(last_point.y - first_point.y)) axis = 1;
+ if (Math::abs(last_point.x - first_point.x) < Math::abs(last_point.y - first_point.y))
+ axis = 1;
// Insert the point at the appropriate index.
for (int insert_idx = 0; insert_idx < r_vertex_indices.size(); ++insert_idx) {
@@ -853,7 +859,8 @@ void CSGBrushOperation::Build2DFaces::_add_vertex_idx_sorted(Vector<int> &r_vert
void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_indices) {
int segments = p_segment_indices.size() - 1;
- if (segments < 2) return;
+ if (segments < 2)
+ return;
// Faces around an inner vertex are merged by moving the inner vertex to the first vertex.
for (int sorted_idx = 1; sorted_idx < segments; ++sorted_idx) {
@@ -893,7 +900,8 @@ void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_
// Skip flattened faces.
if (outer_edge_idx[0] == p_segment_indices[closest_idx] ||
- outer_edge_idx[1] == p_segment_indices[closest_idx]) continue;
+ outer_edge_idx[1] == p_segment_indices[closest_idx])
+ continue;
//Don't create degenerate triangles.
Vector2 edge1[2] = {
@@ -924,7 +932,8 @@ void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_
for (int i = 0; i < merge_faces_idx.size(); ++i)
faces.remove(merge_faces_idx[i]);
- if (degenerate_points.size() == 0) continue;
+ if (degenerate_points.size() == 0)
+ continue;
// Split faces using degenerate points.
for (int face_idx = 0; face_idx < faces.size(); ++face_idx) {
@@ -954,7 +963,8 @@ void CSGBrushOperation::Build2DFaces::_merge_faces(const Vector<int> &p_segment_
break;
}
}
- if (existing) continue;
+ if (existing)
+ continue;
// Check if point is on an each edge.
for (int face_edge_idx = 0; face_edge_idx < 3; ++face_edge_idx) {
@@ -1043,10 +1053,12 @@ void CSGBrushOperation::Build2DFaces::_find_edge_intersections(const Vector2 p_s
// Check if intersection point is an edge point.
if ((intersection_point - edge_points[0]).length_squared() < vertex_snap2 ||
- (intersection_point - edge_points[1]).length_squared() < vertex_snap2) continue;
+ (intersection_point - edge_points[1]).length_squared() < vertex_snap2)
+ continue;
// Check if edge exists, by checking if the intersecting segment is parallel to the edge.
- if (are_segements_parallel(p_segment_points, edge_points, vertex_snap2)) continue;
+ if (are_segements_parallel(p_segment_points, edge_points, vertex_snap2))
+ continue;
// Add the intersection point as a new vertex.
Vertex2D new_vertex;
@@ -1384,7 +1396,8 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face
p_collection.build2DFacesB[p_face_idx_b] = Build2DFaces();
has_degenerate = true;
}
- if (has_degenerate) return;
+ if (has_degenerate)
+ return;
// Ensure B has points either side of or in the plane of A.
int in_plane_count = 0, over_count = 0, under_count = 0;
@@ -1400,7 +1413,8 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face
under_count++;
}
// If all points under or over the plane, there is no intesection.
- if (over_count == 3 || under_count == 3) return;
+ if (over_count == 3 || under_count == 3)
+ return;
// Ensure A has points either side of or in the plane of B.
in_plane_count = 0;
@@ -1418,7 +1432,8 @@ void CSGBrushOperation::update_faces(const CSGBrush &p_brush_a, const int p_face
under_count++;
}
// If all points under or over the plane, there is no intesection.
- if (over_count == 3 || under_count == 3) return;
+ if (over_count == 3 || under_count == 3)
+ return;
// Check for intersection using the SAT theorem.
{
diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp
index fa176cb94e..a859ef80a6 100644
--- a/modules/csg/csg_gizmos.cpp
+++ b/modules/csg/csg_gizmos.cpp
@@ -90,9 +90,12 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
switch (p_idx) {
- case 0: return s->get_width();
- case 1: return s->get_height();
- case 2: return s->get_depth();
+ case 0:
+ return s->get_width();
+ case 1:
+ return s->get_height();
+ case 2:
+ return s->get_depth();
}
}
@@ -157,9 +160,15 @@ void CSGShape3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Ca
d = 0.001;
switch (p_idx) {
- case 0: s->set_width(d * 2); break;
- case 1: s->set_height(d * 2); break;
- case 2: s->set_depth(d * 2); break;
+ case 0:
+ s->set_width(d * 2);
+ break;
+ case 1:
+ s->set_height(d * 2);
+ break;
+ case 2:
+ s->set_depth(d * 2);
+ break;
}
}
@@ -229,9 +238,15 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx,
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
if (p_cancel) {
switch (p_idx) {
- case 0: s->set_width(p_restore); break;
- case 1: s->set_height(p_restore); break;
- case 2: s->set_depth(p_restore); break;
+ case 0:
+ s->set_width(p_restore);
+ break;
+ case 1:
+ s->set_height(p_restore);
+ break;
+ case 2:
+ s->set_depth(p_restore);
+ break;
}
return;
}
@@ -241,9 +256,15 @@ void CSGShape3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx,
static const char *method[3] = { "set_width", "set_height", "set_depth" };
float current = 0;
switch (p_idx) {
- case 0: current = s->get_width(); break;
- case 1: current = s->get_height(); break;
- case 2: current = s->get_depth(); break;
+ case 0:
+ current = s->get_width();
+ break;
+ case 1:
+ current = s->get_height();
+ break;
+ case 2:
+ current = s->get_depth();
+ break;
}
ur->add_do_method(s, method[p_idx], current);
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 550a919d0d..a5b664eeab 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -185,9 +185,15 @@ CSGBrush *CSGShape3D::_get_brush() {
CSGBrushOperation bop;
switch (child->get_operation()) {
- case CSGShape3D::OPERATION_UNION: bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap); break;
- case CSGShape3D::OPERATION_INTERSECTION: bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap); break;
- case CSGShape3D::OPERATION_SUBTRACTION: bop.merge_brushes(CSGBrushOperation::OPERATION_SUBSTRACTION, *n, *nn2, *nn, snap); break;
+ case CSGShape3D::OPERATION_UNION:
+ bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap);
+ break;
+ case CSGShape3D::OPERATION_INTERSECTION:
+ bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap);
+ break;
+ case CSGShape3D::OPERATION_SUBTRACTION:
+ bop.merge_brushes(CSGBrushOperation::OPERATION_SUBSTRACTION, *n, *nn2, *nn, snap);
+ break;
}
memdelete(n);
memdelete(nn2);
@@ -340,21 +346,31 @@ void CSGShape3D::_update_shape() {
}
}
- //fill arrays
- Vector<Vector3> physics_faces;
- bool fill_physics_faces = false;
+ // Update collision faces.
if (root_collision_shape.is_valid()) {
+
+ Vector<Vector3> physics_faces;
physics_faces.resize(n->faces.size() * 3);
- fill_physics_faces = true;
- }
+ Vector3 *physicsw = physics_faces.ptrw();
- {
- Vector3 *physicsw;
+ for (int i = 0; i < n->faces.size(); i++) {
+
+ int order[3] = { 0, 1, 2 };
+
+ if (n->faces[i].invert) {
+ SWAP(order[1], order[2]);
+ }
- if (fill_physics_faces) {
- physicsw = physics_faces.ptrw();
+ physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]];
+ physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]];
+ physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]];
}
+ root_collision_shape->set_faces(physics_faces);
+ }
+
+ //fill arrays
+ {
for (int i = 0; i < n->faces.size(); i++) {
int order[3] = { 0, 1, 2 };
@@ -363,12 +379,6 @@ void CSGShape3D::_update_shape() {
SWAP(order[1], order[2]);
}
- if (fill_physics_faces) {
- physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]];
- physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]];
- physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]];
- }
-
int mat = n->faces[i].material;
ERR_CONTINUE(mat < -1 || mat >= face_count.size());
int idx = mat == -1 ? face_count.size() - 1 : mat;
@@ -452,10 +462,6 @@ void CSGShape3D::_update_shape() {
root_mesh->surface_set_material(idx, surfaces[i].material);
}
- if (root_collision_shape.is_valid()) {
- root_collision_shape->set_faces(physics_faces);
- }
-
set_base(root_mesh->get_rid());
}
AABB CSGShape3D::get_aabb() const {
@@ -1783,11 +1789,15 @@ CSGBrush *CSGPolygon3D::_build_brush() {
final_polygon_min = p;
final_polygon_max = final_polygon_min;
} else {
- if (p.x < final_polygon_min.x) final_polygon_min.x = p.x;
- if (p.y < final_polygon_min.y) final_polygon_min.y = p.y;
-
- if (p.x > final_polygon_max.x) final_polygon_max.x = p.x;
- if (p.y > final_polygon_max.y) final_polygon_max.y = p.y;
+ if (p.x < final_polygon_min.x)
+ final_polygon_min.x = p.x;
+ if (p.y < final_polygon_min.y)
+ final_polygon_min.y = p.y;
+
+ if (p.x > final_polygon_max.x)
+ final_polygon_max.x = p.x;
+ if (p.y > final_polygon_max.y)
+ final_polygon_max.y = p.y;
}
}
Vector2 final_polygon_size = final_polygon_max - final_polygon_min;
@@ -1826,8 +1836,12 @@ CSGBrush *CSGPolygon3D::_build_brush() {
int face_count = 0;
switch (mode) {
- case MODE_DEPTH: face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2; break;
- case MODE_SPIN: face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (final_polygon.size()) * 2 * spin_sides; break;
+ case MODE_DEPTH:
+ face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2;
+ break;
+ case MODE_SPIN:
+ face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (final_polygon.size()) * 2 * spin_sides;
+ break;
case MODE_PATH: {
float bl = curve->get_baked_length();
int splits = MAX(2, Math::ceil(bl / path_interval));
diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub
new file mode 100644
index 0000000000..0fa65c6296
--- /dev/null
+++ b/modules/denoise/SCsub
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+
+import resource_to_cpp
+from platform_methods import run_in_subprocess
+
+Import("env")
+Import("env_modules")
+
+env_oidn = env_modules.Clone()
+
+# Thirdparty source files
+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",
+ "NDEBUG",
+ ]
+)
+
+env_thirdparty = env_oidn.Clone()
+env_thirdparty.disable_warnings()
+env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
+
+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)
+
+env_oidn.add_source_files(env.modules_sources, "denoise_wrapper.cpp")
+env_modules.add_source_files(env.modules_sources, ["register_types.cpp", "lightmap_denoiser.cpp"])
diff --git a/modules/denoise/config.py b/modules/denoise/config.py
new file mode 100644
index 0000000000..53b8f2f2e3
--- /dev/null
+++ b/modules/denoise/config.py
@@ -0,0 +1,6 @@
+def can_build(env, platform):
+ return env["tools"]
+
+
+def configure(env):
+ pass
diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/denoise/denoise_wrapper.cpp
new file mode 100644
index 0000000000..feeeaef507
--- /dev/null
+++ b/modules/denoise/denoise_wrapper.cpp
@@ -0,0 +1,34 @@
+#include "denoise_wrapper.h"
+#include "thirdparty/oidn/include/OpenImageDenoise/oidn.h"
+#include <stdio.h>
+
+void *oidn_denoiser_init() {
+ OIDNDeviceImpl *device = oidnNewDevice(OIDN_DEVICE_TYPE_CPU);
+ oidnCommitDevice(device);
+ return device;
+}
+
+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);
+
+ const char *msg;
+ bool success = true;
+ if (oidnGetDeviceError(device, &msg) != OIDN_ERROR_NONE) {
+ printf("LightmapDenoiser: %s\n", msg);
+ success = false;
+ }
+
+ oidnReleaseFilter(filter);
+ return success;
+}
+
+void oidn_denoiser_finish(void *device) {
+ oidnReleaseDevice((OIDNDeviceImpl *)device);
+}
diff --git a/modules/denoise/denoise_wrapper.h b/modules/denoise/denoise_wrapper.h
new file mode 100644
index 0000000000..3aef326e22
--- /dev/null
+++ b/modules/denoise/denoise_wrapper.h
@@ -0,0 +1,8 @@
+#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/lightmap_denoiser.cpp b/modules/denoise/lightmap_denoiser.cpp
new file mode 100644
index 0000000000..c821b22d85
--- /dev/null
+++ b/modules/denoise/lightmap_denoiser.cpp
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* lightmap_denoiser.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "lightmap_denoiser.h"
+#include "denoise_wrapper.h"
+
+LightmapDenoiser *LightmapDenoiserOIDN::create_oidn_denoiser() {
+ return memnew(LightmapDenoiserOIDN);
+}
+
+void LightmapDenoiserOIDN::make_default_denoiser() {
+ create_function = create_oidn_denoiser;
+}
+
+Ref<Image> LightmapDenoiserOIDN::denoise_image(const Ref<Image> &p_image) {
+
+ Ref<Image> img = p_image->duplicate();
+
+ img->convert(Image::FORMAT_RGBF);
+
+ Vector<uint8_t> data = img->get_data();
+ if (!oidn_denoise(device, (float *)data.ptrw(), img->get_width(), img->get_height())) {
+ return p_image;
+ }
+
+ img->create(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);
+}
diff --git a/modules/denoise/lightmap_denoiser.h b/modules/denoise/lightmap_denoiser.h
new file mode 100644
index 0000000000..ac0cc8b9db
--- /dev/null
+++ b/modules/denoise/lightmap_denoiser.h
@@ -0,0 +1,57 @@
+/*************************************************************************/
+/* lightmap_denoiser.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 LIGHTMAP_DENOISER_H
+#define LIGHTMAP_DENOISER_H
+
+#include "core/object.h"
+#include "scene/3d/lightmapper.h"
+
+struct OIDNDeviceImpl;
+
+class LightmapDenoiserOIDN : public LightmapDenoiser {
+
+ GDCLASS(LightmapDenoiserOIDN, LightmapDenoiser);
+
+protected:
+ void *device = nullptr;
+
+public:
+ static LightmapDenoiser *create_oidn_denoiser();
+
+ Ref<Image> denoise_image(const Ref<Image> &p_image);
+
+ static void make_default_denoiser();
+
+ LightmapDenoiserOIDN();
+ ~LightmapDenoiserOIDN();
+};
+
+#endif // LIGHTMAP_DENOISER_H
diff --git a/modules/denoise/register_types.cpp b/modules/denoise/register_types.cpp
new file mode 100644
index 0000000000..b6b92701c8
--- /dev/null
+++ b/modules/denoise/register_types.cpp
@@ -0,0 +1,41 @@
+/*************************************************************************/
+/* register_types.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "core/engine.h"
+#include "lightmap_denoiser.h"
+
+void register_denoise_types() {
+
+ LightmapDenoiserOIDN::make_default_denoiser();
+}
+
+void unregister_denoise_types() {
+}
diff --git a/modules/denoise/register_types.h b/modules/denoise/register_types.h
new file mode 100644
index 0000000000..2ffc36ee2c
--- /dev/null
+++ b/modules/denoise/register_types.h
@@ -0,0 +1,32 @@
+/*************************************************************************/
+/* register_types.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+void register_denoise_types();
+void unregister_denoise_types();
diff --git a/modules/denoise/resource_to_cpp.py b/modules/denoise/resource_to_cpp.py
new file mode 100644
index 0000000000..4c0b67f701
--- /dev/null
+++ b/modules/denoise/resource_to_cpp.py
@@ -0,0 +1,70 @@
+#!/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
+import sys
+import argparse
+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/gdnative/gdnative/gdnative.cpp b/modules/gdnative/gdnative/gdnative.cpp
index 3175340448..1216d1d9d3 100644
--- a/modules/gdnative/gdnative/gdnative.cpp
+++ b/modules/gdnative/gdnative/gdnative.cpp
@@ -177,7 +177,8 @@ void *godot_get_class_tag(const godot_string_name *p_class) {
}
godot_object *godot_object_cast_to(const godot_object *p_object, void *p_class_tag) {
- if (!p_object) return nullptr;
+ if (!p_object)
+ return nullptr;
Object *o = (Object *)p_object;
return o->is_class_ptr(p_class_tag) ? (godot_object *)o : nullptr;
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 06ab9e226d..8559fac57c 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -376,10 +376,15 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro
}
#endif
-bool GDScript::_update_exports() {
+bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) {
#ifdef TOOLS_ENABLED
+ static Vector<GDScript *> base_caches;
+ if (!p_recursive_call)
+ base_caches.clear();
+ base_caches.append(this);
+
bool changed = false;
if (source_changed_cache) {
@@ -473,7 +478,22 @@ bool GDScript::_update_exports() {
placeholder_fallback_enabled = false;
if (base_cache.is_valid() && base_cache->is_valid()) {
- if (base_cache->_update_exports()) {
+ for (int i = 0; i < base_caches.size(); i++) {
+ if (base_caches[i] == base_cache.ptr()) {
+ if (r_err)
+ *r_err = true;
+ valid = false; // to show error in the editor
+ base_cache->valid = false;
+ base_cache->inheriters_cache.clear(); // to prevent future stackoverflows
+ base_cache.unref();
+ base.unref();
+ _base = nullptr;
+ ERR_FAIL_V_MSG(false, "Cyclic inheritance in script class.");
+ }
+ }
+ if (base_cache->_update_exports(r_err, true)) {
+ if (r_err && *r_err)
+ return false;
changed = true;
}
}
@@ -501,7 +521,10 @@ void GDScript::update_exports() {
#ifdef TOOLS_ENABLED
- _update_exports();
+ bool cyclic_error = false;
+ _update_exports(&cyclic_error);
+ if (cyclic_error)
+ return;
Set<ObjectID> copy = inheriters_cache; //might get modified
@@ -635,12 +658,14 @@ uint16_t GDScript::get_rpc_method_id(const StringName &p_method) const {
}
StringName GDScript::get_rpc_method(const uint16_t p_rpc_method_id) const {
- if (p_rpc_method_id >= rpc_functions.size()) return StringName();
+ if (p_rpc_method_id >= rpc_functions.size())
+ return StringName();
return rpc_functions[p_rpc_method_id].name;
}
MultiplayerAPI::RPCMode GDScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const {
- if (p_rpc_method_id >= rpc_functions.size()) return MultiplayerAPI::RPC_MODE_DISABLED;
+ if (p_rpc_method_id >= rpc_functions.size())
+ return MultiplayerAPI::RPC_MODE_DISABLED;
return rpc_functions[p_rpc_method_id].mode;
}
@@ -662,12 +687,14 @@ uint16_t GDScript::get_rset_property_id(const StringName &p_variable) const {
}
StringName GDScript::get_rset_property(const uint16_t p_rset_member_id) const {
- if (p_rset_member_id >= rpc_variables.size()) return StringName();
+ if (p_rset_member_id >= rpc_variables.size())
+ return StringName();
return rpc_variables[p_rset_member_id].name;
}
MultiplayerAPI::RPCMode GDScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const {
- if (p_rset_member_id >= rpc_variables.size()) return MultiplayerAPI::RPC_MODE_DISABLED;
+ if (p_rset_member_id >= rpc_variables.size())
+ return MultiplayerAPI::RPC_MODE_DISABLED;
return rpc_variables[p_rset_member_id].mode;
}
@@ -1052,6 +1079,16 @@ void GDScript::_init_rpc_methods_properties() {
}
GDScript::~GDScript() {
+
+ {
+ MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+
+ while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
+ E->self()->_clear_stack();
+ pending_func_states.remove(E);
+ }
+ }
+
for (Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) {
memdelete(E->get());
}
@@ -1470,9 +1507,15 @@ GDScriptInstance::GDScriptInstance() {
}
GDScriptInstance::~GDScriptInstance() {
- if (script.is_valid() && owner) {
- MutexLock lock(GDScriptLanguage::singleton->lock);
+ MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+
+ while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
+ E->self()->_clear_stack();
+ pending_func_states.remove(E);
+ }
+
+ if (script.is_valid() && owner) {
script->instances.erase(owner);
}
}
@@ -2141,7 +2184,8 @@ String GDScriptWarning::get_message() const {
case STANDALONE_TERNARY: {
return "Standalone ternary conditional operator: the return value is being discarded.";
}
- case WARNING_MAX: break; // Can't happen, but silences warning
+ case WARNING_MAX:
+ break; // Can't happen, but silences warning
}
ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 5fdc25669f..b7ac2bd0c5 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -117,6 +117,8 @@ class GDScript : public Script {
String fully_qualified_name;
SelfList<GDScript> script_list;
+ SelfList<GDScriptFunctionState>::List pending_func_states;
+
GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error);
void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path);
@@ -133,7 +135,7 @@ class GDScript : public Script {
#endif
- bool _update_exports();
+ bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false);
void _save_orphaned_subclasses();
void _init_rpc_methods_properties();
@@ -254,6 +256,8 @@ class GDScriptInstance : public ScriptInstance {
Vector<Variant> members;
bool base_ref;
+ SelfList<GDScriptFunctionState>::List pending_func_states;
+
void _ml_call_reversed(GDScript *sptr, const StringName &p_method, const Variant **p_args, int p_argcount);
public:
@@ -347,6 +351,8 @@ struct GDScriptWarning {
class GDScriptLanguage : public ScriptLanguage {
+ friend class GDScriptFunctionState;
+
static GDScriptLanguage *singleton;
Variant *_global_array;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 2bbec29043..473b6fab05 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -177,16 +177,36 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS
switch (p_expression->op) {
- case GDScriptParser::OperatorNode::OP_ASSIGN_ADD: var_op = Variant::OP_ADD; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SUB: var_op = Variant::OP_SUBTRACT; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_MUL: var_op = Variant::OP_MULTIPLY; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_DIV: var_op = Variant::OP_DIVIDE; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_MOD: var_op = Variant::OP_MODULE; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT: var_op = Variant::OP_SHIFT_LEFT; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT: var_op = Variant::OP_SHIFT_RIGHT; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND: var_op = Variant::OP_BIT_AND; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR: var_op = Variant::OP_BIT_OR; break;
- case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR: var_op = Variant::OP_BIT_XOR; break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_ADD:
+ var_op = Variant::OP_ADD;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_SUB:
+ var_op = Variant::OP_SUBTRACT;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_MUL:
+ var_op = Variant::OP_MULTIPLY;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_DIV:
+ var_op = Variant::OP_DIVIDE;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_MOD:
+ var_op = Variant::OP_MODULE;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ var_op = Variant::OP_SHIFT_LEFT;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ var_op = Variant::OP_SHIFT_RIGHT;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_AND:
+ var_op = Variant::OP_BIT_AND;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_OR:
+ var_op = Variant::OP_BIT_OR;
+ break;
+ case GDScriptParser::OperatorNode::OP_ASSIGN_BIT_XOR:
+ var_op = Variant::OP_BIT_XOR;
+ break;
case GDScriptParser::OperatorNode::OP_INIT_ASSIGN:
case GDScriptParser::OperatorNode::OP_ASSIGN: {
@@ -861,71 +881,92 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
} break;
//unary operators
case GDScriptParser::OperatorNode::OP_NEG: {
- if (!_create_unary_operator(codegen, on, Variant::OP_NEGATE, p_stack_level)) return -1;
+ if (!_create_unary_operator(codegen, on, Variant::OP_NEGATE, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_POS: {
- if (!_create_unary_operator(codegen, on, Variant::OP_POSITIVE, p_stack_level)) return -1;
+ if (!_create_unary_operator(codegen, on, Variant::OP_POSITIVE, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_NOT: {
- if (!_create_unary_operator(codegen, on, Variant::OP_NOT, p_stack_level)) return -1;
+ if (!_create_unary_operator(codegen, on, Variant::OP_NOT, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_BIT_INVERT: {
- if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level)) return -1;
+ if (!_create_unary_operator(codegen, on, Variant::OP_BIT_NEGATE, p_stack_level))
+ return -1;
} break;
//binary operators (in precedence order)
case GDScriptParser::OperatorNode::OP_IN: {
- if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_IN, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_EQUAL, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_EQUAL, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_NOT_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_NOT_EQUAL, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_NOT_EQUAL, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_LESS: {
- if (!_create_binary_operator(codegen, on, Variant::OP_LESS, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_LESS, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_LESS_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_LESS_EQUAL, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_LESS_EQUAL, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_GREATER: {
- if (!_create_binary_operator(codegen, on, Variant::OP_GREATER, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_GREATER, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_GREATER_EQUAL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_GREATER_EQUAL, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_GREATER_EQUAL, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_ADD: {
- if (!_create_binary_operator(codegen, on, Variant::OP_ADD, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_ADD, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_SUB: {
- if (!_create_binary_operator(codegen, on, Variant::OP_SUBTRACT, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_SUBTRACT, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_MUL: {
- if (!_create_binary_operator(codegen, on, Variant::OP_MULTIPLY, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_MULTIPLY, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_DIV: {
- if (!_create_binary_operator(codegen, on, Variant::OP_DIVIDE, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_DIVIDE, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_MOD: {
- if (!_create_binary_operator(codegen, on, Variant::OP_MODULE, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_MODULE, p_stack_level))
+ return -1;
} break;
//case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_LEFT,p_stack_level)) return -1;} break;
//case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: { if (!_create_binary_operator(codegen,on,Variant::OP_SHIFT_RIGHT,p_stack_level)) return -1;} break;
case GDScriptParser::OperatorNode::OP_BIT_AND: {
- if (!_create_binary_operator(codegen, on, Variant::OP_BIT_AND, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_BIT_AND, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_BIT_OR: {
- if (!_create_binary_operator(codegen, on, Variant::OP_BIT_OR, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_BIT_OR, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_BIT_XOR: {
- if (!_create_binary_operator(codegen, on, Variant::OP_BIT_XOR, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_BIT_XOR, p_stack_level))
+ return -1;
} break;
//shift
case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: {
- if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_LEFT, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_LEFT, p_stack_level))
+ return -1;
} break;
case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: {
- if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_RIGHT, p_stack_level)) return -1;
+ if (!_create_binary_operator(codegen, on, Variant::OP_SHIFT_RIGHT, p_stack_level))
+ return -1;
} break;
//assignment operators
case GDScriptParser::OperatorNode::OP_ASSIGN_ADD:
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 34b066b5c7..08e1ec74af 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -123,10 +123,12 @@ class GDScriptCompiler {
Vector<int> opcodes;
void alloc_stack(int p_level) {
- if (p_level >= stack_max) stack_max = p_level + 1;
+ if (p_level >= stack_max)
+ stack_max = p_level + 1;
}
void alloc_call(int p_params) {
- if (p_params >= call_max) call_max = p_params;
+ if (p_params >= call_max)
+ call_max = p_params;
}
int current_line;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index ab3228d076..8af98c187f 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -1082,16 +1082,36 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
Variant::Operator vop = Variant::OP_MAX;
switch (op->op) {
- case GDScriptParser::OperatorNode::OP_ADD: vop = Variant::OP_ADD; break;
- case GDScriptParser::OperatorNode::OP_SUB: vop = Variant::OP_SUBTRACT; break;
- case GDScriptParser::OperatorNode::OP_MUL: vop = Variant::OP_MULTIPLY; break;
- case GDScriptParser::OperatorNode::OP_DIV: vop = Variant::OP_DIVIDE; break;
- case GDScriptParser::OperatorNode::OP_MOD: vop = Variant::OP_MODULE; break;
- case GDScriptParser::OperatorNode::OP_SHIFT_LEFT: vop = Variant::OP_SHIFT_LEFT; break;
- case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: vop = Variant::OP_SHIFT_RIGHT; break;
- case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break;
- case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break;
- case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break;
+ case GDScriptParser::OperatorNode::OP_ADD:
+ vop = Variant::OP_ADD;
+ break;
+ case GDScriptParser::OperatorNode::OP_SUB:
+ vop = Variant::OP_SUBTRACT;
+ break;
+ case GDScriptParser::OperatorNode::OP_MUL:
+ vop = Variant::OP_MULTIPLY;
+ break;
+ case GDScriptParser::OperatorNode::OP_DIV:
+ vop = Variant::OP_DIVIDE;
+ break;
+ case GDScriptParser::OperatorNode::OP_MOD:
+ vop = Variant::OP_MODULE;
+ break;
+ case GDScriptParser::OperatorNode::OP_SHIFT_LEFT:
+ vop = Variant::OP_SHIFT_LEFT;
+ break;
+ case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT:
+ vop = Variant::OP_SHIFT_RIGHT;
+ break;
+ case GDScriptParser::OperatorNode::OP_BIT_AND:
+ vop = Variant::OP_BIT_AND;
+ break;
+ case GDScriptParser::OperatorNode::OP_BIT_OR:
+ vop = Variant::OP_BIT_OR;
+ break;
+ case GDScriptParser::OperatorNode::OP_BIT_XOR:
+ vop = Variant::OP_BIT_XOR;
+ break;
default: {
}
}
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 44640411bb..df0fac956c 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -294,8 +294,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
line = p_state->line;
ip = p_state->ip;
alloca_size = p_state->stack.size();
- script = static_cast<GDScript *>(ObjectDB::get_instance(p_state->script_id));
- p_instance = p_state->instance_id.is_valid() ? static_cast<GDScriptInstance *>(ObjectDB::get_instance(p_state->instance_id)->get_script_instance()) : nullptr;
+ script = p_state->script;
+ p_instance = p_state->instance;
defarg = p_state->defarg;
self = p_state->self;
@@ -1281,11 +1281,21 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
gdfs->state.alloca_size = alloca_size;
gdfs->state.ip = ip + ipofs;
gdfs->state.line = line;
- gdfs->state.script_id = script->get_instance_id();
+ gdfs->state.script = _script;
+ {
+ MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+ _script->pending_func_states.add(&gdfs->scripts_list);
+ if (p_instance) {
+ gdfs->state.instance = p_instance;
+ p_instance->pending_func_states.add(&gdfs->instances_list);
+ } else {
+ gdfs->state.instance = NULL;
+ }
+ }
#ifdef DEBUG_ENABLED
+ gdfs->state.function_name = name;
gdfs->state.script_path = _script->get_path();
#endif
- gdfs->state.instance_id = (p_instance && p_instance->get_owner()) ? p_instance->get_owner()->get_instance_id() : ObjectID();
gdfs->state.defarg = defarg;
gdfs->function = this;
@@ -1833,12 +1843,14 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
return false;
if (p_extended_check) {
- // Class instance gone? (Otherwise script is valid for sure, because the instance has a ref to the script)
- if (state.instance_id.is_valid() && !ObjectDB::get_instance(state.instance_id)) {
+ MutexLock lock(GDScriptLanguage::get_singleton()->lock);
+
+ // Script gone?
+ if (!scripts_list.in_list()) {
return false;
}
- // Script gone? (Static method, so there's no instance whose ref to the script can ensure it's valid)
- if (!ObjectDB::get_instance(state.script_id)) {
+ // Class instance gone? (if not static function)
+ if (state.instance && !instances_list.in_list()) {
return false;
}
}
@@ -1849,19 +1861,26 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
Variant GDScriptFunctionState::resume(const Variant &p_arg) {
ERR_FAIL_COND_V(!function, Variant());
- if (state.instance_id.is_valid() && !ObjectDB::get_instance(state.instance_id)) {
+ {
+ MutexLock lock(GDScriptLanguage::singleton->lock);
+
+ if (!scripts_list.in_list()) {
#ifdef DEBUG_ENABLED
- ERR_FAIL_V_MSG(Variant(), "Resumed function '" + String(function->get_name()) + "()' after yield, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
+ ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
- return Variant();
+ return Variant();
#endif
- }
- if (!ObjectDB::get_instance(state.script_id)) {
+ }
+ if (state.instance && !instances_list.in_list()) {
#ifdef DEBUG_ENABLED
- ERR_FAIL_V_MSG(Variant(), "Resumed function '" + String(function->get_name()) + "()' after yield, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
+ ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after yield, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
- return Variant();
+ return Variant();
#endif
+ }
+ // Do these now to avoid locking again after the call
+ scripts_list.remove_from_list();
+ instances_list.remove_from_list();
}
state.result = p_arg;
@@ -1884,6 +1903,8 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
state.result = Variant();
if (completed) {
+ _clear_stack();
+
if (first_state.is_valid()) {
first_state->emit_signal("completed", ret);
} else {
@@ -1893,18 +1914,22 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
#ifdef DEBUG_ENABLED
if (EngineDebugger::is_active())
GDScriptLanguage::get_singleton()->exit_function();
- if (state.stack_size) {
- //free stack
- Variant *stack = (Variant *)state.stack.ptr();
- for (int i = 0; i < state.stack_size; i++)
- stack[i].~Variant();
- }
#endif
}
return ret;
}
+void GDScriptFunctionState::_clear_stack() {
+
+ if (state.stack_size) {
+ Variant *stack = (Variant *)state.stack.ptr();
+ for (int i = 0; i < state.stack_size; i++)
+ stack[i].~Variant();
+ state.stack_size = 0;
+ }
+}
+
void GDScriptFunctionState::_bind_methods() {
ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant()));
@@ -1914,18 +1939,20 @@ void GDScriptFunctionState::_bind_methods() {
ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
}
-GDScriptFunctionState::GDScriptFunctionState() {
+GDScriptFunctionState::GDScriptFunctionState() :
+ scripts_list(this),
+ instances_list(this) {
function = nullptr;
}
GDScriptFunctionState::~GDScriptFunctionState() {
- if (function != nullptr) {
- //never called, deinitialize stack
- for (int i = 0; i < state.stack_size; i++) {
- Variant *v = (Variant *)&state.stack[sizeof(Variant) * i];
- v->~Variant();
- }
+ _clear_stack();
+
+ {
+ MutexLock lock(GDScriptLanguage::singleton->lock);
+ scripts_list.remove_from_list();
+ instances_list.remove_from_list();
}
}
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 9d8e23d994..89dbeacf34 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -56,7 +56,8 @@ struct GDScriptDataType {
Ref<Script> script_type;
bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const {
- if (!has_type) return true; // Can't type check
+ if (!has_type)
+ return true; // Can't type check
switch (kind) {
case UNINITIALIZED:
@@ -293,11 +294,12 @@ private:
public:
struct CallState {
- ObjectID script_id;
+ GDScript *script;
+ GDScriptInstance *instance;
#ifdef DEBUG_ENABLED
+ StringName function_name;
String script_path;
#endif
- ObjectID instance_id;
Vector<uint8_t> stack;
int stack_size;
Variant self;
@@ -357,12 +359,18 @@ class GDScriptFunctionState : public Reference {
Variant _signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Ref<GDScriptFunctionState> first_state;
+ SelfList<GDScriptFunctionState> scripts_list;
+ SelfList<GDScriptFunctionState> instances_list;
+
protected:
static void _bind_methods();
public:
bool is_valid(bool p_extended_check = false) const;
Variant resume(const Variant &p_arg = Variant());
+
+ void _clear_stack();
+
GDScriptFunctionState();
~GDScriptFunctionState();
};
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index 0199af642f..85a5d86ca0 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -1199,6 +1199,11 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
}
r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
+ if (r_error.error != Callable::CallError::CALL_OK) {
+ r_ret = Variant();
+ return;
+ }
+
GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(r_ret)->get_script_instance());
Ref<GDScript> gd_ref = ins->get_script();
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 17077567c7..b03d041aaf 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -72,6 +72,16 @@ bool GDScriptParser::_end_statement() {
return false;
}
+void GDScriptParser::_set_end_statement_error(String p_name) {
+ String error_msg;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER) {
+ error_msg = vformat("Expected end of statement (\"%s\"), got %s (\"%s\") instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()), tokenizer->get_token_identifier());
+ } else {
+ error_msg = vformat("Expected end of statement (\"%s\"), got %s instead.", p_name, tokenizer->get_token_name(tokenizer->get_token()));
+ }
+ _set_error(error_msg);
+}
+
bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_COLON) {
@@ -910,10 +920,18 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
e.is_op = true;
switch (tokenizer->get_token()) {
- case GDScriptTokenizer::TK_OP_ADD: e.op = OperatorNode::OP_POS; break;
- case GDScriptTokenizer::TK_OP_SUB: e.op = OperatorNode::OP_NEG; break;
- case GDScriptTokenizer::TK_OP_NOT: e.op = OperatorNode::OP_NOT; break;
- case GDScriptTokenizer::TK_OP_BIT_INVERT: e.op = OperatorNode::OP_BIT_INVERT; break;
+ case GDScriptTokenizer::TK_OP_ADD:
+ e.op = OperatorNode::OP_POS;
+ break;
+ case GDScriptTokenizer::TK_OP_SUB:
+ e.op = OperatorNode::OP_NEG;
+ break;
+ case GDScriptTokenizer::TK_OP_NOT:
+ e.op = OperatorNode::OP_NOT;
+ break;
+ case GDScriptTokenizer::TK_OP_BIT_INVERT:
+ e.op = OperatorNode::OP_BIT_INVERT;
+ break;
default: {
}
}
@@ -1329,25 +1347,55 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
switch (tokenizer->get_token()) { //see operator
- case GDScriptTokenizer::TK_OP_IN: op = OperatorNode::OP_IN; break;
- case GDScriptTokenizer::TK_OP_EQUAL: op = OperatorNode::OP_EQUAL; break;
- case GDScriptTokenizer::TK_OP_NOT_EQUAL: op = OperatorNode::OP_NOT_EQUAL; break;
- case GDScriptTokenizer::TK_OP_LESS: op = OperatorNode::OP_LESS; break;
- case GDScriptTokenizer::TK_OP_LESS_EQUAL: op = OperatorNode::OP_LESS_EQUAL; break;
- case GDScriptTokenizer::TK_OP_GREATER: op = OperatorNode::OP_GREATER; break;
- case GDScriptTokenizer::TK_OP_GREATER_EQUAL: op = OperatorNode::OP_GREATER_EQUAL; break;
- case GDScriptTokenizer::TK_OP_AND: op = OperatorNode::OP_AND; break;
- case GDScriptTokenizer::TK_OP_OR: op = OperatorNode::OP_OR; break;
- case GDScriptTokenizer::TK_OP_ADD: op = OperatorNode::OP_ADD; break;
- case GDScriptTokenizer::TK_OP_SUB: op = OperatorNode::OP_SUB; break;
- case GDScriptTokenizer::TK_OP_MUL: op = OperatorNode::OP_MUL; break;
- case GDScriptTokenizer::TK_OP_DIV: op = OperatorNode::OP_DIV; break;
+ case GDScriptTokenizer::TK_OP_IN:
+ op = OperatorNode::OP_IN;
+ break;
+ case GDScriptTokenizer::TK_OP_EQUAL:
+ op = OperatorNode::OP_EQUAL;
+ break;
+ case GDScriptTokenizer::TK_OP_NOT_EQUAL:
+ op = OperatorNode::OP_NOT_EQUAL;
+ break;
+ case GDScriptTokenizer::TK_OP_LESS:
+ op = OperatorNode::OP_LESS;
+ break;
+ case GDScriptTokenizer::TK_OP_LESS_EQUAL:
+ op = OperatorNode::OP_LESS_EQUAL;
+ break;
+ case GDScriptTokenizer::TK_OP_GREATER:
+ op = OperatorNode::OP_GREATER;
+ break;
+ case GDScriptTokenizer::TK_OP_GREATER_EQUAL:
+ op = OperatorNode::OP_GREATER_EQUAL;
+ break;
+ case GDScriptTokenizer::TK_OP_AND:
+ op = OperatorNode::OP_AND;
+ break;
+ case GDScriptTokenizer::TK_OP_OR:
+ op = OperatorNode::OP_OR;
+ break;
+ case GDScriptTokenizer::TK_OP_ADD:
+ op = OperatorNode::OP_ADD;
+ break;
+ case GDScriptTokenizer::TK_OP_SUB:
+ op = OperatorNode::OP_SUB;
+ break;
+ case GDScriptTokenizer::TK_OP_MUL:
+ op = OperatorNode::OP_MUL;
+ break;
+ case GDScriptTokenizer::TK_OP_DIV:
+ op = OperatorNode::OP_DIV;
+ break;
case GDScriptTokenizer::TK_OP_MOD:
op = OperatorNode::OP_MOD;
break;
//case GDScriptTokenizer::TK_OP_NEG: op=OperatorNode::OP_NEG ; break;
- case GDScriptTokenizer::TK_OP_SHIFT_LEFT: op = OperatorNode::OP_SHIFT_LEFT; break;
- case GDScriptTokenizer::TK_OP_SHIFT_RIGHT: op = OperatorNode::OP_SHIFT_RIGHT; break;
+ case GDScriptTokenizer::TK_OP_SHIFT_LEFT:
+ op = OperatorNode::OP_SHIFT_LEFT;
+ break;
+ case GDScriptTokenizer::TK_OP_SHIFT_RIGHT:
+ op = OperatorNode::OP_SHIFT_RIGHT;
+ break;
case GDScriptTokenizer::TK_OP_ASSIGN: {
_VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN;
@@ -1364,23 +1412,57 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
} break;
- case GDScriptTokenizer::TK_OP_ASSIGN_ADD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_SUB: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_MUL: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_DIV: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_MOD: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR; break;
- case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR; break;
- case GDScriptTokenizer::TK_OP_BIT_AND: op = OperatorNode::OP_BIT_AND; break;
- case GDScriptTokenizer::TK_OP_BIT_OR: op = OperatorNode::OP_BIT_OR; break;
- case GDScriptTokenizer::TK_OP_BIT_XOR: op = OperatorNode::OP_BIT_XOR; break;
- case GDScriptTokenizer::TK_PR_IS: op = OperatorNode::OP_IS; break;
- case GDScriptTokenizer::TK_CF_IF: op = OperatorNode::OP_TERNARY_IF; break;
- case GDScriptTokenizer::TK_CF_ELSE: op = OperatorNode::OP_TERNARY_ELSE; break;
- default: valid = false; break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_ADD;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SUB;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MUL;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_DIV;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_MOD;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_LEFT;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_SHIFT_RIGHT;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_AND;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_OR;
+ break;
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
+ _VALIDATE_ASSIGN op = OperatorNode::OP_ASSIGN_BIT_XOR;
+ break;
+ case GDScriptTokenizer::TK_OP_BIT_AND:
+ op = OperatorNode::OP_BIT_AND;
+ break;
+ case GDScriptTokenizer::TK_OP_BIT_OR:
+ op = OperatorNode::OP_BIT_OR;
+ break;
+ case GDScriptTokenizer::TK_OP_BIT_XOR:
+ op = OperatorNode::OP_BIT_XOR;
+ break;
+ case GDScriptTokenizer::TK_PR_IS:
+ op = OperatorNode::OP_IS;
+ break;
+ case GDScriptTokenizer::TK_CF_IF:
+ op = OperatorNode::OP_TERNARY_IF;
+ break;
+ case GDScriptTokenizer::TK_CF_ELSE:
+ op = OperatorNode::OP_TERNARY_ELSE;
+ break;
+ default:
+ valid = false;
+ break;
}
if (valid) {
@@ -1433,36 +1515,74 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
unary = true;
break;
- case OperatorNode::OP_MUL: priority = 2; break;
- case OperatorNode::OP_DIV: priority = 2; break;
- case OperatorNode::OP_MOD: priority = 2; break;
+ case OperatorNode::OP_MUL:
+ priority = 2;
+ break;
+ case OperatorNode::OP_DIV:
+ priority = 2;
+ break;
+ case OperatorNode::OP_MOD:
+ priority = 2;
+ break;
- case OperatorNode::OP_ADD: priority = 3; break;
- case OperatorNode::OP_SUB: priority = 3; break;
+ case OperatorNode::OP_ADD:
+ priority = 3;
+ break;
+ case OperatorNode::OP_SUB:
+ priority = 3;
+ break;
- case OperatorNode::OP_SHIFT_LEFT: priority = 4; break;
- case OperatorNode::OP_SHIFT_RIGHT: priority = 4; break;
+ case OperatorNode::OP_SHIFT_LEFT:
+ priority = 4;
+ break;
+ case OperatorNode::OP_SHIFT_RIGHT:
+ priority = 4;
+ break;
- case OperatorNode::OP_BIT_AND: priority = 5; break;
- case OperatorNode::OP_BIT_XOR: priority = 6; break;
- case OperatorNode::OP_BIT_OR: priority = 7; break;
+ case OperatorNode::OP_BIT_AND:
+ priority = 5;
+ break;
+ case OperatorNode::OP_BIT_XOR:
+ priority = 6;
+ break;
+ case OperatorNode::OP_BIT_OR:
+ priority = 7;
+ break;
- case OperatorNode::OP_LESS: priority = 8; break;
- case OperatorNode::OP_LESS_EQUAL: priority = 8; break;
- case OperatorNode::OP_GREATER: priority = 8; break;
- case OperatorNode::OP_GREATER_EQUAL: priority = 8; break;
+ case OperatorNode::OP_LESS:
+ priority = 8;
+ break;
+ case OperatorNode::OP_LESS_EQUAL:
+ priority = 8;
+ break;
+ case OperatorNode::OP_GREATER:
+ priority = 8;
+ break;
+ case OperatorNode::OP_GREATER_EQUAL:
+ priority = 8;
+ break;
- case OperatorNode::OP_EQUAL: priority = 8; break;
- case OperatorNode::OP_NOT_EQUAL: priority = 8; break;
+ case OperatorNode::OP_EQUAL:
+ priority = 8;
+ break;
+ case OperatorNode::OP_NOT_EQUAL:
+ priority = 8;
+ break;
- case OperatorNode::OP_IN: priority = 10; break;
+ case OperatorNode::OP_IN:
+ priority = 10;
+ break;
case OperatorNode::OP_NOT:
priority = 11;
unary = true;
break;
- case OperatorNode::OP_AND: priority = 12; break;
- case OperatorNode::OP_OR: priority = 13; break;
+ case OperatorNode::OP_AND:
+ priority = 12;
+ break;
+ case OperatorNode::OP_OR:
+ priority = 13;
+ break;
case OperatorNode::OP_TERNARY_IF:
priority = 14;
@@ -1475,17 +1595,39 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
// Rigth-to-left should be false in this case, otherwise it would always error.
break;
- case OperatorNode::OP_ASSIGN: priority = 15; break;
- case OperatorNode::OP_ASSIGN_ADD: priority = 15; break;
- case OperatorNode::OP_ASSIGN_SUB: priority = 15; break;
- case OperatorNode::OP_ASSIGN_MUL: priority = 15; break;
- case OperatorNode::OP_ASSIGN_DIV: priority = 15; break;
- case OperatorNode::OP_ASSIGN_MOD: priority = 15; break;
- case OperatorNode::OP_ASSIGN_SHIFT_LEFT: priority = 15; break;
- case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: priority = 15; break;
- case OperatorNode::OP_ASSIGN_BIT_AND: priority = 15; break;
- case OperatorNode::OP_ASSIGN_BIT_OR: priority = 15; break;
- case OperatorNode::OP_ASSIGN_BIT_XOR: priority = 15; break;
+ case OperatorNode::OP_ASSIGN:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_ADD:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_SUB:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_MUL:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_DIV:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_MOD:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_BIT_AND:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_BIT_OR:
+ priority = 15;
+ break;
+ case OperatorNode::OP_ASSIGN_BIT_XOR:
+ priority = 15;
+ break;
default: {
_set_error("GDScriptParser bug, invalid operator in expression: " + itos(expression[i].op));
@@ -2037,7 +2179,8 @@ bool GDScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) {
if (p_value.get_type() == Variant::ARRAY) {
Array arr = p_value;
for (int i = 0; i < arr.size(); i++) {
- if (!_reduce_export_var_type(arr[i], p_line)) return false;
+ if (!_reduce_export_var_type(arr[i], p_line))
+ return false;
}
return true;
}
@@ -2046,7 +2189,8 @@ bool GDScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) {
Dictionary dict = p_value;
for (int i = 0; i < dict.size(); i++) {
Variant value = dict.get_value_at_index(i);
- if (!_reduce_export_var_type(value, p_line)) return false;
+ if (!_reduce_export_var_type(value, p_line))
+ return false;
}
return true;
}
@@ -2734,6 +2878,7 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
IdentifierNode *id2 = alloc_node<IdentifierNode>();
id2->name = local_var->name;
+ id2->datatype = local_var->datatype;
id2->declared_block = branch->body;
id2->set_datatype(local_var->assign->get_datatype());
@@ -2937,7 +3082,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
lv->assign = assigned;
if (!_end_statement()) {
- _set_error("Expected end of statement (\"var\").");
+ _set_end_statement_error("var");
return;
}
@@ -3171,9 +3316,15 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
ConstantNode *cn = alloc_node<ConstantNode>();
switch (args.size()) {
- case 1: cn->value = (int)constants[0]; break;
- case 2: cn->value = Vector2(constants[0], constants[1]); break;
- case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break;
+ case 1:
+ cn->value = (int64_t)constants[0];
+ break;
+ case 2:
+ cn->value = Vector2i(constants[0], constants[1]);
+ break;
+ case 3:
+ cn->value = Vector3i(constants[0], constants[1], constants[2]);
+ break;
}
cn->datatype = _type_from_variant(cn->value);
container = cn;
@@ -3185,9 +3336,15 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
on->arguments.push_back(tn);
switch (args.size()) {
- case 1: tn->vtype = Variant::INT; break;
- case 2: tn->vtype = Variant::VECTOR2; break;
- case 3: tn->vtype = Variant::VECTOR3; break;
+ case 1:
+ tn->vtype = Variant::INT;
+ break;
+ case 2:
+ tn->vtype = Variant::VECTOR2I;
+ break;
+ case 3:
+ tn->vtype = Variant::VECTOR3I;
+ break;
}
for (int i = 0; i < args.size(); i++) {
@@ -3248,7 +3405,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
cf_continue->cf_type = ControlFlowNode::CF_CONTINUE;
p_block->statements.push_back(cf_continue);
if (!_end_statement()) {
- _set_error("Expected end of statement (\"continue\").");
+ _set_end_statement_error("continue");
return;
}
} break;
@@ -3260,7 +3417,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
cf_break->cf_type = ControlFlowNode::CF_BREAK;
p_block->statements.push_back(cf_break);
if (!_end_statement()) {
- _set_error("Expected end of statement (\"break\").");
+ _set_end_statement_error("break");
return;
}
} break;
@@ -3289,7 +3446,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
cf_return->arguments.push_back(retexpr);
p_block->statements.push_back(cf_return);
if (!_end_statement()) {
- _set_error("Expected end of statement after return expression.");
+ _set_end_statement_error("return");
return;
}
}
@@ -3326,7 +3483,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
_parse_pattern_block(compiled_branches, match_node->branches, p_static);
- if (error_set) return;
+ if (error_set)
+ return;
ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>();
match_cf_node->cf_type = ControlFlowNode::CF_MATCH;
@@ -3378,7 +3536,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->statements.push_back(an);
if (!_end_statement()) {
- _set_error("Expected end of statement after \"assert\".", assert_line);
+ _set_end_statement_error("assert");
return;
}
} break;
@@ -3389,7 +3547,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->statements.push_back(bn);
if (!_end_statement()) {
- _set_error("Expected end of statement after \"breakpoint\".");
+ _set_end_statement_error("breakpoint");
return;
}
} break;
@@ -3408,7 +3566,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
_set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression.");
} else {
- _set_error(String() + "Expected end of statement after expression, got " + tokenizer->get_token_name(tokenizer->get_token()) + " instead");
+ _set_error(vformat("Expected end of statement after expression, got %s instead.", tokenizer->get_token_name(tokenizer->get_token())));
}
return;
}
@@ -3598,7 +3756,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (error_set)
return;
if (!_end_statement()) {
- _set_error("Expected end of statement after \"extends\".");
+ _set_end_statement_error("extends");
return;
}
@@ -4103,7 +4261,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
p_class->_signals.push_back(sig);
if (!_end_statement()) {
- _set_error("Expected end of statement (\"signal\").");
+ _set_end_statement_error("signal");
return;
}
} break;
@@ -4923,7 +5081,8 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
return;
}
- if (!_reduce_export_var_type(cn->value, member.line)) return;
+ if (!_reduce_export_var_type(cn->value, member.line))
+ return;
member._export.type = cn->value.get_type();
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
@@ -4960,6 +5119,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = member.identifier;
+ id->datatype = member.data_type;
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_INIT_ASSIGN;
@@ -5002,6 +5162,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = member.identifier;
+ id->datatype = member.data_type;
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_INIT_ASSIGN;
@@ -5044,7 +5205,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
p_class->variables.push_back(member);
if (!_end_statement()) {
- _set_error("Expected end of statement (\"continue\").");
+ _set_end_statement_error("var");
return;
}
} break;
@@ -5124,7 +5285,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
p_class->constant_expressions.insert(const_id, constant);
if (!_end_statement()) {
- _set_error("Expected end of statement (constant).", line);
+ _set_end_statement_error("const");
return;
}
@@ -5278,7 +5439,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (!_end_statement()) {
- _set_error("Expected end of statement (\"enum\").");
+ _set_end_statement_error("enum");
return;
}
@@ -5438,8 +5599,10 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive
}
}
- if (base_class) break;
- if (found) continue;
+ if (base_class)
+ break;
+ if (found)
+ continue;
if (p->constant_expressions.has(base)) {
if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) {
@@ -5541,10 +5704,12 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive
}
String GDScriptParser::DataType::to_string() const {
- if (!has_type) return "var";
+ if (!has_type)
+ return "var";
switch (kind) {
case BUILTIN: {
- if (builtin_type == Variant::NIL) return "null";
+ if (builtin_type == Variant::NIL)
+ return "null";
return Variant::get_type_name(builtin_type);
} break;
case NATIVE: {
@@ -5708,8 +5873,10 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
}
GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) {
- if (!p_source.has_type) return p_source;
- if (p_source.kind != DataType::UNRESOLVED) return p_source;
+ if (!p_source.has_type)
+ return p_source;
+ if (p_source.kind != DataType::UNRESOLVED)
+ return p_source;
Vector<String> full_name = p_source.native_type.operator String().split(".", false);
int name_part = 0;
@@ -6647,6 +6814,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = cn->value.operator StringName();
+ id->datatype = cn->datatype;
op->op = OperatorNode::OP_INDEX_NAMED;
op->arguments.write[1] = id;
@@ -6948,7 +7116,8 @@ bool GDScriptParser::_get_function_signature(DataType &p_base_type, const String
native = "_" + native.operator String();
}
if (!ClassDB::class_exists(native)) {
- if (!check_types) return false;
+ if (!check_types)
+ return false;
ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found.");
}
@@ -7039,7 +7208,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]);
}
- if (error_set) return DataType();
+ if (error_set)
+ return DataType();
// Special case: check copy constructor. Those are defined implicitly in Variant.
if (par_types.size() == 1) {
@@ -7107,7 +7277,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
err += "' matches the signature '";
err += Variant::get_type_name(tn->vtype) + "(";
for (int i = 0; i < par_types.size(); i++) {
- if (i > 0) err += ", ";
+ if (i > 0)
+ err += ", ";
err += par_types[i].to_string();
}
err += ")'.";
@@ -7465,7 +7636,8 @@ bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringN
native = "_" + native.operator String();
}
if (!ClassDB::class_exists(native)) {
- if (!check_types) return false;
+ if (!check_types)
+ return false;
ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found.");
}
@@ -7571,6 +7743,10 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
}
if (_get_member_type(base_type, p_identifier, member_type)) {
+ if (!p_base_type && current_function && current_function->_static) {
+ _set_error("Can't access member variable (\"" + p_identifier.operator String() + "\") from a static function.", p_line);
+ return DataType();
+ }
return member_type;
}
@@ -7758,12 +7934,14 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
// Function declarations
for (int i = 0; i < p_class->static_functions.size(); i++) {
_check_function_types(p_class->static_functions[i]);
- if (error_set) return;
+ if (error_set)
+ return;
}
for (int i = 0; i < p_class->functions.size(); i++) {
_check_function_types(p_class->functions[i]);
- if (error_set) return;
+ if (error_set)
+ return;
}
// Class variables
@@ -7802,7 +7980,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
ConstantNode *tgt_type = alloc_node<ConstantNode>();
tgt_type->line = v.line;
- tgt_type->value = (int)v.data_type.builtin_type;
+ tgt_type->value = (int64_t)v.data_type.builtin_type;
OperatorNode *convert_call = alloc_node<OperatorNode>();
convert_call->line = v.line;
@@ -7838,7 +8016,8 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
}
// Setter and getter
- if (v.setter == StringName() && v.getter == StringName()) continue;
+ if (v.setter == StringName() && v.getter == StringName())
+ continue;
bool found_getter = false;
bool found_setter = false;
@@ -7881,10 +8060,12 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
return;
}
}
- if (found_getter && found_setter) break;
+ if (found_getter && found_setter)
+ break;
}
- if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue;
+ if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName()))
+ continue;
// Check for static functions
for (int j = 0; j < p_class->static_functions.size(); j++) {
@@ -7915,7 +8096,8 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
for (int i = 0; i < p_class->subclasses.size(); i++) {
current_class = p_class->subclasses[i];
_check_class_level_types(current_class);
- if (error_set) return;
+ if (error_set)
+ return;
current_class = p_class;
}
}
@@ -8063,7 +8245,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
_check_block_types(current_block);
current_block = nullptr;
current_function = nullptr;
- if (error_set) return;
+ if (error_set)
+ return;
}
for (int i = 0; i < p_class->functions.size(); i++) {
@@ -8073,7 +8256,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
_check_block_types(current_block);
current_block = nullptr;
current_function = nullptr;
- if (error_set) return;
+ if (error_set)
+ return;
}
#ifdef DEBUG_ENABLED
@@ -8094,7 +8278,8 @@ void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
for (int i = 0; i < p_class->subclasses.size(); i++) {
current_class = p_class->subclasses[i];
_check_class_blocks_types(current_class);
- if (error_set) return;
+ if (error_set)
+ return;
current_class = p_class;
}
}
@@ -8179,7 +8364,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
ConstantNode *tgt_type = alloc_node<ConstantNode>();
tgt_type->line = lv->line;
- tgt_type->value = (int)lv->datatype.builtin_type;
+ tgt_type->value = (int64_t)lv->datatype.builtin_type;
tgt_type->datatype = _type_from_variant(tgt_type->value);
OperatorNode *convert_call = alloc_node<OperatorNode>();
@@ -8357,7 +8542,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
_add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name);
}
#endif // DEBUG_ENABLED
- if (error_set) return;
+ if (error_set)
+ return;
} break;
case OperatorNode::OP_YIELD: {
_mark_line_as_safe(op->line);
@@ -8392,7 +8578,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
}
}
- if (!function_type.has_type) break;
+ if (!function_type.has_type)
+ break;
if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
// Return void, should not have arguments
@@ -8452,7 +8639,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
current_block = p_block->sub_blocks[i];
_check_block_types(current_block);
current_block = p_block;
- if (error_set) return;
+ if (error_set)
+ return;
}
#ifdef DEBUG_ENABLED
@@ -8595,7 +8783,8 @@ Error GDScriptParser::_parse(const String &p_base_path) {
current_function = nullptr;
current_block = nullptr;
- if (for_completion) check_types = false;
+ if (for_completion)
+ check_types = false;
// Resolve all class-level stuff before getting into function blocks
_check_class_level_types(main_class);
@@ -8784,7 +8973,7 @@ int GDScriptParser::get_completion_argument_index() {
return completion_argument;
}
-int GDScriptParser::get_completion_identifier_is_function() {
+bool GDScriptParser::get_completion_identifier_is_function() {
return completion_ident_is_call;
}
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index f254352423..a33417e536 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -624,6 +624,7 @@ private:
void _parse_extends(ClassNode *p_class);
void _parse_class(ClassNode *p_class);
bool _end_statement();
+ void _set_end_statement_error(String p_name);
void _determine_inheritance(ClassNode *p_class, bool p_recursive = true);
bool _parse_type(DataType &r_type, bool p_can_be_void = false);
@@ -647,12 +648,14 @@ private:
void _check_block_types(BlockNode *p_block);
_FORCE_INLINE_ void _mark_line_as_safe(int p_line) const {
#ifdef DEBUG_ENABLED
- if (safe_lines) safe_lines->insert(p_line);
+ if (safe_lines)
+ safe_lines->insert(p_line);
#endif // DEBUG_ENABLED
}
_FORCE_INLINE_ void _mark_line_as_unsafe(int p_line) const {
#ifdef DEBUG_ENABLED
- if (safe_lines) safe_lines->erase(p_line);
+ if (safe_lines)
+ safe_lines->erase(p_line);
#endif // DEBUG_ENABLED
}
@@ -683,7 +686,7 @@ public:
BlockNode *get_completion_block();
FunctionNode *get_completion_function();
int get_completion_argument_index();
- int get_completion_identifier_is_function();
+ bool get_completion_identifier_is_function();
const List<String> &get_dependencies() const { return dependencies; }
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index d42ca52731..1c8282e13e 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -803,19 +803,36 @@ void GDScriptTokenizerText::_advance() {
switch (next) {
- case 'a': res = 7; break;
- case 'b': res = 8; break;
- case 't': res = 9; break;
- case 'n': res = 10; break;
- case 'v': res = 11; break;
- case 'f': res = 12; break;
- case 'r': res = 13; break;
- case '\'': res = '\''; break;
- case '\"': res = '\"'; break;
- case '\\': res = '\\'; break;
- case '/':
- res = '/';
- break; //wtf
+ case 'a':
+ res = '\a';
+ break;
+ case 'b':
+ res = '\b';
+ break;
+ case 't':
+ res = '\t';
+ break;
+ case 'n':
+ res = '\n';
+ break;
+ case 'v':
+ res = '\v';
+ break;
+ case 'f':
+ res = '\f';
+ break;
+ case 'r':
+ res = '\r';
+ break;
+ case '\'':
+ res = '\'';
+ break;
+ case '\"':
+ res = '\"';
+ break;
+ case '\\':
+ res = '\\';
+ break;
case 'u': {
// hex number
@@ -847,6 +864,10 @@ void GDScriptTokenizerText::_advance() {
i += 3;
} break;
+ case '\n': {
+ line++;
+ column = 1;
+ } break;
default: {
_make_error("Invalid escape sequence");
@@ -854,7 +875,8 @@ void GDScriptTokenizerText::_advance() {
} break;
}
- str += res;
+ if (next != '\n')
+ str += res;
} else {
if (CharType(GETCHAR(i)) == '\n') {
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index b2c6b0e1ab..a6b749059a 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -385,7 +385,8 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
int start_line = p_docs_down ? p_line : p_line - 1;
for (int i = start_line; true; i += step) {
- if (i < 0 || i >= lines.size()) break;
+ if (i < 0 || i >= lines.size())
+ break;
String line_comment = lines[i].strip_edges(true, false);
if (line_comment.begins_with("#")) {
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 32fc8f36f0..be036b44c4 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -185,7 +185,8 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) {
}
Error GDScriptWorkspace::initialize() {
- if (initialized) return OK;
+ if (initialized)
+ return OK;
DocData *doc = EditorHelp::get_doc_data();
for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) {
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
index 124fcbfed8..e469a26df8 100644
--- a/modules/gdscript/language_server/lsp.hpp
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -282,7 +282,8 @@ struct Command {
Dictionary dict;
dict["title"] = title;
dict["command"] = command;
- if (arguments.size()) dict["arguments"] = arguments;
+ if (arguments.size())
+ dict["arguments"] = arguments;
return dict;
}
};
@@ -946,16 +947,20 @@ struct CompletionItem {
dict["preselect"] = preselect;
dict["sortText"] = sortText;
dict["filterText"] = filterText;
- if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters;
+ if (commitCharacters.size())
+ dict["commitCharacters"] = commitCharacters;
dict["command"] = command.to_json();
}
return dict;
}
void load(const Dictionary &p_dict) {
- if (p_dict.has("label")) label = p_dict["label"];
- if (p_dict.has("kind")) kind = p_dict["kind"];
- if (p_dict.has("detail")) detail = p_dict["detail"];
+ if (p_dict.has("label"))
+ label = p_dict["label"];
+ if (p_dict.has("kind"))
+ kind = p_dict["kind"];
+ if (p_dict.has("detail"))
+ detail = p_dict["detail"];
if (p_dict.has("documentation")) {
Variant doc = p_dict["documentation"];
if (doc.get_type() == Variant::STRING) {
@@ -965,12 +970,18 @@ struct CompletionItem {
documentation.value = v["value"];
}
}
- if (p_dict.has("deprecated")) deprecated = p_dict["deprecated"];
- if (p_dict.has("preselect")) preselect = p_dict["preselect"];
- if (p_dict.has("sortText")) sortText = p_dict["sortText"];
- if (p_dict.has("filterText")) filterText = p_dict["filterText"];
- if (p_dict.has("insertText")) insertText = p_dict["insertText"];
- if (p_dict.has("data")) data = p_dict["data"];
+ if (p_dict.has("deprecated"))
+ deprecated = p_dict["deprecated"];
+ if (p_dict.has("preselect"))
+ preselect = p_dict["preselect"];
+ if (p_dict.has("sortText"))
+ sortText = p_dict["sortText"];
+ if (p_dict.has("filterText"))
+ filterText = p_dict["filterText"];
+ if (p_dict.has("insertText"))
+ insertText = p_dict["insertText"];
+ if (p_dict.has("data"))
+ data = p_dict["data"];
}
};
diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp
index 393269d422..208ce24f3d 100644
--- a/modules/jsonrpc/jsonrpc.cpp
+++ b/modules/jsonrpc/jsonrpc.cpp
@@ -148,7 +148,8 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem
String JSONRPC::process_string(const String &p_input) {
- if (p_input.empty()) return String();
+ if (p_input.empty())
+ return String();
Variant ret;
Variant input;
diff --git a/modules/lightmapper_rd/SCsub b/modules/lightmapper_rd/SCsub
new file mode 100644
index 0000000000..2f04f1833e
--- /dev/null
+++ b/modules/lightmapper_rd/SCsub
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_lightmapper_rd = env_modules.Clone()
+env_lightmapper_rd.GLSL_HEADER("lm_raster.glsl")
+env_lightmapper_rd.GLSL_HEADER("lm_compute.glsl")
+env_lightmapper_rd.GLSL_HEADER("lm_blendseams.glsl")
+
+# Godot source files
+env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/lightmapper_rd/config.py b/modules/lightmapper_rd/config.py
new file mode 100644
index 0000000000..d22f9454ed
--- /dev/null
+++ b/modules/lightmapper_rd/config.py
@@ -0,0 +1,6 @@
+def can_build(env, platform):
+ return True
+
+
+def configure(env):
+ pass
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
new file mode 100644
index 0000000000..6983c222c0
--- /dev/null
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -0,0 +1,1754 @@
+#include "lightmapper_rd.h"
+#include "core/math/geometry.h"
+#include "core/project_settings.h"
+#include "lm_blendseams.glsl.gen.h"
+#include "lm_compute.glsl.gen.h"
+#include "lm_raster.glsl.gen.h"
+#include "servers/rendering/rendering_device_binds.h"
+
+//uncomment this if you want to see textures from all the process saved
+//#define DEBUG_TEXTURES
+
+void LightmapperRD::add_mesh(const MeshData &p_mesh) {
+ ERR_FAIL_COND(p_mesh.albedo_on_uv2.is_null() || p_mesh.albedo_on_uv2->empty());
+ ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->empty());
+ ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_width() != p_mesh.emission_on_uv2->get_width());
+ ERR_FAIL_COND(p_mesh.albedo_on_uv2->get_height() != p_mesh.emission_on_uv2->get_height());
+ ERR_FAIL_COND(p_mesh.points.size() == 0);
+ MeshInstance mi;
+ mi.data = p_mesh;
+ mesh_instances.push_back(mi);
+}
+
+void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance) {
+ Light l;
+ l.type = LIGHT_TYPE_DIRECTIONAL;
+ l.direction[0] = p_direction.x;
+ l.direction[1] = p_direction.y;
+ l.direction[2] = p_direction.z;
+ l.color[0] = p_color.r;
+ l.color[1] = p_color.g;
+ l.color[2] = p_color.b;
+ l.energy = p_energy;
+ l.static_bake = p_static;
+ l.size = p_angular_distance;
+ lights.push_back(l);
+}
+void LightmapperRD::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) {
+ Light l;
+ l.type = LIGHT_TYPE_OMNI;
+ l.position[0] = p_position.x;
+ l.position[1] = p_position.y;
+ l.position[2] = p_position.z;
+ l.range = p_range;
+ l.attenuation = p_attenuation;
+ l.color[0] = p_color.r;
+ l.color[1] = p_color.g;
+ l.color[2] = p_color.b;
+ l.energy = p_energy;
+ l.static_bake = p_static;
+ l.size = p_size;
+ lights.push_back(l);
+}
+void LightmapperRD::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) {
+
+ Light l;
+ l.type = LIGHT_TYPE_SPOT;
+ l.position[0] = p_position.x;
+ l.position[1] = p_position.y;
+ l.position[2] = p_position.z;
+ l.direction[0] = p_direction.x;
+ l.direction[1] = p_direction.y;
+ l.direction[2] = p_direction.z;
+ l.range = p_range;
+ l.attenuation = p_attenuation;
+ l.spot_angle = Math::deg2rad(p_spot_angle);
+ l.spot_attenuation = p_spot_attenuation;
+ l.color[0] = p_color.r;
+ l.color[1] = p_color.g;
+ l.color[2] = p_color.b;
+ l.energy = p_energy;
+ l.static_bake = p_static;
+ l.size = p_size;
+ lights.push_back(l);
+}
+
+void LightmapperRD::add_probe(const Vector3 &p_position) {
+ Probe probe;
+ probe.position[0] = p_position.x;
+ probe.position[1] = p_position.y;
+ probe.position[2] = p_position.z;
+ probe.position[3] = 0;
+ probe_positions.push_back(probe);
+}
+
+void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size) {
+
+ int half_size = p_size / 2;
+
+ for (int i = 0; i < 8; i++) {
+
+ AABB aabb = p_bounds;
+ aabb.size *= 0.5;
+ Vector3i n = p_ofs;
+
+ if (i & 1) {
+ aabb.position.x += aabb.size.x;
+ n.x += half_size;
+ }
+ if (i & 2) {
+ aabb.position.y += aabb.size.y;
+ n.y += half_size;
+ }
+ if (i & 4) {
+ aabb.position.z += aabb.size.z;
+ n.z += half_size;
+ }
+
+ {
+ Vector3 qsize = aabb.size * 0.5; //quarter size, for fast aabb test
+
+ if (!Geometry::triangle_box_overlap(aabb.position + qsize, qsize, p_points)) {
+ //does not fit in child, go on
+ continue;
+ }
+ }
+
+ if (half_size == 1) {
+ //got to the end
+ TriangleSort ts;
+ ts.cell_index = n.x + (n.y * p_grid_size) + (n.z * p_grid_size * p_grid_size);
+ ts.triangle_index = p_triangle_index;
+ triangles.push_back(ts);
+ } else {
+ _plot_triangle_into_triangle_index_list(half_size, n, aabb, p_points, p_triangle_index, triangles, p_grid_size);
+ }
+ }
+}
+
+Lightmapper::BakeError LightmapperRD::_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) {
+
+ Vector<Size2i> sizes;
+
+ for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
+
+ MeshInstance &mi = mesh_instances.write[m_i];
+ Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height());
+ sizes.push_back(s);
+ atlas_size.width = MAX(atlas_size.width, s.width);
+ atlas_size.height = MAX(atlas_size.height, s.height);
+ }
+
+ int max = nearest_power_of_2_templated(atlas_size.width);
+ max = MAX(max, nearest_power_of_2_templated(atlas_size.height));
+
+ if (max > p_max_texture_size) {
+ return BAKE_ERROR_LIGHTMAP_TOO_SMALL;
+ }
+
+ if (p_step_function) {
+ p_step_function(0.1, TTR("Determining optimal atlas size"), p_bake_userdata, true);
+ }
+
+ atlas_size = Size2i(max, max);
+
+ Size2i best_atlas_size;
+ int best_atlas_slices = 0;
+ int best_atlas_memory = 0x7FFFFFFF;
+ Vector<Vector3i> best_atlas_offsets;
+
+ //determine best texture array atlas size by bruteforce fitting
+ while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) {
+
+ Vector<Vector2i> source_sizes = sizes;
+ Vector<int> source_indices;
+ source_indices.resize(source_sizes.size());
+ for (int i = 0; i < source_indices.size(); i++) {
+ source_indices.write[i] = i;
+ }
+ Vector<Vector3i> atlas_offsets;
+ atlas_offsets.resize(source_sizes.size());
+
+ int slices = 0;
+
+ while (source_sizes.size() > 0) {
+
+ Vector<Vector3i> offsets = Geometry::partial_pack_rects(source_sizes, atlas_size);
+ Vector<int> new_indices;
+ Vector<Vector2i> new_sources;
+ for (int i = 0; i < offsets.size(); i++) {
+ Vector3i ofs = offsets[i];
+ int sidx = source_indices[i];
+ if (ofs.z > 0) {
+ //valid
+ ofs.z = slices;
+ atlas_offsets.write[sidx] = ofs;
+ } else {
+ new_indices.push_back(sidx);
+ new_sources.push_back(source_sizes[i]);
+ }
+ }
+
+ source_sizes = new_sources;
+ source_indices = new_indices;
+ slices++;
+ }
+
+ int mem_used = atlas_size.x * atlas_size.y * slices;
+ if (mem_used < best_atlas_memory) {
+ best_atlas_size = atlas_size;
+ best_atlas_offsets = atlas_offsets;
+ best_atlas_slices = slices;
+ best_atlas_memory = mem_used;
+ }
+
+ if (atlas_size.width == atlas_size.height) {
+ atlas_size.width *= 2;
+ } else {
+ atlas_size.height *= 2;
+ }
+ }
+ atlas_size = best_atlas_size;
+ atlas_slices = best_atlas_slices;
+
+ // apply the offsets and slice to all images, and also blit albedo and emission
+ albedo_images.resize(atlas_slices);
+ emission_images.resize(atlas_slices);
+
+ if (p_step_function) {
+ p_step_function(0.2, TTR("Blitting albedo and emission"), p_bake_userdata, true);
+ }
+
+ for (int i = 0; i < atlas_slices; i++) {
+ Ref<Image> albedo;
+ albedo.instance();
+ albedo->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBA8);
+ albedo->set_as_black();
+ albedo_images.write[i] = albedo;
+
+ Ref<Image> emission;
+ emission.instance();
+ emission->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH);
+ emission->set_as_black();
+ emission_images.write[i] = emission;
+ }
+
+ //assign uv positions
+
+ for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
+
+ MeshInstance &mi = mesh_instances.write[m_i];
+ mi.offset.x = best_atlas_offsets[m_i].x;
+ mi.offset.y = best_atlas_offsets[m_i].y;
+ mi.slice = best_atlas_offsets[m_i].z;
+ albedo_images.write[mi.slice]->blit_rect(mi.data.albedo_on_uv2, Rect2(Vector2(), Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height())), mi.offset);
+ emission_images.write[mi.slice]->blit_rect(mi.data.emission_on_uv2, Rect2(Vector2(), Size2i(mi.data.emission_on_uv2->get_width(), mi.data.emission_on_uv2->get_height())), mi.offset);
+ }
+
+ return BAKE_OK;
+}
+
+void LightmapperRD::_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 &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &grid_texture_sdf, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) {
+
+ HashMap<Vertex, uint32_t, VertexHash> vertex_map;
+
+ //fill triangles array and vertex array
+ LocalVector<Triangle> triangles;
+ LocalVector<Vertex> vertex_array;
+ LocalVector<Box> box_array;
+ LocalVector<Seam> seams;
+
+ slice_triangle_count.resize(atlas_slices);
+ slice_seam_count.resize(atlas_slices);
+
+ for (int i = 0; i < atlas_slices; i++) {
+ slice_triangle_count.write[i] = 0;
+ slice_seam_count.write[i] = 0;
+ }
+
+ bounds = AABB();
+
+ for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
+
+ if (p_step_function) {
+ float p = float(m_i + 1) / mesh_instances.size() * 0.1;
+ p_step_function(0.3 + p, vformat(TTR("Plotting mesh into acceleration structure %d/%d"), m_i + 1, mesh_instances.size()), p_bake_userdata, false);
+ }
+
+ HashMap<Edge, EdgeUV2, EdgeHash> edges;
+
+ MeshInstance &mi = mesh_instances.write[m_i];
+
+ Vector2 uv_scale = Vector2(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height()) / Vector2(atlas_size);
+ Vector2 uv_offset = Vector2(mi.offset) / Vector2(atlas_size);
+ if (m_i == 0) {
+ bounds.position = mi.data.points[0];
+ }
+
+ for (int i = 0; i < mi.data.points.size(); i += 3) {
+
+ Vector3 vtxs[3] = { mi.data.points[i + 0], mi.data.points[i + 1], mi.data.points[i + 2] };
+ Vector2 uvs[3] = { mi.data.uv2[i + 0] * uv_scale + uv_offset, mi.data.uv2[i + 1] * uv_scale + uv_offset, mi.data.uv2[i + 2] * uv_scale + uv_offset };
+ Vector3 normal[3] = { mi.data.normal[i + 0], mi.data.normal[i + 1], mi.data.normal[i + 2] };
+
+ AABB taabb;
+ Triangle t;
+ t.slice = mi.slice;
+ for (int k = 0; k < 3; k++) {
+
+ bounds.expand_to(vtxs[k]);
+
+ Vertex v;
+ v.position[0] = vtxs[k].x;
+ v.position[1] = vtxs[k].y;
+ v.position[2] = vtxs[k].z;
+ v.uv[0] = uvs[k].x;
+ v.uv[1] = uvs[k].y;
+ v.normal_xy[0] = normal[k].x;
+ v.normal_xy[1] = normal[k].y;
+ v.normal_z = normal[k].z;
+
+ uint32_t *indexptr = vertex_map.getptr(v);
+
+ if (indexptr) {
+ t.indices[k] = *indexptr;
+ } else {
+ uint32_t new_index = vertex_map.size();
+ t.indices[k] = new_index;
+ vertex_map[v] = new_index;
+ vertex_array.push_back(v);
+ }
+
+ if (k == 0) {
+ taabb.position = vtxs[k];
+ } else {
+ taabb.expand_to(vtxs[k]);
+ }
+ }
+
+ //compute seams that will need to be blended later
+ for (int k = 0; k < 3; k++) {
+ int n = (k + 1) % 3;
+
+ Edge edge(vtxs[k], vtxs[n], normal[k], normal[n]);
+ Vector2i edge_indices(t.indices[k], t.indices[n]);
+ EdgeUV2 uv2(uvs[k], uvs[n], edge_indices);
+
+ if (edge.b == edge.a) {
+ continue; //degenerate, somehow
+ }
+ if (edge.b < edge.a) {
+ SWAP(edge.a, edge.b);
+ SWAP(edge.na, edge.nb);
+ SWAP(uv2.a, uv2.b);
+ SWAP(edge_indices.x, edge_indices.y);
+ }
+
+ EdgeUV2 *euv2 = edges.getptr(edge);
+ if (!euv2) {
+ edges[edge] = uv2;
+ } else {
+ if (*euv2 == uv2) {
+ continue; // seam shared UV space, no need to blend
+ }
+ if (euv2->seam_found) {
+ continue; //bad geometry
+ }
+
+ Seam seam;
+ seam.a = edge_indices;
+ seam.b = euv2->indices;
+ seam.slice = mi.slice;
+ seams.push_back(seam);
+ slice_seam_count.write[mi.slice]++;
+ euv2->seam_found = true;
+ }
+ }
+
+ Box box;
+ box.min_bounds[0] = taabb.position.x;
+ box.min_bounds[1] = taabb.position.y;
+ box.min_bounds[2] = taabb.position.z;
+ box.max_bounds[0] = taabb.position.x + MAX(taabb.size.x, 0.0001);
+ box.max_bounds[1] = taabb.position.y + MAX(taabb.size.y, 0.0001);
+ box.max_bounds[2] = taabb.position.z + MAX(taabb.size.z, 0.0001);
+ box.pad0 = box.pad1 = 0; //make valgrind not complain
+ box_array.push_back(box);
+
+ triangles.push_back(t);
+ slice_triangle_count.write[t.slice]++;
+ }
+ }
+
+ //also consider probe positions for bounds
+ for (int i = 0; i < probe_positions.size(); i++) {
+ Vector3 pp(probe_positions[i].position[0], probe_positions[i].position[1], probe_positions[i].position[2]);
+ bounds.expand_to(pp);
+ }
+ bounds.grow_by(0.1); //grow a bit to avoid numerical error
+
+ triangles.sort(); //sort by slice
+ seams.sort();
+
+ if (p_step_function) {
+ p_step_function(0.4, TTR("Optimizing acceleration structure"), p_bake_userdata, true);
+ }
+
+ //fill list of triangles in grid
+ LocalVector<TriangleSort> triangle_sort;
+ for (uint32_t i = 0; i < triangles.size(); i++) {
+
+ const Triangle &t = triangles[i];
+ Vector3 face[3] = {
+ Vector3(vertex_array[t.indices[0]].position[0], vertex_array[t.indices[0]].position[1], vertex_array[t.indices[0]].position[2]),
+ Vector3(vertex_array[t.indices[1]].position[0], vertex_array[t.indices[1]].position[1], vertex_array[t.indices[1]].position[2]),
+ Vector3(vertex_array[t.indices[2]].position[0], vertex_array[t.indices[2]].position[1], vertex_array[t.indices[2]].position[2])
+ };
+ _plot_triangle_into_triangle_index_list(grid_size, Vector3i(), bounds, face, i, triangle_sort, grid_size);
+ }
+ //sort it
+ triangle_sort.sort();
+
+ Vector<uint32_t> triangle_indices;
+ triangle_indices.resize(triangle_sort.size());
+ Vector<uint32_t> grid_indices;
+ grid_indices.resize(grid_size * grid_size * grid_size * 2);
+ zeromem(grid_indices.ptrw(), grid_indices.size() * sizeof(uint32_t));
+ Vector<bool> solid;
+ solid.resize(grid_size * grid_size * grid_size);
+ zeromem(solid.ptrw(), solid.size() * sizeof(bool));
+
+ {
+ uint32_t *tiw = triangle_indices.ptrw();
+ uint32_t last_cell = 0xFFFFFFFF;
+ uint32_t *giw = grid_indices.ptrw();
+ bool *solidw = solid.ptrw();
+ for (uint32_t i = 0; i < triangle_sort.size(); i++) {
+ uint32_t cell = triangle_sort[i].cell_index;
+ if (cell != last_cell) {
+ //cell changed, update pointer to indices
+ giw[cell * 2 + 1] = i;
+ last_cell = cell;
+ solidw[cell] = true;
+ }
+ tiw[i] = triangle_sort[i].triangle_index;
+ giw[cell * 2]++; //update counter
+ last_cell = cell;
+ }
+ }
+#if 0
+ for (int i = 0; i < grid_size; i++) {
+ for (int j = 0; j < grid_size; j++) {
+ for (int k = 0; k < grid_size; k++) {
+ uint32_t index = i * (grid_size * grid_size) + j * grid_size + k;
+ grid_indices.write[index * 2] = float(i) / grid_size * 255;
+ grid_indices.write[index * 2 + 1] = float(j) / grid_size * 255;
+ }
+ }
+ }
+#endif
+
+#if 0
+ for (int i = 0; i < grid_size; i++) {
+ Vector<uint8_t> grid_usage;
+ grid_usage.resize(grid_size * grid_size);
+ for (int j = 0; j < grid_usage.size(); j++) {
+ uint32_t ofs = i * grid_size * grid_size + j;
+ uint32_t count = grid_indices[ofs * 2];
+ grid_usage.write[j] = count > 0 ? 255 : 0;
+ }
+
+ Ref<Image> img;
+ img.instance();
+ img->create(grid_size, grid_size, false, Image::FORMAT_L8, grid_usage);
+ img->save_png("res://grid_layer_" + itos(1000 + i).substr(1, 3) + ".png");
+ }
+#endif
+ if (p_step_function) {
+ p_step_function(0.45, TTR("Generating Signed Distance Field"), p_bake_userdata, true);
+ }
+
+ //generate SDF for raytracing
+ Vector<uint32_t> euclidean_pos = Geometry::generate_edf(solid, Vector3i(grid_size, grid_size, grid_size), false);
+ Vector<uint32_t> euclidean_neg = Geometry::generate_edf(solid, Vector3i(grid_size, grid_size, grid_size), true);
+ Vector<int8_t> sdf8 = Geometry::generate_sdf8(euclidean_pos, euclidean_neg);
+
+ /*****************************/
+ /*** CREATE GPU STRUCTURES ***/
+ /*****************************/
+
+ lights.sort();
+
+ Vector<Vector2i> seam_buffer_vec;
+ seam_buffer_vec.resize(seams.size() * 2);
+ for (uint32_t i = 0; i < seams.size(); i++) {
+ seam_buffer_vec.write[i * 2 + 0] = seams[i].a;
+ seam_buffer_vec.write[i * 2 + 1] = seams[i].b;
+ }
+
+ { //buffers
+ Vector<uint8_t> vb = vertex_array.to_byte_array();
+ vertex_buffer = rd->storage_buffer_create(vb.size(), vb);
+
+ Vector<uint8_t> tb = triangles.to_byte_array();
+ triangle_buffer = rd->storage_buffer_create(tb.size(), tb);
+
+ Vector<uint8_t> bb = box_array.to_byte_array();
+ box_buffer = rd->storage_buffer_create(bb.size(), bb);
+
+ Vector<uint8_t> tib = triangle_indices.to_byte_array();
+ triangle_cell_indices_buffer = rd->storage_buffer_create(tib.size(), tib);
+
+ Vector<uint8_t> lb = lights.to_byte_array();
+ if (lb.size() == 0) {
+ lb.resize(sizeof(Light)); //even if no lights, the buffer must exist
+ }
+ lights_buffer = rd->storage_buffer_create(lb.size(), lb);
+
+ Vector<uint8_t> sb = seam_buffer_vec.to_byte_array();
+ if (sb.size() == 0) {
+ sb.resize(sizeof(Vector2i) * 2); //even if no seams, the buffer must exist
+ }
+ seams_buffer = rd->storage_buffer_create(sb.size(), sb);
+
+ Vector<uint8_t> pb = probe_positions.to_byte_array();
+ if (pb.size() == 0) {
+ pb.resize(sizeof(Probe));
+ }
+ probe_positions_buffer = rd->storage_buffer_create(pb.size(), pb);
+ }
+
+ { //grid
+
+ RD::TextureFormat tf;
+ tf.width = grid_size;
+ tf.height = grid_size;
+ tf.depth = grid_size;
+ tf.type = RD::TEXTURE_TYPE_3D;
+ tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
+
+ Vector<Vector<uint8_t>> texdata;
+ texdata.resize(1);
+ //grid and indices
+ tf.format = RD::DATA_FORMAT_R32G32_UINT;
+ texdata.write[0] = grid_indices.to_byte_array();
+ grid_texture = rd->texture_create(tf, RD::TextureView(), texdata);
+ //sdf
+ tf.format = RD::DATA_FORMAT_R8_SNORM;
+ texdata.write[0] = sdf8.to_byte_array();
+ grid_texture_sdf = rd->texture_create(tf, RD::TextureView(), texdata);
+ }
+}
+
+void LightmapperRD::_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) {
+
+ Vector<RID> framebuffers;
+
+ for (int i = 0; i < atlas_slices; i++) {
+ RID slice_pos_tex = rd->texture_create_shared_from_slice(RD::TextureView(), position_tex, i, 0);
+ RID slice_unoc_tex = rd->texture_create_shared_from_slice(RD::TextureView(), unocclude_tex, i, 0);
+ RID slice_norm_tex = rd->texture_create_shared_from_slice(RD::TextureView(), normal_tex, i, 0);
+ Vector<RID> fb;
+ fb.push_back(slice_pos_tex);
+ fb.push_back(slice_norm_tex);
+ fb.push_back(slice_unoc_tex);
+ fb.push_back(raster_depth_buffer);
+ framebuffers.push_back(rd->framebuffer_create(fb));
+ }
+
+ RD::PipelineDepthStencilState ds;
+ ds.enable_depth_test = true;
+ ds.enable_depth_write = true;
+ ds.depth_compare_operator = RD::COMPARE_OP_LESS; //so it does render same pixel twice
+
+ RID raster_pipeline = rd->render_pipeline_create(rasterize_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, RD::PipelineColorBlendState::create_disabled(3), 0);
+ RID raster_pipeline_wire;
+ {
+
+ RD::PipelineRasterizationState rw;
+ rw.wireframe = true;
+ raster_pipeline_wire = rd->render_pipeline_create(rasterize_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, rw, RD::PipelineMultisampleState(), ds, RD::PipelineColorBlendState::create_disabled(3), 0);
+ }
+
+ uint32_t triangle_offset = 0;
+ Vector<Color> clear_colors;
+ clear_colors.push_back(Color(0, 0, 0, 0));
+ clear_colors.push_back(Color(0, 0, 0, 0));
+ clear_colors.push_back(Color(0, 0, 0, 0));
+
+ for (int i = 0; i < atlas_slices; i++) {
+
+ RasterPushConstant raster_push_constant;
+ raster_push_constant.atlas_size[0] = atlas_size.x;
+ raster_push_constant.atlas_size[1] = atlas_size.y;
+ raster_push_constant.base_triangle = triangle_offset;
+ raster_push_constant.to_cell_offset[0] = bounds.position.x;
+ raster_push_constant.to_cell_offset[1] = bounds.position.y;
+ raster_push_constant.to_cell_offset[2] = bounds.position.z;
+ raster_push_constant.bias = p_bias;
+ raster_push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size);
+ raster_push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size);
+ raster_push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size);
+ 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;
+
+ 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
+ rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline);
+ rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
+ rd->draw_list_set_push_constant(draw_list, &raster_push_constant, sizeof(RasterPushConstant));
+ rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3);
+ //draw wire
+ rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline_wire);
+ rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
+ rd->draw_list_set_push_constant(draw_list, &raster_push_constant, sizeof(RasterPushConstant));
+ rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3);
+
+ rd->draw_list_end();
+
+ triangle_offset += slice_triangle_count[i];
+ }
+}
+
+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) {
+
+ if (p_step_function) {
+ p_step_function(0.0, TTR("Begin Bake"), p_bake_userdata, true);
+ }
+ bake_textures.clear();
+ int grid_size = 128;
+
+ /* STEP 1: Fetch material textures and compute the bounds */
+
+ AABB bounds;
+ Size2i atlas_size;
+ int atlas_slices;
+ Vector<Ref<Image>> albedo_images;
+ Vector<Ref<Image>> emission_images;
+
+ BakeError bake_error = _blit_meshes_into_atlas(p_max_texture_size, albedo_images, emission_images, bounds, atlas_size, atlas_slices, p_step_function, p_bake_userdata);
+ if (bake_error != BAKE_OK) {
+ return bake_error;
+ }
+
+#ifdef DEBUG_TEXTURES
+ for (int i = 0; i < atlas_slices; i++) {
+ albedo_images[i]->save_png("res://0_albedo_" + itos(i) + ".png");
+ emission_images[i]->save_png("res://0_emission_" + itos(i) + ".png");
+ }
+#endif
+
+ RenderingDevice *rd = RenderingDevice::get_singleton()->create_local_device();
+
+ RID albedo_array_tex;
+ RID emission_array_tex;
+ RID normal_tex;
+ RID position_tex;
+ RID unocclude_tex;
+ RID light_source_tex;
+ RID light_dest_tex;
+ RID light_accum_tex;
+ RID light_accum_tex2;
+ RID light_primary_dynamic_tex;
+ RID light_environment_tex;
+
+#define FREE_TEXTURES \
+ rd->free(albedo_array_tex); \
+ rd->free(emission_array_tex); \
+ rd->free(normal_tex); \
+ rd->free(position_tex); \
+ rd->free(unocclude_tex); \
+ rd->free(light_source_tex); \
+ rd->free(light_accum_tex2); \
+ rd->free(light_accum_tex); \
+ rd->free(light_primary_dynamic_tex); \
+ rd->free(light_environment_tex);
+
+ { // create all textures
+
+ Vector<Vector<uint8_t>> albedo_data;
+ Vector<Vector<uint8_t>> emission_data;
+ for (int i = 0; i < atlas_slices; i++) {
+ albedo_data.push_back(albedo_images[i]->get_data());
+ emission_data.push_back(emission_images[i]->get_data());
+ }
+
+ RD::TextureFormat tf;
+ tf.width = atlas_size.width;
+ tf.height = atlas_size.height;
+ tf.array_layers = atlas_slices;
+ tf.type = RD::TEXTURE_TYPE_2D_ARRAY;
+ tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
+ tf.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+
+ albedo_array_tex = rd->texture_create(tf, RD::TextureView(), albedo_data);
+
+ tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
+
+ emission_array_tex = rd->texture_create(tf, RD::TextureView(), emission_data);
+
+ //this will be rastered to
+ tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
+ normal_tex = rd->texture_create(tf, RD::TextureView());
+ tf.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
+ position_tex = rd->texture_create(tf, RD::TextureView());
+ unocclude_tex = rd->texture_create(tf, RD::TextureView());
+
+ tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
+ tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
+
+ light_source_tex = rd->texture_create(tf, RD::TextureView());
+ rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
+ light_primary_dynamic_tex = rd->texture_create(tf, RD::TextureView());
+ rd->texture_clear(light_primary_dynamic_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
+
+ if (p_bake_sh) {
+ tf.array_layers *= 4;
+ }
+ light_accum_tex = rd->texture_create(tf, RD::TextureView());
+ rd->texture_clear(light_accum_tex, Color(0, 0, 0, 0), 0, 1, 0, tf.array_layers);
+ light_dest_tex = rd->texture_create(tf, RD::TextureView());
+ rd->texture_clear(light_dest_tex, Color(0, 0, 0, 0), 0, 1, 0, tf.array_layers);
+ light_accum_tex2 = light_dest_tex;
+
+ //env
+ {
+ Ref<Image> panorama_tex;
+ if (p_environment_panorama.is_valid()) {
+ panorama_tex = p_environment_panorama;
+ panorama_tex->convert(Image::FORMAT_RGBAF);
+ } else {
+ panorama_tex.instance();
+ panorama_tex->create(8, 8, false, Image::FORMAT_RGBAF);
+ for (int i = 0; i < 8; i++) {
+ for (int j = 0; j < 8; j++) {
+ panorama_tex->set_pixel(i, j, Color(0, 0, 0, 1));
+ }
+ }
+ }
+
+ RD::TextureFormat tfp;
+ tfp.width = panorama_tex->get_width();
+ tfp.height = panorama_tex->get_height();
+ tfp.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
+ tfp.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
+
+ Vector<Vector<uint8_t>> tdata;
+ tdata.push_back(panorama_tex->get_data());
+ light_environment_tex = rd->texture_create(tfp, RD::TextureView(), tdata);
+
+#ifdef DEBUG_TEXTURES
+ panorama_tex->convert(Image::FORMAT_RGB8);
+ panorama_tex->save_png("res://0_panorama.png");
+#endif
+ }
+ }
+
+ /* STEP 2: create the acceleration structure for the GPU*/
+
+ Vector<int> slice_triangle_count;
+ RID vertex_buffer;
+ RID triangle_buffer;
+ RID box_buffer;
+ RID lights_buffer;
+ RID triangle_cell_indices_buffer;
+ RID grid_texture;
+ RID grid_texture_sdf;
+ RID seams_buffer;
+ RID probe_positions_buffer;
+
+ Vector<int> slice_seam_count;
+
+#define FREE_BUFFERS \
+ rd->free(vertex_buffer); \
+ rd->free(triangle_buffer); \
+ rd->free(box_buffer); \
+ rd->free(lights_buffer); \
+ rd->free(triangle_cell_indices_buffer); \
+ rd->free(grid_texture); \
+ rd->free(grid_texture_sdf); \
+ rd->free(seams_buffer); \
+ rd->free(probe_positions_buffer);
+
+ _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, box_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, grid_texture_sdf, seams_buffer, p_step_function, p_bake_userdata);
+
+ if (p_step_function) {
+ p_step_function(0.47, TTR("Preparing shaders"), p_bake_userdata, true);
+ }
+
+ //shaders
+ Ref<RDShaderFile> raster_shader;
+ raster_shader.instance();
+ Error err = raster_shader->parse_versions_from_text(lm_raster_shader_glsl);
+ if (err != OK) {
+ raster_shader->print_errors("raster_shader");
+
+ FREE_TEXTURES
+ FREE_BUFFERS
+
+ memdelete(rd);
+ }
+ ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
+
+ RID rasterize_shader = rd->shader_create_from_bytecode(raster_shader->get_bytecode());
+
+ ERR_FAIL_COND_V(rasterize_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //this is a bug check, though, should not happen
+
+ RID sampler;
+ {
+ RD::SamplerState s;
+ s.mag_filter = RD::SAMPLER_FILTER_LINEAR;
+ s.min_filter = RD::SAMPLER_FILTER_LINEAR;
+ s.max_lod = 0;
+
+ sampler = rd->sampler_create(s);
+ }
+
+ Vector<RD::Uniform> base_uniforms;
+ {
+ {
+
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 1;
+ u.ids.push_back(vertex_buffer);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 2;
+ u.ids.push_back(triangle_buffer);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 3;
+ u.ids.push_back(box_buffer);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 4;
+ u.ids.push_back(triangle_cell_indices_buffer);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 5;
+ u.ids.push_back(lights_buffer);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 6;
+ u.ids.push_back(seams_buffer);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 7;
+ u.ids.push_back(probe_positions_buffer);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 8;
+ u.ids.push_back(grid_texture);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 9;
+ u.ids.push_back(grid_texture_sdf);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 10;
+ u.ids.push_back(albedo_array_tex);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 11;
+ u.ids.push_back(emission_array_tex);
+ base_uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_SAMPLER;
+ u.binding = 12;
+ u.ids.push_back(sampler);
+ base_uniforms.push_back(u);
+ }
+ }
+
+ RID raster_base_uniform = rd->uniform_set_create(base_uniforms, rasterize_shader, 0);
+ RID raster_depth_buffer;
+ {
+ RD::TextureFormat tf;
+ tf.width = atlas_size.width;
+ tf.height = atlas_size.height;
+ tf.depth = 1;
+ tf.type = RD::TEXTURE_TYPE_2D;
+ tf.usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ tf.format = RD::DATA_FORMAT_D32_SFLOAT;
+
+ raster_depth_buffer = rd->texture_create(tf, RD::TextureView());
+ }
+
+ rd->submit();
+ rd->sync();
+
+ /* STEP 3: Raster the geometry to UV2 coords in the atlas textures GPU*/
+
+ _raster_geometry(rd, atlas_size, atlas_slices, grid_size, bounds, p_bias, slice_triangle_count, position_tex, unocclude_tex, normal_tex, raster_depth_buffer, rasterize_shader, raster_base_uniform);
+
+#ifdef DEBUG_TEXTURES
+
+ for (int i = 0; i < atlas_slices; i++) {
+ Vector<uint8_t> s = rd->texture_get_data(position_tex, i);
+ Ref<Image> img;
+ img.instance();
+ img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAF, s);
+ img->convert(Image::FORMAT_RGBA8);
+ img->save_png("res://1_position_" + itos(i) + ".png");
+
+ s = rd->texture_get_data(normal_tex, i);
+ img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
+ img->convert(Image::FORMAT_RGBA8);
+ img->save_png("res://1_normal_" + itos(i) + ".png");
+ }
+#endif
+
+#define FREE_RASTER_RESOURCES \
+ rd->free(rasterize_shader); \
+ rd->free(sampler); \
+ rd->free(raster_depth_buffer);
+
+ /* Plot direct light */
+
+ Ref<RDShaderFile> compute_shader;
+ compute_shader.instance();
+ err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : "");
+ if (err != OK) {
+
+ FREE_TEXTURES
+ FREE_BUFFERS
+ FREE_RASTER_RESOURCES
+ memdelete(rd);
+ compute_shader->print_errors("compute_shader");
+ }
+ ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
+
+ //unoccluder
+ RID compute_shader_unocclude = rd->shader_create_from_bytecode(compute_shader->get_bytecode("unocclude"));
+ ERR_FAIL_COND_V(compute_shader_unocclude.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen
+ RID compute_shader_unocclude_pipeline = rd->compute_pipeline_create(compute_shader_unocclude);
+
+ //direct light
+ RID compute_shader_primary = rd->shader_create_from_bytecode(compute_shader->get_bytecode("primary"));
+ ERR_FAIL_COND_V(compute_shader_primary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen
+ RID compute_shader_primary_pipeline = rd->compute_pipeline_create(compute_shader_primary);
+
+ //indirect light
+ RID compute_shader_secondary = rd->shader_create_from_bytecode(compute_shader->get_bytecode("secondary"));
+ ERR_FAIL_COND_V(compute_shader_secondary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
+ RID compute_shader_secondary_pipeline = rd->compute_pipeline_create(compute_shader_secondary);
+
+ //dilate
+ RID compute_shader_dilate = rd->shader_create_from_bytecode(compute_shader->get_bytecode("dilate"));
+ ERR_FAIL_COND_V(compute_shader_dilate.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
+ RID compute_shader_dilate_pipeline = rd->compute_pipeline_create(compute_shader_dilate);
+
+ //dilate
+ RID compute_shader_light_probes = rd->shader_create_from_bytecode(compute_shader->get_bytecode("light_probes"));
+ ERR_FAIL_COND_V(compute_shader_light_probes.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
+ RID compute_shader_light_probes_pipeline = rd->compute_pipeline_create(compute_shader_light_probes);
+
+ RID compute_base_uniform_set = rd->uniform_set_create(base_uniforms, compute_shader_primary, 0);
+
+#define FREE_COMPUTE_RESOURCES \
+ rd->free(compute_shader_unocclude); \
+ rd->free(compute_shader_primary); \
+ rd->free(compute_shader_secondary); \
+ rd->free(compute_shader_dilate); \
+ rd->free(compute_shader_light_probes);
+
+ PushConstant push_constant;
+ {
+ //set defaults
+ push_constant.atlas_size[0] = atlas_size.width;
+ push_constant.atlas_size[1] = atlas_size.height;
+ push_constant.world_size[0] = bounds.size.x;
+ push_constant.world_size[1] = bounds.size.y;
+ push_constant.world_size[2] = bounds.size.z;
+ push_constant.to_cell_offset[0] = bounds.position.x;
+ push_constant.to_cell_offset[1] = bounds.position.y;
+ push_constant.to_cell_offset[2] = bounds.position.z;
+ push_constant.bias = p_bias;
+ push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size);
+ push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size);
+ push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size);
+ push_constant.light_count = lights.size();
+ push_constant.grid_size = grid_size;
+ push_constant.atlas_slice = 0;
+ push_constant.region_ofs[0] = 0;
+ push_constant.region_ofs[1] = 0;
+ push_constant.environment_xform[0] = p_environment_transform.elements[0][0];
+ push_constant.environment_xform[1] = p_environment_transform.elements[1][0];
+ push_constant.environment_xform[2] = p_environment_transform.elements[2][0];
+ push_constant.environment_xform[3] = 0;
+ push_constant.environment_xform[4] = p_environment_transform.elements[0][1];
+ push_constant.environment_xform[5] = p_environment_transform.elements[1][1];
+ push_constant.environment_xform[6] = p_environment_transform.elements[2][1];
+ push_constant.environment_xform[7] = 0;
+ push_constant.environment_xform[8] = p_environment_transform.elements[0][2];
+ push_constant.environment_xform[9] = p_environment_transform.elements[1][2];
+ push_constant.environment_xform[10] = p_environment_transform.elements[2][2];
+ push_constant.environment_xform[11] = 0;
+ }
+
+ Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1);
+ rd->submit();
+ rd->sync();
+
+ if (p_step_function) {
+ p_step_function(0.49, TTR("Un-occluding geometry"), p_bake_userdata, true);
+ }
+
+ /* UNOCCLUDE */
+ {
+
+ Vector<RD::Uniform> uniforms;
+ {
+ {
+
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 0;
+ u.ids.push_back(position_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 1;
+ u.ids.push_back(unocclude_tex); //will be unused
+ uniforms.push_back(u);
+ }
+ }
+
+ RID unocclude_uniform_set = rd->uniform_set_create(uniforms, compute_shader_unocclude, 1);
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_unocclude_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, unocclude_uniform_set, 1);
+
+ for (int i = 0; i < atlas_slices; i++) {
+ push_constant.atlas_slice = i;
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
+ //no barrier, let them run all together
+ }
+ rd->compute_list_end(); //done
+ }
+
+ if (p_step_function) {
+ p_step_function(0.5, TTR("Plot direct lighting"), p_bake_userdata, true);
+ }
+
+ /* PRIMARY (direct) LIGHT PASS */
+ {
+
+ Vector<RD::Uniform> uniforms;
+ {
+ {
+
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 0;
+ u.ids.push_back(light_source_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 1;
+ u.ids.push_back(light_dest_tex); //will be unused
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 2;
+ u.ids.push_back(position_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 3;
+ u.ids.push_back(normal_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 4;
+ u.ids.push_back(light_accum_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 5;
+ u.ids.push_back(light_primary_dynamic_tex);
+ uniforms.push_back(u);
+ }
+ }
+
+ RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1);
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_primary_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, light_uniform_set, 1);
+
+ for (int i = 0; i < atlas_slices; i++) {
+ push_constant.atlas_slice = i;
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
+ //no barrier, let them run all together
+ }
+ rd->compute_list_end(); //done
+ }
+
+#ifdef DEBUG_TEXTURES
+
+ for (int i = 0; i < atlas_slices; i++) {
+ Vector<uint8_t> s = rd->texture_get_data(light_source_tex, i);
+ Ref<Image> img;
+ img.instance();
+ img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
+ img->convert(Image::FORMAT_RGBA8);
+ img->save_png("res://2_light_primary_" + itos(i) + ".png");
+ }
+#endif
+
+ /* SECONDARY (indirect) LIGHT PASS(ES) */
+ if (p_step_function) {
+ p_step_function(0.6, TTR("Integrate indirect lighting"), p_bake_userdata, true);
+ }
+
+ if (p_bounces > 0) {
+
+ Vector<RD::Uniform> uniforms;
+ {
+ {
+
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 0;
+ u.ids.push_back(light_dest_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 1;
+ u.ids.push_back(light_source_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 2;
+ u.ids.push_back(position_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 3;
+ u.ids.push_back(normal_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 4;
+ u.ids.push_back(light_accum_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 5;
+ u.ids.push_back(unocclude_tex); //reuse unocclude tex
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 6;
+ u.ids.push_back(light_environment_tex); //reuse unocclude tex
+ uniforms.push_back(u);
+ }
+ }
+
+ RID secondary_uniform_set[2];
+ secondary_uniform_set[0] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1);
+ uniforms.write[0].ids.write[0] = light_source_tex;
+ uniforms.write[1].ids.write[0] = light_dest_tex;
+ secondary_uniform_set[1] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1);
+
+ switch (p_quality) {
+ case BAKE_QUALITY_LOW: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/low_quality_ray_count");
+ } break;
+ case BAKE_QUALITY_MEDIUM: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/medium_quality_ray_count");
+ } break;
+ case BAKE_QUALITY_HIGH: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/high_quality_ray_count");
+ } break;
+ case BAKE_QUALITY_ULTRA: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/ultra_quality_ray_count");
+ } break;
+ }
+
+ push_constant.ray_count = CLAMP(push_constant.ray_count, 16, 8192);
+
+ int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/gpu_lightmapper/performance/region_size")));
+ int max_rays = GLOBAL_GET("rendering/gpu_lightmapper/performance/max_rays_per_pass");
+
+ int x_regions = (atlas_size.width - 1) / max_region_size + 1;
+ int y_regions = (atlas_size.height - 1) / max_region_size + 1;
+ int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
+
+ rd->submit();
+ rd->sync();
+
+ for (int b = 0; b < p_bounces; b++) {
+ int count = 0;
+ if (b > 0) {
+ SWAP(light_source_tex, light_dest_tex);
+ SWAP(secondary_uniform_set[0], secondary_uniform_set[1]);
+ }
+
+ for (int s = 0; s < atlas_slices; s++) {
+ 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, atlas_size.width) - x;
+ int h = MIN((j + 1) * max_region_size, atlas_size.height) - y;
+
+ push_constant.region_ofs[0] = x;
+ push_constant.region_ofs[1] = y;
+
+ group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1);
+
+ for (int k = 0; k < ray_iterations; k++) {
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_secondary_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, secondary_uniform_set[0], 1);
+
+ push_constant.ray_from = k * max_rays;
+ push_constant.ray_to = MIN((k + 1) * max_rays, int32_t(push_constant.ray_count));
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
+
+ rd->compute_list_end(); //done
+ rd->submit();
+ rd->sync();
+
+ count++;
+ if (p_step_function) {
+ int total = (atlas_slices * x_regions * y_regions * ray_iterations);
+ int percent = count * 100 / total;
+ float p = float(count) / total * 0.1;
+ p_step_function(0.6 + p, vformat(TTR("Bounce %d/%d: Integrate indirect lighting %d%%"), b + 1, p_bounces, percent), p_bake_userdata, false);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* LIGHPROBES */
+
+ RID light_probe_buffer;
+
+ if (probe_positions.size()) {
+
+ light_probe_buffer = rd->storage_buffer_create(sizeof(float) * 4 * 9 * probe_positions.size());
+
+ if (p_step_function) {
+ p_step_function(0.7, TTR("Baking lightprobes"), p_bake_userdata, true);
+ }
+
+ Vector<RD::Uniform> uniforms;
+ {
+
+ {
+
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 0;
+ u.ids.push_back(light_probe_buffer);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 1;
+ u.ids.push_back(light_dest_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 2;
+ u.ids.push_back(light_primary_dynamic_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 3;
+ u.ids.push_back(light_environment_tex);
+ uniforms.push_back(u);
+ }
+ }
+ RID light_probe_uniform_set = rd->uniform_set_create(uniforms, compute_shader_light_probes, 1);
+
+ switch (p_quality) {
+ case BAKE_QUALITY_LOW: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/low_quality_probe_ray_count");
+ } break;
+ case BAKE_QUALITY_MEDIUM: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/medium_quality_probe_ray_count");
+ } break;
+ case BAKE_QUALITY_HIGH: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/high_quality_probe_ray_count");
+ } break;
+ case BAKE_QUALITY_ULTRA: {
+ push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/ultra_quality_probe_ray_count");
+ } break;
+ }
+
+ push_constant.atlas_size[0] = probe_positions.size();
+ push_constant.ray_count = CLAMP(push_constant.ray_count, 16, 8192);
+
+ int max_rays = GLOBAL_GET("rendering/gpu_lightmapper/performance/max_rays_per_probe_pass");
+ int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1;
+
+ for (int i = 0; i < ray_iterations; i++) {
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_light_probes_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, light_probe_uniform_set, 1);
+
+ push_constant.ray_from = i * max_rays;
+ push_constant.ray_to = MIN((i + 1) * max_rays, int32_t(push_constant.ray_count));
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, (probe_positions.size() - 1) / 64 + 1, 1, 1);
+
+ rd->compute_list_end(); //done
+ rd->submit();
+ rd->sync();
+
+ if (p_step_function) {
+ int percent = i * 100 / ray_iterations;
+ float p = float(i) / ray_iterations * 0.1;
+ p_step_function(0.7 + p, vformat(TTR("Integrating light probes %d%%"), percent), p_bake_userdata, false);
+ }
+ }
+
+ push_constant.atlas_size[0] = atlas_size.x; //restore
+ }
+
+#if 0
+ for (int i = 0; i < probe_positions.size(); i++) {
+ Ref<Image> img;
+ img.instance();
+ img->create(6, 4, false, Image::FORMAT_RGB8);
+ for (int j = 0; j < 6; j++) {
+ Vector<uint8_t> s = rd->texture_get_data(lightprobe_tex, i * 6 + j);
+ Ref<Image> img2;
+ img2.instance();
+ img2->create(2, 2, false, Image::FORMAT_RGBAF, s);
+ img2->convert(Image::FORMAT_RGB8);
+ img->blit_rect(img2, Rect2(0, 0, 2, 2), Point2((j % 3) * 2, (j / 3) * 2));
+ }
+ img->save_png("res://3_light_probe_" + itos(i) + ".png");
+ }
+#endif
+
+ /* DENOISE */
+
+ if (p_use_denoiser) {
+ if (p_step_function) {
+ p_step_function(0.8, TTR("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;
+ img.instance();
+ img->create(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, true);
+ }
+ }
+ }
+ }
+
+#ifdef DEBUG_TEXTURES
+
+ 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;
+ img.instance();
+ img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
+ img->convert(Image::FORMAT_RGBA8);
+ img->save_png("res://4_light_secondary_" + itos(i) + ".png");
+ }
+#endif
+
+ /* DILATE LIGHTMAP */
+ {
+
+ SWAP(light_accum_tex, light_accum_tex2);
+
+ Vector<RD::Uniform> uniforms;
+ {
+ {
+
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 0;
+ u.ids.push_back(light_accum_tex);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 1;
+ u.ids.push_back(light_accum_tex2);
+ uniforms.push_back(u);
+ }
+ }
+
+ RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_dilate, 1);
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_dilate_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1);
+ push_constant.region_ofs[0] = 0;
+ push_constant.region_ofs[1] = 0;
+ group_size = Vector3i((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1); //restore group size
+
+ for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
+ push_constant.atlas_slice = i;
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
+ //no barrier, let them run all together
+ }
+ rd->compute_list_end();
+ }
+
+#ifdef DEBUG_TEXTURES
+
+ 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;
+ img.instance();
+ img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
+ img->convert(Image::FORMAT_RGBA8);
+ img->save_png("res://5_dilated_" + itos(i) + ".png");
+ }
+#endif
+
+ /* BLEND SEAMS */
+ //shaders
+ Ref<RDShaderFile> blendseams_shader;
+ blendseams_shader.instance();
+ err = blendseams_shader->parse_versions_from_text(lm_blendseams_shader_glsl);
+ if (err != OK) {
+ FREE_TEXTURES
+ FREE_BUFFERS
+ FREE_RASTER_RESOURCES
+ FREE_COMPUTE_RESOURCES
+ memdelete(rd);
+ blendseams_shader->print_errors("blendseams_shader");
+ }
+ ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
+
+ RID blendseams_line_raster_shader = rd->shader_create_from_bytecode(blendseams_shader->get_bytecode("lines"));
+
+ ERR_FAIL_COND_V(blendseams_line_raster_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
+
+ RID blendseams_triangle_raster_shader = rd->shader_create_from_bytecode(blendseams_shader->get_bytecode("triangles"));
+
+ ERR_FAIL_COND_V(blendseams_triangle_raster_shader.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
+
+#define FREE_BLENDSEAMS_RESOURCES \
+ rd->free(blendseams_line_raster_shader); \
+ rd->free(blendseams_triangle_raster_shader);
+
+ {
+
+ //pre copy
+ for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
+ rd->texture_copy(light_accum_tex, light_accum_tex2, Vector3(), Vector3(), Vector3(atlas_size.width, atlas_size.height, 1), 0, 0, i, i, true);
+ }
+
+ Vector<RID> framebuffers;
+ for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
+ RID slice_tex = rd->texture_create_shared_from_slice(RD::TextureView(), light_accum_tex, i, 0);
+ Vector<RID> fb;
+ fb.push_back(slice_tex);
+ fb.push_back(raster_depth_buffer);
+ framebuffers.push_back(rd->framebuffer_create(fb));
+ }
+
+ Vector<RD::Uniform> uniforms;
+ {
+ {
+
+ RD::Uniform u;
+ u.type = RD::UNIFORM_TYPE_TEXTURE;
+ u.binding = 0;
+ u.ids.push_back(light_accum_tex2);
+ uniforms.push_back(u);
+ }
+ }
+
+ RID blendseams_raster_uniform = rd->uniform_set_create(uniforms, blendseams_line_raster_shader, 1);
+
+ bool debug = false;
+ RD::PipelineColorBlendState bs = RD::PipelineColorBlendState::create_blend(1);
+ bs.attachments.write[0].src_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
+ bs.attachments.write[0].dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+
+ RD::PipelineDepthStencilState ds;
+ ds.enable_depth_test = true;
+ ds.enable_depth_write = true;
+ ds.depth_compare_operator = RD::COMPARE_OP_LESS; //so it does not render same pixel twice, this avoids wrong blending
+
+ RID blendseams_line_raster_pipeline = rd->render_pipeline_create(blendseams_line_raster_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_LINES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, bs, 0);
+ RID blendseams_triangle_raster_pipeline = rd->render_pipeline_create(blendseams_triangle_raster_shader, rd->framebuffer_get_format(framebuffers[0]), RD::INVALID_FORMAT_ID, RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), ds, bs, 0);
+
+ uint32_t seam_offset = 0;
+ uint32_t triangle_offset = 0;
+
+ Vector<Color> clear_colors;
+ clear_colors.push_back(Color(0, 0, 0, 1));
+ for (int i = 0; i < atlas_slices; i++) {
+
+ int subslices = (p_bake_sh ? 4 : 1);
+ for (int k = 0; k < subslices; k++) {
+
+ RasterSeamsPushConstant seams_push_constant;
+ seams_push_constant.slice = uint32_t(i * subslices + k);
+ seams_push_constant.debug = debug;
+
+ RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+
+ rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
+ rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1);
+
+ const int uv_offset_count = 9;
+ static const Vector3 uv_offsets[uv_offset_count] = {
+ Vector3(0, 0, 0.5), //using zbuffer, so go inwards-outwards
+ Vector3(0, 1, 0.2),
+ Vector3(0, -1, 0.2),
+ Vector3(1, 0, 0.2),
+ Vector3(-1, 0, 0.2),
+ Vector3(-1, -1, 0.1),
+ Vector3(1, -1, 0.1),
+ Vector3(1, 1, 0.1),
+ Vector3(-1, 1, 0.1),
+ };
+
+ /* step 1 use lines to blend the edges */
+ {
+ 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.blend = uv_offsets[0].z;
+
+ rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
+ rd->draw_list_draw(draw_list, false, 1, slice_seam_count[i] * 4);
+ }
+
+ /* step 2 use triangles to mask the interior */
+
+ {
+ seams_push_constant.base_index = triangle_offset;
+ rd->draw_list_bind_render_pipeline(draw_list, blendseams_triangle_raster_pipeline);
+ seams_push_constant.blend = 0; //do not draw them, just fill the z-buffer so its used as a mask
+
+ rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
+ rd->draw_list_draw(draw_list, false, 1, slice_triangle_count[i] * 3);
+ }
+ /* step 3 blend around the triangle */
+
+ rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline);
+
+ 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.blend = uv_offsets[0].z;
+
+ rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
+ rd->draw_list_draw(draw_list, false, 1, slice_seam_count[i] * 4);
+ }
+ rd->draw_list_end();
+ }
+ seam_offset += slice_seam_count[i];
+ triangle_offset += slice_triangle_count[i];
+ }
+ }
+
+#ifdef DEBUG_TEXTURES
+
+ 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;
+ img.instance();
+ img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
+ img->convert(Image::FORMAT_RGBA8);
+ img->save_png("res://5_blendseams" + itos(i) + ".png");
+ }
+#endif
+ if (p_step_function) {
+ p_step_function(0.9, TTR("Retrieving textures"), p_bake_userdata, true);
+ }
+
+ 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;
+ img.instance();
+ img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
+ img->convert(Image::FORMAT_RGBH); //remove alpha
+ bake_textures.push_back(img);
+ }
+
+ if (probe_positions.size() > 0) {
+ probe_values.resize(probe_positions.size() * 9);
+ Vector<uint8_t> probe_data = rd->buffer_get_data(light_probe_buffer);
+ copymem(probe_values.ptrw(), probe_data.ptr(), probe_data.size());
+ rd->free(light_probe_buffer);
+
+#ifdef DEBUG_TEXTURES
+ {
+ Ref<Image> img2;
+ img2.instance();
+ img2->create(probe_values.size(), 1, false, Image::FORMAT_RGBAF, probe_data);
+ img2->save_png("res://6_lightprobes.png");
+ }
+#endif
+ }
+
+ FREE_TEXTURES
+ FREE_BUFFERS
+ FREE_RASTER_RESOURCES
+ FREE_COMPUTE_RESOURCES
+ FREE_BLENDSEAMS_RESOURCES
+
+ memdelete(rd);
+
+ return BAKE_OK;
+}
+
+int LightmapperRD::get_bake_texture_count() const {
+ return bake_textures.size();
+}
+Ref<Image> LightmapperRD::get_bake_texture(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, bake_textures.size(), Ref<Image>());
+ return bake_textures[p_index];
+}
+int LightmapperRD::get_bake_mesh_count() const {
+ return mesh_instances.size();
+}
+Variant LightmapperRD::get_bake_mesh_userdata(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, mesh_instances.size(), Variant());
+ return mesh_instances[p_index].data.userdata;
+}
+Rect2 LightmapperRD::get_bake_mesh_uv_scale(int p_index) const {
+
+ ERR_FAIL_COND_V(bake_textures.size() == 0, Rect2());
+ Rect2 uv_ofs;
+ Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height());
+ uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size;
+ uv_ofs.size = Vector2(mesh_instances[p_index].data.albedo_on_uv2->get_width(), mesh_instances[p_index].data.albedo_on_uv2->get_height()) / atlas_size;
+ return uv_ofs;
+}
+int LightmapperRD::get_bake_mesh_texture_slice(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, mesh_instances.size(), Variant());
+ return mesh_instances[p_index].slice;
+}
+
+int LightmapperRD::get_bake_probe_count() const {
+ return probe_positions.size();
+}
+
+Vector3 LightmapperRD::get_bake_probe_point(int p_probe) const {
+ ERR_FAIL_INDEX_V(p_probe, probe_positions.size(), Variant());
+ return Vector3(probe_positions[p_probe].position[0], probe_positions[p_probe].position[1], probe_positions[p_probe].position[2]);
+}
+
+Vector<Color> LightmapperRD::get_bake_probe_sh(int p_probe) const {
+ ERR_FAIL_INDEX_V(p_probe, probe_positions.size(), Vector<Color>());
+ Vector<Color> ret;
+ ret.resize(9);
+ copymem(ret.ptrw(), &probe_values[p_probe * 9], sizeof(Color) * 9);
+ return ret;
+}
+
+LightmapperRD::LightmapperRD() {
+}
diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h
new file mode 100644
index 0000000000..cb98efbeaa
--- /dev/null
+++ b/modules/lightmapper_rd/lightmapper_rd.h
@@ -0,0 +1,229 @@
+#ifndef LIGHTMAPPER_RD_H
+#define LIGHTMAPPER_RD_H
+
+#include "core/local_vector.h"
+#include "scene/3d/lightmapper.h"
+#include "scene/resources/mesh.h"
+#include "servers/rendering/rendering_device.h"
+
+class LightmapperRD : public Lightmapper {
+ GDCLASS(LightmapperRD, Lightmapper)
+
+ struct MeshInstance {
+ MeshData data;
+ int slice = 0;
+ Vector2i offset;
+ };
+
+ struct Light {
+ float position[3];
+ uint32_t type = LIGHT_TYPE_DIRECTIONAL;
+ float direction[3];
+ float energy;
+ float color[3];
+ float size;
+ float range;
+ float attenuation;
+ float spot_angle;
+ float spot_attenuation;
+ uint32_t static_bake;
+ uint32_t pad[3];
+
+ bool operator<(const Light &p_light) const {
+ return type < p_light.type;
+ }
+ };
+
+ struct Vertex {
+ float position[3];
+ float normal_z;
+ float uv[2];
+ float normal_xy[2];
+
+ bool operator==(const Vertex &p_vtx) const {
+ return (position[0] == p_vtx.position[0]) &&
+ (position[1] == p_vtx.position[1]) &&
+ (position[2] == p_vtx.position[2]) &&
+ (uv[0] == p_vtx.uv[0]) &&
+ (uv[1] == p_vtx.uv[1]) &&
+ (normal_xy[0] == p_vtx.normal_xy[0]) &&
+ (normal_xy[1] == p_vtx.normal_xy[1]) &&
+ (normal_z == p_vtx.normal_z);
+ }
+ };
+
+ struct Edge {
+ Vector3 a;
+ Vector3 b;
+ Vector3 na;
+ Vector3 nb;
+ bool operator==(const Edge &p_seam) const {
+ return a == p_seam.a && b == p_seam.b && na == p_seam.na && nb == p_seam.nb;
+ }
+ Edge() {
+ }
+
+ Edge(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_na, const Vector3 &p_nb) {
+ a = p_a;
+ b = p_b;
+ na = p_na;
+ nb = p_nb;
+ }
+ };
+
+ struct Probe {
+ float position[4];
+ };
+
+ Vector<Probe> probe_positions;
+
+ struct EdgeHash {
+ _FORCE_INLINE_ static uint32_t hash(const Edge &p_edge) {
+ uint32_t h = hash_djb2_one_float(p_edge.a.x);
+ h = hash_djb2_one_float(p_edge.a.y, h);
+ h = hash_djb2_one_float(p_edge.a.z, h);
+ h = hash_djb2_one_float(p_edge.b.x, h);
+ h = hash_djb2_one_float(p_edge.b.y, h);
+ h = hash_djb2_one_float(p_edge.b.z, h);
+ return h;
+ }
+ };
+ struct EdgeUV2 {
+ Vector2 a;
+ Vector2 b;
+ Vector2i indices;
+ bool operator==(const EdgeUV2 &p_uv2) const {
+ return a == p_uv2.a && b == p_uv2.b;
+ }
+ bool seam_found = false;
+ EdgeUV2(Vector2 p_a, Vector2 p_b, Vector2i p_indices) {
+ a = p_a;
+ b = p_b;
+ indices = p_indices;
+ }
+ EdgeUV2() {}
+ };
+
+ struct Seam {
+ Vector2i a;
+ Vector2i b;
+ uint32_t slice;
+ bool operator<(const Seam &p_seam) const {
+ return slice < p_seam.slice;
+ }
+ };
+
+ struct VertexHash {
+ _FORCE_INLINE_ static uint32_t hash(const Vertex &p_vtx) {
+ uint32_t h = hash_djb2_one_float(p_vtx.position[0]);
+ h = hash_djb2_one_float(p_vtx.position[1], h);
+ h = hash_djb2_one_float(p_vtx.position[2], h);
+ h = hash_djb2_one_float(p_vtx.uv[0], h);
+ h = hash_djb2_one_float(p_vtx.uv[1], h);
+ h = hash_djb2_one_float(p_vtx.normal_xy[0], h);
+ h = hash_djb2_one_float(p_vtx.normal_xy[1], h);
+ h = hash_djb2_one_float(p_vtx.normal_z, h);
+ return h;
+ }
+ };
+
+ struct Box {
+ float min_bounds[3];
+ float pad0;
+ float max_bounds[3];
+ float pad1;
+ };
+
+ struct Triangle {
+ uint32_t indices[3];
+ uint32_t slice;
+ bool operator<(const Triangle &p_triangle) const {
+ return slice < p_triangle.slice;
+ }
+ };
+
+ Vector<MeshInstance> mesh_instances;
+
+ Vector<Light> lights;
+
+ struct TriangleSort {
+ uint32_t cell_index;
+ uint32_t triangle_index;
+ bool operator<(const TriangleSort &p_triangle_sort) const {
+ return cell_index < p_triangle_sort.cell_index; //sorting by triangle index in this case makes no sense
+ }
+ };
+
+ void _plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size);
+
+ struct RasterPushConstant {
+ float atlas_size[2];
+ float uv_offset[2];
+ float to_cell_size[3];
+ uint32_t base_triangle;
+ float to_cell_offset[3];
+ float bias;
+ int32_t grid_size[3];
+ uint32_t pad2;
+ };
+
+ struct RasterSeamsPushConstant {
+
+ uint32_t base_index;
+ uint32_t slice;
+ float uv_offset[2];
+ uint32_t debug;
+ float blend;
+ uint32_t pad[2];
+ };
+
+ struct PushConstant {
+ int32_t atlas_size[2];
+ uint32_t ray_count;
+ uint32_t ray_to;
+
+ float world_size[3];
+ float bias;
+
+ float to_cell_offset[3];
+ uint32_t ray_from;
+
+ float to_cell_size[3];
+ uint32_t light_count;
+
+ int32_t grid_size;
+ int32_t atlas_slice;
+ int32_t region_ofs[2];
+
+ float environment_xform[12];
+ };
+
+ Vector<Ref<Image>> bake_textures;
+ Vector<Color> probe_values;
+
+ 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 &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &grid_texture_sdf, 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);
+
+public:
+ virtual void add_mesh(const MeshData &p_mesh);
+ virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance);
+ 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);
+ 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);
+ virtual void add_probe(const Vector3 &p_position);
+ 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);
+
+ int get_bake_texture_count() const;
+ Ref<Image> get_bake_texture(int p_index) const;
+ int get_bake_mesh_count() const;
+ Variant get_bake_mesh_userdata(int p_index) const;
+ Rect2 get_bake_mesh_uv_scale(int p_index) const;
+ int get_bake_mesh_texture_slice(int p_index) const;
+ int get_bake_probe_count() const;
+ Vector3 get_bake_probe_point(int p_probe) const;
+ Vector<Color> get_bake_probe_sh(int p_probe) const;
+
+ LightmapperRD();
+};
+
+#endif // LIGHTMAPPER_H
diff --git a/modules/lightmapper_rd/lm_blendseams.glsl b/modules/lightmapper_rd/lm_blendseams.glsl
new file mode 100644
index 0000000000..ef1ece8ea1
--- /dev/null
+++ b/modules/lightmapper_rd/lm_blendseams.glsl
@@ -0,0 +1,117 @@
+/* clang-format off */
+[versions]
+
+lines = "#define MODE_LINES"
+triangles = "#define MODE_TRIANGLES"
+
+[vertex]
+
+#version 450
+
+VERSION_DEFINES
+
+#include "lm_common_inc.glsl"
+
+ /* clang-format on */
+
+ layout(push_constant, binding = 0, std430) uniform Params {
+ uint base_index;
+ uint slice;
+ vec2 uv_offset;
+ bool debug;
+ float blend;
+ uint pad[2];
+ } params;
+
+layout(location = 0) out vec3 uv_interp;
+
+void main() {
+
+#ifdef MODE_TRIANGLES
+
+ uint triangle_idx = params.base_index + gl_VertexIndex / 3;
+ uint triangle_subidx = gl_VertexIndex % 3;
+
+ vec2 uv;
+ if (triangle_subidx == 0) {
+ uv = vertices.data[triangles.data[triangle_idx].indices.x].uv;
+ } else if (triangle_subidx == 1) {
+ uv = vertices.data[triangles.data[triangle_idx].indices.y].uv;
+ } else {
+ uv = vertices.data[triangles.data[triangle_idx].indices.z].uv;
+ }
+
+ uv_interp = vec3(uv, float(params.slice));
+ gl_Position = vec4((uv + params.uv_offset) * 2.0 - 1.0, 0.0001, 1.0);
+
+#endif
+
+#ifdef MODE_LINES
+ uint seam_idx = params.base_index + gl_VertexIndex / 4;
+ uint seam_subidx = gl_VertexIndex % 4;
+
+ uint src_idx;
+ uint dst_idx;
+
+ if (seam_subidx == 0) {
+ src_idx = seams.data[seam_idx].b.x;
+ dst_idx = seams.data[seam_idx].a.x;
+ } else if (seam_subidx == 1) {
+ src_idx = seams.data[seam_idx].b.y;
+ dst_idx = seams.data[seam_idx].a.y;
+ } else if (seam_subidx == 2) {
+ src_idx = seams.data[seam_idx].a.x;
+ dst_idx = seams.data[seam_idx].b.x;
+ } else if (seam_subidx == 3) {
+ src_idx = seams.data[seam_idx].a.y;
+ dst_idx = seams.data[seam_idx].b.y;
+ }
+
+ vec2 src_uv = vertices.data[src_idx].uv;
+ vec2 dst_uv = vertices.data[dst_idx].uv + params.uv_offset;
+
+ uv_interp = vec3(src_uv, float(params.slice));
+ gl_Position = vec4(dst_uv * 2.0 - 1.0, 0.0001, 1.0);
+ ;
+#endif
+}
+
+/* clang-format off */
+[fragment]
+
+#version 450
+
+VERSION_DEFINES
+
+#include "lm_common_inc.glsl"
+
+ /* clang-format on */
+
+ layout(push_constant, binding = 0, std430) uniform Params {
+ uint base_index;
+ uint slice;
+ vec2 uv_offset;
+ bool debug;
+ float blend;
+ uint pad[2];
+ } params;
+
+layout(location = 0) in vec3 uv_interp;
+
+layout(location = 0) out vec4 dst_color;
+
+layout(set = 1, binding = 0) uniform texture2DArray src_color_tex;
+
+void main() {
+
+ if (params.debug) {
+#ifdef MODE_TRIANGLES
+ dst_color = vec4(1, 0, 1, 1);
+#else
+ dst_color = vec4(1, 1, 0, 1);
+#endif
+ } else {
+ vec4 src_color = textureLod(sampler2DArray(src_color_tex, linear_sampler), uv_interp, 0.0);
+ dst_color = vec4(src_color.rgb, params.blend); //mix
+ }
+}
diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl
new file mode 100644
index 0000000000..0ff455936e
--- /dev/null
+++ b/modules/lightmapper_rd/lm_common_inc.glsl
@@ -0,0 +1,92 @@
+
+/* SET 0, static data that does not change between any call */
+
+struct Vertex {
+ vec3 position;
+ float normal_z;
+ vec2 uv;
+ vec2 normal_xy;
+};
+
+layout(set = 0, binding = 1, std430) restrict readonly buffer Vertices {
+ Vertex data[];
+}
+vertices;
+
+struct Triangle {
+ uvec3 indices;
+ uint slice;
+};
+
+layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles {
+ Triangle data[];
+}
+triangles;
+
+struct Box {
+ vec3 min_bounds;
+ uint pad0;
+ vec3 max_bounds;
+ uint pad1;
+};
+
+layout(set = 0, binding = 3, std430) restrict readonly buffer Boxes {
+ Box data[];
+}
+boxes;
+
+layout(set = 0, binding = 4, std430) restrict readonly buffer GridIndices {
+ uint data[];
+}
+grid_indices;
+
+#define LIGHT_TYPE_DIRECTIONAL 0
+#define LIGHT_TYPE_OMNI 1
+#define LIGHT_TYPE_SPOT 2
+
+struct Light {
+ vec3 position;
+ uint type;
+
+ vec3 direction;
+ float energy;
+
+ vec3 color;
+ float size;
+
+ float range;
+ float attenuation;
+ float spot_angle;
+ float spot_attenuation;
+
+ bool static_bake;
+ uint pad[3];
+};
+
+layout(set = 0, binding = 5, std430) restrict readonly buffer Lights {
+ Light data[];
+}
+lights;
+
+struct Seam {
+ uvec2 a;
+ uvec2 b;
+};
+
+layout(set = 0, binding = 6, std430) restrict readonly buffer Seams {
+ Seam data[];
+}
+seams;
+
+layout(set = 0, binding = 7, std430) restrict readonly buffer Probes {
+ vec4 data[];
+}
+probe_positions;
+
+layout(set = 0, binding = 8) uniform utexture3D grid;
+layout(set = 0, binding = 9) uniform texture3D grid_sdf;
+
+layout(set = 0, binding = 10) uniform texture2DArray albedo_tex;
+layout(set = 0, binding = 11) uniform texture2DArray emission_tex;
+
+layout(set = 0, binding = 12) uniform sampler linear_sampler;
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
new file mode 100644
index 0000000000..a178bd9b2e
--- /dev/null
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -0,0 +1,657 @@
+/* clang-format off */
+[versions]
+
+primary = "#define MODE_DIRECT_LIGHT"
+secondary = "#define MODE_BOUNCE_LIGHT"
+dilate = "#define MODE_DILATE"
+unocclude = "#define MODE_UNOCCLUDE"
+light_probes = "#define MODE_LIGHT_PROBES"
+
+[compute]
+
+#version 450
+
+VERSION_DEFINES
+
+// One 2D local group focusing in one layer at a time, though all
+// in parallel (no barriers) makes more sense than a 3D local group
+// as this can take more advantage of the cache for each group.
+
+#ifdef MODE_LIGHT_PROBES
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#else
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#endif
+
+#include "lm_common_inc.glsl"
+
+/* clang-format on */
+
+#ifdef MODE_LIGHT_PROBES
+
+layout(set = 1, binding = 0, std430) restrict buffer LightProbeData {
+ vec4 data[];
+}
+light_probes;
+
+layout(set = 1, binding = 1) uniform texture2DArray source_light;
+layout(set = 1, binding = 2) uniform texture2DArray source_direct_light; //also need the direct light, which was omitted
+layout(set = 1, binding = 3) uniform texture2D environment;
+#endif
+
+#ifdef MODE_UNOCCLUDE
+
+layout(rgba32f, set = 1, binding = 0) uniform restrict image2DArray position;
+layout(rgba32f, set = 1, binding = 1) uniform restrict readonly image2DArray unocclude;
+
+#endif
+
+#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT)
+
+layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
+layout(set = 1, binding = 1) uniform texture2DArray source_light;
+layout(set = 1, binding = 2) uniform texture2DArray source_position;
+layout(set = 1, binding = 3) uniform texture2DArray source_normal;
+layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
+
+#endif
+
+#ifdef MODE_BOUNCE_LIGHT
+layout(rgba32f, set = 1, binding = 5) uniform restrict image2DArray bounce_accum;
+layout(set = 1, binding = 6) uniform texture2D environment;
+#endif
+#ifdef MODE_DIRECT_LIGHT
+layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic;
+#endif
+
+#ifdef MODE_DILATE
+layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
+layout(set = 1, binding = 1) uniform texture2DArray source_light;
+#endif
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ ivec2 atlas_size; // x used for light probe mode total probes
+ uint ray_count;
+ uint ray_to;
+
+ vec3 world_size;
+ float bias;
+
+ vec3 to_cell_offset;
+ uint ray_from;
+
+ vec3 to_cell_size;
+ uint light_count;
+
+ int grid_size;
+ int atlas_slice;
+ ivec2 region_ofs;
+
+ mat3x4 env_transform;
+}
+params;
+
+//check it, but also return distance and barycentric coords (for uv lookup)
+bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, vec3 p2, out float r_distance, out vec3 r_barycentric) {
+
+ const vec3 e0 = p1 - p0;
+ const vec3 e1 = p0 - p2;
+ vec3 triangleNormal = cross(e1, e0);
+
+ const vec3 e2 = (1.0 / dot(triangleNormal, dir)) * (p0 - from);
+ const vec3 i = cross(dir, e2);
+
+ r_barycentric.y = dot(i, e1);
+ r_barycentric.z = dot(i, e0);
+ r_barycentric.x = 1.0 - (r_barycentric.z + r_barycentric.y);
+ r_distance = dot(triangleNormal, e2);
+ return (r_distance > params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0)));
+}
+
+bool trace_ray(vec3 p_from, vec3 p_to
+#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
+ ,
+ out uint r_triangle, out vec3 r_barycentric
+#endif
+#if defined(MODE_UNOCCLUDE)
+ ,
+ out float r_distance, out vec3 r_normal
+#endif
+) {
+
+ /* world coords */
+
+ vec3 rel = p_to - p_from;
+ float rel_len = length(rel);
+ vec3 dir = normalize(rel);
+ vec3 inv_dir = 1.0 / dir;
+
+ /* cell coords */
+
+ vec3 from_cell = (p_from - params.to_cell_offset) * params.to_cell_size;
+ vec3 to_cell = (p_to - params.to_cell_offset) * params.to_cell_size;
+
+ //prepare DDA
+ vec3 rel_cell = to_cell - from_cell;
+ ivec3 icell = ivec3(from_cell);
+ ivec3 iendcell = ivec3(to_cell);
+ vec3 dir_cell = normalize(rel_cell);
+ vec3 delta = abs(1.0 / dir_cell); //vec3(length(rel_cell)) / rel_cell);
+ ivec3 step = ivec3(sign(rel_cell));
+ vec3 side = (sign(rel_cell) * (vec3(icell) - from_cell) + (sign(rel_cell) * 0.5) + 0.5) * delta;
+
+ uint iters = 0;
+ while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(params.grid_size))) && iters < 1000) {
+
+ uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy;
+ if (cell_data.x > 0) { //triangles here
+
+ bool hit = false;
+#if defined(MODE_UNOCCLUDE)
+ bool hit_backface = false;
+#endif
+ float best_distance = 1e20;
+
+ for (uint i = 0; i < cell_data.x; i++) {
+ uint tidx = grid_indices.data[cell_data.y + i];
+
+ //Ray-Box test
+ vec3 t0 = (boxes.data[tidx].min_bounds - p_from) * inv_dir;
+ vec3 t1 = (boxes.data[tidx].max_bounds - p_from) * inv_dir;
+ vec3 tmin = min(t0, t1), tmax = max(t0, t1);
+
+ if (max(tmin.x, max(tmin.y, tmin.z)) <= min(tmax.x, min(tmax.y, tmax.z))) {
+ continue; //ray box failed
+ }
+
+ //prepare triangle vertices
+ vec3 vtx0 = vertices.data[triangles.data[tidx].indices.x].position;
+ vec3 vtx1 = vertices.data[triangles.data[tidx].indices.y].position;
+ vec3 vtx2 = vertices.data[triangles.data[tidx].indices.z].position;
+#if defined(MODE_UNOCCLUDE)
+ vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2)));
+
+ bool backface = dot(normal, dir) >= 0.0;
+#endif
+ float distance;
+ vec3 barycentric;
+
+ if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) {
+#ifdef MODE_DIRECT_LIGHT
+ return true; //any hit good
+#endif
+
+#if defined(MODE_UNOCCLUDE)
+ if (!backface) {
+ // the case of meshes having both a front and back face in the same plane is more common than
+ // expected, so if this is a front-face, bias it closer to the ray origin, so it always wins over the back-face
+ distance = max(params.bias, distance - params.bias);
+ }
+
+ hit = true;
+
+ if (distance < best_distance) {
+ hit_backface = backface;
+ best_distance = distance;
+ r_distance = distance;
+ r_normal = normal;
+ }
+
+#endif
+
+#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
+
+ hit = true;
+ if (distance < best_distance) {
+ best_distance = distance;
+ r_triangle = tidx;
+ r_barycentric = barycentric;
+ }
+
+#endif
+ }
+ }
+#if defined(MODE_UNOCCLUDE)
+
+ if (hit) {
+ return hit_backface;
+ }
+#endif
+#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
+ if (hit) {
+ return true;
+ }
+#endif
+ }
+
+ if (icell == iendcell) {
+ break;
+ }
+
+ bvec3 mask = lessThanEqual(side.xyz, min(side.yzx, side.zxy));
+ side += vec3(mask) * delta;
+ icell += ivec3(vec3(mask)) * step;
+
+ iters++;
+ }
+
+ return false;
+}
+
+const float PI = 3.14159265f;
+const float GOLDEN_ANGLE = PI * (3.0 - sqrt(5.0));
+
+vec3 vogel_hemisphere(uint p_index, uint p_count, float p_offset) {
+ float r = sqrt(float(p_index) + 0.5f) / sqrt(float(p_count));
+ float theta = float(p_index) * GOLDEN_ANGLE + p_offset;
+ float y = cos(r * PI * 0.5);
+ float l = sin(r * PI * 0.5);
+ return vec3(l * cos(theta), l * sin(theta), y);
+}
+
+float quick_hash(vec2 pos) {
+ return fract(sin(dot(pos * 19.19, vec2(49.5791, 97.413))) * 49831.189237);
+}
+
+void main() {
+
+#ifdef MODE_LIGHT_PROBES
+ int probe_index = int(gl_GlobalInvocationID.x);
+ if (probe_index >= params.atlas_size.x) { //too large, do nothing
+ return;
+ }
+
+#else
+ ivec2 atlas_pos = ivec2(gl_GlobalInvocationID.xy) + params.region_ofs;
+ if (any(greaterThanEqual(atlas_pos, params.atlas_size))) { //too large, do nothing
+ return;
+ }
+#endif
+
+#ifdef MODE_DIRECT_LIGHT
+
+ vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
+ if (length(normal) < 0.5) {
+ return; //empty texel, no process
+ }
+ vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
+
+ //go through all lights
+ //start by own light (emissive)
+ vec3 static_light = vec3(0.0);
+ vec3 dynamic_light = vec3(0.0);
+
+#ifdef USE_SH_LIGHTMAPS
+ vec4 sh_accum[4] = vec4[](
+ vec4(0.0, 0.0, 0.0, 1.0),
+ vec4(0.0, 0.0, 0.0, 1.0),
+ vec4(0.0, 0.0, 0.0, 1.0),
+ vec4(0.0, 0.0, 0.0, 1.0));
+#endif
+
+ for (uint i = 0; i < params.light_count; i++) {
+
+ vec3 light_pos;
+ float attenuation;
+ if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) {
+ vec3 light_vec = lights.data[i].direction;
+ light_pos = position - light_vec * length(params.world_size);
+ attenuation = 1.0;
+ } else {
+ light_pos = lights.data[i].position;
+ float d = distance(position, light_pos);
+ if (d > lights.data[i].range) {
+ continue;
+ }
+
+ d /= lights.data[i].range;
+
+ attenuation = pow(max(1.0 - d, 0.0), lights.data[i].attenuation);
+
+ if (lights.data[i].type == LIGHT_TYPE_SPOT) {
+
+ vec3 rel = normalize(position - light_pos);
+ float angle = acos(dot(rel, lights.data[i].direction));
+ if (angle > lights.data[i].spot_angle) {
+ continue; //invisible, dont try
+ }
+
+ float d = clamp(angle / lights.data[i].spot_angle, 0, 1);
+ attenuation *= pow(1.0 - d, lights.data[i].spot_attenuation);
+ }
+ }
+
+ vec3 light_dir = normalize(light_pos - position);
+ attenuation *= max(0.0, dot(normal, light_dir));
+
+ if (attenuation <= 0.0001) {
+ continue; //no need to do anything
+ }
+
+ if (!trace_ray(position + light_dir * params.bias, light_pos)) {
+ vec3 light = lights.data[i].color * lights.data[i].energy * attenuation;
+ if (lights.data[i].static_bake) {
+ static_light += light;
+#ifdef USE_SH_LIGHTMAPS
+
+ float c[4] = float[](
+ 0.282095, //l0
+ 0.488603 * light_dir.y, //l1n1
+ 0.488603 * light_dir.z, //l1n0
+ 0.488603 * light_dir.x //l1p1
+ );
+
+ for (uint j = 0; j < 4; j++) {
+ sh_accum[j].rgb += light * c[j] * (1.0 / 3.0);
+ }
+#endif
+
+ } else {
+ dynamic_light += light;
+ }
+ }
+ }
+
+ vec3 albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
+ vec3 emissive = texelFetch(sampler2DArray(emission_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb;
+
+ dynamic_light *= albedo; //if it will bounce, must multiply by albedo
+ dynamic_light += emissive;
+
+ //keep for lightprobes
+ imageStore(primary_dynamic, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0));
+
+ dynamic_light += static_light * albedo; //send for bounces
+ imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0));
+
+#ifdef USE_SH_LIGHTMAPS
+ //keep for adding at the end
+ imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 0), sh_accum[0]);
+ imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 1), sh_accum[1]);
+ imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 2), sh_accum[2]);
+ imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 3), sh_accum[3]);
+
+#else
+ imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(static_light, 1.0));
+#endif
+
+#endif
+
+#ifdef MODE_BOUNCE_LIGHT
+
+ vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
+ if (length(normal) < 0.5) {
+ return; //empty texel, no process
+ }
+
+ vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
+
+ vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
+ vec3 tangent = normalize(cross(v0, normal));
+ vec3 bitangent = normalize(cross(tangent, normal));
+ mat3 normal_mat = mat3(tangent, bitangent, normal);
+
+#ifdef USE_SH_LIGHTMAPS
+ vec4 sh_accum[4] = vec4[](
+ vec4(0.0, 0.0, 0.0, 1.0),
+ vec4(0.0, 0.0, 0.0, 1.0),
+ vec4(0.0, 0.0, 0.0, 1.0),
+ vec4(0.0, 0.0, 0.0, 1.0));
+#endif
+ vec3 light_average = vec3(0.0);
+ for (uint i = params.ray_from; i < params.ray_to; i++) {
+ vec3 ray_dir = normal_mat * vogel_hemisphere(i, params.ray_count, quick_hash(vec2(atlas_pos)));
+
+ uint tidx;
+ vec3 barycentric;
+
+ vec3 light;
+ if (trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric)) {
+ //hit a triangle
+ vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
+ vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
+ vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv;
+ vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
+
+ light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
+ } else {
+ //did not hit a triangle, reach out for the sky
+ vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
+
+ vec2 st = vec2(
+ atan(sky_dir.x, sky_dir.z),
+ acos(sky_dir.y));
+
+ if (st.x < 0.0)
+ st.x += PI * 2.0;
+
+ st /= vec2(PI * 2.0, PI);
+
+ light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb;
+ }
+
+ light_average += light;
+
+#ifdef USE_SH_LIGHTMAPS
+
+ float c[4] = float[](
+ 0.282095, //l0
+ 0.488603 * ray_dir.y, //l1n1
+ 0.488603 * ray_dir.z, //l1n0
+ 0.488603 * ray_dir.x //l1p1
+ );
+
+ for (uint j = 0; j < 4; j++) {
+ sh_accum[j].rgb += light * c[j] * (8.0 / float(params.ray_count));
+ }
+#endif
+ }
+
+ vec3 light_total;
+ if (params.ray_from == 0) {
+ light_total = vec3(0.0);
+ } else {
+ light_total = imageLoad(bounce_accum, ivec3(atlas_pos, params.atlas_slice)).rgb;
+ }
+
+ light_total += light_average;
+
+#ifdef USE_SH_LIGHTMAPS
+
+ for (int i = 0; i < 4; i++) {
+ vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i));
+ accum.rgb += sh_accum[i].rgb;
+ imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), accum);
+ }
+
+#endif
+ if (params.ray_to == params.ray_count) {
+ light_total /= float(params.ray_count);
+ imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0));
+#ifndef USE_SH_LIGHTMAPS
+ vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice));
+ accum.rgb += light_total;
+ imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum);
+#endif
+ } else {
+ imageStore(bounce_accum, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0));
+ }
+
+#endif
+
+#ifdef MODE_UNOCCLUDE
+
+ //texel_size = 0.5;
+ //compute tangents
+
+ vec4 position_alpha = imageLoad(position, ivec3(atlas_pos, params.atlas_slice));
+ if (position_alpha.a < 0.5) {
+ return;
+ }
+
+ vec3 vertex_pos = position_alpha.xyz;
+ vec4 normal_tsize = imageLoad(unocclude, ivec3(atlas_pos, params.atlas_slice));
+
+ vec3 face_normal = normal_tsize.xyz;
+ float texel_size = normal_tsize.w;
+
+ vec3 v0 = abs(face_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
+ vec3 tangent = normalize(cross(v0, face_normal));
+ vec3 bitangent = normalize(cross(tangent, face_normal));
+ vec3 base_pos = vertex_pos + face_normal * params.bias; //raise a bit
+
+ vec3 rays[4] = vec3[](tangent, bitangent, -tangent, -bitangent);
+ float min_d = 1e20;
+ for (int i = 0; i < 4; i++) {
+ vec3 ray_to = base_pos + rays[i] * texel_size;
+ float d;
+ vec3 norm;
+
+ if (trace_ray(base_pos, ray_to, d, norm)) {
+
+ if (d < min_d) {
+ vertex_pos = base_pos + rays[i] * d + norm * params.bias * 10.0; //this bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back.
+ min_d = d;
+ }
+ }
+ }
+
+ position_alpha.xyz = vertex_pos;
+
+ imageStore(position, ivec3(atlas_pos, params.atlas_slice), position_alpha);
+
+#endif
+
+#ifdef MODE_LIGHT_PROBES
+
+ vec3 position = probe_positions.data[probe_index].xyz;
+
+ vec4 probe_sh_accum[9] = vec4[](
+ vec4(0.0),
+ vec4(0.0),
+ vec4(0.0),
+ vec4(0.0),
+ vec4(0.0),
+ vec4(0.0),
+ vec4(0.0),
+ vec4(0.0),
+ vec4(0.0));
+
+ for (uint i = params.ray_from; i < params.ray_to; i++) {
+ vec3 ray_dir = vogel_hemisphere(i, params.ray_count, quick_hash(vec2(float(probe_index), 0.0)));
+ if (bool(i & 1)) {
+ //throw to both sides, so alternate them
+ ray_dir.z *= -1.0;
+ }
+
+ uint tidx;
+ vec3 barycentric;
+ vec3 light;
+
+ if (trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric)) {
+ vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
+ vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv;
+ vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv;
+ vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
+
+ light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
+ light += textureLod(sampler2DArray(source_direct_light, linear_sampler), uvw, 0.0).rgb;
+ } else {
+
+ //did not hit a triangle, reach out for the sky
+ vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
+
+ vec2 st = vec2(
+ atan(sky_dir.x, sky_dir.z),
+ acos(sky_dir.y));
+
+ if (st.x < 0.0)
+ st.x += PI * 2.0;
+
+ st /= vec2(PI * 2.0, PI);
+
+ light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb;
+ }
+
+ {
+ float c[9] = float[](
+ 0.282095, //l0
+ 0.488603 * ray_dir.y, //l1n1
+ 0.488603 * ray_dir.z, //l1n0
+ 0.488603 * ray_dir.x, //l1p1
+ 1.092548 * ray_dir.x * ray_dir.y, //l2n2
+ 1.092548 * ray_dir.y * ray_dir.z, //l2n1
+ //0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20
+ 0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20
+ 1.092548 * ray_dir.x * ray_dir.z, //l2p1
+ 0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2
+ );
+
+ for (uint j = 0; j < 9; j++) {
+ probe_sh_accum[j].rgb += light * c[j];
+ }
+ }
+ }
+
+ if (params.ray_from > 0) {
+ for (uint j = 0; j < 9; j++) { //accum from existing
+ probe_sh_accum[j] += light_probes.data[probe_index * 9 + j];
+ }
+ }
+
+ if (params.ray_to == params.ray_count) {
+ for (uint j = 0; j < 9; j++) { //accum from existing
+ probe_sh_accum[j] *= 4.0 / float(params.ray_count);
+ }
+ }
+
+ for (uint j = 0; j < 9; j++) { //accum from existing
+ light_probes.data[probe_index * 9 + j] = probe_sh_accum[j];
+ }
+
+#endif
+
+#ifdef MODE_DILATE
+
+ vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0);
+ //sides first, as they are closer
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 0), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 1), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 0), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -1), params.atlas_slice), 0);
+ //endpoints second
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -1), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 1), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -1), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 1), params.atlas_slice), 0);
+
+ //far sides third
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 0), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, 2), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 0), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(0, -2), params.atlas_slice), 0);
+
+ //far-mid endpoints
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -1), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 1), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -1), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 1), params.atlas_slice), 0);
+
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, -2), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-1, 2), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, -2), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(1, 2), params.atlas_slice), 0);
+ //far endpoints
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, -2), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(-2, 2), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, -2), params.atlas_slice), 0);
+ c = c.a > 0.5 ? c : texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos + ivec2(2, 2), params.atlas_slice), 0);
+
+ imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), c);
+
+#endif
+}
diff --git a/modules/lightmapper_rd/lm_raster.glsl b/modules/lightmapper_rd/lm_raster.glsl
new file mode 100644
index 0000000000..ae3038aead
--- /dev/null
+++ b/modules/lightmapper_rd/lm_raster.glsl
@@ -0,0 +1,170 @@
+/* clang-format off */
+[vertex]
+
+#version 450
+
+VERSION_DEFINES
+
+#include "lm_common_inc.glsl"
+
+ /* clang-format on */
+
+ layout(location = 0) out vec3 vertex_interp;
+layout(location = 1) out vec3 normal_interp;
+layout(location = 2) out vec2 uv_interp;
+layout(location = 3) out vec3 barycentric;
+layout(location = 4) flat out uvec3 vertex_indices;
+layout(location = 5) flat out vec3 face_normal;
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ vec2 atlas_size;
+ vec2 uv_offset;
+ vec3 to_cell_size;
+ uint base_triangle;
+ vec3 to_cell_offset;
+ float bias;
+ ivec3 grid_size;
+ uint pad2;
+}
+params;
+
+/* clang-format on */
+
+void main() {
+
+ uint triangle_idx = params.base_triangle + gl_VertexIndex / 3;
+ uint triangle_subidx = gl_VertexIndex % 3;
+
+ vertex_indices = triangles.data[triangle_idx].indices;
+
+ uint vertex_idx;
+ if (triangle_subidx == 0) {
+ vertex_idx = vertex_indices.x;
+ barycentric = vec3(1, 0, 0);
+ } else if (triangle_subidx == 1) {
+ vertex_idx = vertex_indices.y;
+ barycentric = vec3(0, 1, 0);
+ } else {
+ vertex_idx = vertex_indices.z;
+ barycentric = vec3(0, 0, 1);
+ }
+
+ vertex_interp = vertices.data[vertex_idx].position;
+ uv_interp = vertices.data[vertex_idx].uv;
+ normal_interp = vec3(vertices.data[vertex_idx].normal_xy, vertices.data[vertex_idx].normal_z);
+
+ face_normal = -normalize(cross((vertices.data[vertex_indices.x].position - vertices.data[vertex_indices.y].position), (vertices.data[vertex_indices.x].position - vertices.data[vertex_indices.z].position)));
+
+ gl_Position = vec4((uv_interp + params.uv_offset) * 2.0 - 1.0, 0.0001, 1.0);
+ ;
+}
+
+/* clang-format off */
+
+[fragment]
+
+#version 450
+
+VERSION_DEFINES
+
+#include "lm_common_inc.glsl"
+
+
+layout(push_constant, binding = 0, std430) uniform Params {
+ vec2 atlas_size;
+ vec2 uv_offset;
+ vec3 to_cell_size;
+ uint base_triangle;
+ vec3 to_cell_offset;
+ float bias;
+ ivec3 grid_size;
+ uint pad2;
+} params;
+
+/* clang-format on */
+
+layout(location = 0) in vec3 vertex_interp;
+layout(location = 1) in vec3 normal_interp;
+layout(location = 2) in vec2 uv_interp;
+layout(location = 3) in vec3 barycentric;
+layout(location = 4) in flat uvec3 vertex_indices;
+layout(location = 5) in flat vec3 face_normal;
+
+layout(location = 0) out vec4 position;
+layout(location = 1) out vec4 normal;
+layout(location = 2) out vec4 unocclude;
+
+void main() {
+
+ vec3 vertex_pos = vertex_interp;
+
+ {
+ // smooth out vertex position by interpolating its projection in the 3 normal planes (normal plane is created by vertex pos and normal)
+ // because we don't want to interpolate inwards, normals found pointing inwards are pushed out.
+
+ vec3 pos_a = vertices.data[vertex_indices.x].position;
+ vec3 pos_b = vertices.data[vertex_indices.y].position;
+ vec3 pos_c = vertices.data[vertex_indices.z].position;
+ vec3 center = (pos_a + pos_b + pos_c) * 0.3333333;
+ vec3 norm_a = vec3(vertices.data[vertex_indices.x].normal_xy, vertices.data[vertex_indices.x].normal_z);
+ vec3 norm_b = vec3(vertices.data[vertex_indices.y].normal_xy, vertices.data[vertex_indices.y].normal_z);
+ vec3 norm_c = vec3(vertices.data[vertex_indices.z].normal_xy, vertices.data[vertex_indices.z].normal_z);
+
+ {
+ vec3 dir_a = normalize(pos_a - center);
+ float d_a = dot(dir_a, norm_a);
+ if (d_a < 0) {
+ //pointing inwards
+ norm_a = normalize(norm_a - dir_a * d_a);
+ }
+ }
+ {
+ vec3 dir_b = normalize(pos_b - center);
+ float d_b = dot(dir_b, norm_b);
+ if (d_b < 0) {
+ //pointing inwards
+ norm_b = normalize(norm_b - dir_b * d_b);
+ }
+ }
+ {
+ vec3 dir_c = normalize(pos_c - center);
+ float d_c = dot(dir_c, norm_c);
+ if (d_c < 0) {
+ //pointing inwards
+ norm_c = normalize(norm_c - dir_c * d_c);
+ }
+ }
+
+ float d_a = dot(norm_a, pos_a);
+ float d_b = dot(norm_b, pos_b);
+ float d_c = dot(norm_c, pos_c);
+
+ vec3 proj_a = vertex_pos - norm_a * (dot(norm_a, vertex_pos) - d_a);
+ vec3 proj_b = vertex_pos - norm_b * (dot(norm_b, vertex_pos) - d_b);
+ vec3 proj_c = vertex_pos - norm_c * (dot(norm_c, vertex_pos) - d_c);
+
+ vec3 smooth_position = proj_a * barycentric.x + proj_b * barycentric.y + proj_c * barycentric.z;
+
+ if (dot(face_normal, smooth_position) > dot(face_normal, vertex_pos)) { //only project outwards
+ vertex_pos = smooth_position;
+ }
+ }
+
+ {
+ // unocclusion technique based on:
+ // https://ndotl.wordpress.com/2018/08/29/baking-artifact-free-lightmaps/
+
+ /* compute texel size */
+ vec3 delta_uv = max(abs(dFdx(vertex_interp)), abs(dFdy(vertex_interp)));
+ float texel_size = max(delta_uv.x, max(delta_uv.y, delta_uv.z));
+ texel_size *= sqrt(2.0); //expand to unit box edge length (again, worst case)
+
+ unocclude.xyz = face_normal;
+ unocclude.w = texel_size;
+
+ //continued on lm_compute.glsl
+ }
+
+ position = vec4(vertex_pos, 1.0);
+ normal = vec4(normalize(normal_interp), 1.0);
+}
diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp
new file mode 100644
index 0000000000..f3938f3190
--- /dev/null
+++ b/modules/lightmapper_rd/register_types.cpp
@@ -0,0 +1,64 @@
+/*************************************************************************/
+/* register_types.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "core/project_settings.h"
+#include "lightmapper_rd.h"
+#include "scene/3d/lightmapper.h"
+
+#ifndef _3D_DISABLED
+static Lightmapper *create_lightmapper_rd() {
+ return memnew(LightmapperRD);
+}
+#endif
+
+void register_lightmapper_rd_types() {
+
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/low_quality_ray_count", 16);
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/medium_quality_ray_count", 64);
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/high_quality_ray_count", 256);
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/ultra_quality_ray_count", 1024);
+ GLOBAL_DEF("rendering/gpu_lightmapper/performance/max_rays_per_pass", 32);
+ GLOBAL_DEF("rendering/gpu_lightmapper/performance/region_size", 512);
+
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/low_quality_probe_ray_count", 64);
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/medium_quality_probe_ray_count", 256);
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/high_quality_probe_ray_count", 512);
+ GLOBAL_DEF("rendering/gpu_lightmapper/quality/ultra_quality_probe_ray_count", 2048);
+ GLOBAL_DEF("rendering/gpu_lightmapper/performance/max_rays_per_probe_pass", 64);
+#ifndef _3D_DISABLED
+ ClassDB::register_class<LightmapperRD>();
+ Lightmapper::create_gpu = create_lightmapper_rd;
+#endif
+}
+
+void unregister_lightmapper_rd_types() {
+}
diff --git a/modules/lightmapper_rd/register_types.h b/modules/lightmapper_rd/register_types.h
new file mode 100644
index 0000000000..b0e15a927f
--- /dev/null
+++ b/modules/lightmapper_rd/register_types.h
@@ -0,0 +1,37 @@
+/*************************************************************************/
+/* register_types.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 LIGHTMAPPER_RD_REGISTER_TYPES_H
+#define LIGHTMAPPER_RD_REGISTER_TYPES_H
+
+void register_lightmapper_rd_types();
+void unregister_lightmapper_rd_types();
+
+#endif // XATLAS_UNWRAP_REGISTER_TYPES_H
diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp
index b2aa5f5827..37477e1246 100755..100644
--- a/modules/mbedtls/packet_peer_mbed_dtls.cpp
+++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp
@@ -36,7 +36,8 @@
int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) {
- if (buf == nullptr || len <= 0) return 0;
+ if (buf == nullptr || len <= 0)
+ return 0;
PacketPeerMbedDTLS *sp = (PacketPeerMbedDTLS *)ctx;
@@ -53,7 +54,8 @@ int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len
int PacketPeerMbedDTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) {
- if (buf == nullptr || len <= 0) return 0;
+ if (buf == nullptr || len <= 0)
+ return 0;
PacketPeerMbedDTLS *sp = (PacketPeerMbedDTLS *)ctx;
diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp
index 983095c536..af36b29dac 100755..100644
--- a/modules/mbedtls/stream_peer_mbedtls.cpp
+++ b/modules/mbedtls/stream_peer_mbedtls.cpp
@@ -35,7 +35,8 @@
int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) {
- if (buf == nullptr || len <= 0) return 0;
+ if (buf == nullptr || len <= 0)
+ return 0;
StreamPeerMbedTLS *sp = (StreamPeerMbedTLS *)ctx;
@@ -54,7 +55,8 @@ int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len)
int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) {
- if (buf == nullptr || len <= 0) return 0;
+ if (buf == nullptr || len <= 0)
+ return 0;
StreamPeerMbedTLS *sp = (StreamPeerMbedTLS *)ctx;
diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp
index 48276c0d3d..6d10de0096 100644
--- a/modules/mobile_vr/mobile_vr_interface.cpp
+++ b/modules/mobile_vr/mobile_vr_interface.cpp
@@ -61,13 +61,19 @@ Vector3 MobileVRInterface::scale_magneto(const Vector3 &p_magnetometer) {
};
// adjust our min and max
- if (mag_raw.x > mag_next_max.x) mag_next_max.x = mag_raw.x;
- if (mag_raw.y > mag_next_max.y) mag_next_max.y = mag_raw.y;
- if (mag_raw.z > mag_next_max.z) mag_next_max.z = mag_raw.z;
-
- if (mag_raw.x < mag_next_min.x) mag_next_min.x = mag_raw.x;
- if (mag_raw.y < mag_next_min.y) mag_next_min.y = mag_raw.y;
- if (mag_raw.z < mag_next_min.z) mag_next_min.z = mag_raw.z;
+ if (mag_raw.x > mag_next_max.x)
+ mag_next_max.x = mag_raw.x;
+ if (mag_raw.y > mag_next_max.y)
+ mag_next_max.y = mag_raw.y;
+ if (mag_raw.z > mag_next_max.z)
+ mag_next_max.z = mag_raw.z;
+
+ if (mag_raw.x < mag_next_min.x)
+ mag_next_min.x = mag_raw.x;
+ if (mag_raw.y < mag_next_min.y)
+ mag_next_min.y = mag_raw.y;
+ if (mag_raw.z < mag_next_min.z)
+ mag_next_min.z = mag_raw.z;
// scale our x, y and z
if (!(mag_current_max.x - mag_current_min.x)) {
diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py
index cffacf2577..7391e8790d 100644
--- a/modules/mono/build_scripts/godot_tools_build.py
+++ b/modules/mono/build_scripts/godot_tools_build.py
@@ -13,13 +13,9 @@ def build_godot_tools(source, target, env):
solution_path = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln")
build_config = "Debug" if env["target"] == "debug" else "Release"
- # Custom build target to make sure output is always copied to the data dir.
- extra_build_args = ["/Target:Build;GodotTools:BuildAlwaysCopyToDataDir"]
+ from .solution_builder import build_solution
- from .solution_builder import build_solution, nuget_restore
-
- nuget_restore(env, solution_path)
- build_solution(env, solution_path, build_config, extra_build_args)
+ build_solution(env, solution_path, build_config)
# No need to copy targets. The GodotTools csproj takes care of copying them.
diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py
index db6b4ff7aa..371819fd72 100644
--- a/modules/mono/build_scripts/solution_builder.py
+++ b/modules/mono/build_scripts/solution_builder.py
@@ -4,91 +4,41 @@ import os
verbose = False
-def find_nuget_unix():
- import os
-
- if "NUGET_PATH" in os.environ:
- hint_path = os.environ["NUGET_PATH"]
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
- hint_path = os.path.join(hint_path, "nuget")
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
-
+def find_dotnet_cli():
import os.path
- import sys
-
- hint_dirs = ["/opt/novell/mono/bin"]
- if sys.platform == "darwin":
- hint_dirs = [
- "/Library/Frameworks/Mono.framework/Versions/Current/bin",
- "/usr/local/var/homebrew/linked/mono/bin",
- ] + hint_dirs
-
- for hint_dir in hint_dirs:
- hint_path = os.path.join(hint_dir, "nuget")
- if os.path.isfile(hint_path):
- return hint_path
- elif os.path.isfile(hint_path + ".exe"):
- return hint_path + ".exe"
-
- for hint_dir in os.environ["PATH"].split(os.pathsep):
- hint_dir = hint_dir.strip('"')
- hint_path = os.path.join(hint_dir, "nuget")
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
- if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
- return hint_path + ".exe"
-
- return None
-
-
-def find_nuget_windows(env):
- import os
-
- if "NUGET_PATH" in os.environ:
- hint_path = os.environ["NUGET_PATH"]
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
- hint_path = os.path.join(hint_path, "nuget.exe")
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
-
- from .mono_reg_utils import find_mono_root_dir
-
- mono_root = env["mono_prefix"] or find_mono_root_dir(env["bits"])
-
- if mono_root:
- mono_bin_dir = os.path.join(mono_root, "bin")
- nuget_mono = os.path.join(mono_bin_dir, "nuget.bat")
-
- if os.path.isfile(nuget_mono):
- return nuget_mono
-
- # Standalone NuGet
- for hint_dir in os.environ["PATH"].split(os.pathsep):
- hint_dir = hint_dir.strip('"')
- hint_path = os.path.join(hint_dir, "nuget.exe")
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
-
- return None
+ if os.name == "nt":
+ windows_exts = os.environ["PATHEXT"]
+ windows_exts = windows_exts.split(os.pathsep) if windows_exts else []
+
+ for hint_dir in os.environ["PATH"].split(os.pathsep):
+ hint_dir = hint_dir.strip('"')
+ hint_path = os.path.join(hint_dir, "dotnet")
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+ if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
+ return hint_path + ".exe"
+ else:
+ for hint_dir in os.environ["PATH"].split(os.pathsep):
+ hint_dir = hint_dir.strip('"')
+ hint_path = os.path.join(hint_dir, "dotnet")
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
-def find_msbuild_unix(filename):
+def find_msbuild_unix():
import os.path
import sys
- hint_dirs = ["/opt/novell/mono/bin"]
+ hint_dirs = []
if sys.platform == "darwin":
- hint_dirs = [
+ hint_dirs[:0] = [
"/Library/Frameworks/Mono.framework/Versions/Current/bin",
"/usr/local/var/homebrew/linked/mono/bin",
- ] + hint_dirs
+ ]
for hint_dir in hint_dirs:
- hint_path = os.path.join(hint_dir, filename)
+ hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path):
return hint_path
elif os.path.isfile(hint_path + ".exe"):
@@ -96,7 +46,7 @@ def find_msbuild_unix(filename):
for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
- hint_path = os.path.join(hint_dir, filename)
+ hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
@@ -158,21 +108,6 @@ def run_command(command, args, env_override=None, name=None):
raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode))
-def nuget_restore(env, *args):
- global verbose
- verbose = env["verbose"]
-
- # Find NuGet
- nuget_path = find_nuget_windows(env) if os.name == "nt" else find_nuget_unix()
- if nuget_path is None:
- raise RuntimeError("Cannot find NuGet executable")
-
- print("NuGet path: " + nuget_path)
-
- # Do NuGet restore
- run_command(nuget_path, ["restore"] + list(args), name="nuget restore")
-
-
def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
global verbose
verbose = env["verbose"]
@@ -183,38 +118,33 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
if "PLATFORM" in msbuild_env:
del msbuild_env["PLATFORM"]
- # Find MSBuild
- if os.name == "nt":
- msbuild_info = find_msbuild_windows(env)
- if msbuild_info is None:
- raise RuntimeError("Cannot find MSBuild executable")
- msbuild_path = msbuild_info[0]
- msbuild_env.update(msbuild_info[1])
- else:
- msbuild_path = find_msbuild_unix("msbuild")
- if msbuild_path is None:
- xbuild_fallback = env["xbuild_fallback"]
-
- if xbuild_fallback and os.name == "nt":
- print("Option 'xbuild_fallback' not supported on Windows")
- xbuild_fallback = False
+ msbuild_args = []
- if xbuild_fallback:
- print("Cannot find MSBuild executable, trying with xbuild")
- print("Warning: xbuild is deprecated")
+ dotnet_cli = find_dotnet_cli()
- msbuild_path = find_msbuild_unix("xbuild")
-
- if msbuild_path is None:
- raise RuntimeError("Cannot find xbuild executable")
- else:
+ if dotnet_cli:
+ msbuild_path = dotnet_cli
+ msbuild_args += ["msbuild"] # `dotnet msbuild` command
+ else:
+ # Find MSBuild
+ if os.name == "nt":
+ msbuild_info = find_msbuild_windows(env)
+ if msbuild_info is None:
+ raise RuntimeError("Cannot find MSBuild executable")
+ msbuild_path = msbuild_info[0]
+ msbuild_env.update(msbuild_info[1])
+ else:
+ msbuild_path = find_msbuild_unix()
+ if msbuild_path is None:
raise RuntimeError("Cannot find MSBuild executable")
print("MSBuild path: " + msbuild_path)
# Build solution
- msbuild_args = [solution_path, "/p:Configuration=" + build_config]
+ targets = ["Restore", "Build"]
+
+ msbuild_args += [solution_path, "/t:%s" % ",".join(targets), "/p:Configuration=" + build_config]
msbuild_args += extra_msbuild_args
run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild")
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 106ca6e028..d41f3755b5 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -35,7 +35,6 @@ def configure(env):
"copy_mono_root", "Make a copy of the mono installation directory to bundle with the editor", False
)
)
- envvars.Add(BoolVariable("xbuild_fallback", "If MSBuild is not found, fallback to xbuild", False))
# TODO: It would be great if this could be detected automatically instead
envvars.Add(
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index f5911275c9..7d00d2d314 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -850,7 +850,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
to_reload.push_back(script);
if (script->get_path().empty()) {
- script->tied_class_name_for_reload = script->script_class->get_name();
+ script->tied_class_name_for_reload = script->script_class->get_name_for_lookup();
script->tied_class_namespace_for_reload = script->script_class->get_namespace();
}
@@ -2421,58 +2421,68 @@ void CSharpScript::_update_member_info_no_exports() {
bool CSharpScript::_update_exports() {
#ifdef TOOLS_ENABLED
- if (!Engine::get_singleton()->is_editor_hint())
- return false;
-
- placeholder_fallback_enabled = true; // until proven otherwise
-
+ bool is_editor = Engine::get_singleton()->is_editor_hint();
+ if (is_editor)
+ placeholder_fallback_enabled = true; // until proven otherwise
+#endif
if (!valid)
return false;
bool changed = false;
- if (exports_invalidated) {
+#ifdef TOOLS_ENABLED
+ if (exports_invalidated)
+#endif
+ {
GD_MONO_SCOPE_THREAD_ATTACH;
- exports_invalidated = false;
-
changed = true;
member_info.clear();
- exported_members_cache.clear();
- exported_members_defval_cache.clear();
- // Here we create a temporary managed instance of the class to get the initial values
+#ifdef TOOLS_ENABLED
+ MonoObject *tmp_object = nullptr;
+ Object *tmp_native = nullptr;
+ uint32_t tmp_pinned_gchandle = 0;
- MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
+ if (is_editor) {
+ exports_invalidated = false;
- if (!tmp_object) {
- ERR_PRINT("Failed to allocate temporary MonoObject.");
- return false;
- }
+ exported_members_cache.clear();
+ exported_members_defval_cache.clear();
- uint32_t tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed)
+ // Here we create a temporary managed instance of the class to get the initial values
+ tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
- GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
+ if (!tmp_object) {
+ ERR_PRINT("Failed to allocate temporary MonoObject.");
+ return false;
+ }
- ERR_FAIL_NULL_V_MSG(ctor, false,
- "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'.");
+ tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed)
- MonoException *ctor_exc = nullptr;
- ctor->invoke(tmp_object, nullptr, &ctor_exc);
+ GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
- Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object));
+ ERR_FAIL_NULL_V_MSG(ctor, false,
+ "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'.");
- if (ctor_exc) {
- // TODO: Should we free 'tmp_native' if the exception was thrown after its creation?
+ MonoException *ctor_exc = nullptr;
+ ctor->invoke(tmp_object, nullptr, &ctor_exc);
- GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
- tmp_object = nullptr;
+ tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object));
- ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
- GDMonoUtils::debug_print_unhandled_exception(ctor_exc);
- return false;
+ if (ctor_exc) {
+ // TODO: Should we free 'tmp_native' if the exception was thrown after its creation?
+
+ GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
+ tmp_object = nullptr;
+
+ ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
+ GDMonoUtils::debug_print_unhandled_exception(ctor_exc);
+ return false;
+ }
}
+#endif
GDMonoClass *top = script_class;
@@ -2488,16 +2498,16 @@ bool CSharpScript::_update_exports() {
if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = field->get_name();
- if (exported) {
- member_info[member_name] = prop_info;
+ member_info[member_name] = prop_info;
+#ifdef TOOLS_ENABLED
+ if (is_editor && exported) {
exported_members_cache.push_front(prop_info);
if (tmp_object) {
exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
}
- } else {
- member_info[member_name] = prop_info;
}
+#endif
}
}
@@ -2509,10 +2519,10 @@ bool CSharpScript::_update_exports() {
if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = property->get_name();
- if (exported) {
- member_info[member_name] = prop_info;
+ member_info[member_name] = prop_info;
+#ifdef TOOLS_ENABLED
+ if (is_editor && exported) {
exported_members_cache.push_front(prop_info);
-
if (tmp_object) {
MonoException *exc = nullptr;
MonoObject *ret = property->get_value(tmp_object, &exc);
@@ -2523,57 +2533,62 @@ bool CSharpScript::_update_exports() {
exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret);
}
}
- } else {
- member_info[member_name] = prop_info;
}
+#endif
}
}
top = top->get_parent_class();
}
- // Need to check this here, before disposal
- bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr;
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ // Need to check this here, before disposal
+ bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr;
- // Dispose the temporary managed instance
+ // Dispose the temporary managed instance
- MonoException *exc = nullptr;
- GDMonoUtils::dispose(tmp_object, &exc);
+ MonoException *exc = nullptr;
+ GDMonoUtils::dispose(tmp_object, &exc);
- if (exc) {
- ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
- GDMonoUtils::debug_print_unhandled_exception(exc);
- }
+ if (exc) {
+ ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ }
- GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
- tmp_object = nullptr;
+ GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
+ tmp_object = nullptr;
- if (tmp_native && !base_ref) {
- Node *node = Object::cast_to<Node>(tmp_native);
- if (node && node->is_inside_tree()) {
- ERR_PRINT("Temporary instance was added to the scene tree.");
- } else {
- memdelete(tmp_native);
+ if (tmp_native && !base_ref) {
+ Node *node = Object::cast_to<Node>(tmp_native);
+ if (node && node->is_inside_tree()) {
+ ERR_PRINT("Temporary instance was added to the scene tree.");
+ } else {
+ memdelete(tmp_native);
+ }
}
}
+#endif
}
- placeholder_fallback_enabled = false;
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ placeholder_fallback_enabled = false;
- if (placeholders.size()) {
- // Update placeholders if any
- Map<StringName, Variant> values;
- List<PropertyInfo> propnames;
- _update_exports_values(values, propnames);
+ if (placeholders.size()) {
+ // Update placeholders if any
+ Map<StringName, Variant> values;
+ List<PropertyInfo> propnames;
+ _update_exports_values(values, propnames);
- for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) {
- E->get()->update(propnames, values);
+ for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) {
+ E->get()->update(propnames, values);
+ }
}
}
+#endif
return changed;
-#endif
- return false;
}
void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) {
@@ -2679,7 +2694,6 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_in
return true;
}
-#ifdef TOOLS_ENABLED
/**
* Returns false if there was an error, otherwise true.
* If there was an error, r_prop_info and r_exported are not assigned any value.
@@ -2693,8 +2707,10 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
(m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name())
if (p_member->is_static()) {
+#ifdef TOOLS_ENABLED
if (p_member->has_attribute(CACHED_CLASS(ExportAttribute)))
ERR_PRINT("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#endif
return false;
}
@@ -2716,13 +2732,17 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) {
GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
if (!property->has_getter()) {
+#ifdef TOOLS_ENABLED
if (exported)
ERR_PRINT("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#endif
return false;
}
if (!property->has_setter()) {
+#ifdef TOOLS_ENABLED
if (exported)
ERR_PRINT("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#endif
return false;
}
}
@@ -2742,10 +2762,13 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
String hint_string;
if (variant_type == Variant::NIL && !nil_is_variant) {
+#ifdef TOOLS_ENABLED
ERR_PRINT("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#endif
return false;
}
+#ifdef TOOLS_ENABLED
int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string);
ERR_FAIL_COND_V_MSG(hint_res == -1, false,
@@ -2756,6 +2779,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
}
+#endif
uint32_t prop_usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE;
@@ -2772,6 +2796,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
#undef MEMBER_FULL_QUALIFIED_NAME
}
+#ifdef TOOLS_ENABLED
int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) {
if (p_variant_type == Variant::NIL) {
@@ -3542,10 +3567,15 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
return false;
}
-#ifndef _MSC_VER
-#warning TODO: Implement CSharpScript::inherits_script and other relevant changes after GH-38063.
-#endif
- return false;
+ if (script_class == nullptr || cs->script_class == nullptr) {
+ return false;
+ }
+
+ if (script_class == cs->script_class) {
+ return true;
+ }
+
+ return cs->script_class->is_assignable_from(script_class);
}
Ref<Script> CSharpScript::get_base_script() const {
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 05e2857538..c9ea482c36 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -147,8 +147,9 @@ private:
bool _get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> &params);
bool _update_exports();
-#ifdef TOOLS_ENABLED
+
bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
+#ifdef TOOLS_ENABLED
static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
#endif
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
index 6015cb22b6..c2549b4ad5 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
-using GodotTools.Core;
namespace GodotTools.BuildLogger
{
@@ -183,4 +182,17 @@ namespace GodotTools.BuildLogger
private StreamWriter issuesStreamWriter;
private int indent;
}
+
+ internal static class StringExtensions
+ {
+ public static string CsvEscape(this string value, char delimiter = ',')
+ {
+ bool hasSpecialChar = value.IndexOfAny(new[] { '\"', '\n', '\r', delimiter }) != -1;
+
+ if (hasSpecialChar)
+ return "\"" + value.Replace("\"", "\"\"") + "\"";
+
+ return value;
+ }
+ }
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
index 8fdd485209..0afec970c6 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
@@ -1,60 +1,10 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>GodotTools.BuildLogger</RootNamespace>
- <AssemblyName>GodotTools.BuildLogger</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <FileAlignment>512</FileAlignment>
- <LangVersion>7</LangVersion>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>7.2</LangVersion>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="Microsoft.Build.Framework" />
- <Reference Include="System" />
- <Reference Include="System.Core" />
- <Reference Include="System.Data" />
- <Reference Include="System.Xml" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="GodotBuildLogger.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
- <Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project>
- <Name>GodotTools.Core</Name>
- </ProjectReference>
+ <PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" />
</ItemGroup>
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs
deleted file mode 100644
index 4374f21cfa..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("GodotTools.BuildLogger")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
index c9ea7d3a2c..d6d8962f90 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
@@ -1,40 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
- <OutputType>Library</OutputType>
- <RootNamespace>GodotTools.Core</RootNamespace>
- <AssemblyName>GodotTools.Core</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <LangVersion>7</LangVersion>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>7.2</LangVersion>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug</OutputPath>
- <DefineConstants>DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <Optimize>true</Optimize>
- <OutputPath>bin\Release</OutputPath>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="FileUtils.cs" />
- <Compile Include="ProcessExtensions.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="StringExtensions.cs" />
- </ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs
deleted file mode 100644
index 699ae6e741..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotTools.Core")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
index 326c49f096..7ab5c5fc59 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
@@ -33,23 +33,13 @@ namespace GodotTools.Core
return rooted ? Path.DirectorySeparatorChar + path : path;
}
- private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
+ private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
public static bool IsAbsolutePath(this string path)
{
return path.StartsWith("/", StringComparison.Ordinal) ||
path.StartsWith("\\", StringComparison.Ordinal) ||
- path.StartsWith(driveRoot, StringComparison.Ordinal);
- }
-
- public static string CsvEscape(this string value, char delimiter = ',')
- {
- bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
-
- if (hasSpecialChar)
- return "\"" + value.Replace("\"", "\"\"") + "\"";
-
- return value;
+ path.StartsWith(DriveRoot, StringComparison.Ordinal);
}
public static string ToSafeDirName(this string dirName, bool allowDirSeparator)
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
deleted file mode 100644
index 7a2ff2ca56..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-
-namespace GodotTools.IdeConnection
-{
- public class ConsoleLogger : ILogger
- {
- public void LogDebug(string message)
- {
- Console.WriteLine("DEBUG: " + message);
- }
-
- public void LogInfo(string message)
- {
- Console.WriteLine("INFO: " + message);
- }
-
- public void LogWarning(string message)
- {
- Console.WriteLine("WARN: " + message);
- }
-
- public void LogError(string message)
- {
- Console.WriteLine("ERROR: " + message);
- }
-
- public void LogError(string message, Exception e)
- {
- Console.WriteLine("EXCEPTION: " + message);
- Console.WriteLine(e);
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
deleted file mode 100644
index be89638241..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using Path = System.IO.Path;
-
-namespace GodotTools.IdeConnection
-{
- public class GodotIdeBase : IDisposable
- {
- private ILogger logger;
-
- public ILogger Logger
- {
- get => logger ?? (logger = new ConsoleLogger());
- set => logger = value;
- }
-
- private readonly string projectMetadataDir;
-
- protected const string MetaFileName = "ide_server_meta.txt";
- protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName);
-
- private GodotIdeConnection connection;
- protected readonly object ConnectionLock = new object();
-
- public bool IsDisposed { get; private set; } = false;
-
- public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected;
-
- public event Action Connected
- {
- add
- {
- if (connection != null && !connection.IsDisposed)
- connection.Connected += value;
- }
- remove
- {
- if (connection != null && !connection.IsDisposed)
- connection.Connected -= value;
- }
- }
-
- protected GodotIdeConnection Connection
- {
- get => connection;
- set
- {
- connection?.Dispose();
- connection = value;
- }
- }
-
- protected GodotIdeBase(string projectMetadataDir)
- {
- this.projectMetadataDir = projectMetadataDir;
- }
-
- protected void DisposeConnection()
- {
- lock (ConnectionLock)
- {
- connection?.Dispose();
- }
- }
-
- ~GodotIdeBase()
- {
- Dispose(disposing: false);
- }
-
- public void Dispose()
- {
- if (IsDisposed)
- return;
-
- lock (ConnectionLock)
- {
- if (IsDisposed) // lock may not be fair
- return;
- IsDisposed = true;
- }
-
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- connection?.Dispose();
- }
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
deleted file mode 100644
index 2bf3b83c75..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-
-namespace GodotTools.IdeConnection
-{
- public abstract class GodotIdeClient : GodotIdeBase
- {
- protected GodotIdeMetadata GodotIdeMetadata;
-
- private readonly FileSystemWatcher fsWatcher;
-
- protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir)
- {
- messageHandlers = InitializeMessageHandlers();
-
- // FileSystemWatcher requires an existing directory
- if (!File.Exists(projectMetadataDir))
- Directory.CreateDirectory(projectMetadataDir);
-
- fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName);
- }
-
- private void OnMetaFileChanged(object sender, FileSystemEventArgs e)
- {
- if (IsDisposed)
- return;
-
- lock (ConnectionLock)
- {
- if (IsDisposed)
- return;
-
- if (!File.Exists(MetaFilePath))
- return;
-
- var metadata = ReadMetadataFile();
-
- if (metadata != null && metadata != GodotIdeMetadata)
- {
- GodotIdeMetadata = metadata.Value;
- ConnectToServer();
- }
- }
- }
-
- private void OnMetaFileDeleted(object sender, FileSystemEventArgs e)
- {
- if (IsDisposed)
- return;
-
- if (IsConnected)
- DisposeConnection();
-
- // The file may have been re-created
-
- lock (ConnectionLock)
- {
- if (IsDisposed)
- return;
-
- if (IsConnected || !File.Exists(MetaFilePath))
- return;
-
- var metadata = ReadMetadataFile();
-
- if (metadata != null)
- {
- GodotIdeMetadata = metadata.Value;
- ConnectToServer();
- }
- }
- }
-
- private GodotIdeMetadata? ReadMetadataFile()
- {
- using (var reader = File.OpenText(MetaFilePath))
- {
- string portStr = reader.ReadLine();
-
- if (portStr == null)
- return null;
-
- string editorExecutablePath = reader.ReadLine();
-
- if (editorExecutablePath == null)
- return null;
-
- if (!int.TryParse(portStr, out int port))
- return null;
-
- return new GodotIdeMetadata(port, editorExecutablePath);
- }
- }
-
- private void ConnectToServer()
- {
- var tcpClient = new TcpClient();
-
- Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage);
- Connection.Logger = Logger;
-
- try
- {
- Logger.LogInfo("Connecting to Godot Ide Server");
-
- tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port);
-
- Logger.LogInfo("Connection open with Godot Ide Server");
-
- var clientThread = new Thread(Connection.Start)
- {
- IsBackground = true,
- Name = "Godot Ide Connection Client"
- };
- clientThread.Start();
- }
- catch (SocketException e)
- {
- if (e.SocketErrorCode == SocketError.ConnectionRefused)
- Logger.LogError("The connection to the Godot Ide Server was refused");
- else
- throw;
- }
- }
-
- public void Start()
- {
- Logger.LogInfo("Starting Godot Ide Client");
-
- fsWatcher.Changed += OnMetaFileChanged;
- fsWatcher.Deleted += OnMetaFileDeleted;
- fsWatcher.EnableRaisingEvents = true;
-
- lock (ConnectionLock)
- {
- if (IsDisposed)
- return;
-
- if (!File.Exists(MetaFilePath))
- {
- Logger.LogInfo("There is no Godot Ide Server running");
- return;
- }
-
- var metadata = ReadMetadataFile();
-
- if (metadata != null)
- {
- GodotIdeMetadata = metadata.Value;
- ConnectToServer();
- }
- else
- {
- Logger.LogError("Failed to read Godot Ide metadata file");
- }
- }
- }
-
- public bool WriteMessage(Message message)
- {
- return Connection.WriteMessage(message);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- fsWatcher?.Dispose();
- }
- }
-
- protected virtual bool HandleMessage(Message message)
- {
- if (messageHandlers.TryGetValue(message.Id, out var action))
- {
- action(message.Arguments);
- return true;
- }
-
- return false;
- }
-
- private readonly Dictionary<string, Action<string[]>> messageHandlers;
-
- private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
- {
- return new Dictionary<string, Action<string[]>>
- {
- ["OpenFile"] = args =>
- {
- switch (args.Length)
- {
- case 1:
- OpenFile(file: args[0]);
- return;
- case 2:
- OpenFile(file: args[0], line: int.Parse(args[1]));
- return;
- case 3:
- OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2]));
- return;
- default:
- throw new ArgumentException();
- }
- }
- };
- }
-
- protected abstract void OpenFile(string file);
- protected abstract void OpenFile(string file, int line);
- protected abstract void OpenFile(string file, int line, int column);
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
deleted file mode 100644
index 6441be8d6e..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
+++ /dev/null
@@ -1,207 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Sockets;
-using System.Text;
-
-namespace GodotTools.IdeConnection
-{
- public abstract class GodotIdeConnection : IDisposable
- {
- protected const string Version = "1.0";
-
- protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}";
- protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}";
-
- private const int ClientWriteTimeout = 8000;
- private readonly TcpClient tcpClient;
-
- private TextReader clientReader;
- private TextWriter clientWriter;
-
- private readonly object writeLock = new object();
-
- private readonly Func<Message, bool> messageHandler;
-
- public event Action Connected;
-
- private ILogger logger;
-
- public ILogger Logger
- {
- get => logger ?? (logger = new ConsoleLogger());
- set => logger = value;
- }
-
- public bool IsDisposed { get; private set; } = false;
-
- public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected;
-
- protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler)
- {
- this.tcpClient = tcpClient;
- this.messageHandler = messageHandler;
- }
-
- public void Start()
- {
- try
- {
- if (!StartConnection())
- return;
-
- string messageLine;
- while ((messageLine = ReadLine()) != null)
- {
- if (!MessageParser.TryParse(messageLine, out Message msg))
- {
- Logger.LogError($"Received message with invalid format: {messageLine}");
- continue;
- }
-
- Logger.LogDebug($"Received message: {msg}");
-
- if (msg.Id == "close")
- {
- Logger.LogInfo("Closing connection");
- return;
- }
-
- try
- {
- try
- {
- Debug.Assert(messageHandler != null);
-
- if (!messageHandler(msg))
- Logger.LogError($"Received unknown message: {msg}");
- }
- catch (Exception e)
- {
- Logger.LogError($"Message handler for '{msg}' failed with exception", e);
- }
- }
- catch (Exception e)
- {
- Logger.LogError($"Exception thrown from message handler. Message: {msg}", e);
- }
- }
- }
- catch (Exception e)
- {
- Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e);
- }
- finally
- {
- Dispose();
- }
- }
-
- private bool StartConnection()
- {
- NetworkStream clientStream = tcpClient.GetStream();
-
- clientReader = new StreamReader(clientStream, Encoding.UTF8);
-
- lock (writeLock)
- clientWriter = new StreamWriter(clientStream, Encoding.UTF8);
-
- clientStream.WriteTimeout = ClientWriteTimeout;
-
- if (!WriteHandshake())
- {
- Logger.LogError("Could not write handshake");
- return false;
- }
-
- if (!IsValidResponseHandshake(ReadLine()))
- {
- Logger.LogError("Received invalid handshake");
- return false;
- }
-
- Connected?.Invoke();
-
- Logger.LogInfo("Godot Ide connection started");
-
- return true;
- }
-
- private string ReadLine()
- {
- try
- {
- return clientReader?.ReadLine();
- }
- catch (Exception e)
- {
- if (IsDisposed)
- {
- var se = e as SocketException ?? e.InnerException as SocketException;
- if (se != null && se.SocketErrorCode == SocketError.Interrupted)
- return null;
- }
-
- throw;
- }
- }
-
- public bool WriteMessage(Message message)
- {
- Logger.LogDebug($"Sending message {message}");
-
- var messageComposer = new MessageComposer();
-
- messageComposer.AddArgument(message.Id);
- foreach (string argument in message.Arguments)
- messageComposer.AddArgument(argument);
-
- return WriteLine(messageComposer.ToString());
- }
-
- protected bool WriteLine(string text)
- {
- if (clientWriter == null || IsDisposed || !IsConnected)
- return false;
-
- lock (writeLock)
- {
- try
- {
- clientWriter.WriteLine(text);
- clientWriter.Flush();
- }
- catch (Exception e)
- {
- if (!IsDisposed)
- {
- var se = e as SocketException ?? e.InnerException as SocketException;
- if (se != null && se.SocketErrorCode == SocketError.Shutdown)
- Logger.LogInfo("Client disconnected ungracefully");
- else
- Logger.LogError("Exception thrown when trying to write to client", e);
-
- Dispose();
- }
- }
- }
-
- return true;
- }
-
- protected abstract bool WriteHandshake();
- protected abstract bool IsValidResponseHandshake(string handshakeLine);
-
- public void Dispose()
- {
- if (IsDisposed)
- return;
-
- IsDisposed = true;
-
- clientReader?.Dispose();
- clientWriter?.Dispose();
- ((IDisposable)tcpClient)?.Dispose();
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
deleted file mode 100644
index 1b11a14358..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-
-namespace GodotTools.IdeConnection
-{
- public class GodotIdeConnectionClient : GodotIdeConnection
- {
- public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler)
- : base(tcpClient, messageHandler)
- {
- }
-
- protected override bool WriteHandshake()
- {
- return WriteLine(ClientHandshake);
- }
-
- protected override bool IsValidResponseHandshake(string handshakeLine)
- {
- return handshakeLine == ServerHandshake;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
deleted file mode 100644
index aa98dc7ca3..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-
-namespace GodotTools.IdeConnection
-{
- public class GodotIdeConnectionServer : GodotIdeConnection
- {
- public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler)
- : base(tcpClient, messageHandler)
- {
- }
-
- protected override bool WriteHandshake()
- {
- return WriteLine(ServerHandshake);
- }
-
- protected override bool IsValidResponseHandshake(string handshakeLine)
- {
- return handshakeLine == ClientHandshake;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
deleted file mode 100644
index 8454535fba..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
- <PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>GodotTools.IdeConnection</RootNamespace>
- <AssemblyName>GodotTools.IdeConnection</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <FileAlignment>512</FileAlignment>
- <LangVersion>7</LangVersion>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="ConsoleLogger.cs" />
- <Compile Include="GodotIdeMetadata.cs" />
- <Compile Include="GodotIdeBase.cs" />
- <Compile Include="GodotIdeClient.cs" />
- <Compile Include="GodotIdeConnection.cs" />
- <Compile Include="GodotIdeConnectionClient.cs" />
- <Compile Include="GodotIdeConnectionServer.cs" />
- <Compile Include="ILogger.cs" />
- <Compile Include="Message.cs" />
- <Compile Include="MessageComposer.cs" />
- <Compile Include="MessageParser.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
deleted file mode 100644
index f24d324ae3..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Linq;
-
-namespace GodotTools.IdeConnection
-{
- public struct Message
- {
- public string Id { get; set; }
- public string[] Arguments { get; set; }
-
- public Message(string id, params string[] arguments)
- {
- Id = id;
- Arguments = arguments;
- }
-
- public override string ToString()
- {
- return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')";
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
deleted file mode 100644
index 30ffe7a06e..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Linq;
-using System.Text;
-
-namespace GodotTools.IdeConnection
-{
- public class MessageComposer
- {
- private readonly StringBuilder stringBuilder = new StringBuilder();
-
- private static readonly char[] CharsToEscape = { '\\', '"' };
-
- public void AddArgument(string argument)
- {
- AddArgument(argument, quoted: argument.Contains(","));
- }
-
- public void AddArgument(string argument, bool quoted)
- {
- if (stringBuilder.Length > 0)
- stringBuilder.Append(',');
-
- if (quoted)
- {
- stringBuilder.Append('"');
-
- foreach (char @char in argument)
- {
- if (CharsToEscape.Contains(@char))
- stringBuilder.Append('\\');
- stringBuilder.Append(@char);
- }
-
- stringBuilder.Append('"');
- }
- else
- {
- stringBuilder.Append(argument);
- }
- }
-
- public override string ToString()
- {
- return stringBuilder.ToString();
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
deleted file mode 100644
index 4365d69989..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace GodotTools.IdeConnection
-{
- public static class MessageParser
- {
- public static bool TryParse(string messageLine, out Message message)
- {
- var arguments = new List<string>();
- var stringBuilder = new StringBuilder();
-
- bool expectingArgument = true;
-
- for (int i = 0; i < messageLine.Length; i++)
- {
- char @char = messageLine[i];
-
- if (@char == ',')
- {
- if (expectingArgument)
- arguments.Add(string.Empty);
-
- expectingArgument = true;
- continue;
- }
-
- bool quoted = false;
-
- if (messageLine[i] == '"')
- {
- quoted = true;
- i++;
- }
-
- while (i < messageLine.Length)
- {
- @char = messageLine[i];
-
- if (quoted && @char == '"')
- {
- i++;
- break;
- }
-
- if (@char == '\\')
- {
- i++;
- if (i < messageLine.Length)
- break;
-
- stringBuilder.Append(messageLine[i]);
- }
- else if (!quoted && @char == ',')
- {
- break; // We don't increment the counter to allow the colon to be parsed after this
- }
- else
- {
- stringBuilder.Append(@char);
- }
-
- i++;
- }
-
- arguments.Add(stringBuilder.ToString());
- stringBuilder.Clear();
-
- expectingArgument = false;
- }
-
- if (arguments.Count == 0)
- {
- message = new Message();
- return false;
- }
-
- message = new Message
- {
- Id = arguments[0],
- Arguments = arguments.Skip(1).ToArray()
- };
-
- return true;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs
deleted file mode 100644
index 0806d02ca0..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("GodotTools.IdeConnection")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs
new file mode 100644
index 0000000000..3cb6a6687e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs
@@ -0,0 +1,57 @@
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Utils;
+
+namespace GodotTools.IdeMessaging.CLI
+{
+ public class ForwarderMessageHandler : IMessageHandler
+ {
+ private readonly StreamWriter outputWriter;
+ private readonly SemaphoreSlim outputWriteSem = new SemaphoreSlim(1);
+
+ public ForwarderMessageHandler(StreamWriter outputWriter)
+ {
+ this.outputWriter = outputWriter;
+ }
+
+ public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger)
+ {
+ await WriteRequestToOutput(id, content);
+ return new MessageContent(MessageStatus.RequestNotSupported, "null");
+ }
+
+ private async Task WriteRequestToOutput(string id, MessageContent content)
+ {
+ using (await outputWriteSem.UseAsync())
+ {
+ await outputWriter.WriteLineAsync("======= Request =======");
+ await outputWriter.WriteLineAsync(id);
+ await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString());
+ await outputWriter.WriteLineAsync(content.Body);
+ await outputWriter.WriteLineAsync("=======================");
+ await outputWriter.FlushAsync();
+ }
+ }
+
+ public async Task WriteResponseToOutput(string id, MessageContent content)
+ {
+ using (await outputWriteSem.UseAsync())
+ {
+ await outputWriter.WriteLineAsync("======= Response =======");
+ await outputWriter.WriteLineAsync(id);
+ await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString());
+ await outputWriter.WriteLineAsync(content.Body);
+ await outputWriter.WriteLineAsync("========================");
+ await outputWriter.FlushAsync();
+ }
+ }
+
+ public async Task WriteLineToOutput(string eventName)
+ {
+ using (await outputWriteSem.UseAsync())
+ await outputWriter.WriteLineAsync($"======= {eventName} =======");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
new file mode 100644
index 0000000000..ae78da27bc
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs
new file mode 100644
index 0000000000..99a55c471b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging.CLI
+{
+ internal static class Program
+ {
+ private static readonly ILogger Logger = new CustomLogger();
+
+ public static int Main(string[] args)
+ {
+ try
+ {
+ var mainTask = StartAsync(args, Console.OpenStandardInput(), Console.OpenStandardOutput());
+ mainTask.Wait();
+ return mainTask.Result;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Unhandled exception: ", ex);
+ return 1;
+ }
+ }
+
+ private static async Task<int> StartAsync(string[] args, Stream inputStream, Stream outputStream)
+ {
+ var inputReader = new StreamReader(inputStream, Encoding.UTF8);
+ var outputWriter = new StreamWriter(outputStream, Encoding.UTF8);
+
+ try
+ {
+ if (args.Length == 0)
+ {
+ Logger.LogError("Expected at least 1 argument");
+ return 1;
+ }
+
+ string godotProjectDir = args[0];
+
+ if (!Directory.Exists(godotProjectDir))
+ {
+ Logger.LogError($"The specified Godot project directory does not exist: {godotProjectDir}");
+ return 1;
+ }
+
+ var forwarder = new ForwarderMessageHandler(outputWriter);
+
+ using (var fwdClient = new Client("VisualStudioCode", godotProjectDir, forwarder, Logger))
+ {
+ fwdClient.Start();
+
+ // ReSharper disable AccessToDisposedClosure
+ fwdClient.Connected += async () => await forwarder.WriteLineToOutput("Event=Connected");
+ fwdClient.Disconnected += async () => await forwarder.WriteLineToOutput("Event=Disconnected");
+ // ReSharper restore AccessToDisposedClosure
+
+ // TODO: Await connected with timeout
+
+ while (!fwdClient.IsDisposed)
+ {
+ string firstLine = await inputReader.ReadLineAsync();
+
+ if (firstLine == null || firstLine == "QUIT")
+ goto ExitMainLoop;
+
+ string messageId = firstLine;
+
+ string messageArgcLine = await inputReader.ReadLineAsync();
+
+ if (messageArgcLine == null)
+ {
+ Logger.LogInfo("EOF when expecting argument count");
+ goto ExitMainLoop;
+ }
+
+ if (!int.TryParse(messageArgcLine, out int messageArgc))
+ {
+ Logger.LogError("Received invalid line for argument count: " + firstLine);
+ continue;
+ }
+
+ var body = new StringBuilder();
+
+ for (int i = 0; i < messageArgc; i++)
+ {
+ string bodyLine = await inputReader.ReadLineAsync();
+
+ if (bodyLine == null)
+ {
+ Logger.LogInfo($"EOF when expecting body line #{i + 1}");
+ goto ExitMainLoop;
+ }
+
+ body.AppendLine(bodyLine);
+ }
+
+ var response = await SendRequest(fwdClient, messageId, new MessageContent(MessageStatus.Ok, body.ToString()));
+
+ if (response == null)
+ {
+ Logger.LogError($"Failed to write message to the server: {messageId}");
+ }
+ else
+ {
+ var content = new MessageContent(response.Status, JsonConvert.SerializeObject(response));
+ await forwarder.WriteResponseToOutput(messageId, content);
+ }
+ }
+
+ ExitMainLoop:
+
+ await forwarder.WriteLineToOutput("Event=Quit");
+ }
+
+ return 0;
+ }
+ catch (Exception e)
+ {
+ Logger.LogError("Unhandled exception", e);
+ return 1;
+ }
+ }
+
+ private static async Task<Response> SendRequest(Client client, string id, MessageContent content)
+ {
+ var handlers = new Dictionary<string, Func<Task<Response>>>
+ {
+ [PlayRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<PlayRequest>(content.Body);
+ return await client.SendRequest<PlayResponse>(request);
+ },
+ [DebugPlayRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body);
+ return await client.SendRequest<DebugPlayResponse>(request);
+ },
+ [ReloadScriptsRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body);
+ return await client.SendRequest<ReloadScriptsResponse>(request);
+ },
+ [CodeCompletionRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body);
+ return await client.SendRequest<CodeCompletionResponse>(request);
+ }
+ };
+
+ if (handlers.TryGetValue(id, out var handler))
+ return await handler();
+
+ Console.WriteLine("INVALID REQUEST");
+ return null;
+ }
+
+ private class CustomLogger : ILogger
+ {
+ private static string ThisAppPath => Assembly.GetExecutingAssembly().Location;
+ private static string ThisAppPathWithoutExtension => Path.ChangeExtension(ThisAppPath, null);
+
+ private static readonly string LogPath = $"{ThisAppPathWithoutExtension}.log";
+
+ private static StreamWriter NewWriter() => new StreamWriter(LogPath, append: true, encoding: Encoding.UTF8);
+
+ private static void Log(StreamWriter writer, string message)
+ {
+ writer.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}: {message}");
+ }
+
+ public void LogDebug(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "DEBUG: " + message);
+ }
+ }
+
+ public void LogInfo(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "INFO: " + message);
+ }
+ }
+
+ public void LogWarning(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "WARN: " + message);
+ }
+ }
+
+ public void LogError(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "ERROR: " + message);
+ }
+ }
+
+ public void LogError(string message, Exception e)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "EXCEPTION: " + message + '\n' + e);
+ }
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
new file mode 100644
index 0000000000..d069651dd3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
@@ -0,0 +1,332 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using Newtonsoft.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+
+namespace GodotTools.IdeMessaging
+{
+ // ReSharper disable once UnusedType.Global
+ public sealed class Client : IDisposable
+ {
+ private readonly ILogger logger;
+
+ private readonly string identity;
+
+ private string MetaFilePath { get; }
+ private GodotIdeMetadata godotIdeMetadata;
+ private readonly FileSystemWatcher fsWatcher;
+
+ private readonly IMessageHandler messageHandler;
+
+ private Peer peer;
+ private readonly SemaphoreSlim connectionSem = new SemaphoreSlim(1);
+
+ private readonly Queue<NotifyAwaiter<bool>> clientConnectedAwaiters = new Queue<NotifyAwaiter<bool>>();
+ private readonly Queue<NotifyAwaiter<bool>> clientDisconnectedAwaiters = new Queue<NotifyAwaiter<bool>>();
+
+ // ReSharper disable once UnusedMember.Global
+ public async Task<bool> AwaitConnected()
+ {
+ var awaiter = new NotifyAwaiter<bool>();
+ clientConnectedAwaiters.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ // ReSharper disable once UnusedMember.Global
+ public async Task<bool> AwaitDisconnected()
+ {
+ var awaiter = new NotifyAwaiter<bool>();
+ clientDisconnectedAwaiters.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ // ReSharper disable once MemberCanBePrivate.Global
+ public bool IsDisposed { get; private set; }
+
+ // ReSharper disable once MemberCanBePrivate.Global
+ public bool IsConnected => peer != null && !peer.IsDisposed && peer.IsTcpClientConnected;
+
+ // ReSharper disable once EventNeverSubscribedTo.Global
+ public event Action Connected
+ {
+ add
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Connected += value;
+ }
+ remove
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Connected -= value;
+ }
+ }
+
+ // ReSharper disable once EventNeverSubscribedTo.Global
+ public event Action Disconnected
+ {
+ add
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Disconnected += value;
+ }
+ remove
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Disconnected -= value;
+ }
+ }
+
+ ~Client()
+ {
+ Dispose(disposing: false);
+ }
+
+ public async void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed) // lock may not be fair
+ return;
+ IsDisposed = true;
+ }
+
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ peer?.Dispose();
+ fsWatcher?.Dispose();
+ }
+ }
+
+ public Client(string identity, string godotProjectDir, IMessageHandler messageHandler, ILogger logger)
+ {
+ this.identity = identity;
+ this.messageHandler = messageHandler;
+ this.logger = logger;
+
+ string projectMetadataDir = Path.Combine(godotProjectDir, ".mono", "metadata");
+
+ MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName);
+
+ // FileSystemWatcher requires an existing directory
+ if (!File.Exists(projectMetadataDir))
+ Directory.CreateDirectory(projectMetadataDir);
+
+ fsWatcher = new FileSystemWatcher(projectMetadataDir, GodotIdeMetadata.DefaultFileName);
+ }
+
+ private async void OnMetaFileChanged(object sender, FileSystemEventArgs e)
+ {
+ if (IsDisposed)
+ return;
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed)
+ return;
+
+ if (!File.Exists(MetaFilePath))
+ return;
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null && metadata != godotIdeMetadata)
+ {
+ godotIdeMetadata = metadata.Value;
+ _ = Task.Run(ConnectToServer);
+ }
+ }
+ }
+
+ private async void OnMetaFileDeleted(object sender, FileSystemEventArgs e)
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected)
+ {
+ using (await connectionSem.UseAsync())
+ peer?.Dispose();
+ }
+
+ // The file may have been re-created
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected || !File.Exists(MetaFilePath))
+ return;
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null)
+ {
+ godotIdeMetadata = metadata.Value;
+ _ = Task.Run(ConnectToServer);
+ }
+ }
+ }
+
+ private GodotIdeMetadata? ReadMetadataFile()
+ {
+ using (var reader = File.OpenText(MetaFilePath))
+ {
+ string portStr = reader.ReadLine();
+
+ if (portStr == null)
+ return null;
+
+ string editorExecutablePath = reader.ReadLine();
+
+ if (editorExecutablePath == null)
+ return null;
+
+ if (!int.TryParse(portStr, out int port))
+ return null;
+
+ return new GodotIdeMetadata(port, editorExecutablePath);
+ }
+ }
+
+ private async Task AcceptClient(TcpClient tcpClient)
+ {
+ logger.LogDebug("Accept client...");
+
+ using (peer = new Peer(tcpClient, new ClientHandshake(), messageHandler, logger))
+ {
+ // ReSharper disable AccessToDisposedClosure
+ peer.Connected += () =>
+ {
+ logger.LogInfo("Connection open with Ide Client");
+
+ while (clientConnectedAwaiters.Count > 0)
+ clientConnectedAwaiters.Dequeue().SetResult(true);
+ };
+
+ peer.Disconnected += () =>
+ {
+ while (clientDisconnectedAwaiters.Count > 0)
+ clientDisconnectedAwaiters.Dequeue().SetResult(true);
+ };
+ // ReSharper restore AccessToDisposedClosure
+
+ try
+ {
+ if (!await peer.DoHandshake(identity))
+ {
+ logger.LogError("Handshake failed");
+ return;
+ }
+ }
+ catch (Exception e)
+ {
+ logger.LogError("Handshake failed with unhandled exception: ", e);
+ return;
+ }
+
+ await peer.Process();
+
+ logger.LogInfo("Connection closed with Ide Client");
+ }
+ }
+
+ private async Task ConnectToServer()
+ {
+ var tcpClient = new TcpClient();
+
+ try
+ {
+ logger.LogInfo("Connecting to Godot Ide Server");
+
+ await tcpClient.ConnectAsync(IPAddress.Loopback, godotIdeMetadata.Port);
+
+ logger.LogInfo("Connection open with Godot Ide Server");
+
+ await AcceptClient(tcpClient);
+ }
+ catch (SocketException e)
+ {
+ if (e.SocketErrorCode == SocketError.ConnectionRefused)
+ logger.LogError("The connection to the Godot Ide Server was refused");
+ else
+ throw;
+ }
+ }
+
+ // ReSharper disable once UnusedMember.Global
+ public async void Start()
+ {
+ fsWatcher.Changed += OnMetaFileChanged;
+ fsWatcher.Deleted += OnMetaFileDeleted;
+ fsWatcher.EnableRaisingEvents = true;
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected)
+ return;
+
+ if (!File.Exists(MetaFilePath))
+ {
+ logger.LogInfo("There is no Godot Ide Server running");
+ return;
+ }
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null)
+ {
+ godotIdeMetadata = metadata.Value;
+ _ = Task.Run(ConnectToServer);
+ }
+ else
+ {
+ logger.LogError("Failed to read Godot Ide metadata file");
+ }
+ }
+ }
+
+ public async Task<TResponse> SendRequest<TResponse>(Request request)
+ where TResponse : Response, new()
+ {
+ if (!IsConnected)
+ {
+ logger.LogError("Cannot write request. Not connected to the Godot Ide Server.");
+ return null;
+ }
+
+ string body = JsonConvert.SerializeObject(request);
+ return await peer.SendRequest<TResponse>(request.Id, body);
+ }
+
+ public async Task<TResponse> SendRequest<TResponse>(string id, string body)
+ where TResponse : Response, new()
+ {
+ if (!IsConnected)
+ {
+ logger.LogError("Cannot write request. Not connected to the Godot Ide Server.");
+ return null;
+ }
+
+ return await peer.SendRequest<TResponse>(id, body);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs
new file mode 100644
index 0000000000..43041be7be
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs
@@ -0,0 +1,44 @@
+using System.Text.RegularExpressions;
+
+namespace GodotTools.IdeMessaging
+{
+ public class ClientHandshake : IHandshake
+ {
+ private static readonly string ClientHandshakeBase = $"{Peer.ClientHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}";
+ private static readonly string ServerHandshakePattern = $@"{Regex.Escape(Peer.ServerHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})";
+
+ public string GetHandshakeLine(string identity) => $"{ClientHandshakeBase},{identity}";
+
+ public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger)
+ {
+ identity = null;
+
+ var match = Regex.Match(handshake, ServerHandshakePattern);
+
+ if (!match.Success)
+ return false;
+
+ if (!uint.TryParse(match.Groups[1].Value, out uint serverMajor) || Peer.ProtocolVersionMajor != serverMajor)
+ {
+ logger.LogDebug("Incompatible major version: " + match.Groups[1].Value);
+ return false;
+ }
+
+ if (!uint.TryParse(match.Groups[2].Value, out uint serverMinor) || Peer.ProtocolVersionMinor < serverMinor)
+ {
+ logger.LogDebug("Incompatible minor version: " + match.Groups[2].Value);
+ return false;
+ }
+
+ if (!uint.TryParse(match.Groups[3].Value, out uint _)) // Revision
+ {
+ logger.LogDebug("Incompatible revision build: " + match.Groups[3].Value);
+ return false;
+ }
+
+ identity = match.Groups[4].Value;
+
+ return true;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs
new file mode 100644
index 0000000000..64bcfd824c
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging
+{
+ // ReSharper disable once UnusedType.Global
+ public abstract class ClientMessageHandler : IMessageHandler
+ {
+ private readonly Dictionary<string, Peer.RequestHandler> requestHandlers;
+
+ protected ClientMessageHandler()
+ {
+ requestHandlers = InitializeRequestHandlers();
+ }
+
+ public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger)
+ {
+ if (!requestHandlers.TryGetValue(id, out var handler))
+ {
+ logger.LogError($"Received unknown request: {id}");
+ return new MessageContent(MessageStatus.RequestNotSupported, "null");
+ }
+
+ try
+ {
+ var response = await handler(peer, content);
+ return new MessageContent(response.Status, JsonConvert.SerializeObject(response));
+ }
+ catch (JsonException)
+ {
+ logger.LogError($"Received request with invalid body: {id}");
+ return new MessageContent(MessageStatus.InvalidRequestBody, "null");
+ }
+ }
+
+ private Dictionary<string, Peer.RequestHandler> InitializeRequestHandlers()
+ {
+ return new Dictionary<string, Peer.RequestHandler>
+ {
+ [OpenFileRequest.Id] = async (peer, content) =>
+ {
+ var request = JsonConvert.DeserializeObject<OpenFileRequest>(content.Body);
+ return await HandleOpenFile(request);
+ }
+ };
+ }
+
+ protected abstract Task<Response> HandleOpenFile(OpenFileRequest request);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs
index d16daba0e2..686202e81e 100644
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs
@@ -1,10 +1,12 @@
-namespace GodotTools.IdeConnection
+namespace GodotTools.IdeMessaging
{
- public struct GodotIdeMetadata
+ public readonly struct GodotIdeMetadata
{
public int Port { get; }
public string EditorExecutablePath { get; }
+ public const string DefaultFileName = "ide_messaging_meta.txt";
+
public GodotIdeMetadata(int port, string editorExecutablePath)
{
Port = port;
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj
new file mode 100644
index 0000000000..67815959a6
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ <PackageId>GodotTools.IdeMessaging</PackageId>
+ <Version>1.1.0</Version>
+ <AssemblyVersion>$(Version)</AssemblyVersion>
+ <Authors>Godot Engine contributors</Authors>
+ <Company />
+ <PackageTags>godot</PackageTags>
+ <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/GodotTools/GodotTools.IdeMessaging</RepositoryUrl>
+ <PackageLicenseExpression>MIT</PackageLicenseExpression>
+ <Description>
+This library enables communication with the Godot Engine editor (the version with .NET support).
+It's intended for use in IDEs/editors plugins for a better experience working with Godot C# projects.
+
+A client using this library is only compatible with servers of the same major version and of a lower or equal minor version.
+ </Description>
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs
new file mode 100644
index 0000000000..6387145a28
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs
@@ -0,0 +1,8 @@
+namespace GodotTools.IdeMessaging
+{
+ public interface IHandshake
+ {
+ string GetHandshakeLine(string identity);
+ bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs
index 614bb30271..d2855f93a1 100644
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs
@@ -1,6 +1,6 @@
using System;
-namespace GodotTools.IdeConnection
+namespace GodotTools.IdeMessaging
{
public interface ILogger
{
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs
new file mode 100644
index 0000000000..9622fcc96d
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace GodotTools.IdeMessaging
+{
+ public interface IMessageHandler
+ {
+ Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs
new file mode 100644
index 0000000000..6903ec197b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs
@@ -0,0 +1,52 @@
+namespace GodotTools.IdeMessaging
+{
+ public class Message
+ {
+ public MessageKind Kind { get; }
+ public string Id { get; }
+ public MessageContent Content { get; }
+
+ public Message(MessageKind kind, string id, MessageContent content)
+ {
+ Kind = kind;
+ Id = id;
+ Content = content;
+ }
+
+ public override string ToString()
+ {
+ return $"{Kind} | {Id}";
+ }
+ }
+
+ public enum MessageKind
+ {
+ Request,
+ Response
+ }
+
+ public enum MessageStatus
+ {
+ Ok,
+ RequestNotSupported,
+ InvalidRequestBody
+ }
+
+ public readonly struct MessageContent
+ {
+ public MessageStatus Status { get; }
+ public string Body { get; }
+
+ public MessageContent(string body)
+ {
+ Status = MessageStatus.Ok;
+ Body = body;
+ }
+
+ public MessageContent(MessageStatus status, string body)
+ {
+ Status = status;
+ Body = body;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs
new file mode 100644
index 0000000000..a00575a2a1
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Text;
+
+namespace GodotTools.IdeMessaging
+{
+ public class MessageDecoder
+ {
+ private class DecodedMessage
+ {
+ public MessageKind? Kind;
+ public string Id;
+ public MessageStatus? Status;
+ public readonly StringBuilder Body = new StringBuilder();
+ public uint? PendingBodyLines;
+
+ public void Clear()
+ {
+ Kind = null;
+ Id = null;
+ Status = null;
+ Body.Clear();
+ PendingBodyLines = null;
+ }
+
+ public Message ToMessage()
+ {
+ if (!Kind.HasValue || Id == null || !Status.HasValue ||
+ !PendingBodyLines.HasValue || PendingBodyLines.Value > 0)
+ throw new InvalidOperationException();
+
+ return new Message(Kind.Value, Id, new MessageContent(Status.Value, Body.ToString()));
+ }
+ }
+
+ public enum State
+ {
+ Decoding,
+ Decoded,
+ Errored
+ }
+
+ private readonly DecodedMessage decodingMessage = new DecodedMessage();
+
+ public State Decode(string messageLine, out Message decodedMessage)
+ {
+ decodedMessage = null;
+
+ if (!decodingMessage.Kind.HasValue)
+ {
+ if (!Enum.TryParse(messageLine, ignoreCase: true, out MessageKind kind))
+ {
+ decodingMessage.Clear();
+ return State.Errored;
+ }
+
+ decodingMessage.Kind = kind;
+ }
+ else if (decodingMessage.Id == null)
+ {
+ decodingMessage.Id = messageLine;
+ }
+ else if (decodingMessage.Status == null)
+ {
+ if (!Enum.TryParse(messageLine, ignoreCase: true, out MessageStatus status))
+ {
+ decodingMessage.Clear();
+ return State.Errored;
+ }
+
+ decodingMessage.Status = status;
+ }
+ else if (decodingMessage.PendingBodyLines == null)
+ {
+ if (!uint.TryParse(messageLine, out uint pendingBodyLines))
+ {
+ decodingMessage.Clear();
+ return State.Errored;
+ }
+
+ decodingMessage.PendingBodyLines = pendingBodyLines;
+ }
+ else
+ {
+ if (decodingMessage.PendingBodyLines > 0)
+ {
+ decodingMessage.Body.AppendLine(messageLine);
+ decodingMessage.PendingBodyLines -= 1;
+ }
+ else
+ {
+ decodedMessage = decodingMessage.ToMessage();
+ decodingMessage.Clear();
+ return State.Decoded;
+ }
+ }
+
+ return State.Decoding;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs
new file mode 100644
index 0000000000..a4e86d6177
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs
@@ -0,0 +1,302 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+
+namespace GodotTools.IdeMessaging
+{
+ public sealed class Peer : IDisposable
+ {
+ /// <summary>
+ /// Major version.
+ /// There is no forward nor backward compatibility between different major versions.
+ /// Connection is refused if client and server have different major versions.
+ /// </summary>
+ public static readonly int ProtocolVersionMajor = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Major;
+
+ /// <summary>
+ /// Minor version, which clients must be backward compatible with.
+ /// Connection is refused if the client's minor version is lower than the server's.
+ /// </summary>
+ public static readonly int ProtocolVersionMinor = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Minor;
+
+ /// <summary>
+ /// Revision, which doesn't affect compatibility.
+ /// </summary>
+ public static readonly int ProtocolVersionRevision = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Revision;
+
+ public const string ClientHandshakeName = "GodotIdeClient";
+ public const string ServerHandshakeName = "GodotIdeServer";
+
+ private const int ClientWriteTimeout = 8000;
+
+ public delegate Task<Response> RequestHandler(Peer peer, MessageContent content);
+
+ private readonly TcpClient tcpClient;
+
+ private readonly TextReader clientReader;
+ private readonly TextWriter clientWriter;
+
+ private readonly SemaphoreSlim writeSem = new SemaphoreSlim(1);
+
+ private string remoteIdentity = string.Empty;
+ public string RemoteIdentity => remoteIdentity;
+
+ public event Action Connected;
+ public event Action Disconnected;
+
+ private ILogger Logger { get; }
+
+ public bool IsDisposed { get; private set; }
+
+ public bool IsTcpClientConnected => tcpClient.Client != null && tcpClient.Client.Connected;
+
+ private bool IsConnected { get; set; }
+
+ private readonly IHandshake handshake;
+ private readonly IMessageHandler messageHandler;
+
+ private readonly Dictionary<string, Queue<ResponseAwaiter>> requestAwaiterQueues = new Dictionary<string, Queue<ResponseAwaiter>>();
+ private readonly SemaphoreSlim requestsSem = new SemaphoreSlim(1);
+
+ public Peer(TcpClient tcpClient, IHandshake handshake, IMessageHandler messageHandler, ILogger logger)
+ {
+ this.tcpClient = tcpClient;
+ this.handshake = handshake;
+ this.messageHandler = messageHandler;
+
+ Logger = logger;
+
+ NetworkStream clientStream = tcpClient.GetStream();
+ clientStream.WriteTimeout = ClientWriteTimeout;
+
+ clientReader = new StreamReader(clientStream, Encoding.UTF8);
+ clientWriter = new StreamWriter(clientStream, Encoding.UTF8) {NewLine = "\n"};
+ }
+
+ public async Task Process()
+ {
+ try
+ {
+ var decoder = new MessageDecoder();
+
+ string messageLine;
+ while ((messageLine = await ReadLine()) != null)
+ {
+ var state = decoder.Decode(messageLine, out var msg);
+
+ if (state == MessageDecoder.State.Decoding)
+ continue; // Not finished decoding yet
+
+ if (state == MessageDecoder.State.Errored)
+ {
+ Logger.LogError($"Received message line with invalid format: {messageLine}");
+ continue;
+ }
+
+ Logger.LogDebug($"Received message: {msg}");
+
+ try
+ {
+ try
+ {
+ if (msg.Kind == MessageKind.Request)
+ {
+ var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger);
+ await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent));
+ }
+ else if (msg.Kind == MessageKind.Response)
+ {
+ ResponseAwaiter responseAwaiter;
+
+ using (await requestsSem.UseAsync())
+ {
+ if (!requestAwaiterQueues.TryGetValue(msg.Id, out var queue) || queue.Count <= 0)
+ {
+ Logger.LogError($"Received unexpected response: {msg.Id}");
+ return;
+ }
+
+ responseAwaiter = queue.Dequeue();
+ }
+
+ responseAwaiter.SetResult(msg.Content);
+ }
+ else
+ {
+ throw new IndexOutOfRangeException($"Invalid message kind {msg.Kind}");
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Message handler for '{msg}' failed with exception", e);
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Exception thrown from message handler. Message: {msg}", e);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError("Unhandled exception in the peer loop", e);
+ }
+ }
+
+ public async Task<bool> DoHandshake(string identity)
+ {
+ if (!await WriteLine(handshake.GetHandshakeLine(identity)))
+ {
+ Logger.LogError("Could not write handshake");
+ return false;
+ }
+
+ var readHandshakeTask = ReadLine();
+
+ if (await Task.WhenAny(readHandshakeTask, Task.Delay(8000)) != readHandshakeTask)
+ {
+ Logger.LogError("Timeout waiting for the client handshake");
+ return false;
+ }
+
+ string peerHandshake = await readHandshakeTask;
+
+ if (handshake == null || !handshake.IsValidPeerHandshake(peerHandshake, out remoteIdentity, Logger))
+ {
+ Logger.LogError("Received invalid handshake: " + peerHandshake);
+ return false;
+ }
+
+ IsConnected = true;
+ Connected?.Invoke();
+
+ Logger.LogInfo("Peer connection started");
+
+ return true;
+ }
+
+ private async Task<string> ReadLine()
+ {
+ try
+ {
+ return await clientReader.ReadLineAsync();
+ }
+ catch (Exception e)
+ {
+ if (IsDisposed)
+ {
+ var se = e as SocketException ?? e.InnerException as SocketException;
+ if (se != null && se.SocketErrorCode == SocketError.Interrupted)
+ return null;
+ }
+
+ throw;
+ }
+ }
+
+ private Task<bool> WriteMessage(Message message)
+ {
+ Logger.LogDebug($"Sending message: {message}");
+ int bodyLineCount = message.Content.Body.Count(c => c == '\n');
+
+ bodyLineCount += 1; // Extra line break at the end
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine(message.Kind.ToString());
+ builder.AppendLine(message.Id);
+ builder.AppendLine(message.Content.Status.ToString());
+ builder.AppendLine(bodyLineCount.ToString());
+ builder.AppendLine(message.Content.Body);
+
+ return WriteLine(builder.ToString());
+ }
+
+ public async Task<TResponse> SendRequest<TResponse>(string id, string body)
+ where TResponse : Response, new()
+ {
+ ResponseAwaiter responseAwaiter;
+
+ using (await requestsSem.UseAsync())
+ {
+ bool written = await WriteMessage(new Message(MessageKind.Request, id, new MessageContent(body)));
+
+ if (!written)
+ return null;
+
+ if (!requestAwaiterQueues.TryGetValue(id, out var queue))
+ {
+ queue = new Queue<ResponseAwaiter>();
+ requestAwaiterQueues.Add(id, queue);
+ }
+
+ responseAwaiter = new ResponseAwaiter<TResponse>();
+ queue.Enqueue(responseAwaiter);
+ }
+
+ return (TResponse)await responseAwaiter;
+ }
+
+ private async Task<bool> WriteLine(string text)
+ {
+ if (clientWriter == null || IsDisposed || !IsTcpClientConnected)
+ return false;
+
+ using (await writeSem.UseAsync())
+ {
+ try
+ {
+ await clientWriter.WriteLineAsync(text);
+ await clientWriter.FlushAsync();
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed)
+ {
+ var se = e as SocketException ?? e.InnerException as SocketException;
+ if (se != null && se.SocketErrorCode == SocketError.Shutdown)
+ Logger.LogInfo("Client disconnected ungracefully");
+ else
+ Logger.LogError("Exception thrown when trying to write to client", e);
+
+ Dispose();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // ReSharper disable once UnusedMember.Global
+ public void ShutdownSocketSend()
+ {
+ tcpClient.Client.Shutdown(SocketShutdown.Send);
+ }
+
+ public void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ IsDisposed = true;
+
+ if (IsTcpClientConnected)
+ {
+ if (IsConnected)
+ Disconnected?.Invoke();
+ }
+
+ clientReader?.Dispose();
+ clientWriter?.Dispose();
+ ((IDisposable)tcpClient)?.Dispose();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs
new file mode 100644
index 0000000000..1dd4f852e5
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs
@@ -0,0 +1,116 @@
+// ReSharper disable ClassNeverInstantiated.Global
+// ReSharper disable UnusedMember.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging.Requests
+{
+ public abstract class Request
+ {
+ [JsonIgnore] public string Id { get; }
+
+ protected Request(string id)
+ {
+ Id = id;
+ }
+ }
+
+ public abstract class Response
+ {
+ [JsonIgnore] public MessageStatus Status { get; set; } = MessageStatus.Ok;
+ }
+
+ public sealed class CodeCompletionRequest : Request
+ {
+ public enum CompletionKind
+ {
+ InputActions = 0,
+ NodePaths,
+ ResourcePaths,
+ ScenePaths,
+ ShaderParams,
+ Signals,
+ ThemeColors,
+ ThemeConstants,
+ ThemeFonts,
+ ThemeStyles
+ }
+
+ public CompletionKind Kind { get; set; }
+ public string ScriptFile { get; set; }
+
+ public new const string Id = "CodeCompletion";
+
+ public CodeCompletionRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class CodeCompletionResponse : Response
+ {
+ public CodeCompletionRequest.CompletionKind Kind;
+ public string ScriptFile { get; set; }
+ public string[] Suggestions { get; set; }
+ }
+
+ public sealed class PlayRequest : Request
+ {
+ public new const string Id = "Play";
+
+ public PlayRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class PlayResponse : Response
+ {
+ }
+
+ public sealed class DebugPlayRequest : Request
+ {
+ public string DebuggerHost { get; set; }
+ public int DebuggerPort { get; set; }
+ public bool? BuildBeforePlaying { get; set; }
+
+ public new const string Id = "DebugPlay";
+
+ public DebugPlayRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class DebugPlayResponse : Response
+ {
+ }
+
+ public sealed class OpenFileRequest : Request
+ {
+ public string File { get; set; }
+ public int? Line { get; set; }
+ public int? Column { get; set; }
+
+ public new const string Id = "OpenFile";
+
+ public OpenFileRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class OpenFileResponse : Response
+ {
+ }
+
+ public sealed class ReloadScriptsRequest : Request
+ {
+ public new const string Id = "ReloadScripts";
+
+ public ReloadScriptsRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class ReloadScriptsResponse : Response
+ {
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs
new file mode 100644
index 0000000000..548e7f06ee
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs
@@ -0,0 +1,23 @@
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging
+{
+ public abstract class ResponseAwaiter : NotifyAwaiter<Response>
+ {
+ public abstract void SetResult(MessageContent content);
+ }
+
+ public class ResponseAwaiter<T> : ResponseAwaiter
+ where T : Response, new()
+ {
+ public override void SetResult(MessageContent content)
+ {
+ if (content.Status == MessageStatus.Ok)
+ SetResult(JsonConvert.DeserializeObject<T>(content.Body));
+ else
+ SetResult(new T {Status = content.Status});
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs
index 700b786752..d84a63c83c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs
@@ -1,9 +1,9 @@
using System;
using System.Runtime.CompilerServices;
-namespace GodotTools.Utils
+namespace GodotTools.IdeMessaging.Utils
{
- public sealed class NotifyAwaiter<T> : INotifyCompletion
+ public class NotifyAwaiter<T> : INotifyCompletion
{
private Action continuation;
private Exception exception;
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs
new file mode 100644
index 0000000000..9d593fbf8a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GodotTools.IdeMessaging.Utils
+{
+ public static class SemaphoreExtensions
+ {
+ public static ConfiguredTaskAwaitable<IDisposable> UseAsync(this SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var wrapper = new SemaphoreSlimWaitReleaseWrapper(semaphoreSlim, out Task waitAsyncTask, cancellationToken);
+ return waitAsyncTask.ContinueWith<IDisposable>(t => wrapper, cancellationToken).ConfigureAwait(false);
+ }
+
+ private struct SemaphoreSlimWaitReleaseWrapper : IDisposable
+ {
+ private readonly SemaphoreSlim semaphoreSlim;
+
+ public SemaphoreSlimWaitReleaseWrapper(SemaphoreSlim semaphoreSlim, out Task waitAsyncTask, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ this.semaphoreSlim = semaphoreSlim;
+ waitAsyncTask = this.semaphoreSlim.WaitAsync(cancellationToken);
+ }
+
+ public void Dispose()
+ {
+ semaphoreSlim.Release();
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
index b60e501beb..9cb50014b0 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
@@ -1,57 +1,23 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
- <OutputType>Library</OutputType>
- <RootNamespace>GodotTools.ProjectEditor</RootNamespace>
- <AssemblyName>GodotTools.ProjectEditor</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
- <LangVersion>7</LangVersion>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug</OutputPath>
- <DefineConstants>DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <Optimize>true</Optimize>
- <OutputPath>bin\Release</OutputPath>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- <Reference Include="Microsoft.Build" />
- <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL">
- <HintPath>$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
- </Reference>
- </ItemGroup>
<ItemGroup>
- <Compile Include="ApiAssembliesInfo.cs" />
- <Compile Include="DotNetSolution.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="IdentifierUtils.cs" />
- <Compile Include="ProjectExtensions.cs" />
- <Compile Include="ProjectGenerator.cs" />
- <Compile Include="ProjectUtils.cs" />
+ <PackageReference Include="Microsoft.Build" Version="16.5.0" />
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
- <None Include="packages.config" />
+ <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
- <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
- <Name>GodotTools.Core</Name>
- </ProjectReference>
+ <!--
+ The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
+ here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
+ We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
+ searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
+ -->
+ <None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
</ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
index f0e0d1b33d..704f2ec194 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
@@ -2,8 +2,8 @@ using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.IO;
-using DotNet.Globbing;
using Microsoft.Build.Construction;
+using Microsoft.Build.Globbing;
namespace GodotTools.ProjectEditor
{
@@ -11,8 +11,6 @@ namespace GodotTools.ProjectEditor
{
public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
{
- GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
-
string normalizedInclude = include.NormalizePath();
foreach (var itemGroup in root.ItemGroups)
@@ -25,7 +23,8 @@ namespace GodotTools.ProjectEditor
if (item.ItemType != itemType)
continue;
- var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
+ //var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
+ var glob = MSBuildGlob.Parse(item.Include.NormalizePath());
if (glob.IsMatch(normalizedInclude))
return item;
@@ -36,8 +35,6 @@ namespace GodotTools.ProjectEditor
}
public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
{
- GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
-
string normalizedInclude = Path.GetFullPath(include).NormalizePath();
foreach (var itemGroup in root.ItemGroups)
@@ -50,7 +47,7 @@ namespace GodotTools.ProjectEditor
if (item.ItemType != itemType)
continue;
- var glob = Glob.Parse(Path.GetFullPath(item.Include).NormalizePath(), globOptions);
+ var glob = MSBuildGlob.Parse(Path.GetFullPath(item.Include).NormalizePath());
if (glob.IsMatch(normalizedInclude))
return item;
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index cbe3afaedd..fb2beb6995 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -125,6 +125,12 @@ namespace GodotTools.ProjectEditor
// References
var referenceGroup = root.AddItemGroup();
referenceGroup.AddItem("Reference", "System");
+ var frameworkRefAssembliesItem = referenceGroup.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
+
+ // Use metadata (child nodes) instead of attributes for the PackageReference.
+ // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
+ frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
+ frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
index f2ebef1a7d..069a1edaa3 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -4,8 +4,8 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
-using DotNet.Globbing;
using Microsoft.Build.Construction;
+using Microsoft.Build.Globbing;
namespace GodotTools.ProjectEditor
{
@@ -133,9 +133,6 @@ namespace GodotTools.ProjectEditor
var result = new List<string>();
var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
- var globOptions = new GlobOptions();
- globOptions.Evaluation.CaseInsensitive = false;
-
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
@@ -151,7 +148,7 @@ namespace GodotTools.ProjectEditor
string normalizedInclude = item.Include.NormalizePath();
- var glob = Glob.Parse(normalizedInclude, globOptions);
+ var glob = MSBuildGlob.Parse(normalizedInclude);
// TODO Check somehow if path has no blob to avoid the following loop...
@@ -176,7 +173,7 @@ namespace GodotTools.ProjectEditor
void AddPropertyIfNotPresent(string name, string condition, string value)
{
if (root.PropertyGroups
- .Any(g => (g.Condition == string.Empty || g.Condition.Trim() == condition) &&
+ .Any(g => (string.IsNullOrEmpty(g.Condition) || g.Condition.Trim() == condition) &&
g.Properties
.Any(p => p.Name == name &&
p.Value == value &&
@@ -267,7 +264,7 @@ namespace GodotTools.ProjectEditor
bool hasGodotProjectGeneratorVersion = false;
bool foundOldConfiguration = false;
- foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition == string.Empty))
+ foreach (var propertyGroup in root.PropertyGroups.Where(g => string.IsNullOrEmpty(g.Condition)))
{
if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion"))
hasGodotProjectGeneratorVersion = true;
@@ -283,7 +280,7 @@ namespace GodotTools.ProjectEditor
if (!hasGodotProjectGeneratorVersion)
{
- root.PropertyGroups.First(g => g.Condition == string.Empty)?
+ root.PropertyGroups.First(g => string.IsNullOrEmpty(g.Condition))?
.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
project.HasUnsavedChanges = true;
}
@@ -351,5 +348,25 @@ namespace GodotTools.ProjectEditor
MigrateConfigurationConditions("Tools", "Debug"); // Must be last
}
}
+
+ public static void EnsureHasNugetNetFrameworkRefAssemblies(MSBuildProject project)
+ {
+ var root = project.Root;
+
+ bool found = root.ItemGroups.Any(g => string.IsNullOrEmpty(g.Condition) && g.Items.Any(
+ item => item.ItemType == "PackageReference" && item.Include == "Microsoft.NETFramework.ReferenceAssemblies"));
+
+ if (found)
+ return;
+
+ var frameworkRefAssembliesItem = root.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
+
+ // Use metadata (child nodes) instead of attributes for the PackageReference.
+ // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
+ frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
+ frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
+
+ project.HasUnsavedChanges = true;
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs
deleted file mode 100644
index 3a0464c9bc..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotTools.ProjectEditor")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
deleted file mode 100644
index 2db030f9d8..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
- <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" />
-</packages>
diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln
index a3438ea5f3..f6147eb5bb 100644
--- a/modules/mono/editor/GodotTools/GodotTools.sln
+++ b/modules/mono/editor/GodotTools/GodotTools.sln
@@ -9,7 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeMessaging", "GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 43c96d2e30..e55558c100 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -14,16 +14,6 @@ namespace GodotTools.Build
{
public static class BuildSystem
{
- private static string GetMsBuildPath()
- {
- string msbuildPath = MsBuildFinder.FindMsBuild();
-
- if (msbuildPath == null)
- throw new FileNotFoundException("Cannot find the MSBuild executable.");
-
- return msbuildPath;
- }
-
private static string MonoWindowsBinDir
{
get
@@ -46,8 +36,8 @@ namespace GodotTools.Build
{
if (OS.IsWindows)
{
- return (BuildManager.BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
- == BuildManager.BuildTool.MsBuildMono;
+ return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
+ == BuildTool.MsBuildMono;
}
return false;
@@ -57,16 +47,21 @@ namespace GodotTools.Build
private static bool PrintBuildOutput =>
(bool)EditorSettings.GetSetting("mono/builds/print_build_output");
- private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ private static Process LaunchBuild(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
+ (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
+
+ if (msbuildPath == null)
+ throw new FileNotFoundException("Cannot find the MSBuild executable.");
+
var customPropertiesList = new List<string>();
if (customProperties != null)
customPropertiesList.AddRange(customProperties);
- string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList);
+ string compilerArgs = BuildArguments(buildTool, solution, targets, config, loggerOutputDir, customPropertiesList);
- var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs);
+ var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
@@ -90,7 +85,7 @@ namespace GodotTools.Build
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
- var process = new Process { StartInfo = startInfo };
+ var process = new Process {StartInfo = startInfo};
process.Start();
@@ -105,19 +100,19 @@ namespace GodotTools.Build
public static int Build(BuildInfo buildInfo)
{
- return Build(buildInfo.Solution, buildInfo.Configuration,
+ return Build(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration,
buildInfo.LogsDirPath, buildInfo.CustomProperties);
}
- public static async Task<int> BuildAsync(BuildInfo buildInfo)
+ public static Task<int> BuildAsync(BuildInfo buildInfo)
{
- return await BuildAsync(buildInfo.Solution, buildInfo.Configuration,
+ return BuildAsync(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration,
buildInfo.LogsDirPath, buildInfo.CustomProperties);
}
- public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ public static int Build(string solution, string[] targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
- using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties))
{
process.WaitForExit();
@@ -125,9 +120,9 @@ namespace GodotTools.Build
}
}
- public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ public static async Task<int> BuildAsync(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
- using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties))
{
await process.WaitForExitAsync();
@@ -135,10 +130,15 @@ namespace GodotTools.Build
}
}
- private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties)
+ private static string BuildArguments(BuildTool buildTool, string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties)
{
- string arguments = $@"""{solution}"" /v:normal /t:Build ""/p:{"Configuration=" + config}"" " +
- $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
+ string arguments = string.Empty;
+
+ if (buildTool == BuildTool.DotnetCli)
+ arguments += "msbuild "; // `dotnet msbuild` command
+
+ arguments += $@"""{solution}"" /v:normal /t:{string.Join(",", targets)} ""/p:{"Configuration=" + config}"" " +
+ $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
foreach (string customProperty in customProperties)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs
new file mode 100644
index 0000000000..a1a69334e3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs
@@ -0,0 +1,10 @@
+namespace GodotTools.Build
+{
+ public enum BuildTool
+ {
+ MsBuildMono,
+ MsBuildVs,
+ JetBrainsMsBuild,
+ DotnetCli
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index af8d070cbd..f36e581a5f 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -17,75 +17,96 @@ namespace GodotTools.Build
private static string _msbuildToolsPath = string.Empty;
private static string _msbuildUnixPath = string.Empty;
- public static string FindMsBuild()
+ public static (string, BuildTool) FindMsBuild()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var buildTool = (BuildManager.BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
+ var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
if (OS.IsWindows)
{
switch (buildTool)
{
- case BuildManager.BuildTool.MsBuildVs:
+ case BuildTool.DotnetCli:
{
- if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
+ string dotnetCliPath = OS.PathWhich("dotnet");
+ if (!string.IsNullOrEmpty(dotnetCliPath))
+ return (dotnetCliPath, BuildTool.DotnetCli);
+ GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Visual Studio.");
+ goto case BuildTool.MsBuildVs;
+ }
+ case BuildTool.MsBuildVs:
+ {
+ if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_msbuildToolsPath = FindMsBuildToolsPathOnWindows();
- if (_msbuildToolsPath.Empty())
- {
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'.");
- }
+ if (string.IsNullOrEmpty(_msbuildToolsPath))
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'.");
}
if (!_msbuildToolsPath.EndsWith("\\"))
_msbuildToolsPath += "\\";
- return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
+ return (Path.Combine(_msbuildToolsPath, "MSBuild.exe"), BuildTool.MsBuildVs);
}
- case BuildManager.BuildTool.MsBuildMono:
+ case BuildTool.MsBuildMono:
{
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
if (!File.Exists(msbuildPath))
- {
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
- }
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}");
- return msbuildPath;
+ return (msbuildPath, BuildTool.MsBuildMono);
}
- case BuildManager.BuildTool.JetBrainsMsBuild:
+ case BuildTool.JetBrainsMsBuild:
+ {
var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName);
+
if (!File.Exists(editorPath))
throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}");
- var riderDir = new FileInfo(editorPath).Directory.Parent;
- return Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe");
+
+ var riderDir = new FileInfo(editorPath).Directory?.Parent;
+
+ string msbuildPath = Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe");
+
+ if (!File.Exists(msbuildPath))
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildJetBrains}'. Tried with path: {msbuildPath}");
+
+ return (msbuildPath, BuildTool.JetBrainsMsBuild);
+ }
default:
throw new IndexOutOfRangeException("Invalid build tool in editor settings");
}
}
- if (OS.IsUnixLike())
+ if (OS.IsUnixLike)
{
- if (buildTool == BuildManager.BuildTool.MsBuildMono)
+ switch (buildTool)
{
- if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath))
+ case BuildTool.DotnetCli:
{
- // Try to search it again if it wasn't found last time or if it was removed from its location
- _msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
+ string dotnetCliPath = OS.PathWhich("dotnet");
+ if (!string.IsNullOrEmpty(dotnetCliPath))
+ return (dotnetCliPath, BuildTool.DotnetCli);
+ GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Mono.");
+ goto case BuildTool.MsBuildMono;
}
-
- if (_msbuildUnixPath.Empty())
+ case BuildTool.MsBuildMono:
{
- throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'");
- }
+ if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath))
+ {
+ // Try to search it again if it wasn't found last time or if it was removed from its location
+ _msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
+ }
- return _msbuildUnixPath;
- }
- else
- {
- throw new IndexOutOfRangeException("Invalid build tool in editor settings");
+ if (string.IsNullOrEmpty(_msbuildUnixPath))
+ throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'");
+
+ return (_msbuildUnixPath, BuildTool.MsBuildMono);
+ }
+ default:
+ throw new IndexOutOfRangeException("Invalid build tool in editor settings");
}
}
@@ -114,12 +135,12 @@ namespace GodotTools.Build
{
string ret = OS.PathWhich(name);
- if (!ret.Empty())
+ if (!string.IsNullOrEmpty(ret))
return ret;
string retFallback = OS.PathWhich($"{name}.exe");
- if (!retFallback.Empty())
+ if (!string.IsNullOrEmpty(retFallback))
return retFallback;
foreach (string hintDir in MsBuildHintDirs)
@@ -143,7 +164,7 @@ namespace GodotTools.Build
string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)");
vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
- var vsWhereArgs = new[] { "-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild" };
+ var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"};
var outputArray = new Godot.Collections.Array<string>();
int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
@@ -171,7 +192,7 @@ namespace GodotTools.Build
string value = line.Substring(sepIdx + 1).StripEdges();
- if (value.Empty())
+ if (string.IsNullOrEmpty(value))
throw new FormatException("installationPath value is empty");
if (!value.EndsWith("\\"))
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
index 70bd552f2f..cca0983c01 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
@@ -10,6 +10,7 @@ namespace GodotTools
public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization
{
public string Solution { get; }
+ public string[] Targets { get; }
public string Configuration { get; }
public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization
@@ -38,9 +39,10 @@ namespace GodotTools
{
}
- public BuildInfo(string solution, string configuration)
+ public BuildInfo(string solution, string[] targets, string configuration)
{
Solution = solution;
+ Targets = targets;
Configuration = configuration;
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
index 520e665595..598787ba03 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -15,20 +15,14 @@ namespace GodotTools
{
private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>();
- public const string PropNameMsbuildMono = "MSBuild (Mono)";
- public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
- public const string PropNameMsbuildJetBrains = "MSBuild (JetBrains Rider)";
+ public const string PropNameMSBuildMono = "MSBuild (Mono)";
+ public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)";
+ public const string PropNameMSBuildJetBrains = "MSBuild (JetBrains Rider)";
+ public const string PropNameDotnetCli = "dotnet CLI";
public const string MsBuildIssuesFileName = "msbuild_issues.csv";
public const string MsBuildLogFileName = "msbuild_log.txt";
- public enum BuildTool
- {
- MsBuildMono,
- MsBuildVs,
- JetBrainsMsBuild
- }
-
private static void RemoveOldIssuesFile(BuildInfo buildInfo)
{
var issuesFile = GetIssuesFilePath(buildInfo);
@@ -181,10 +175,12 @@ namespace GodotTools
{
pr.Step("Building project solution", 0);
- var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config);
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Restore", "Build"}, config);
+
+ bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli;
// Add Godot defines
- string constants = buildTool != BuildTool.MsBuildMono ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
+ string constants = !escapeNeedsDoubleBackslash ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
foreach (var godotDefine in godotDefines)
constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
@@ -192,7 +188,7 @@ namespace GodotTools
if (Internal.GodotIsRealTDouble())
constants += "GODOT_REAL_T_IS_DOUBLE;";
- constants += buildTool != BuildTool.MsBuildMono ? "\"" : "\\\"";
+ constants += !escapeNeedsDoubleBackslash ? "\"" : "\\\"";
buildInfo.CustomProperties.Add(constants);
@@ -219,7 +215,7 @@ namespace GodotTools
if (File.Exists(editorScriptsMetadataPath))
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
- var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest;
+ var currentPlayRequest = GodotSharpEditor.Instance.CurrentPlaySettings;
if (currentPlayRequest != null)
{
@@ -233,7 +229,8 @@ namespace GodotTools
",server=n");
}
- return true; // Requested play from an external editor/IDE which already built the project
+ if (!currentPlayRequest.Value.BuildBeforePlaying)
+ return true; // Requested play from an external editor/IDE which already built the project
}
var godotDefines = new[]
@@ -249,22 +246,44 @@ namespace GodotTools
{
// Build tool settings
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var msbuild = BuildTool.MsBuildMono;
+
+ BuildTool msbuildDefault;
+
if (OS.IsWindows)
- msbuild = RiderPathManager.IsExternalEditorSetToRider(editorSettings)
- ? BuildTool.JetBrainsMsBuild
- : BuildTool.MsBuildVs;
+ {
+ if (RiderPathManager.IsExternalEditorSetToRider(editorSettings))
+ msbuildDefault = BuildTool.JetBrainsMsBuild;
+ else
+ msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildVs;
+ }
+ else
+ {
+ msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildMono;
+ }
+
+ EditorDef("mono/builds/build_tool", msbuildDefault);
- EditorDef("mono/builds/build_tool", msbuild);
+ string hintString;
+
+ if (OS.IsWindows)
+ {
+ hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
+ $"{PropNameMSBuildVs}:{(int)BuildTool.MsBuildVs}," +
+ $"{PropNameMSBuildJetBrains}:{(int)BuildTool.JetBrainsMsBuild}," +
+ $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
+ }
+ else
+ {
+ hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
+ $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
+ }
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Godot.Variant.Type.Int,
["name"] = "mono/builds/build_tool",
["hint"] = Godot.PropertyHint.Enum,
- ["hint_string"] = OS.IsWindows ?
- $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameMsbuildJetBrains}" :
- $"{PropNameMsbuildMono}"
+ ["hint_string"] = hintString
});
EditorDef("mono/builds/print_build_output", false);
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
index 938c3d8be1..0106e1f1ac 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
@@ -72,7 +72,7 @@ namespace GodotTools
{
string[] csvColumns = file.GetCsvLine();
- if (csvColumns.Length == 1 && csvColumns[0].Empty())
+ if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0]))
return;
if (csvColumns.Length != 7)
@@ -115,12 +115,12 @@ namespace GodotTools
// Get correct issue idx from issue list
int issueIndex = (int)issuesList.GetItemMetadata(idx);
- if (idx < 0 || idx >= issues.Count)
+ if (issueIndex < 0 || issueIndex >= issues.Count)
throw new IndexOutOfRangeException("Issue index out of range");
BuildIssue issue = issues[issueIndex];
- if (issue.ProjectFile.Empty() && issue.File.Empty())
+ if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
return;
string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
@@ -158,14 +158,14 @@ namespace GodotTools
string tooltip = string.Empty;
tooltip += $"Message: {issue.Message}";
- if (!issue.Code.Empty())
+ if (!string.IsNullOrEmpty(issue.Code))
tooltip += $"\nCode: {issue.Code}";
tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
string text = string.Empty;
- if (!issue.File.Empty())
+ if (!string.IsNullOrEmpty(issue.File))
{
text += $"{issue.File}({issue.Line},{issue.Column}): ";
@@ -174,7 +174,7 @@ namespace GodotTools
tooltip += $"\nColumn: {issue.Column}";
}
- if (!issue.ProjectFile.Empty())
+ if (!string.IsNullOrEmpty(issue.ProjectFile))
tooltip += $"\nProject: {issue.ProjectFile}";
text += issue.Message;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
index f1765f7e19..f60e469503 100755
--- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
@@ -587,7 +587,7 @@ MONO_AOT_MODE_LAST = 1000,
string arch = "x86_64";
return $"{platform}-{arch}";
}
- case OS.Platforms.X11:
+ case OS.Platforms.LinuxBSD:
case OS.Platforms.Server:
{
string arch = bits == "64" ? "x86_64" : "i686";
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 2ceb4888a2..6bfbc62f3b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -414,7 +414,7 @@ namespace GodotTools.Export
case OS.Platforms.UWP:
return "net_4_x_win";
case OS.Platforms.OSX:
- case OS.Platforms.X11:
+ case OS.Platforms.LinuxBSD:
case OS.Platforms.Server:
case OS.Platforms.Haiku:
return "net_4_x";
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index c070cb16d9..eb7696685f 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -37,6 +37,8 @@ namespace GodotTools
public BottomPanel BottomPanel { get; private set; }
+ public PlaySettings? CurrentPlaySettings { get; set; }
+
public static string ProjectAssemblyName
{
get
@@ -228,12 +230,12 @@ namespace GodotTools
[UsedImplicitly]
public Error OpenInExternalEditor(Script script, int line, int col)
{
- var editor = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor");
+ var editorId = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor");
- switch (editor)
+ switch (editorId)
{
case ExternalEditorId.None:
- // Tells the caller to fallback to the global external editor settings or the built-in editor
+ // Not an error. Tells the caller to fallback to the global external editor settings or the built-in editor.
return Error.Unavailable;
case ExternalEditorId.VisualStudio:
throw new NotSupportedException();
@@ -249,17 +251,20 @@ namespace GodotTools
{
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
- if (line >= 0)
- GodotIdeManager.SendOpenFile(scriptPath, line + 1, col);
- else
- GodotIdeManager.SendOpenFile(scriptPath);
+ GodotIdeManager.LaunchIdeAsync().ContinueWith(launchTask =>
+ {
+ var editorPick = launchTask.Result;
+ if (line >= 0)
+ editorPick?.SendOpenFile(scriptPath, line + 1, col);
+ else
+ editorPick?.SendOpenFile(scriptPath);
+ });
break;
}
-
case ExternalEditorId.VsCode:
{
- if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
+ if (string.IsNullOrEmpty(_vsCodePath) || !File.Exists(_vsCodePath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty);
@@ -300,7 +305,7 @@ namespace GodotTools
if (line >= 0)
{
args.Add("-g");
- args.Add($"{scriptPath}:{line + 1}:{col}");
+ args.Add($"{scriptPath}:{line}:{col}");
}
else
{
@@ -311,7 +316,7 @@ namespace GodotTools
if (OS.IsOSX)
{
- if (!osxAppBundleInstalled && _vsCodePath.Empty())
+ if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@@ -321,7 +326,7 @@ namespace GodotTools
}
else
{
- if (_vsCodePath.Empty())
+ if (string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@@ -341,7 +346,6 @@ namespace GodotTools
break;
}
-
default:
throw new ArgumentOutOfRangeException();
}
@@ -457,6 +461,9 @@ namespace GodotTools
// Make sure the existing project has Api assembly references configured correctly
ProjectUtils.FixApiHintPath(msbuildProject);
+ // Make sure the existing project references the Microsoft.NETFramework.ReferenceAssemblies nuget package
+ ProjectUtils.EnsureHasNugetNetFrameworkRefAssemblies(msbuildProject);
+
if (msbuildProject.HasUnsavedChanges)
{
// Save a copy of the project before replacing it
@@ -505,7 +512,7 @@ namespace GodotTools
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider:{(int)ExternalEditorId.Rider}";
}
- else if (OS.IsUnixLike())
+ else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index ac9379adf8..ba527ca3b5 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -1,123 +1,37 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
- <OutputType>Library</OutputType>
- <RootNamespace>GodotTools</RootNamespace>
- <AssemblyName>GodotTools</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ <GodotApiConfiguration>Debug</GodotApiConfiguration> <!-- The Godot editor uses the Debug Godot API assemblies -->
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
- <DataDirToolsOutputPath>$(GodotSourceRootPath)/bin/GodotSharp/Tools</DataDirToolsOutputPath>
- <GodotApiConfiguration>Debug</GodotApiConfiguration>
- <LangVersion>7</LangVersion>
+ <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
+ <GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug</OutputPath>
- <DefineConstants>DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <Optimize>true</Optimize>
- <OutputPath>bin\Release</OutputPath>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
+ <PropertyGroup Condition=" Exists('$(GodotApiAssembliesDir)/GodotSharp.dll') ">
+ <!-- The project is part of the Godot source tree -->
+ <!-- Use the Godot source tree output folder instead of '$(ProjectDir)/bin' -->
+ <OutputPath>$(GodotOutputDataDir)/Tools</OutputPath>
+ <!-- Must not append '$(TargetFramework)' to the output path in this case -->
+ <AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<ItemGroup>
- <Reference Include="JetBrains.Annotations, Version=2019.1.3.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325">
- <HintPath>..\packages\JetBrains.Annotations.2019.1.3\lib\net20\JetBrains.Annotations.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
- <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="System" />
+ <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<Reference Include="GodotSharp">
- <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath>
+ <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="GodotSharpEditor">
- <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath>
+ <HintPath>$(GodotApiAssembliesDir)/GodotSharpEditor.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
- <Compile Include="Build\MsBuildFinder.cs" />
- <Compile Include="Export\AotBuilder.cs" />
- <Compile Include="Export\ExportPlugin.cs" />
- <Compile Include="Export\XcodeHelper.cs" />
- <Compile Include="ExternalEditorId.cs" />
- <Compile Include="Ides\GodotIdeManager.cs" />
- <Compile Include="Ides\GodotIdeServer.cs" />
- <Compile Include="Ides\MonoDevelop\EditorId.cs" />
- <Compile Include="Ides\MonoDevelop\Instance.cs" />
- <Compile Include="Ides\Rider\RiderPathLocator.cs" />
- <Compile Include="Ides\Rider\RiderPathManager.cs" />
- <Compile Include="Internals\EditorProgress.cs" />
- <Compile Include="Internals\GodotSharpDirs.cs" />
- <Compile Include="Internals\Internal.cs" />
- <Compile Include="Internals\ScriptClassParser.cs" />
- <Compile Include="Internals\Globals.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="Build\BuildSystem.cs" />
- <Compile Include="Utils\Directory.cs" />
- <Compile Include="Utils\File.cs" />
- <Compile Include="Utils\NotifyAwaiter.cs" />
- <Compile Include="Utils\OS.cs" />
- <Compile Include="GodotSharpEditor.cs" />
- <Compile Include="BuildManager.cs" />
- <Compile Include="HotReloadAssemblyWatcher.cs" />
- <Compile Include="BuildInfo.cs" />
- <Compile Include="BuildTab.cs" />
- <Compile Include="BottomPanel.cs" />
- <Compile Include="CsProjOperations.cs" />
- <Compile Include="Utils\CollectionExtensions.cs" />
- <Compile Include="Utils\User32Dll.cs" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj">
- <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project>
- <Name>GodotTools.BuildLogger</Name>
- </ProjectReference>
- <ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj">
- <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project>
- <Name>GodotTools.IdeConnection</Name>
- </ProjectReference>
- <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj">
- <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project>
- <Name>GodotTools.ProjectEditor</Name>
- </ProjectReference>
- <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
- <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
- <Name>GodotTools.Core</Name>
- </ProjectReference>
- </ItemGroup>
- <ItemGroup>
- <None Include="packages.config" />
+ <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" />
+ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
+ <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" />
+ <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
</ItemGroup>
- <Target Name="CopyToDataDir" AfterTargets="Build">
- <ItemGroup>
- <GodotToolsCopy Include="$(OutputPath)\GodotTools*.dll" />
- <GodotToolsCopy Include="$(OutputPath)\Newtonsoft.Json.dll" />
- <GodotToolsCopy Include="$(OutputPath)\DotNet.Glob.dll" />
- </ItemGroup>
- <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <GodotToolsCopy Include="$(OutputPath)\GodotTools*.pdb" />
- </ItemGroup>
- <Copy SourceFiles="@(GodotToolsCopy)" DestinationFolder="$(DataDirToolsOutputPath)" ContinueOnError="false" />
- </Target>
- <Target Name="BuildAlwaysCopyToDataDir">
- <!-- Custom target run by SCons to make sure the CopyToDataDir target is always executed, without having to use DisableFastUpToDateCheck -->
- <CallTarget Targets="Build" />
- <CallTarget Targets="CopyToDataDir" />
- </Target>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 54f0ffab96..e4932ca217 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -1,73 +1,104 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using Godot;
-using GodotTools.IdeConnection;
+using GodotTools.IdeMessaging;
+using GodotTools.IdeMessaging.Requests;
using GodotTools.Internals;
namespace GodotTools.Ides
{
- public class GodotIdeManager : Node, ISerializationListener
+ public sealed class GodotIdeManager : Node, ISerializationListener
{
- public GodotIdeServer GodotIdeServer { get; private set; }
+ private MessagingServer MessagingServer { get; set; }
private MonoDevelop.Instance monoDevelInstance;
private MonoDevelop.Instance vsForMacInstance;
- private GodotIdeServer GetRunningServer()
+ private MessagingServer GetRunningOrNewServer()
{
- if (GodotIdeServer != null && !GodotIdeServer.IsDisposed)
- return GodotIdeServer;
- StartServer();
- return GodotIdeServer;
+ if (MessagingServer != null && !MessagingServer.IsDisposed)
+ return MessagingServer;
+
+ MessagingServer?.Dispose();
+ MessagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger());
+
+ _ = MessagingServer.Listen();
+
+ return MessagingServer;
}
public override void _Ready()
{
- StartServer();
+ _ = GetRunningOrNewServer();
}
public void OnBeforeSerialize()
{
- GodotIdeServer?.Dispose();
}
public void OnAfterDeserialize()
{
- StartServer();
+ _ = GetRunningOrNewServer();
}
- private ILogger logger;
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
- protected ILogger Logger
+ if (disposing)
+ {
+ MessagingServer?.Dispose();
+ }
+ }
+
+ private string GetExternalEditorIdentity(ExternalEditorId editorId)
{
- get => logger ?? (logger = new GodotLogger());
+ // Manually convert to string to avoid breaking compatibility in case we rename the enum fields.
+ switch (editorId)
+ {
+ case ExternalEditorId.None:
+ return null;
+ case ExternalEditorId.VisualStudio:
+ return "VisualStudio";
+ case ExternalEditorId.VsCode:
+ return "VisualStudioCode";
+ case ExternalEditorId.Rider:
+ return "Rider";
+ case ExternalEditorId.VisualStudioForMac:
+ return "VisualStudioForMac";
+ case ExternalEditorId.MonoDevelop:
+ return "MonoDevelop";
+ default:
+ throw new NotImplementedException();
+ }
}
- private void StartServer()
+ public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000)
{
- GodotIdeServer?.Dispose();
- GodotIdeServer = new GodotIdeServer(LaunchIde,
- OS.GetExecutablePath(),
- ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir));
+ var editorId = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface()
+ .GetEditorSettings().GetSetting("mono/editor/external_editor");
+ string editorIdentity = GetExternalEditorIdentity(editorId);
- GodotIdeServer.Logger = Logger;
+ var runningServer = GetRunningOrNewServer();
- GodotIdeServer.StartServer();
- }
+ if (runningServer.IsAnyConnected(editorIdentity))
+ return new EditorPick(editorIdentity);
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
+ LaunchIde(editorId, editorIdentity);
+
+ var timeoutTask = Task.Delay(millisecondsTimeout);
+ var completedTask = await Task.WhenAny(timeoutTask, runningServer.AwaitClientConnected(editorIdentity));
+
+ if (completedTask != timeoutTask)
+ return new EditorPick(editorIdentity);
- GodotIdeServer?.Dispose();
+ return null;
}
- private void LaunchIde()
+ private void LaunchIde(ExternalEditorId editorId, string editorIdentity)
{
- var editor = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface()
- .GetEditorSettings().GetSetting("mono/editor/external_editor");
-
- switch (editor)
+ switch (editorId)
{
case ExternalEditorId.None:
case ExternalEditorId.VisualStudio:
@@ -80,14 +111,14 @@ namespace GodotTools.Ides
{
MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath)
{
- if (Utils.OS.IsOSX && editor == ExternalEditorId.VisualStudioForMac)
+ if (Utils.OS.IsOSX && editorId == ExternalEditorId.VisualStudioForMac)
{
- vsForMacInstance = vsForMacInstance ??
+ vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ??
new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
return vsForMacInstance;
}
- monoDevelInstance = monoDevelInstance ??
+ monoDevelInstance = (monoDevelInstance?.IsDisposed ?? true ? null : monoDevelInstance) ??
new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop);
return monoDevelInstance;
}
@@ -96,12 +127,25 @@ namespace GodotTools.Ides
{
var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath);
- if (!instance.IsRunning)
+ if (instance.IsRunning && !GetRunningOrNewServer().IsAnyConnected(editorIdentity))
+ {
+ // After launch we wait up to 30 seconds for the IDE to connect to our messaging server.
+ var waitAfterLaunch = TimeSpan.FromSeconds(30);
+ var timeSinceLaunch = DateTime.Now - instance.LaunchTime;
+ if (timeSinceLaunch > waitAfterLaunch)
+ {
+ instance.Dispose();
+ instance.Execute();
+ }
+ }
+ else if (!instance.IsRunning)
+ {
instance.Execute();
+ }
}
catch (FileNotFoundException)
{
- string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
+ string editorName = editorId == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
GD.PushError($"Cannot find code editor: {editorName}");
}
@@ -113,26 +157,45 @@ namespace GodotTools.Ides
}
}
- private void WriteMessage(string id, params string[] arguments)
+ public readonly struct EditorPick
{
- GetRunningServer().WriteMessage(new Message(id, arguments));
- }
+ private readonly string identity;
- public void SendOpenFile(string file)
- {
- WriteMessage("OpenFile", file);
- }
+ public EditorPick(string identity)
+ {
+ this.identity = identity;
+ }
- public void SendOpenFile(string file, int line)
- {
- WriteMessage("OpenFile", file, line.ToString());
- }
+ public bool IsAnyConnected() =>
+ GodotSharpEditor.Instance.GodotIdeManager.GetRunningOrNewServer().IsAnyConnected(identity);
- public void SendOpenFile(string file, int line, int column)
- {
- WriteMessage("OpenFile", file, line.ToString(), column.ToString());
+ private void SendRequest<TResponse>(Request request)
+ where TResponse : Response, new()
+ {
+ // Logs an error if no client is connected with the specified identity
+ GodotSharpEditor.Instance.GodotIdeManager
+ .GetRunningOrNewServer()
+ .BroadcastRequest<TResponse>(identity, request);
+ }
+
+ public void SendOpenFile(string file)
+ {
+ SendRequest<OpenFileResponse>(new OpenFileRequest {File = file});
+ }
+
+ public void SendOpenFile(string file, int line)
+ {
+ SendRequest<OpenFileResponse>(new OpenFileRequest {File = file, Line = line});
+ }
+
+ public void SendOpenFile(string file, int line, int column)
+ {
+ SendRequest<OpenFileResponse>(new OpenFileRequest {File = file, Line = line, Column = column});
+ }
}
+ public EditorPick PickEditor(ExternalEditorId editorId) => new EditorPick(GetExternalEditorIdentity(editorId));
+
private class GodotLogger : ILogger
{
public void LogDebug(string message)
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
deleted file mode 100644
index 72676a8b24..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
+++ /dev/null
@@ -1,212 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using GodotTools.IdeConnection;
-using GodotTools.Internals;
-using GodotTools.Utils;
-using Directory = System.IO.Directory;
-using File = System.IO.File;
-using Thread = System.Threading.Thread;
-
-namespace GodotTools.Ides
-{
- public class GodotIdeServer : GodotIdeBase
- {
- private readonly TcpListener listener;
- private readonly FileStream metaFile;
- private readonly Action launchIdeAction;
- private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>();
-
- private async Task<bool> AwaitClientConnected()
- {
- return await clientConnectedAwaiter.Reset();
- }
-
- public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir)
- : base(projectMetadataDir)
- {
- messageHandlers = InitializeMessageHandlers();
-
- this.launchIdeAction = launchIdeAction;
-
- // Make sure the directory exists
- Directory.CreateDirectory(projectMetadataDir);
-
- // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing...
- const FileShare metaFileShare = FileShare.ReadWrite;
-
- metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare);
-
- listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
- listener.Start();
-
- int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port;
- using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8))
- {
- metaFileWriter.WriteLine(port);
- metaFileWriter.WriteLine(editorExecutablePath);
- }
-
- StartServer();
- }
-
- public void StartServer()
- {
- var serverThread = new Thread(RunServerThread) { Name = "Godot Ide Connection Server" };
- serverThread.Start();
- }
-
- private void RunServerThread()
- {
- SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext);
-
- try
- {
- while (!IsDisposed)
- {
- TcpClient tcpClient = listener.AcceptTcpClient();
-
- Logger.LogInfo("Connection open with Ide Client");
-
- lock (ConnectionLock)
- {
- Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage);
- Connection.Logger = Logger;
- }
-
- Connected += () => clientConnectedAwaiter.SetResult(true);
-
- Connection.Start();
- }
- }
- catch (Exception e)
- {
- if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted))
- throw;
- }
- }
-
- public async void WriteMessage(Message message)
- {
- async Task LaunchIde()
- {
- if (IsConnected)
- return;
-
- launchIdeAction();
- await Task.WhenAny(Task.Delay(10000), AwaitClientConnected());
- }
-
- await LaunchIde();
-
- if (!IsConnected)
- {
- Logger.LogError("Cannot write message: Godot Ide Server not connected");
- return;
- }
-
- Connection.WriteMessage(message);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- listener?.Stop();
-
- metaFile?.Dispose();
-
- File.Delete(MetaFilePath);
- }
- }
-
- protected virtual bool HandleMessage(Message message)
- {
- if (messageHandlers.TryGetValue(message.Id, out var action))
- {
- action(message.Arguments);
- return true;
- }
-
- return false;
- }
-
- private readonly Dictionary<string, Action<string[]>> messageHandlers;
-
- private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
- {
- return new Dictionary<string, Action<string[]>>
- {
- ["Play"] = args =>
- {
- switch (args.Length)
- {
- case 0:
- Play();
- return;
- case 2:
- Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1]));
- return;
- default:
- throw new ArgumentException();
- }
- },
- ["ReloadScripts"] = args => ReloadScripts()
- };
- }
-
- private void DispatchToMainThread(Action action)
- {
- var d = new SendOrPostCallback(state => action());
- Godot.Dispatcher.SynchronizationContext.Post(d, null);
- }
-
- private void Play()
- {
- DispatchToMainThread(() =>
- {
- CurrentPlayRequest = new PlayRequest();
- Internal.EditorRunPlay();
- CurrentPlayRequest = null;
- });
- }
-
- private void Play(string debuggerHost, int debuggerPort)
- {
- DispatchToMainThread(() =>
- {
- CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort);
- Internal.EditorRunPlay();
- CurrentPlayRequest = null;
- });
- }
-
- private void ReloadScripts()
- {
- DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts);
- }
-
- public PlayRequest? CurrentPlayRequest { get; private set; }
-
- public struct PlayRequest
- {
- public bool HasDebugger { get; }
- public string DebuggerHost { get; }
- public int DebuggerPort { get; }
-
- public PlayRequest(string debuggerHost, int debuggerPort)
- {
- HasDebugger = true;
- DebuggerHost = debuggerHost;
- DebuggerPort = debuggerPort;
- }
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
new file mode 100644
index 0000000000..32f264d100
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
@@ -0,0 +1,360 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging;
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+using GodotTools.Internals;
+using Newtonsoft.Json;
+using Directory = System.IO.Directory;
+using File = System.IO.File;
+
+namespace GodotTools.Ides
+{
+ public sealed class MessagingServer : IDisposable
+ {
+ private readonly ILogger logger;
+
+ private readonly FileStream metaFile;
+ private string MetaFilePath { get; }
+
+ private readonly SemaphoreSlim peersSem = new SemaphoreSlim(1);
+
+ private readonly TcpListener listener;
+
+ private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientConnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>();
+ private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientDisconnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>();
+
+ public async Task<bool> AwaitClientConnected(string identity)
+ {
+ if (!clientConnectedAwaiters.TryGetValue(identity, out var queue))
+ {
+ queue = new Queue<NotifyAwaiter<bool>>();
+ clientConnectedAwaiters.Add(identity, queue);
+ }
+
+ var awaiter = new NotifyAwaiter<bool>();
+ queue.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ public async Task<bool> AwaitClientDisconnected(string identity)
+ {
+ if (!clientDisconnectedAwaiters.TryGetValue(identity, out var queue))
+ {
+ queue = new Queue<NotifyAwaiter<bool>>();
+ clientDisconnectedAwaiters.Add(identity, queue);
+ }
+
+ var awaiter = new NotifyAwaiter<bool>();
+ queue.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ public bool IsDisposed { get; private set; }
+
+ public bool IsAnyConnected(string identity) => string.IsNullOrEmpty(identity) ?
+ Peers.Count > 0 :
+ Peers.Any(c => c.RemoteIdentity == identity);
+
+ private List<Peer> Peers { get; } = new List<Peer>();
+
+ ~MessagingServer()
+ {
+ Dispose(disposing: false);
+ }
+
+ public async void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ using (await peersSem.UseAsync())
+ {
+ if (IsDisposed) // lock may not be fair
+ return;
+ IsDisposed = true;
+ }
+
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ foreach (var connection in Peers)
+ connection.Dispose();
+ Peers.Clear();
+ listener?.Stop();
+
+ metaFile?.Dispose();
+
+ File.Delete(MetaFilePath);
+ }
+ }
+
+ public MessagingServer(string editorExecutablePath, string projectMetadataDir, ILogger logger)
+ {
+ this.logger = logger;
+
+ MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName);
+
+ // Make sure the directory exists
+ Directory.CreateDirectory(projectMetadataDir);
+
+ // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing...
+ const FileShare metaFileShare = FileShare.ReadWrite;
+
+ metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare);
+
+ listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
+ listener.Start();
+
+ int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port;
+ using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8))
+ {
+ metaFileWriter.WriteLine(port);
+ metaFileWriter.WriteLine(editorExecutablePath);
+ }
+ }
+
+ private async Task AcceptClient(TcpClient tcpClient)
+ {
+ logger.LogDebug("Accept client...");
+
+ using (var peer = new Peer(tcpClient, new ServerHandshake(), new ServerMessageHandler(), logger))
+ {
+ // ReSharper disable AccessToDisposedClosure
+ peer.Connected += () =>
+ {
+ logger.LogInfo("Connection open with Ide Client");
+
+ if (clientConnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue))
+ {
+ while (queue.Count > 0)
+ queue.Dequeue().SetResult(true);
+ clientConnectedAwaiters.Remove(peer.RemoteIdentity);
+ }
+ };
+
+ peer.Disconnected += () =>
+ {
+ if (clientDisconnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue))
+ {
+ while (queue.Count > 0)
+ queue.Dequeue().SetResult(true);
+ clientDisconnectedAwaiters.Remove(peer.RemoteIdentity);
+ }
+ };
+ // ReSharper restore AccessToDisposedClosure
+
+ try
+ {
+ if (!await peer.DoHandshake("server"))
+ {
+ logger.LogError("Handshake failed");
+ return;
+ }
+ }
+ catch (Exception e)
+ {
+ logger.LogError("Handshake failed with unhandled exception: ", e);
+ return;
+ }
+
+ using (await peersSem.UseAsync())
+ Peers.Add(peer);
+
+ try
+ {
+ await peer.Process();
+ }
+ finally
+ {
+ using (await peersSem.UseAsync())
+ Peers.Remove(peer);
+ }
+ }
+ }
+
+ public async Task Listen()
+ {
+ try
+ {
+ while (!IsDisposed)
+ _ = AcceptClient(await listener.AcceptTcpClientAsync());
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted))
+ throw;
+ }
+ }
+
+ public async void BroadcastRequest<TResponse>(string identity, Request request)
+ where TResponse : Response, new()
+ {
+ using (await peersSem.UseAsync())
+ {
+ if (!IsAnyConnected(identity))
+ {
+ logger.LogError("Cannot write request. No client connected to the Godot Ide Server.");
+ return;
+ }
+
+ var selectedConnections = string.IsNullOrEmpty(identity) ?
+ Peers :
+ Peers.Where(c => c.RemoteIdentity == identity);
+
+ string body = JsonConvert.SerializeObject(request);
+
+ foreach (var connection in selectedConnections)
+ _ = connection.SendRequest<TResponse>(request.Id, body);
+ }
+ }
+
+ private class ServerHandshake : IHandshake
+ {
+ private static readonly string ServerHandshakeBase = $"{Peer.ServerHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}";
+ private static readonly string ClientHandshakePattern = $@"{Regex.Escape(Peer.ClientHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})";
+
+ public string GetHandshakeLine(string identity) => $"{ServerHandshakeBase},{identity}";
+
+ public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger)
+ {
+ identity = null;
+
+ var match = Regex.Match(handshake, ClientHandshakePattern);
+
+ if (!match.Success)
+ return false;
+
+ if (!uint.TryParse(match.Groups[1].Value, out uint clientMajor) || Peer.ProtocolVersionMajor != clientMajor)
+ {
+ logger.LogDebug("Incompatible major version: " + match.Groups[1].Value);
+ return false;
+ }
+
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+ if (!uint.TryParse(match.Groups[2].Value, out uint clientMinor) || Peer.ProtocolVersionMinor > clientMinor)
+ {
+ logger.LogDebug("Incompatible minor version: " + match.Groups[2].Value);
+ return false;
+ }
+
+ if (!uint.TryParse(match.Groups[3].Value, out uint _)) // Revision
+ {
+ logger.LogDebug("Incompatible revision build: " + match.Groups[3].Value);
+ return false;
+ }
+
+ identity = match.Groups[4].Value;
+
+ return true;
+ }
+ }
+
+ private class ServerMessageHandler : IMessageHandler
+ {
+ private static void DispatchToMainThread(Action action)
+ {
+ var d = new SendOrPostCallback(state => action());
+ Godot.Dispatcher.SynchronizationContext.Post(d, null);
+ }
+
+ private readonly Dictionary<string, Peer.RequestHandler> requestHandlers = InitializeRequestHandlers();
+
+ public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger)
+ {
+ if (!requestHandlers.TryGetValue(id, out var handler))
+ {
+ logger.LogError($"Received unknown request: {id}");
+ return new MessageContent(MessageStatus.RequestNotSupported, "null");
+ }
+
+ try
+ {
+ var response = await handler(peer, content);
+ return new MessageContent(response.Status, JsonConvert.SerializeObject(response));
+ }
+ catch (JsonException)
+ {
+ logger.LogError($"Received request with invalid body: {id}");
+ return new MessageContent(MessageStatus.InvalidRequestBody, "null");
+ }
+ }
+
+ private static Dictionary<string, Peer.RequestHandler> InitializeRequestHandlers()
+ {
+ return new Dictionary<string, Peer.RequestHandler>
+ {
+ [PlayRequest.Id] = async (peer, content) =>
+ {
+ _ = JsonConvert.DeserializeObject<PlayRequest>(content.Body);
+ return await HandlePlay();
+ },
+ [DebugPlayRequest.Id] = async (peer, content) =>
+ {
+ var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body);
+ return await HandleDebugPlay(request);
+ },
+ [ReloadScriptsRequest.Id] = async (peer, content) =>
+ {
+ _ = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body);
+ return await HandleReloadScripts();
+ },
+ [CodeCompletionRequest.Id] = async (peer, content) =>
+ {
+ var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body);
+ return await HandleCodeCompletionRequest(request);
+ }
+ };
+ }
+
+ private static Task<Response> HandlePlay()
+ {
+ DispatchToMainThread(() =>
+ {
+ GodotSharpEditor.Instance.CurrentPlaySettings = new PlaySettings();
+ Internal.EditorRunPlay();
+ GodotSharpEditor.Instance.CurrentPlaySettings = null;
+ });
+ return Task.FromResult<Response>(new PlayResponse());
+ }
+
+ private static Task<Response> HandleDebugPlay(DebugPlayRequest request)
+ {
+ DispatchToMainThread(() =>
+ {
+ GodotSharpEditor.Instance.CurrentPlaySettings =
+ new PlaySettings(request.DebuggerHost, request.DebuggerPort, request.BuildBeforePlaying ?? true);
+ Internal.EditorRunPlay();
+ GodotSharpEditor.Instance.CurrentPlaySettings = null;
+ });
+ return Task.FromResult<Response>(new DebugPlayResponse());
+ }
+
+ private static Task<Response> HandleReloadScripts()
+ {
+ DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts);
+ return Task.FromResult<Response>(new ReloadScriptsResponse());
+ }
+
+ private static async Task<Response> HandleCodeCompletionRequest(CodeCompletionRequest request)
+ {
+ var response = new CodeCompletionResponse {Kind = request.Kind, ScriptFile = request.ScriptFile};
+ response.Suggestions = await Task.Run(() => Internal.CodeCompletionRequest(response.Kind, response.ScriptFile));
+ return response;
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
index 6026c109ad..d6fa2eeba7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
@@ -7,14 +7,16 @@ using GodotTools.Utils;
namespace GodotTools.Ides.MonoDevelop
{
- public class Instance
+ public class Instance : IDisposable
{
+ public DateTime LaunchTime { get; private set; }
private readonly string solutionFile;
private readonly EditorId editorId;
private Process process;
public bool IsRunning => process != null && !process.HasExited;
+ public bool IsDisposed { get; private set; }
public void Execute()
{
@@ -59,6 +61,8 @@ namespace GodotTools.Ides.MonoDevelop
if (command == null)
throw new FileNotFoundException();
+ LaunchTime = DateTime.Now;
+
if (newWindow)
{
process = Process.Start(new ProcessStartInfo
@@ -88,6 +92,12 @@ namespace GodotTools.Ides.MonoDevelop
this.editorId = editorId;
}
+ public void Dispose()
+ {
+ IsDisposed = true;
+ process?.Dispose();
+ }
+
private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames;
private static readonly IReadOnlyDictionary<EditorId, string> BundleIds;
@@ -118,7 +128,7 @@ namespace GodotTools.Ides.MonoDevelop
{EditorId.MonoDevelop, "MonoDevelop.exe"}
};
}
- else if (OS.IsUnixLike())
+ else if (OS.IsUnixLike)
{
ExecutableNames = new Dictionary<EditorId, string>
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
index e3a4fa7b45..e22e9af919 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
@@ -36,7 +36,7 @@ namespace GodotTools.Ides.Rider
{
return CollectRiderInfosMac();
}
- if (OS.IsUnixLike())
+ if (OS.IsUnixLike)
{
return CollectAllRiderPathsLinux();
}
@@ -141,16 +141,16 @@ namespace GodotTools.Ides.Rider
if (OS.IsOSX)
{
var home = Environment.GetEnvironmentVariable("HOME");
- if (string.IsNullOrEmpty(home))
+ if (string.IsNullOrEmpty(home))
return string.Empty;
var localAppData = Path.Combine(home, @"Library/Application Support");
return GetToolboxRiderRootPath(localAppData);
}
- if (OS.IsUnixLike())
+ if (OS.IsUnixLike)
{
var home = Environment.GetEnvironmentVariable("HOME");
- if (string.IsNullOrEmpty(home))
+ if (string.IsNullOrEmpty(home))
return string.Empty;
var localAppData = Path.Combine(home, @".local/share");
return GetToolboxRiderRootPath(localAppData);
@@ -209,7 +209,7 @@ namespace GodotTools.Ides.Rider
private static string GetRelativePathToBuildTxt()
{
- if (OS.IsWindows || OS.IsUnixLike())
+ if (OS.IsWindows || OS.IsUnixLike)
return "../../build.txt";
if (OS.IsOSX)
return "Contents/Resources/build.txt";
@@ -322,7 +322,7 @@ namespace GodotTools.Ides.Rider
class SettingsJson
{
public string install_location;
-
+
[CanBeNull]
public static string GetInstallLocationFromJson(string json)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 026a7db89c..7e5049e4b7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -2,6 +2,7 @@ using System;
using System.Runtime.CompilerServices;
using Godot;
using Godot.Collections;
+using GodotTools.IdeMessaging.Requests;
namespace GodotTools.Internals
{
@@ -52,6 +53,9 @@ namespace GodotTools.Internals
public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
+ public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, string scriptFile) =>
+ internal_CodeCompletionRequest((int)kind, scriptFile);
+
#region Internal
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -111,6 +115,9 @@ namespace GodotTools.Internals
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ScriptEditorDebugger_ReloadScripts();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string[] internal_CodeCompletionRequest(int kind, string scriptFile);
+
#endregion
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs
new file mode 100644
index 0000000000..820d0c0b83
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs
@@ -0,0 +1,19 @@
+namespace GodotTools
+{
+ public struct PlaySettings
+ {
+ public bool HasDebugger { get; }
+ public string DebuggerHost { get; }
+ public int DebuggerPort { get; }
+
+ public bool BuildBeforePlaying { get; }
+
+ public PlaySettings(string debuggerHost, int debuggerPort, bool buildBeforePlaying)
+ {
+ HasDebugger = true;
+ DebuggerHost = debuggerHost;
+ DebuggerPort = debuggerPort;
+ BuildBeforePlaying = buildBeforePlaying;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs
deleted file mode 100644
index f5fe85c722..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotTools")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index b057ac12c6..6c05891f2c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -22,7 +22,10 @@ namespace GodotTools.Utils
{
public const string Windows = "Windows";
public const string OSX = "OSX";
- public const string X11 = "X11";
+ public const string Linux = "Linux";
+ public const string FreeBSD = "FreeBSD";
+ public const string NetBSD = "NetBSD";
+ public const string BSD = "BSD";
public const string Server = "Server";
public const string UWP = "UWP";
public const string Haiku = "Haiku";
@@ -35,7 +38,7 @@ namespace GodotTools.Utils
{
public const string Windows = "windows";
public const string OSX = "osx";
- public const string X11 = "linuxbsd";
+ public const string LinuxBSD = "linuxbsd";
public const string Server = "server";
public const string UWP = "uwp";
public const string Haiku = "haiku";
@@ -48,7 +51,10 @@ namespace GodotTools.Utils
{
[Names.Windows] = Platforms.Windows,
[Names.OSX] = Platforms.OSX,
- [Names.X11] = Platforms.X11,
+ [Names.Linux] = Platforms.LinuxBSD,
+ [Names.FreeBSD] = Platforms.LinuxBSD,
+ [Names.NetBSD] = Platforms.LinuxBSD,
+ [Names.BSD] = Platforms.LinuxBSD,
[Names.Server] = Platforms.Server,
[Names.UWP] = Platforms.UWP,
[Names.Haiku] = Platforms.Haiku,
@@ -62,38 +68,39 @@ namespace GodotTools.Utils
return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
+ private static bool IsAnyOS(IEnumerable<string> names)
+ {
+ return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase));
+ }
+
+ private static readonly IEnumerable<string> LinuxBSDPlatforms =
+ new[] {Names.Linux, Names.FreeBSD, Names.NetBSD, Names.BSD};
+
+ private static readonly IEnumerable<string> UnixLikePlatforms =
+ new[] {Names.OSX, Names.Server, Names.Haiku, Names.Android, Names.iOS}
+ .Concat(LinuxBSDPlatforms).ToArray();
+
private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows));
private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX));
- private static readonly Lazy<bool> _isX11 = new Lazy<bool>(() => IsOS(Names.X11));
+ private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms));
private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server));
private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP));
private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku));
private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android));
private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS));
private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5));
+ private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms));
public static bool IsWindows => _isWindows.Value || IsUWP;
public static bool IsOSX => _isOSX.Value;
- public static bool IsX11 => _isX11.Value;
+ public static bool IsLinuxBSD => _isLinuxBSD.Value;
public static bool IsServer => _isServer.Value;
public static bool IsUWP => _isUWP.Value;
public static bool IsHaiku => _isHaiku.Value;
public static bool IsAndroid => _isAndroid.Value;
public static bool IsiOS => _isiOS.Value;
public static bool IsHTML5 => _isHTML5.Value;
-
- private static bool? _isUnixCache;
- private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS };
-
- public static bool IsUnixLike()
- {
- if (_isUnixCache.HasValue)
- return _isUnixCache.Value;
-
- string osName = GetPlatformName();
- _isUnixCache = UnixLikePlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
- return _isUnixCache.Value;
- }
+ public static bool IsUnixLike => _isUnixLike.Value;
public static char PathSep => IsWindows ? ';' : ':';
@@ -121,10 +128,10 @@ namespace GodotTools.Utils
return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists);
return (from dir in searchDirs
- select Path.Combine(dir, name)
+ select Path.Combine(dir, name)
into path
- from ext in windowsExts
- select path + ext).FirstOrDefault(File.Exists);
+ from ext in windowsExts
+ select path + ext).FirstOrDefault(File.Exists);
}
private static string PathWhichUnix([NotNull] string name)
@@ -189,7 +196,7 @@ namespace GodotTools.Utils
startInfo.UseShellExecute = false;
- using (var process = new Process { StartInfo = startInfo })
+ using (var process = new Process {StartInfo = startInfo})
{
process.Start();
process.WaitForExit();
diff --git a/modules/mono/editor/GodotTools/GodotTools/packages.config b/modules/mono/editor/GodotTools/GodotTools/packages.config
deleted file mode 100644
index dd3de2865a..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/packages.config
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
- <package id="JetBrains.Annotations" version="2019.1.3" targetFramework="net45" />
- <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net45" />
-</packages>
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index bdf9cf965f..258b8ed3ed 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -1664,6 +1664,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
}
if (!p_imethod.is_internal) {
+ // TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be
+ // better to generate a table in the C++ glue instead. That way the strings wouldn't
+ // add that much extra bloat as they're already used in engine code. Also, it would
+ // probably be much faster than looking up the attributes when fetching methods.
p_output.append(MEMBER_BEGIN "[GodotMethod(\"");
p_output.append(p_imethod.name);
p_output.append("\")]");
@@ -2139,7 +2143,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
if (return_type->ret_as_byref_arg) {
p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = ");
p_output.append(fail_ret);
- p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n");
+ p_output.append("; ERR_FAIL_MSG(\"Parameter ' " CS_PARAM_INSTANCE " ' is null.\"); }\n");
} else {
p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", ");
p_output.append(fail_ret);
@@ -2390,6 +2394,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY)
continue;
+ if (property.name.find("/") >= 0) {
+ // Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.
+ continue;
+ }
+
PropertyInterface iprop;
iprop.cname = property.name;
iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);
@@ -2402,7 +2411,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
bool valid = false;
iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);
- ERR_FAIL_COND_V(!valid, false);
+ ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");
iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
@@ -2414,8 +2423,6 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
iprop.proxy_name += "_";
}
- iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash...
-
iprop.prop_doc = nullptr;
for (int i = 0; i < itype.class_doc->properties.size(); i++) {
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 7c87c688db..5672231e9a 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -619,7 +619,8 @@ class BindingsGenerator {
const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) {
const List<InternalCall>::Element *it = p_list.front();
while (it) {
- if (it->get().name == p_name) return it;
+ if (it->get().name == p_name)
+ return it;
it = it->next();
}
return nullptr;
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
new file mode 100644
index 0000000000..7a5e465e7a
--- /dev/null
+++ b/modules/mono/editor/code_completion.cpp
@@ -0,0 +1,249 @@
+/*************************************************************************/
+/* code_completion.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "code_completion.h"
+
+#include "core/project_settings.h"
+#include "editor/editor_file_system.h"
+#include "editor/editor_settings.h"
+#include "scene/gui/control.h"
+#include "scene/main/node.h"
+
+namespace gdmono {
+
+// Almost everything here is taken from functions used by GDScript for code completion, adapted for C#.
+
+_FORCE_INLINE_ String quoted(const String &p_str) {
+ return "\"" + p_str + "\"";
+}
+
+void _add_nodes_suggestions(const Node *p_base, const Node *p_node, PackedStringArray &r_suggestions) {
+ if (p_node != p_base && !p_node->get_owner())
+ return;
+
+ String path_relative_to_orig = p_base->get_path_to(p_node);
+
+ r_suggestions.push_back(quoted(path_relative_to_orig));
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ _add_nodes_suggestions(p_base, p_node->get_child(i), r_suggestions);
+ }
+}
+
+Node *_find_node_for_script(Node *p_base, Node *p_current, const Ref<Script> &p_script) {
+ if (p_current->get_owner() != p_base && p_base != p_current)
+ return nullptr;
+
+ Ref<Script> c = p_current->get_script();
+
+ if (c == p_script)
+ return p_current;
+
+ for (int i = 0; i < p_current->get_child_count(); i++) {
+ Node *found = _find_node_for_script(p_base, p_current->get_child(i), p_script);
+ if (found)
+ return found;
+ }
+
+ return nullptr;
+}
+
+void _get_directory_contents(EditorFileSystemDirectory *p_dir, PackedStringArray &r_suggestions) {
+ for (int i = 0; i < p_dir->get_file_count(); i++) {
+ r_suggestions.push_back(quoted(p_dir->get_file_path(i)));
+ }
+
+ for (int i = 0; i < p_dir->get_subdir_count(); i++) {
+ _get_directory_contents(p_dir->get_subdir(i), r_suggestions);
+ }
+}
+
+Node *_try_find_owner_node_in_tree(const Ref<Script> p_script) {
+ SceneTree *tree = SceneTree::get_singleton();
+ if (!tree)
+ return nullptr;
+ Node *base = tree->get_edited_scene_root();
+ if (base) {
+ base = _find_node_for_script(base, base, p_script);
+ }
+ return base;
+}
+
+PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file) {
+ PackedStringArray suggestions;
+
+ switch (p_kind) {
+ case CompletionKind::INPUT_ACTIONS: {
+ List<PropertyInfo> project_props;
+ ProjectSettings::get_singleton()->get_property_list(&project_props);
+
+ for (List<PropertyInfo>::Element *E = project_props.front(); E; E = E->next()) {
+ const PropertyInfo &prop = E->get();
+
+ if (!prop.name.begins_with("input/"))
+ continue;
+
+ String name = prop.name.substr(prop.name.find("/") + 1, prop.name.length());
+ suggestions.push_back(quoted(name));
+ }
+ } break;
+ case CompletionKind::NODE_PATHS: {
+ {
+ // AutoLoads
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ suggestions.push_back(quoted("/root/" + name));
+ }
+ }
+
+ {
+ // Current edited scene tree
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base) {
+ _add_nodes_suggestions(base, base, suggestions);
+ }
+ }
+ } break;
+ case CompletionKind::RESOURCE_PATHS: {
+ if (bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), suggestions);
+ }
+ } break;
+ case CompletionKind::SCENE_PATHS: {
+ DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ List<String> directories;
+ directories.push_back(dir_access->get_current_dir());
+
+ while (!directories.empty()) {
+ dir_access->change_dir(directories.back()->get());
+ directories.pop_back();
+
+ dir_access->list_dir_begin();
+ String filename = dir_access->get_next();
+
+ while (filename != "") {
+ if (filename == "." || filename == "..") {
+ filename = dir_access->get_next();
+ continue;
+ }
+
+ if (dir_access->dir_exists(filename)) {
+ directories.push_back(dir_access->get_current_dir().plus_file(filename));
+ } else if (filename.ends_with(".tscn") || filename.ends_with(".scn")) {
+ suggestions.push_back(quoted(dir_access->get_current_dir().plus_file(filename)));
+ }
+
+ filename = dir_access->get_next();
+ }
+ }
+ } break;
+ case CompletionKind::SHADER_PARAMS: {
+ print_verbose("Shared params completion for C# not implemented.");
+ } break;
+ case CompletionKind::SIGNALS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+
+ List<MethodInfo> signals;
+ script->get_script_signal_list(&signals);
+
+ StringName native = script->get_instance_base_type();
+ if (native != StringName()) {
+ ClassDB::get_signal_list(native, &signals, /* p_no_inheritance: */ false);
+ }
+
+ for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
+ const String &signal = E->get().name;
+ suggestions.push_back(quoted(signal));
+ }
+ } break;
+ case CompletionKind::THEME_COLORS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_color_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ case CompletionKind::THEME_CONSTANTS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_constant_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ case CompletionKind::THEME_FONTS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_font_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ case CompletionKind::THEME_STYLES: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_stylebox_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ default:
+ ERR_FAIL_V_MSG(suggestions, "Invalid completion kind.");
+ }
+
+ return suggestions;
+}
+
+} // namespace gdmono
diff --git a/modules/mono/editor/code_completion.h b/modules/mono/editor/code_completion.h
new file mode 100644
index 0000000000..77673b766f
--- /dev/null
+++ b/modules/mono/editor/code_completion.h
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* code_completion.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 CODE_COMPLETION_H
+#define CODE_COMPLETION_H
+
+#include "core/ustring.h"
+#include "core/variant.h"
+
+namespace gdmono {
+
+enum class CompletionKind {
+ INPUT_ACTIONS = 0,
+ NODE_PATHS,
+ RESOURCE_PATHS,
+ SCENE_PATHS,
+ SHADER_PARAMS,
+ SIGNALS,
+ THEME_COLORS,
+ THEME_CONSTANTS,
+ THEME_FONTS,
+ THEME_STYLES
+};
+
+PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file);
+
+} // namespace gdmono
+
+#endif // CODE_COMPLETION_H
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index c3e7e67ae9..c9117f1312 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -48,6 +48,7 @@
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/osx_utils.h"
#include "bindings_generator.h"
+#include "code_completion.h"
#include "godotsharp_export.h"
#include "script_class_parser.h"
@@ -354,6 +355,12 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
}
}
+MonoArray *godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, MonoString *p_script_file) {
+ String script_file = GDMonoMarshal::mono_string_to_godot(p_script_file);
+ PackedStringArray suggestions = gdmono::get_code_completion((gdmono::CompletionKind)p_kind, script_file);
+ return GDMonoMarshal::PackedStringArray_to_mono_array(suggestions);
+}
+
float godot_icall_Globals_EditorScale() {
return EDSCALE;
}
@@ -454,6 +461,7 @@ void register_editor_internal_calls() {
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay);
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop);
mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts);
+ mono_add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", (void *)godot_icall_Internal_CodeCompletionRequest);
// Globals
mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale);
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index 4126da16be..d6a271f1d9 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -32,68 +32,70 @@
#include <mono/metadata/image.h>
+#include "core/io/file_access_pack.h"
#include "core/os/os.h"
+#include "core/project_settings.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
#include "../mono_gd/gd_mono_cache.h"
+#include "../utils/macros.h"
namespace GodotSharpExport {
-String get_assemblyref_name(MonoImage *p_image, int index) {
+struct AssemblyRefInfo {
+ String name;
+ uint16_t major;
+ uint16_t minor;
+ uint16_t build;
+ uint16_t revision;
+};
+
+AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
uint32_t cols[MONO_ASSEMBLYREF_SIZE];
mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
- return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
+ return {
+ String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])),
+ (uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION],
+ (uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION],
+ (uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER],
+ (uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER]
+ };
}
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
- String ref_name = get_assemblyref_name(image, i);
+ AssemblyRefInfo ref_info = get_assemblyref_name(image, i);
+
+ const String &ref_name = ref_info.name;
if (r_assembly_dependencies.has(ref_name))
continue;
- GDMonoAssembly *ref_assembly = nullptr;
- String path;
- bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe");
-
- for (int j = 0; j < p_search_dirs.size(); j++) {
- const String &search_dir = p_search_dirs[j];
-
- if (has_extension) {
- path = search_dir.plus_file(ref_name);
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true);
- if (ref_assembly != nullptr)
- break;
- }
- } else {
- path = search_dir.plus_file(ref_name + ".dll");
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
- if (ref_assembly != nullptr)
- break;
- }
-
- path = search_dir.plus_file(ref_name + ".exe");
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
- if (ref_assembly != nullptr)
- break;
- }
- }
- }
+ GDMonoAssembly *ref_assembly = NULL;
+
+ {
+ MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A"
+ CRASH_COND(ref_aname == nullptr);
+ SCOPE_EXIT {
+ mono_assembly_name_free(ref_aname);
+ mono_free(ref_aname);
+ };
- ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ mono_assembly_get_assemblyref(image, i, ref_aname);
- // Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir.
- r_assembly_dependencies[ref_name] = path;
+ if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
+ ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ }
+
+ r_assembly_dependencies[ref_name] = ref_assembly->get_path();
+ }
Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
@@ -113,6 +115,11 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
+ if (p_custom_bcl_dir.length()) {
+ // Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory.
+ r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path();
+ }
+
for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) {
String assembly_name = *key;
String assembly_path = p_initial_assemblies[*key];
diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp
index bece23c9a6..3ffbf8ba14 100644
--- a/modules/mono/editor/script_class_parser.cpp
+++ b/modules/mono/editor/script_class_parser.cpp
@@ -181,14 +181,24 @@ ScriptClassParser::Token ScriptClassParser::get_token() {
CharType res = 0;
switch (next) {
- case 'b': res = 8; break;
- case 't': res = 9; break;
- case 'n': res = 10; break;
- case 'f': res = 12; break;
+ case 'b':
+ res = 8;
+ break;
+ case 't':
+ res = 9;
+ break;
+ case 'n':
+ res = 10;
+ break;
+ case 'f':
+ res = 12;
+ break;
case 'r':
res = 13;
break;
- case '\"': res = '\"'; break;
+ case '\"':
+ res = '\"';
+ break;
case '\\':
res = '\\';
break;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index 099eacd7dd..41b4e9367f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -12,7 +12,7 @@ namespace Godot
{
private static int GetSliceCount(this string instance, string splitter)
{
- if (instance.Empty() || splitter.Empty())
+ if (string.IsNullOrEmpty(instance) || string.IsNullOrEmpty(splitter))
return 0;
int pos = 0;
@@ -29,7 +29,7 @@ namespace Godot
private static string GetSliceCharacter(this string instance, char splitter, int slice)
{
- if (!instance.Empty() && slice >= 0)
+ if (!string.IsNullOrEmpty(instance) && slice >= 0)
{
int i = 0;
int prev = 0;
@@ -237,10 +237,10 @@ namespace Godot
// </summary>
public static int CompareTo(this string instance, string to, bool caseSensitive = true)
{
- if (instance.Empty())
- return to.Empty() ? 0 : -1;
+ if (string.IsNullOrEmpty(instance))
+ return string.IsNullOrEmpty(to) ? 0 : -1;
- if (to.Empty())
+ if (string.IsNullOrEmpty(to))
return 1;
int instanceIndex = 0;
@@ -287,14 +287,6 @@ namespace Godot
}
// <summary>
- // Return true if the string is empty.
- // </summary>
- public static bool Empty(this string instance)
- {
- return string.IsNullOrEmpty(instance);
- }
-
- // <summary>
// Return true if the strings ends with the given string.
// </summary>
public static bool EndsWith(this string instance, string text)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index ba0bbd7630..b5ac124c9a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -30,6 +30,7 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index 22853797c1..8785931312 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -30,6 +30,7 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index dfd78a8244..26347e9162 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -82,6 +82,7 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const {
}
ObjectID ManagedCallable::get_object() const {
+ // TODO: If the delegate target extends Godot.Object, use that instead!
return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id();
}
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 3298c5da4c..fbaa81e83f 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -133,6 +133,10 @@ void gd_mono_debug_init() {
CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
+ if (da_args.length()) {
+ OS::get_singleton()->set_environment("GODOT_MONO_DEBUGGER_AGENT", String());
+ }
+
#ifdef TOOLS_ENABLED
int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685);
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
@@ -515,8 +519,8 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) {
GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
- if (p_name == "mscorlib")
- return get_corlib_assembly();
+ if (p_name == "mscorlib" && corlib_assembly)
+ return corlib_assembly;
MonoDomain *domain = mono_domain_get();
uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0;
@@ -526,7 +530,9 @@ GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {
+#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
+#endif
MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
bool result = load_assembly(p_name, aname, r_assembly, p_refonly);
@@ -538,26 +544,27 @@ bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bo
bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {
+#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
+#endif
- print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
+ return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs());
+}
- MonoImageOpenStatus status = MONO_IMAGE_OK;
- MonoAssembly *assembly = mono_assembly_load_full(p_aname, nullptr, &status, p_refonly);
+bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) {
- if (!assembly)
- return false;
-
- ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false);
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!r_assembly);
+#endif
- uint32_t domain_id = mono_domain_get_id(mono_domain_get());
+ print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
- GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
+ GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs);
- ERR_FAIL_COND_V(stored_assembly == nullptr, false);
- ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false);
+ if (!assembly)
+ return false;
- *r_assembly = *stored_assembly;
+ *r_assembly = assembly;
print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 4898833e8e..153ae891f7 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -241,6 +241,7 @@ public:
bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
+ bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs);
bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false);
Error finalize_and_unload_domain(MonoDomain *p_domain);
diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp
index 0f211eebc6..da2ed2a7fc 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -33,6 +33,7 @@
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/tokentype.h>
+#include "core/io/file_access_pack.h"
#include "core/list.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
@@ -99,7 +100,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
// - The 'load' hook is called after the assembly has been loaded. Its job is to add the
// assembly to the list of loaded assemblies so that the 'search' hook can look it up.
-void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
+void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) {
String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
@@ -133,9 +134,7 @@ MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *an
return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true);
}
-MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) {
-
- (void)user_data; // UNUSED
+MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
@@ -147,15 +146,13 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d
return nullptr;
}
-MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
-
- (void)user_data; // UNUSED
+MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
- return _load_assembly_search(name, search_dirs, refonly);
+ return _load_assembly_search(name, aname, refonly, search_dirs);
}
-MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
+MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
MonoAssembly *res = nullptr;
String path;
@@ -168,21 +165,21 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const
if (has_extension) {
path = search_dir.plus_file(p_name);
if (FileAccess::exists(path)) {
- res = _real_load_assembly_from(path, p_refonly);
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
} else {
path = search_dir.plus_file(p_name + ".dll");
if (FileAccess::exists(path)) {
- res = _real_load_assembly_from(path, p_refonly);
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
path = search_dir.plus_file(p_name + ".exe");
if (FileAccess::exists(path)) {
- res = _real_load_assembly_from(path, p_refonly);
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
@@ -230,7 +227,7 @@ void GDMonoAssembly::initialize() {
mono_install_assembly_load_hook(&assembly_load_hook, nullptr);
}
-MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) {
+MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) {
Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
ERR_FAIL_COND_V_MSG(data.empty(), nullptr, "Could read the assembly in the specified location");
@@ -255,7 +252,33 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo
true, &status, p_refonly,
image_filename.utf8());
- ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from the loaded data");
+ ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'.");
+
+ if (p_aname != nullptr) {
+ // Check assembly version
+ const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY);
+
+ ERR_FAIL_NULL_V(table, nullptr);
+
+ if (mono_table_info_get_rows(table)) {
+ uint32_t cols[MONO_ASSEMBLY_SIZE];
+ mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE);
+
+ // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision.
+ uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION];
+ uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION];
+
+ uint16_t required_minor;
+ uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr);
+
+ if (required_major != 0) {
+ if (major != required_major && minor != required_minor) {
+ mono_image_close(image);
+ return nullptr;
+ }
+ }
+ }
+ }
#ifdef DEBUG_ENABLED
Vector<uint8_t> pdb_data;
@@ -425,6 +448,26 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
return match;
}
+GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
+
+ if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll"))
+ return GDMono::get_singleton()->get_corlib_assembly();
+
+ // We need to manually call the search hook in this case, as it won't be called in the next step
+ MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname);
+
+ if (!assembly) {
+ assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs);
+ ERR_FAIL_NULL_V(assembly, nullptr);
+ }
+
+ GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
+ ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
+ ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr);
+
+ return loaded_asm;
+}
+
GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
if (p_name == "mscorlib" || p_name == "mscorlib.dll")
diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h
index 43c8225b74..7fc1817993 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.h
+++ b/modules/mono/mono_gd/gd_mono_assembly.h
@@ -93,8 +93,8 @@ class GDMonoAssembly {
static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly);
static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly);
- static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly);
- static MonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
+ static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr);
+ static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
friend class GDMono;
static void initialize();
@@ -120,7 +120,9 @@ public:
static String find_assembly(const String &p_name);
static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());
+ static const Vector<String> &get_default_search_dirs() { return search_dirs; }
+ static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly);
diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp
index 2c65f7e3a0..9dabb4ec4c 100644
--- a/modules/mono/mono_gd/gd_mono_class.cpp
+++ b/modules/mono/mono_gd/gd_mono_class.cpp
@@ -79,12 +79,26 @@ bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
return mono_class_is_assignable_from(mono_class, p_from->mono_class);
}
-GDMonoClass *GDMonoClass::get_parent_class() {
+StringName GDMonoClass::get_namespace() const {
+ GDMonoClass *nesting_class = get_nesting_class();
+ if (!nesting_class)
+ return namespace_name;
+ return nesting_class->get_namespace();
+}
+
+String GDMonoClass::get_name_for_lookup() const {
+ GDMonoClass *nesting_class = get_nesting_class();
+ if (!nesting_class)
+ return class_name;
+ return nesting_class->get_name_for_lookup() + "/" + class_name;
+}
+
+GDMonoClass *GDMonoClass::get_parent_class() const {
MonoClass *parent_mono_class = mono_class_get_parent(mono_class);
return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : nullptr;
}
-GDMonoClass *GDMonoClass::get_nesting_class() {
+GDMonoClass *GDMonoClass::get_nesting_class() const {
MonoClass *nesting_type = mono_class_get_nesting_type(mono_class);
return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : nullptr;
}
diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h
index 9237aae057..44b146b87c 100644
--- a/modules/mono/mono_gd/gd_mono_class.h
+++ b/modules/mono/mono_gd/gd_mono_class.h
@@ -113,14 +113,15 @@ public:
bool is_assignable_from(GDMonoClass *p_from) const;
- _FORCE_INLINE_ StringName get_namespace() const { return namespace_name; }
+ StringName get_namespace() const;
_FORCE_INLINE_ StringName get_name() const { return class_name; }
+ String get_name_for_lookup() const;
_FORCE_INLINE_ MonoClass *get_mono_ptr() const { return mono_class; }
_FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; }
- GDMonoClass *get_parent_class();
- GDMonoClass *get_nesting_class();
+ GDMonoClass *get_parent_class() const;
+ GDMonoClass *get_nesting_class() const;
#ifdef TOOLS_ENABLED
Vector<MonoClassField *> get_enum_fields();
diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp
index e76cb84d43..948170f51c 100644
--- a/modules/mono/mono_gd/gd_mono_field.cpp
+++ b/modules/mono/mono_gd/gd_mono_field.cpp
@@ -501,7 +501,8 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
case Variant::PACKED_COLOR_ARRAY: {
SET_FROM_ARRAY(PackedColorArray);
} break;
- default: break;
+ default:
+ break;
}
} break;
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index ca16c2b76a..b56350ae1b 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -175,7 +175,7 @@ void GDMonoLog::initialize() {
log_level_id = get_log_level_id(log_level.get_data());
if (log_file) {
- OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data());
+ OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data());
mono_trace_set_log_handler(mono_log_callback, this);
} else {
OS::get_singleton()->printerr("Mono: No log file, using default log handler\n");
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index f9d492dabb..c0fea6ede9 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -564,9 +564,10 @@ namespace Marshal {
#ifdef MONO_GLUE_ENABLED
#ifdef TOOLS_ENABLED
-#define NO_GLUE_RET(m_ret) \
- { \
- if (!GDMonoCache::cached_data.godot_api_cache_updated) return m_ret; \
+#define NO_GLUE_RET(m_ret) \
+ { \
+ if (!GDMonoCache::cached_data.godot_api_cache_updated) \
+ return m_ret; \
}
#else
#define NO_GLUE_RET(m_ret) \
diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h
index 8650d6cc09..dc542477f5 100644
--- a/modules/mono/utils/macros.h
+++ b/modules/mono/utils/macros.h
@@ -68,6 +68,6 @@ public:
} // namespace gdmono
#define SCOPE_EXIT \
- auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]()
+ auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() -> void
#endif // UTIL_MACROS_H
diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp
index 2018f90e9f..16cd04b044 100644
--- a/modules/opensimplex/noise_texture.cpp
+++ b/modules/opensimplex/noise_texture.cpp
@@ -202,19 +202,22 @@ Ref<OpenSimplexNoise> NoiseTexture::get_noise() {
}
void NoiseTexture::set_width(int p_width) {
- if (p_width == size.x) return;
+ if (p_width == size.x)
+ return;
size.x = p_width;
_queue_update();
}
void NoiseTexture::set_height(int p_height) {
- if (p_height == size.y) return;
+ if (p_height == size.y)
+ return;
size.y = p_height;
_queue_update();
}
void NoiseTexture::set_seamless(bool p_seamless) {
- if (p_seamless == seamless) return;
+ if (p_seamless == seamless)
+ return;
seamless = p_seamless;
_queue_update();
}
@@ -224,7 +227,8 @@ bool NoiseTexture::get_seamless() {
}
void NoiseTexture::set_as_normalmap(bool p_as_normalmap) {
- if (p_as_normalmap == as_normalmap) return;
+ if (p_as_normalmap == as_normalmap)
+ return;
as_normalmap = p_as_normalmap;
_queue_update();
_change_notify();
@@ -236,7 +240,8 @@ bool NoiseTexture::is_normalmap() {
void NoiseTexture::set_bump_strength(float p_bump_strength) {
- if (p_bump_strength == bump_strength) return;
+ if (p_bump_strength == bump_strength)
+ return;
bump_strength = p_bump_strength;
if (as_normalmap)
_queue_update();
diff --git a/modules/opensimplex/open_simplex_noise.cpp b/modules/opensimplex/open_simplex_noise.cpp
index 238faa4130..205c033614 100644
--- a/modules/opensimplex/open_simplex_noise.cpp
+++ b/modules/opensimplex/open_simplex_noise.cpp
@@ -70,7 +70,8 @@ int OpenSimplexNoise::get_seed() {
}
void OpenSimplexNoise::set_octaves(int p_octaves) {
- if (p_octaves == octaves) return;
+ if (p_octaves == octaves)
+ return;
ERR_FAIL_COND_MSG(p_octaves > MAX_OCTAVES, vformat("The number of OpenSimplexNoise octaves is limited to %d; ignoring the new value.", MAX_OCTAVES));
@@ -79,19 +80,22 @@ void OpenSimplexNoise::set_octaves(int p_octaves) {
}
void OpenSimplexNoise::set_period(float p_period) {
- if (p_period == period) return;
+ if (p_period == period)
+ return;
period = p_period;
emit_changed();
}
void OpenSimplexNoise::set_persistence(float p_persistence) {
- if (p_persistence == persistence) return;
+ if (p_persistence == persistence)
+ return;
persistence = p_persistence;
emit_changed();
}
void OpenSimplexNoise::set_lacunarity(float p_lacunarity) {
- if (p_lacunarity == lacunarity) return;
+ if (p_lacunarity == lacunarity)
+ return;
lacunarity = p_lacunarity;
emit_changed();
}
diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp
index a8e8a9a2f1..d28199420d 100644
--- a/modules/pvr/texture_loader_pvr.cpp
+++ b/modules/pvr/texture_loader_pvr.cpp
@@ -111,9 +111,13 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path,
switch (flags & 0xFF) {
case 0x18:
- case 0xC: format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC2A : Image::FORMAT_PVRTC2; break;
+ case 0xC:
+ format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC2A : Image::FORMAT_PVRTC2;
+ break;
case 0x19:
- case 0xD: format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC4A : Image::FORMAT_PVRTC4; break;
+ case 0xD:
+ format = (flags & PVR_HAS_ALPHA) ? Image::FORMAT_PVRTC4A : Image::FORMAT_PVRTC4;
+ break;
case 0x16:
format = Image::FORMAT_L8;
break;
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index b9f276fb12..f5a7b5fc06 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -209,7 +209,8 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
while (!stateflag) {
int ret = buffer_data();
- if (ret == 0) break;
+ if (ret == 0)
+ break;
while (ogg_sync_pageout(&oy, &og) > 0) {
ogg_stream_state test;
@@ -286,7 +287,8 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) {
return;
}
vorbis_p++;
- if (vorbis_p == 3) break;
+ if (vorbis_p == 3)
+ break;
}
/* The header pages/packets will arrive before anything else we
diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp
index 05080289bd..bc30f4e4fd 100644
--- a/modules/tinyexr/image_saver_tinyexr.cpp
+++ b/modules/tinyexr/image_saver_tinyexr.cpp
@@ -267,13 +267,21 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
header.channels = channel_infos;
header.pixel_types = pixel_types;
header.requested_pixel_types = requested_pixel_types;
+ header.compression_type = TINYEXR_COMPRESSIONTYPE_PIZ;
- CharString utf8_filename = p_path.utf8();
- const char *err;
- int ret = SaveEXRImageToFile(&image, &header, utf8_filename.ptr(), &err);
- if (ret != TINYEXR_SUCCESS) {
+ unsigned char *mem = nullptr;
+ const char *err = nullptr;
+
+ size_t bytes = SaveEXRImageToMemory(&image, &header, &mem, &err);
+
+ if (bytes == 0) {
print_error(String("Saving EXR failed. Error: {0}").format(varray(err)));
return ERR_FILE_CANT_WRITE;
+ } else {
+ FileAccessRef ref = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(!ref, ERR_FILE_CANT_WRITE);
+ ref->store_buffer(mem, bytes);
+ free(mem);
}
return OK;
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index 9a4076bec4..3649486724 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -342,84 +342,212 @@ static Color _color_from_type(Variant::Type p_type, bool dark_theme = true) {
Color color;
if (dark_theme)
switch (p_type) {
- case Variant::NIL: color = Color(0.41, 0.93, 0.74); break;
-
- case Variant::BOOL: color = Color(0.55, 0.65, 0.94); break;
- case Variant::INT: color = Color(0.49, 0.78, 0.94); break;
- case Variant::FLOAT: color = Color(0.38, 0.85, 0.96); break;
- case Variant::STRING: color = Color(0.42, 0.65, 0.93); break;
-
- case Variant::VECTOR2: color = Color(0.74, 0.57, 0.95); break;
- case Variant::VECTOR2I: color = Color(0.74, 0.57, 0.95); break;
- case Variant::RECT2: color = Color(0.95, 0.57, 0.65); break;
- case Variant::RECT2I: color = Color(0.95, 0.57, 0.65); break;
- case Variant::VECTOR3: color = Color(0.84, 0.49, 0.93); break;
- case Variant::VECTOR3I: color = Color(0.84, 0.49, 0.93); break;
- case Variant::TRANSFORM2D: color = Color(0.77, 0.93, 0.41); break;
- case Variant::PLANE: color = Color(0.97, 0.44, 0.44); break;
- case Variant::QUAT: color = Color(0.93, 0.41, 0.64); break;
- case Variant::AABB: color = Color(0.93, 0.47, 0.57); break;
- case Variant::BASIS: color = Color(0.89, 0.93, 0.41); break;
- case Variant::TRANSFORM: color = Color(0.96, 0.66, 0.43); break;
-
- case Variant::COLOR: color = Color(0.62, 1.0, 0.44); break;
- case Variant::NODE_PATH: color = Color(0.41, 0.58, 0.93); break;
- case Variant::_RID: color = Color(0.41, 0.93, 0.6); break;
- case Variant::OBJECT: color = Color(0.47, 0.95, 0.91); break;
- case Variant::DICTIONARY: color = Color(0.47, 0.93, 0.69); break;
-
- case Variant::ARRAY: color = Color(0.88, 0.88, 0.88); break;
- case Variant::PACKED_BYTE_ARRAY: color = Color(0.67, 0.96, 0.78); break;
- case Variant::PACKED_INT32_ARRAY: color = Color(0.69, 0.86, 0.96); break;
- case Variant::PACKED_FLOAT32_ARRAY: color = Color(0.59, 0.91, 0.97); break;
- case Variant::PACKED_INT64_ARRAY: color = Color(0.69, 0.86, 0.96); break;
- case Variant::PACKED_FLOAT64_ARRAY: color = Color(0.59, 0.91, 0.97); break;
- case Variant::PACKED_STRING_ARRAY: color = Color(0.62, 0.77, 0.95); break;
- case Variant::PACKED_VECTOR2_ARRAY: color = Color(0.82, 0.7, 0.96); break;
- case Variant::PACKED_VECTOR3_ARRAY: color = Color(0.87, 0.61, 0.95); break;
- case Variant::PACKED_COLOR_ARRAY: color = Color(0.91, 1.0, 0.59); break;
+ case Variant::NIL:
+ color = Color(0.41, 0.93, 0.74);
+ break;
+
+ case Variant::BOOL:
+ color = Color(0.55, 0.65, 0.94);
+ break;
+ case Variant::INT:
+ color = Color(0.49, 0.78, 0.94);
+ break;
+ case Variant::FLOAT:
+ color = Color(0.38, 0.85, 0.96);
+ break;
+ case Variant::STRING:
+ color = Color(0.42, 0.65, 0.93);
+ break;
+
+ case Variant::VECTOR2:
+ color = Color(0.74, 0.57, 0.95);
+ break;
+ case Variant::VECTOR2I:
+ color = Color(0.74, 0.57, 0.95);
+ break;
+ case Variant::RECT2:
+ color = Color(0.95, 0.57, 0.65);
+ break;
+ case Variant::RECT2I:
+ color = Color(0.95, 0.57, 0.65);
+ break;
+ case Variant::VECTOR3:
+ color = Color(0.84, 0.49, 0.93);
+ break;
+ case Variant::VECTOR3I:
+ color = Color(0.84, 0.49, 0.93);
+ break;
+ case Variant::TRANSFORM2D:
+ color = Color(0.77, 0.93, 0.41);
+ break;
+ case Variant::PLANE:
+ color = Color(0.97, 0.44, 0.44);
+ break;
+ case Variant::QUAT:
+ color = Color(0.93, 0.41, 0.64);
+ break;
+ case Variant::AABB:
+ color = Color(0.93, 0.47, 0.57);
+ break;
+ case Variant::BASIS:
+ color = Color(0.89, 0.93, 0.41);
+ break;
+ case Variant::TRANSFORM:
+ color = Color(0.96, 0.66, 0.43);
+ break;
+
+ case Variant::COLOR:
+ color = Color(0.62, 1.0, 0.44);
+ break;
+ case Variant::NODE_PATH:
+ color = Color(0.41, 0.58, 0.93);
+ break;
+ case Variant::_RID:
+ color = Color(0.41, 0.93, 0.6);
+ break;
+ case Variant::OBJECT:
+ color = Color(0.47, 0.95, 0.91);
+ break;
+ case Variant::DICTIONARY:
+ color = Color(0.47, 0.93, 0.69);
+ break;
+
+ case Variant::ARRAY:
+ color = Color(0.88, 0.88, 0.88);
+ break;
+ case Variant::PACKED_BYTE_ARRAY:
+ color = Color(0.67, 0.96, 0.78);
+ break;
+ case Variant::PACKED_INT32_ARRAY:
+ color = Color(0.69, 0.86, 0.96);
+ break;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ color = Color(0.59, 0.91, 0.97);
+ break;
+ case Variant::PACKED_INT64_ARRAY:
+ color = Color(0.69, 0.86, 0.96);
+ break;
+ case Variant::PACKED_FLOAT64_ARRAY:
+ color = Color(0.59, 0.91, 0.97);
+ break;
+ case Variant::PACKED_STRING_ARRAY:
+ color = Color(0.62, 0.77, 0.95);
+ break;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ color = Color(0.82, 0.7, 0.96);
+ break;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ color = Color(0.87, 0.61, 0.95);
+ break;
+ case Variant::PACKED_COLOR_ARRAY:
+ color = Color(0.91, 1.0, 0.59);
+ break;
default:
color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.7, 0.7);
}
else
switch (p_type) {
- case Variant::NIL: color = Color(0.15, 0.89, 0.63); break;
-
- case Variant::BOOL: color = Color(0.43, 0.56, 0.92); break;
- case Variant::INT: color = Color(0.31, 0.7, 0.91); break;
- case Variant::FLOAT: color = Color(0.15, 0.8, 0.94); break;
- case Variant::STRING: color = Color(0.27, 0.56, 0.91); break;
-
- case Variant::VECTOR2: color = Color(0.68, 0.46, 0.93); break;
- case Variant::VECTOR2I: color = Color(0.68, 0.46, 0.93); break;
- case Variant::RECT2: color = Color(0.93, 0.46, 0.56); break;
- case Variant::RECT2I: color = Color(0.93, 0.46, 0.56); break;
- case Variant::VECTOR3: color = Color(0.86, 0.42, 0.93); break;
- case Variant::VECTOR3I: color = Color(0.86, 0.42, 0.93); break;
- case Variant::TRANSFORM2D: color = Color(0.59, 0.81, 0.1); break;
- case Variant::PLANE: color = Color(0.97, 0.44, 0.44); break;
- case Variant::QUAT: color = Color(0.93, 0.41, 0.64); break;
- case Variant::AABB: color = Color(0.93, 0.47, 0.57); break;
- case Variant::BASIS: color = Color(0.7, 0.73, 0.1); break;
- case Variant::TRANSFORM: color = Color(0.96, 0.56, 0.28); break;
-
- case Variant::COLOR: color = Color(0.24, 0.75, 0.0); break;
- case Variant::NODE_PATH: color = Color(0.41, 0.58, 0.93); break;
- case Variant::_RID: color = Color(0.17, 0.9, 0.45); break;
- case Variant::OBJECT: color = Color(0.07, 0.84, 0.76); break;
- case Variant::DICTIONARY: color = Color(0.34, 0.91, 0.62); break;
-
- case Variant::ARRAY: color = Color(0.45, 0.45, 0.45); break;
- case Variant::PACKED_BYTE_ARRAY: color = Color(0.38, 0.92, 0.6); break;
- case Variant::PACKED_INT32_ARRAY: color = Color(0.38, 0.73, 0.92); break;
- case Variant::PACKED_FLOAT32_ARRAY: color = Color(0.25, 0.83, 0.95); break;
- case Variant::PACKED_INT64_ARRAY: color = Color(0.38, 0.73, 0.92); break;
- case Variant::PACKED_FLOAT64_ARRAY: color = Color(0.25, 0.83, 0.95); break;
- case Variant::PACKED_STRING_ARRAY: color = Color(0.38, 0.62, 0.92); break;
- case Variant::PACKED_VECTOR2_ARRAY: color = Color(0.62, 0.36, 0.92); break;
- case Variant::PACKED_VECTOR3_ARRAY: color = Color(0.79, 0.35, 0.92); break;
- case Variant::PACKED_COLOR_ARRAY: color = Color(0.57, 0.73, 0.0); break;
+ case Variant::NIL:
+ color = Color(0.15, 0.89, 0.63);
+ break;
+
+ case Variant::BOOL:
+ color = Color(0.43, 0.56, 0.92);
+ break;
+ case Variant::INT:
+ color = Color(0.31, 0.7, 0.91);
+ break;
+ case Variant::FLOAT:
+ color = Color(0.15, 0.8, 0.94);
+ break;
+ case Variant::STRING:
+ color = Color(0.27, 0.56, 0.91);
+ break;
+
+ case Variant::VECTOR2:
+ color = Color(0.68, 0.46, 0.93);
+ break;
+ case Variant::VECTOR2I:
+ color = Color(0.68, 0.46, 0.93);
+ break;
+ case Variant::RECT2:
+ color = Color(0.93, 0.46, 0.56);
+ break;
+ case Variant::RECT2I:
+ color = Color(0.93, 0.46, 0.56);
+ break;
+ case Variant::VECTOR3:
+ color = Color(0.86, 0.42, 0.93);
+ break;
+ case Variant::VECTOR3I:
+ color = Color(0.86, 0.42, 0.93);
+ break;
+ case Variant::TRANSFORM2D:
+ color = Color(0.59, 0.81, 0.1);
+ break;
+ case Variant::PLANE:
+ color = Color(0.97, 0.44, 0.44);
+ break;
+ case Variant::QUAT:
+ color = Color(0.93, 0.41, 0.64);
+ break;
+ case Variant::AABB:
+ color = Color(0.93, 0.47, 0.57);
+ break;
+ case Variant::BASIS:
+ color = Color(0.7, 0.73, 0.1);
+ break;
+ case Variant::TRANSFORM:
+ color = Color(0.96, 0.56, 0.28);
+ break;
+
+ case Variant::COLOR:
+ color = Color(0.24, 0.75, 0.0);
+ break;
+ case Variant::NODE_PATH:
+ color = Color(0.41, 0.58, 0.93);
+ break;
+ case Variant::_RID:
+ color = Color(0.17, 0.9, 0.45);
+ break;
+ case Variant::OBJECT:
+ color = Color(0.07, 0.84, 0.76);
+ break;
+ case Variant::DICTIONARY:
+ color = Color(0.34, 0.91, 0.62);
+ break;
+
+ case Variant::ARRAY:
+ color = Color(0.45, 0.45, 0.45);
+ break;
+ case Variant::PACKED_BYTE_ARRAY:
+ color = Color(0.38, 0.92, 0.6);
+ break;
+ case Variant::PACKED_INT32_ARRAY:
+ color = Color(0.38, 0.73, 0.92);
+ break;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ color = Color(0.25, 0.83, 0.95);
+ break;
+ case Variant::PACKED_INT64_ARRAY:
+ color = Color(0.38, 0.73, 0.92);
+ break;
+ case Variant::PACKED_FLOAT64_ARRAY:
+ color = Color(0.25, 0.83, 0.95);
+ break;
+ case Variant::PACKED_STRING_ARRAY:
+ color = Color(0.38, 0.62, 0.92);
+ break;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ color = Color(0.62, 0.36, 0.92);
+ break;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ color = Color(0.79, 0.35, 0.92);
+ break;
+ case Variant::PACKED_COLOR_ARRAY:
+ color = Color(0.57, 0.73, 0.0);
+ break;
default:
color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.3, 0.3);
diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp
index 71ed483d65..616a621845 100644
--- a/modules/visual_script/visual_script_expression.cpp
+++ b/modules/visual_script/visual_script_expression.cpp
@@ -386,11 +386,21 @@ Error VisualScriptExpression::_get_token(Token &r_token) {
switch (next) {
- case 'b': res = 8; break;
- case 't': res = 9; break;
- case 'n': res = 10; break;
- case 'f': res = 12; break;
- case 'r': res = 13; break;
+ case 'b':
+ res = 8;
+ break;
+ case 't':
+ res = 9;
+ break;
+ case 'n':
+ res = 10;
+ break;
+ case 'f':
+ res = 12;
+ break;
+ case 'r':
+ res = 13;
+ break;
case 'u': {
// hex number
for (int j = 0; j < 4; j++) {
@@ -1005,27 +1015,69 @@ VisualScriptExpression::ENode *VisualScriptExpression::_parse_expression() {
Variant::Operator op = Variant::OP_MAX;
switch (tk.type) {
- case TK_OP_IN: op = Variant::OP_IN; break;
- case TK_OP_EQUAL: op = Variant::OP_EQUAL; break;
- case TK_OP_NOT_EQUAL: op = Variant::OP_NOT_EQUAL; break;
- case TK_OP_LESS: op = Variant::OP_LESS; break;
- case TK_OP_LESS_EQUAL: op = Variant::OP_LESS_EQUAL; break;
- case TK_OP_GREATER: op = Variant::OP_GREATER; break;
- case TK_OP_GREATER_EQUAL: op = Variant::OP_GREATER_EQUAL; break;
- case TK_OP_AND: op = Variant::OP_AND; break;
- case TK_OP_OR: op = Variant::OP_OR; break;
- case TK_OP_NOT: op = Variant::OP_NOT; break;
- case TK_OP_ADD: op = Variant::OP_ADD; break;
- case TK_OP_SUB: op = Variant::OP_SUBTRACT; break;
- case TK_OP_MUL: op = Variant::OP_MULTIPLY; break;
- case TK_OP_DIV: op = Variant::OP_DIVIDE; break;
- case TK_OP_MOD: op = Variant::OP_MODULE; break;
- case TK_OP_SHIFT_LEFT: op = Variant::OP_SHIFT_LEFT; break;
- case TK_OP_SHIFT_RIGHT: op = Variant::OP_SHIFT_RIGHT; break;
- case TK_OP_BIT_AND: op = Variant::OP_BIT_AND; break;
- case TK_OP_BIT_OR: op = Variant::OP_BIT_OR; break;
- case TK_OP_BIT_XOR: op = Variant::OP_BIT_XOR; break;
- case TK_OP_BIT_INVERT: op = Variant::OP_BIT_NEGATE; break;
+ case TK_OP_IN:
+ op = Variant::OP_IN;
+ break;
+ case TK_OP_EQUAL:
+ op = Variant::OP_EQUAL;
+ break;
+ case TK_OP_NOT_EQUAL:
+ op = Variant::OP_NOT_EQUAL;
+ break;
+ case TK_OP_LESS:
+ op = Variant::OP_LESS;
+ break;
+ case TK_OP_LESS_EQUAL:
+ op = Variant::OP_LESS_EQUAL;
+ break;
+ case TK_OP_GREATER:
+ op = Variant::OP_GREATER;
+ break;
+ case TK_OP_GREATER_EQUAL:
+ op = Variant::OP_GREATER_EQUAL;
+ break;
+ case TK_OP_AND:
+ op = Variant::OP_AND;
+ break;
+ case TK_OP_OR:
+ op = Variant::OP_OR;
+ break;
+ case TK_OP_NOT:
+ op = Variant::OP_NOT;
+ break;
+ case TK_OP_ADD:
+ op = Variant::OP_ADD;
+ break;
+ case TK_OP_SUB:
+ op = Variant::OP_SUBTRACT;
+ break;
+ case TK_OP_MUL:
+ op = Variant::OP_MULTIPLY;
+ break;
+ case TK_OP_DIV:
+ op = Variant::OP_DIVIDE;
+ break;
+ case TK_OP_MOD:
+ op = Variant::OP_MODULE;
+ break;
+ case TK_OP_SHIFT_LEFT:
+ op = Variant::OP_SHIFT_LEFT;
+ break;
+ case TK_OP_SHIFT_RIGHT:
+ op = Variant::OP_SHIFT_RIGHT;
+ break;
+ case TK_OP_BIT_AND:
+ op = Variant::OP_BIT_AND;
+ break;
+ case TK_OP_BIT_OR:
+ op = Variant::OP_BIT_OR;
+ break;
+ case TK_OP_BIT_XOR:
+ op = Variant::OP_BIT_XOR;
+ break;
+ case TK_OP_BIT_INVERT:
+ op = Variant::OP_BIT_NEGATE;
+ break;
default: {
};
}
@@ -1074,36 +1126,74 @@ VisualScriptExpression::ENode *VisualScriptExpression::_parse_expression() {
unary = true;
break;
- case Variant::OP_MULTIPLY: priority = 2; break;
- case Variant::OP_DIVIDE: priority = 2; break;
- case Variant::OP_MODULE: priority = 2; break;
+ case Variant::OP_MULTIPLY:
+ priority = 2;
+ break;
+ case Variant::OP_DIVIDE:
+ priority = 2;
+ break;
+ case Variant::OP_MODULE:
+ priority = 2;
+ break;
- case Variant::OP_ADD: priority = 3; break;
- case Variant::OP_SUBTRACT: priority = 3; break;
+ case Variant::OP_ADD:
+ priority = 3;
+ break;
+ case Variant::OP_SUBTRACT:
+ priority = 3;
+ break;
- case Variant::OP_SHIFT_LEFT: priority = 4; break;
- case Variant::OP_SHIFT_RIGHT: priority = 4; break;
+ case Variant::OP_SHIFT_LEFT:
+ priority = 4;
+ break;
+ case Variant::OP_SHIFT_RIGHT:
+ priority = 4;
+ break;
- case Variant::OP_BIT_AND: priority = 5; break;
- case Variant::OP_BIT_XOR: priority = 6; break;
- case Variant::OP_BIT_OR: priority = 7; break;
+ case Variant::OP_BIT_AND:
+ priority = 5;
+ break;
+ case Variant::OP_BIT_XOR:
+ priority = 6;
+ break;
+ case Variant::OP_BIT_OR:
+ priority = 7;
+ break;
- case Variant::OP_LESS: priority = 8; break;
- case Variant::OP_LESS_EQUAL: priority = 8; break;
- case Variant::OP_GREATER: priority = 8; break;
- case Variant::OP_GREATER_EQUAL: priority = 8; break;
+ case Variant::OP_LESS:
+ priority = 8;
+ break;
+ case Variant::OP_LESS_EQUAL:
+ priority = 8;
+ break;
+ case Variant::OP_GREATER:
+ priority = 8;
+ break;
+ case Variant::OP_GREATER_EQUAL:
+ priority = 8;
+ break;
- case Variant::OP_EQUAL: priority = 8; break;
- case Variant::OP_NOT_EQUAL: priority = 8; break;
+ case Variant::OP_EQUAL:
+ priority = 8;
+ break;
+ case Variant::OP_NOT_EQUAL:
+ priority = 8;
+ break;
- case Variant::OP_IN: priority = 10; break;
+ case Variant::OP_IN:
+ priority = 10;
+ break;
case Variant::OP_NOT:
priority = 11;
unary = true;
break;
- case Variant::OP_AND: priority = 12; break;
- case Variant::OP_OR: priority = 13; break;
+ case Variant::OP_AND:
+ priority = 12;
+ break;
+ case Variant::OP_OR:
+ priority = 13;
+ break;
default: {
_set_error("Parser bug, invalid operator in expression: " + itos(expression[i].op));
diff --git a/modules/visual_script/visual_script_yield_nodes.cpp b/modules/visual_script/visual_script_yield_nodes.cpp
index b300aec385..2296745ad0 100644
--- a/modules/visual_script/visual_script_yield_nodes.cpp
+++ b/modules/visual_script/visual_script_yield_nodes.cpp
@@ -81,10 +81,18 @@ String VisualScriptYield::get_caption() const {
String VisualScriptYield::get_text() const {
switch (yield_mode) {
- case YIELD_RETURN: return ""; break;
- case YIELD_FRAME: return "Next Frame"; break;
- case YIELD_PHYSICS_FRAME: return "Next Physics Frame"; break;
- case YIELD_WAIT: return rtos(wait_time) + " sec(s)"; break;
+ case YIELD_RETURN:
+ return "";
+ break;
+ case YIELD_FRAME:
+ return "Next Frame";
+ break;
+ case YIELD_PHYSICS_FRAME:
+ return "Next Physics Frame";
+ break;
+ case YIELD_WAIT:
+ return rtos(wait_time) + " sec(s)";
+ break;
}
return String();
@@ -122,9 +130,15 @@ public:
case VisualScriptYield::YIELD_RETURN:
ret = STEP_EXIT_FUNCTION_BIT;
break; //return the yield
- case VisualScriptYield::YIELD_FRAME: state->connect_to_signal(tree, "idle_frame", Array()); break;
- case VisualScriptYield::YIELD_PHYSICS_FRAME: state->connect_to_signal(tree, "physics_frame", Array()); break;
- case VisualScriptYield::YIELD_WAIT: state->connect_to_signal(tree->create_timer(wait_time).ptr(), "timeout", Array()); break;
+ case VisualScriptYield::YIELD_FRAME:
+ state->connect_to_signal(tree, "idle_frame", Array());
+ break;
+ case VisualScriptYield::YIELD_PHYSICS_FRAME:
+ state->connect_to_signal(tree, "physics_frame", Array());
+ break;
+ case VisualScriptYield::YIELD_WAIT:
+ state->connect_to_signal(tree->create_timer(wait_time).ptr(), "timeout", Array());
+ break;
}
*p_working_mem = state;
diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp
index 1b360720a2..40c3f5801b 100644
--- a/modules/webrtc/webrtc_data_channel_js.cpp
+++ b/modules/webrtc/webrtc_data_channel_js.cpp
@@ -312,14 +312,14 @@ WebRTCDataChannelJS::WebRTCDataChannelJS(int js_id) {
return;
}
var len = buffer.length*buffer.BYTES_PER_ELEMENT;
- var out = Module._malloc(len);
- Module.HEAPU8.set(buffer, out);
+ var out = _malloc(len);
+ HEAPU8.set(buffer, out);
ccall("_emrtc_on_ch_message",
"void",
["number", "number", "number", "number"],
[c_ptr, out, len, is_string]
);
- Module._free(out);
+ _free(out);
}
}, this, js_id);
diff --git a/modules/webrtc/webrtc_multiplayer.cpp b/modules/webrtc/webrtc_multiplayer.cpp
index 78a4d1e61a..f294733961 100644
--- a/modules/webrtc/webrtc_multiplayer.cpp
+++ b/modules/webrtc/webrtc_multiplayer.cpp
@@ -144,7 +144,8 @@ void WebRTCMultiplayer::poll() {
void WebRTCMultiplayer::_find_next_peer() {
Map<int, Ref<ConnectedPeer>>::Element *E = peer_map.find(next_packet_peer);
- if (E) E = E->next();
+ if (E)
+ E = E->next();
// After last.
while (E) {
for (List<Ref<WebRTCDataChannel>>::Element *F = E->get()->channels.front(); F; F = F->next()) {
diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp
index bbe4d6dc5b..bceb65c315 100644
--- a/modules/websocket/emws_client.cpp
+++ b/modules/websocket/emws_client.cpp
@@ -142,14 +142,14 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
}
var len = buffer.length*buffer.BYTES_PER_ELEMENT;
- var out = Module._malloc(len);
- Module.HEAPU8.set(buffer, out);
+ var out = _malloc(len);
+ HEAPU8.set(buffer, out);
ccall("_esws_on_message",
"void",
["number", "number", "number", "number"],
[c_ptr, out, len, is_string]
);
- Module._free(out);
+ _free(out);
});
socket.addEventListener("error", function (event) {
diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp
index 8c5525bed3..f77646ce28 100644
--- a/modules/xatlas_unwrap/register_types.cpp
+++ b/modules/xatlas_unwrap/register_types.cpp
@@ -137,6 +137,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
pack_options.maxChartSize = 4096;
pack_options.blockAlign = true;
+ pack_options.padding = 1;
pack_options.texelsPerUnit = 1.0 / p_texel_size;
xatlas::Atlas *atlas = xatlas::Create();