Przeglądaj źródła

Merge pull request #106792 from lawnjelly/3.6_cherrypicks_may_25

Cherry-picks for the 3.6 branch (future 3.6.1) - 1st batch
Rémi Verschelde 3 miesięcy temu
rodzic
commit
25cab4a23b
41 zmienionych plików z 406 dodań i 123 usunięć
  1. 1 3
      .github/workflows/javascript_builds.yml
  2. 2 1
      SConstruct
  3. 1 1
      core/io/file_access_zip.cpp
  4. 3 1
      core/math/expression.cpp
  5. 7 4
      core/object.cpp
  6. 3 0
      doc/classes/BitMap.xml
  7. 3 0
      doc/classes/Button.xml
  8. 1 0
      doc/classes/TextureProgress.xml
  9. 1 0
      doc/classes/Timer.xml
  10. 4 1
      drivers/gles3/rasterizer_scene_gles3.cpp
  11. 1 2
      drivers/gles3/rasterizer_scene_gles3.h
  12. 3 5
      drivers/gles3/shaders/scene.glsl
  13. 136 0
      main/tests/test_expression.cpp
  14. 41 0
      main/tests/test_expression.h
  15. 6 0
      main/tests/test_main.cpp
  16. 62 37
      methods.py
  17. 12 2
      modules/gdscript/gdscript.cpp
  18. 9 0
      modules/gdscript/gdscript_function.cpp
  19. 1 0
      modules/gdscript/gdscript_function.h
  20. 22 22
      modules/mono/csharp_script.cpp
  21. 5 5
      modules/mono/csharp_script.h
  22. 2 2
      modules/mono/glue/base_object_glue.cpp
  23. 4 0
      modules/mono/mono_gc_handle.cpp
  24. 4 0
      modules/mono/mono_gc_handle.h
  25. 2 2
      modules/mono/mono_gd/gd_mono_cache.cpp
  26. 1 1
      modules/mono/mono_gd/gd_mono_cache.h
  27. 2 2
      modules/mono/mono_gd/gd_mono_internals.cpp
  28. 2 2
      modules/mono/mono_gd/gd_mono_utils.cpp
  29. 1 1
      modules/mono/signal_awaiter_utils.cpp
  30. 2 2
      modules/mono/signal_awaiter_utils.h
  31. 14 1
      scene/2d/physics_body_2d.cpp
  32. 2 0
      scene/2d/physics_body_2d.h
  33. 8 3
      scene/3d/light.cpp
  34. 20 2
      scene/3d/physics_body.cpp
  35. 2 0
      scene/3d/physics_body.h
  36. 6 14
      scene/gui/popup_menu.cpp
  37. 1 1
      scene/gui/range.cpp
  38. 1 1
      scene/gui/tree.cpp
  39. 2 0
      scene/main/node.cpp
  40. 5 5
      scene/main/scene_tree.cpp
  41. 1 0
      scene/resources/default_theme/default_theme.cpp

+ 1 - 3
.github/workflows/javascript_builds.yml

@@ -8,7 +8,6 @@ env:
   GODOT_BASE_BRANCH: 3.6
   SCONSFLAGS: verbose=yes warnings=all werror=yes debug_symbols=no
   EM_VERSION: 3.1.39
-  EM_CACHE_FOLDER: "emsdk-cache"
 
 concurrency:
   group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-javascript
@@ -26,8 +25,7 @@ jobs:
         uses: mymindstorm/setup-emsdk@v14
         with:
           version: ${{env.EM_VERSION}}
-          actions-cache-folder: ${{env.EM_CACHE_FOLDER}}
-          cache-key: emsdk-${{ matrix.cache-name }}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+          no-cache: true
 
       - name: Verify Emscripten setup
         run: |

+ 2 - 1
SConstruct

@@ -796,12 +796,13 @@ elif selected_platform != "":
 
 # The following only makes sense when the 'env' is defined, and assumes it is.
 if "env" in locals():
-    # FIXME: This method mixes both cosmetic progress stuff and cache handling...
     methods.show_progress(env)
     # TODO: replace this with `env.Dump(format="json")`
     # once we start requiring SCons 4.0 as min version.
     methods.dump(env)
 
+    methods.clean_cache(env)
+
 
 def print_elapsed_time():
     elapsed_time_sec = round(time.time() - time_at_start, 3)

+ 1 - 1
core/io/file_access_zip.cpp

@@ -43,7 +43,7 @@ static void *godot_open(void *data, const char *p_fname, int mode) {
 		return nullptr;
 	}
 
-	FileAccess *f = FileAccess::open(p_fname, FileAccess::READ);
+	FileAccess *f = FileAccess::open(String::utf8(p_fname), FileAccess::READ);
 	ERR_FAIL_COND_V(!f, nullptr);
 
 	return f;

+ 3 - 1
core/math/expression.cpp

@@ -1103,7 +1103,9 @@ Error Expression::_get_token(Token &r_token) {
 						is_first_char = false;
 					}
 
-					str_ofs--;
+					if (c != 0) {
+						str_ofs--;
+					}
 
 					r_token.type = TK_CONSTANT;
 

+ 7 - 4
core/object.cpp

@@ -43,14 +43,17 @@
 #ifdef DEBUG_ENABLED
 
 struct _ObjectDebugLock {
-	Object *obj;
+	ObjectID obj_id;
 
 	_ObjectDebugLock(Object *p_obj) {
-		obj = p_obj;
-		obj->_lock_index.ref();
+		obj_id = p_obj->get_instance_id();
+		p_obj->_lock_index.ref();
 	}
 	~_ObjectDebugLock() {
-		obj->_lock_index.unref();
+		Object *obj_ptr = ObjectDB::get_instance(obj_id);
+		if (likely(obj_ptr)) {
+			obj_ptr->_lock_index.unref();
+		}
 	}
 };
 

+ 3 - 0
doc/classes/BitMap.xml

@@ -62,6 +62,9 @@
 			<argument index="0" name="rect" type="Rect2" />
 			<argument index="1" name="epsilon" type="float" default="2.0" />
 			<description>
+				Generates polygon outlines from the opaque (non-transparent) areas of the [BitMap] using a Marching Squares algorithm.
+				Returns an [Array] of [PoolVector2Array], where each [PoolVector2Array] represents a polygon outline. These outlines can be directly assigned to the [code]polygon[/code] property of nodes like [CollisionPolygon2D] or [OccluderPolygon2D].
+				The [code]epsilon[/code] parameter controls polygon simplification. A lower value produces more accurate polygons, but at the cost of increased polygon size and potential performance impact. A higher value simplifies the polygons, reducing their size and improving performance, but with less accuracy.
 			</description>
 		</method>
 		<method name="resize">

+ 3 - 0
doc/classes/Button.xml

@@ -113,6 +113,9 @@
 		<theme_item name="hover" data_type="style" type="StyleBox">
 			[StyleBox] used when the [Button] is being hovered.
 		</theme_item>
+		<theme_item name="hover_pressed" data_type="style" type="StyleBox">
+			[StyleBox] used when the [Button] is being hovered and pressed.
+		</theme_item>
 		<theme_item name="normal" data_type="style" type="StyleBox">
 			Default [StyleBox] for the [Button].
 		</theme_item>

+ 1 - 0
doc/classes/TextureProgress.xml

@@ -33,6 +33,7 @@
 		</member>
 		<member name="radial_center_offset" type="Vector2" setter="set_radial_center_offset" getter="get_radial_center_offset" default="Vector2( 0, 0 )">
 			Offsets [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE] or [constant FILL_COUNTER_CLOCKWISE].
+			[b]Note:[/b] The effective radial center always stays within the [member texture_progress] bounds. If you need to move it outside the texture's bounds, modify the [member texture_progress] to contain additional empty space where needed.
 		</member>
 		<member name="radial_fill_degrees" type="float" setter="set_fill_degrees" getter="get_fill_degrees" default="360.0">
 			Upper limit for the fill of [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE] or [constant FILL_COUNTER_CLOCKWISE]. When the node's [code]value[/code] is equal to its [code]max_value[/code], the texture fills up to this angle.

+ 1 - 0
doc/classes/Timer.xml

@@ -37,6 +37,7 @@
 		<member name="autostart" type="bool" setter="set_autostart" getter="has_autostart" default="false">
 			If [code]true[/code], the timer will automatically start when entering the scene tree.
 			[b]Note:[/b] This property is automatically set to [code]false[/code] after the timer enters the scene tree and starts.
+			[b]Note:[/b] This property does nothing when the timer is running in the editor.
 		</member>
 		<member name="one_shot" type="bool" setter="set_one_shot" getter="is_one_shot" default="false">
 			If [code]true[/code], the timer will stop when reaching 0. If [code]false[/code], it will restart.

+ 4 - 1
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -2790,7 +2790,10 @@ void RasterizerSceneGLES3::_setup_directional_light(int p_index, const Transform
 		const float fade_start = li->light_ptr->param[VS::LIGHT_PARAM_SHADOW_FADE_START];
 		// Using 1.0 would break `smoothstep()` in the shader.
 		ubo_data.fade_from = -ubo_data.shadow_split_offsets[shadow_count - 1] * MIN(fade_start, 0.999);
-		ubo_data.fade_to = -ubo_data.shadow_split_offsets[shadow_count - 1];
+
+		// To prevent the need for a fade to, store the fade to in the final split offset.
+		// It will either be the same as before, or the maximum split offset.
+		ubo_data.shadow_split_offsets[3] = ubo_data.shadow_split_offsets[shadow_count - 1];
 	}
 
 	glBindBuffer(GL_UNIFORM_BUFFER, state.directional_ubo);

+ 1 - 2
drivers/gles3/rasterizer_scene_gles3.h

@@ -589,8 +589,7 @@ public:
 		float shadow_split_offsets[4];
 
 		float fade_from;
-		float fade_to;
-		float pad[2];
+		float pad[3];
 	};
 
 	struct LightInstance : public RID_Data {

+ 3 - 5
drivers/gles3/shaders/scene.glsl

@@ -148,8 +148,7 @@ layout(std140) uniform DirectionalLightData { //ubo:3
 	mediump vec4 shadow_split_offsets;
 
 	mediump float fade_from;
-	mediump float fade_to;
-	mediump vec2 pad;
+	mediump vec3 pad;
 };
 
 #endif //ubershader-skip
@@ -848,8 +847,7 @@ layout(std140) uniform DirectionalLightData {
 	mediump vec4 shadow_split_offsets;
 
 	mediump float fade_from;
-	mediump float fade_to;
-	mediump vec2 pad;
+	mediump vec3 pad;
 };
 
 uniform highp sampler2DShadow directional_shadow; // texunit:-5
@@ -2292,7 +2290,7 @@ FRAGMENT_SHADER_CODE
 			shadow = min(shadow, contact_shadow);
 		}
 #endif //ubershader-runtime
-		float pssm_fade = smoothstep(fade_from, fade_to, vertex.z);
+		float pssm_fade = smoothstep(fade_from, -shadow_split_offsets.w, vertex.z);
 		light_attenuation = mix(mix(shadow_color_contact.rgb, vec3(1.0), shadow), vec3(1.0), pssm_fade);
 	}
 

+ 136 - 0
main/tests/test_expression.cpp

@@ -0,0 +1,136 @@
+/**************************************************************************/
+/*  test_expression.cpp                                                   */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "test_expression.h"
+
+#include "core/math/expression.h"
+#include "core/os/os.h"
+
+#define CHECK_MESSAGE(X, msg)                                      \
+	if (!(X)) {                                                    \
+		OS::get_singleton()->print("\tFAIL at %s: %s\n", #X, msg); \
+		return false;                                              \
+	} else {                                                       \
+		OS::get_singleton()->print("\tPASS\n");                    \
+	}
+
+namespace TestExpression {
+
+bool floating_point_notation() {
+	OS::get_singleton()->print("\n\nTest 1: Floating-point notation\n");
+
+	Expression expression;
+
+	CHECK_MESSAGE(
+			expression.parse("2.") == OK,
+			"The expression should parse successfully.");
+	CHECK_MESSAGE(
+			Math::is_equal_approx(expression.execute(Array()), 2.0),
+			"The expression should return the expected result.");
+
+	CHECK_MESSAGE(
+			expression.parse("(2.)") == OK,
+			"The expression should parse successfully.");
+	CHECK_MESSAGE(
+			Math::is_equal_approx(expression.execute(Array()), 2.0),
+			"The expression should return the expected result.");
+
+	CHECK_MESSAGE(
+			expression.parse(".3") == OK,
+			"The expression should parse successfully.");
+	CHECK_MESSAGE(
+			Math::is_equal_approx(expression.execute(Array()), 0.3),
+			"The expression should return the expected result.");
+
+	CHECK_MESSAGE(
+			expression.parse("2.+5.") == OK,
+			"The expression should parse successfully.");
+	CHECK_MESSAGE(
+			Math::is_equal_approx(expression.execute(Array()), 7.0),
+			"The expression should return the expected result.");
+
+	CHECK_MESSAGE(
+			expression.parse(".3-.8") == OK,
+			"The expression should parse successfully.");
+	CHECK_MESSAGE(
+			Math::is_equal_approx(expression.execute(Array()), -0.5),
+			"The expression should return the expected result.");
+
+	CHECK_MESSAGE(
+			expression.parse("2.+.2") == OK,
+			"The expression should parse successfully.");
+	CHECK_MESSAGE(
+			Math::is_equal_approx(expression.execute(Array()), 2.2),
+			"The expression should return the expected result.");
+
+	CHECK_MESSAGE(
+			expression.parse(".0*0.") == OK,
+			"The expression should parse successfully.");
+	CHECK_MESSAGE(
+			Math::is_equal_approx(expression.execute(Array()), 0.0),
+			"The expression should return the expected result.");
+
+	return true;
+}
+
+typedef bool (*TestFunc)();
+
+TestFunc test_funcs[] = {
+	floating_point_notation,
+	nullptr
+};
+
+MainLoop *test() {
+	int count = 0;
+	int passed = 0;
+
+	while (true) {
+		if (!test_funcs[count]) {
+			break;
+		}
+		bool pass = test_funcs[count]();
+		if (pass) {
+			passed++;
+		}
+		OS::get_singleton()->print("\t%s\n", pass ? "PASS" : "FAILED");
+
+		count++;
+	}
+
+	OS::get_singleton()->print("\n\n\n");
+	OS::get_singleton()->print("*************\n");
+	OS::get_singleton()->print("***TOTALS!***\n");
+	OS::get_singleton()->print("*************\n");
+
+	OS::get_singleton()->print("Passed %i of %i tests\n", passed, count);
+
+	return nullptr;
+}
+} // namespace TestExpression

+ 41 - 0
main/tests/test_expression.h

@@ -0,0 +1,41 @@
+/**************************************************************************/
+/*  test_expression.h                                                     */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef TEST_EXPRESSION_H
+#define TEST_EXPRESSION_H
+
+#include "core/os/main_loop.h"
+
+namespace TestExpression {
+
+MainLoop *test();
+}
+
+#endif // TEST_EXPRESSION_H

+ 6 - 0
main/tests/test_main.cpp

@@ -37,6 +37,7 @@
 #include "test_astar.h"
 #include "test_basis.h"
 #include "test_crypto.h"
+#include "test_expression.h"
 #include "test_gdscript.h"
 #include "test_gui.h"
 #include "test_math.h"
@@ -71,6 +72,7 @@ const char **tests_get_names() {
 		"astar",
 		"xml_parser",
 		"theme",
+		"expression",
 		nullptr
 	};
 
@@ -156,6 +158,10 @@ MainLoop *test_main(String p_test, const List<String> &p_args) {
 		return TestTheme::test();
 	}
 
+	if (p_test == "expression") {
+		return TestExpression::test();
+	}
+
 	print_line("Unknown test: " + p_test);
 	return nullptr;
 }

+ 62 - 37
methods.py

@@ -1128,21 +1128,19 @@ def show_progress(env):
         "fname": str(env.Dir("#")) + "/.scons_node_count",
     }
 
-    import time, math
+    import math
 
     class cache_progress:
-        # The default is 1 GB cache and 12 hours half life
-        def __init__(self, path=None, limit=1073741824, half_life=43200):
+        # The default is 1 GB cache
+        def __init__(self, path=None, limit=pow(1024, 3)):
             self.path = path
             self.limit = limit
-            self.exponent_scale = math.log(2) / half_life
             if env["verbose"] and path != None:
                 screen.write(
                     "Current cache limit is {} (used: {})\n".format(
                         self.convert_size(limit), self.convert_size(self.get_size(path))
                     )
                 )
-            self.delete(self.file_list())
 
         def __call__(self, node, *args, **kw):
             if show_progress:
@@ -1160,12 +1158,65 @@ def show_progress(env):
                     screen.write("\r[Initial build] ")
                     screen.flush()
 
+        def convert_size(self, size_bytes):
+            if size_bytes == 0:
+                return "0 bytes"
+            size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
+            i = int(math.floor(math.log(size_bytes, 1024)))
+            p = math.pow(1024, i)
+            s = round(size_bytes / p, 2)
+            return "%s %s" % (int(s) if i == 0 else s, size_name[i])
+
+        def get_size(self, start_path="."):
+            total_size = 0
+            for dirpath, dirnames, filenames in os.walk(start_path):
+                for f in filenames:
+                    fp = os.path.join(dirpath, f)
+                    total_size += os.path.getsize(fp)
+            return total_size
+
+    def progress_finish(target, source, env):
+        try:
+            with open(node_count_data["fname"], "w") as f:
+                f.write("%d\n" % node_count_data["count"])
+        except Exception:
+            pass
+
+    try:
+        with open(node_count_data["fname"]) as f:
+            node_count_data["max"] = int(f.readline())
+    except Exception:
+        pass
+
+    cache_directory = os.environ.get("SCONS_CACHE")
+    # Simple cache pruning, attached to SCons' progress callback. Trim the
+    # cache directory to a size not larger than cache_limit.
+    cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
+    progressor = cache_progress(cache_directory, cache_limit)
+    Progress(progressor, interval=node_count_data["interval"])
+
+    progress_finish_command = Command("progress_finish", [], progress_finish)
+    AlwaysBuild(progress_finish_command)
+
+
+def clean_cache(env):
+    import atexit
+    import time
+
+    class cache_clean:
+        def __init__(self, path=None, limit=pow(1024, 3)):
+            self.path = path
+            self.limit = limit
+
+        def clean(self):
+            self.delete(self.file_list())
+
         def delete(self, files):
             if len(files) == 0:
                 return
             if env["verbose"]:
                 # Utter something
-                screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file"))
+                print("Purging %d %s from cache..." % (len(files), "files" if len(files) > 1 else "file"))
             [os.remove(f) for f in files]
 
         def file_list(self):
@@ -1199,46 +1250,20 @@ def show_progress(env):
             else:
                 return [x[0] for x in file_stat[mark:]]
 
-        def convert_size(self, size_bytes):
-            if size_bytes == 0:
-                return "0 bytes"
-            size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
-            i = int(math.floor(math.log(size_bytes, 1024)))
-            p = math.pow(1024, i)
-            s = round(size_bytes / p, 2)
-            return "%s %s" % (int(s) if i == 0 else s, size_name[i])
-
-        def get_size(self, start_path="."):
-            total_size = 0
-            for dirpath, dirnames, filenames in os.walk(start_path):
-                for f in filenames:
-                    fp = os.path.join(dirpath, f)
-                    total_size += os.path.getsize(fp)
-            return total_size
-
-    def progress_finish(target, source, env):
+    def cache_finally():
+        nonlocal cleaner
         try:
-            with open(node_count_data["fname"], "w") as f:
-                f.write("%d\n" % node_count_data["count"])
-            progressor.delete(progressor.file_list())
+            cleaner.clean()
         except Exception:
             pass
 
-    try:
-        with open(node_count_data["fname"]) as f:
-            node_count_data["max"] = int(f.readline())
-    except Exception:
-        pass
-
     cache_directory = os.environ.get("SCONS_CACHE")
     # Simple cache pruning, attached to SCons' progress callback. Trim the
     # cache directory to a size not larger than cache_limit.
     cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
-    progressor = cache_progress(cache_directory, cache_limit)
-    Progress(progressor, interval=node_count_data["interval"])
+    cleaner = cache_clean(cache_directory, cache_limit)
 
-    progress_finish_command = Command("progress_finish", [], progress_finish)
-    AlwaysBuild(progress_finish_command)
+    atexit.register(cache_finally)
 
 
 def dump(env):

+ 12 - 2
modules/gdscript/gdscript.cpp

@@ -83,7 +83,12 @@ void GDScript::_clear_pending_func_states() {
 		// Order matters since clearing the stack may already cause
 		// the GDSCriptFunctionState to be destroyed and thus removed from the list.
 		pending_func_states.remove(E);
-		E->self()->_clear_stack();
+		GDScriptFunctionState *state = E->self();
+		ObjectID state_id = state->get_instance_id();
+		state->_clear_connections();
+		if (ObjectDB::get_instance(state_id)) {
+			state->_clear_stack();
+		}
 	}
 	GDScriptLanguage::get_singleton()->lock.unlock();
 }
@@ -1372,7 +1377,12 @@ GDScriptInstance::~GDScriptInstance() {
 		// Order matters since clearing the stack may already cause
 		// the GDSCriptFunctionState to be destroyed and thus removed from the list.
 		pending_func_states.remove(E);
-		E->self()->_clear_stack();
+		GDScriptFunctionState *state = E->self();
+		ObjectID state_id = state->get_instance_id();
+		state->_clear_connections();
+		if (ObjectDB::get_instance(state_id)) {
+			state->_clear_stack();
+		}
 	}
 
 	if (script.is_valid() && owner) {

+ 9 - 0
modules/gdscript/gdscript_function.cpp

@@ -1855,6 +1855,15 @@ void GDScriptFunctionState::_clear_stack() {
 	}
 }
 
+void GDScriptFunctionState::_clear_connections() {
+	List<Object::Connection> conns;
+	get_signals_connected_to_this(&conns);
+	for (List<Object::Connection>::Element *E = conns.front(); E; E = E->next()) {
+		Object::Connection &c = E->get();
+		c.source->disconnect(c.signal, c.target, c.method);
+	}
+}
+
 void GDScriptFunctionState::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant()));
 	ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false));

+ 1 - 0
modules/gdscript/gdscript_function.h

@@ -375,6 +375,7 @@ public:
 	Variant resume(const Variant &p_arg = Variant());
 
 	void _clear_stack();
+	void _clear_connections();
 
 	GDScriptFunctionState();
 	~GDScriptFunctionState();

+ 22 - 22
modules/mono/csharp_script.cpp

@@ -653,7 +653,7 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) {
 
 void CSharpLanguage::frame() {
 	if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) {
-		const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle;
+		const Ref<gdmono::MonoGCHandle> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle;
 
 		if (task_scheduler_handle.is_valid()) {
 			MonoObject *task_scheduler = task_scheduler_handle->get_target();
@@ -1189,15 +1189,15 @@ void CSharpLanguage::set_language_index(int p_idx) {
 	lang_idx = p_idx;
 }
 
-void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) {
+void CSharpLanguage::release_script_gchandle(Ref<gdmono::MonoGCHandle> &p_gchandle) {
 	if (!p_gchandle->is_released()) { // Do not lock unnecessarily
 		MutexLock lock(get_singleton()->script_gchandle_release_mutex);
 		p_gchandle->release();
 	}
 }
 
-void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle) {
-	uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it
+void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<gdmono::MonoGCHandle> &p_gchandle) {
+	uint32_t pinned_gchandle = gdmono::MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it
 
 	if (!p_gchandle->is_released()) { // Do not lock unnecessarily
 		MutexLock lock(get_singleton()->script_gchandle_release_mutex);
@@ -1213,7 +1213,7 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<Mon
 		}
 	}
 
-	MonoGCHandle::free_handle(pinned_gchandle);
+	gdmono::MonoGCHandle::free_handle(pinned_gchandle);
 }
 
 CSharpLanguage::CSharpLanguage() {
@@ -1267,7 +1267,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
 	r_script_binding.inited = true;
 	r_script_binding.type_name = type_name;
 	r_script_binding.wrapper_class = type_class; // cache
-	r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
+	r_script_binding.gchandle = gdmono::MonoGCHandle::create_strong(mono_object);
 	r_script_binding.owner = p_object;
 
 	// Tie managed to unmanaged
@@ -1351,7 +1351,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
 	CRASH_COND(!data);
 
 	CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
-	Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+	Ref<gdmono::MonoGCHandle> &gchandle = script_binding.gchandle;
 
 	if (!script_binding.inited)
 		return;
@@ -1368,9 +1368,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
 			return; // Called after the managed side was collected, so nothing to do here
 
 		// Release the current weak handle and replace it with a strong handle.
-		uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target);
+		uint32_t strong_gchandle = gdmono::MonoGCHandle::new_strong_handle(target);
 		gchandle->release();
-		gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
+		gchandle->set_handle(strong_gchandle, gdmono::MonoGCHandle::STRONG_HANDLE);
 	}
 }
 
@@ -1386,7 +1386,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
 	CRASH_COND(!data);
 
 	CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
-	Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+	Ref<gdmono::MonoGCHandle> &gchandle = script_binding.gchandle;
 
 	int refcount = ref_owner->reference_get_count();
 
@@ -1404,9 +1404,9 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
 			return refcount == 0; // Called after the managed side was collected, so nothing to do here
 
 		// Release the current strong handle and replace it with a weak handle.
-		uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target);
+		uint32_t weak_gchandle = gdmono::MonoGCHandle::new_weak_handle(target);
 		gchandle->release();
-		gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
+		gchandle->set_handle(weak_gchandle, gdmono::MonoGCHandle::WEAK_HANDLE);
 
 		return false;
 	}
@@ -1414,7 +1414,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
 	return refcount == 0;
 }
 
-CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle) {
+CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<gdmono::MonoGCHandle> &p_gchandle) {
 	CSharpInstance *instance = memnew(CSharpInstance);
 
 	Reference *ref = Object::cast_to<Reference>(p_owner);
@@ -1832,7 +1832,7 @@ MonoObject *CSharpInstance::_internal_new_managed() {
 	}
 
 	// Tie managed to unmanaged
-	gchandle = MonoGCHandle::create_strong(mono_object);
+	gchandle = gdmono::MonoGCHandle::create_strong(mono_object);
 
 	if (base_ref)
 		_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
@@ -1902,9 +1902,9 @@ void CSharpInstance::refcount_incremented() {
 		// so the owner must hold the managed side alive again to avoid it from being GCed.
 
 		// Release the current weak handle and replace it with a strong handle.
-		uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target());
+		uint32_t strong_gchandle = gdmono::MonoGCHandle::new_strong_handle(gchandle->get_target());
 		gchandle->release();
-		gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
+		gchandle->set_handle(strong_gchandle, gdmono::MonoGCHandle::STRONG_HANDLE);
 	}
 }
 
@@ -1925,9 +1925,9 @@ bool CSharpInstance::refcount_decremented() {
 		// the managed instance takes responsibility of deleting the owner when GCed.
 
 		// Release the current strong handle and replace it with a weak handle.
-		uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target());
+		uint32_t weak_gchandle = gdmono::MonoGCHandle::new_weak_handle(gchandle->get_target());
 		gchandle->release();
-		gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
+		gchandle->set_handle(weak_gchandle, gdmono::MonoGCHandle::WEAK_HANDLE);
 
 		return false;
 	}
@@ -2298,7 +2298,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
 				return false;
 			}
 
-			tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed)
+			tmp_pinned_gchandle = gdmono::MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed)
 
 			GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
 
@@ -2313,7 +2313,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
 			if (ctor_exc) {
 				// TODO: Should we free 'tmp_native' if the exception was thrown after its creation?
 
-				MonoGCHandle::free_handle(tmp_pinned_gchandle);
+				gdmono::MonoGCHandle::free_handle(tmp_pinned_gchandle);
 				tmp_object = NULL;
 
 				ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
@@ -2409,7 +2409,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
 				GDMonoUtils::debug_print_unhandled_exception(exc);
 			}
 
-			MonoGCHandle::free_handle(tmp_pinned_gchandle);
+			gdmono::MonoGCHandle::free_handle(tmp_pinned_gchandle);
 			tmp_object = NULL;
 
 			if (tmp_native && !base_ref) {
@@ -2970,7 +2970,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
 	}
 
 	// Tie managed to unmanaged
-	instance->gchandle = MonoGCHandle::create_strong(mono_object);
+	instance->gchandle = gdmono::MonoGCHandle::create_strong(mono_object);
 
 	if (instance->base_ref)
 		instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)

+ 5 - 5
modules/mono/csharp_script.h

@@ -220,7 +220,7 @@ class CSharpInstance : public ScriptInstance {
 	bool destructing_script_instance;
 
 	Ref<CSharpScript> script;
-	Ref<MonoGCHandle> gchandle;
+	Ref<gdmono::MonoGCHandle> gchandle;
 
 	bool _reference_owner_unsafe();
 
@@ -236,7 +236,7 @@ class CSharpInstance : public ScriptInstance {
 
 	// Do not use unless you know what you are doing
 	friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
-	static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle);
+	static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<gdmono::MonoGCHandle> &p_gchandle);
 
 	void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
 
@@ -293,7 +293,7 @@ struct CSharpScriptBinding {
 	bool inited;
 	StringName type_name;
 	GDMonoClass *wrapper_class;
-	Ref<MonoGCHandle> gchandle;
+	Ref<gdmono::MonoGCHandle> gchandle;
 	Object *owner;
 };
 
@@ -371,8 +371,8 @@ public:
 	_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
 #endif
 
-	static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
-	static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle);
+	static void release_script_gchandle(Ref<gdmono::MonoGCHandle> &p_gchandle);
+	static void release_script_gchandle(MonoObject *p_expected_obj, Ref<gdmono::MonoGCHandle> &p_gchandle);
 
 	bool debug_break(const String &p_error, bool p_allow_continue = true);
 	bool debug_break_parse(const String &p_file, int p_line, const String &p_error);

+ 2 - 2
modules/mono/glue/base_object_glue.cpp

@@ -70,7 +70,7 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
 	if (data) {
 		CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
 		if (script_binding.inited) {
-			Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+			Ref<gdmono::MonoGCHandle> &gchandle = script_binding.gchandle;
 			if (gchandle.is_valid()) {
 				CSharpLanguage::release_script_gchandle(p_obj, gchandle);
 			}
@@ -117,7 +117,7 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea
 		if (data) {
 			CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
 			if (script_binding.inited) {
-				Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+				Ref<gdmono::MonoGCHandle> &gchandle = script_binding.gchandle;
 				if (gchandle.is_valid()) {
 					CSharpLanguage::release_script_gchandle(p_obj, gchandle);
 				}

+ 4 - 0
modules/mono/mono_gc_handle.cpp

@@ -32,6 +32,8 @@
 
 #include "mono_gd/gd_mono.h"
 
+namespace gdmono {
+
 uint32_t MonoGCHandle::new_strong_handle(MonoObject *p_object) {
 	return mono_gchandle_new(p_object, /* pinned: */ false);
 }
@@ -76,3 +78,5 @@ MonoGCHandle::MonoGCHandle(uint32_t p_handle, HandleType p_handle_type) {
 MonoGCHandle::~MonoGCHandle() {
 	release();
 }
+
+} // namespace gdmono

+ 4 - 0
modules/mono/mono_gc_handle.h

@@ -35,6 +35,8 @@
 
 #include "core/reference.h"
 
+namespace gdmono {
+
 class MonoGCHandle : public Reference {
 	GDCLASS(MonoGCHandle, Reference);
 
@@ -72,4 +74,6 @@ public:
 	~MonoGCHandle();
 };
 
+} // namespace gdmono
+
 #endif // MONO_GC_HANDLE_H

+ 2 - 2
modules/mono/mono_gd/gd_mono_cache.cpp

@@ -178,7 +178,7 @@ void CachedData::clear_godot_api_cache() {
 
 	// End of MarshalUtils methods
 
-	task_scheduler_handle = Ref<MonoGCHandle>();
+	task_scheduler_handle = Ref<gdmono::MonoGCHandle>();
 }
 
 #define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class))
@@ -300,7 +300,7 @@ void update_godot_api_cache() {
 	// TODO Move to CSharpLanguage::init() and do handle disposal
 	MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
 	GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler));
-	cached_data.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
+	cached_data.task_scheduler_handle = gdmono::MonoGCHandle::create_strong(task_scheduler);
 
 	cached_data.godot_api_cache_updated = true;
 }

+ 1 - 1
modules/mono/mono_gd/gd_mono_cache.h

@@ -149,7 +149,7 @@ struct CachedData {
 
 	// End of MarshalUtils methods
 
-	Ref<MonoGCHandle> task_scheduler_handle;
+	Ref<gdmono::MonoGCHandle> task_scheduler_handle;
 
 	bool corlib_cache_updated;
 	bool godot_api_cache_updated;

+ 2 - 2
modules/mono/mono_gd/gd_mono_internals.cpp

@@ -71,7 +71,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
 		script_binding.inited = true;
 		script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass);
 		script_binding.wrapper_class = klass;
-		script_binding.gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed);
+		script_binding.gchandle = ref ? gdmono::MonoGCHandle::create_weak(managed) : gdmono::MonoGCHandle::create_strong(managed);
 		script_binding.owner = unmanaged;
 
 		if (ref) {
@@ -101,7 +101,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
 		return;
 	}
 
-	Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed);
+	Ref<gdmono::MonoGCHandle> gchandle = ref ? gdmono::MonoGCHandle::create_weak(managed) : gdmono::MonoGCHandle::create_strong(managed);
 
 	Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass, native);
 

+ 2 - 2
modules/mono/mono_gd/gd_mono_utils.cpp

@@ -83,7 +83,7 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
 		}
 	}
 
-	Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+	Ref<gdmono::MonoGCHandle> &gchandle = script_binding.gchandle;
 	ERR_FAIL_COND_V(gchandle.is_null(), NULL);
 
 	MonoObject *target = gchandle->get_target();
@@ -103,7 +103,7 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
 	MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged);
 	ERR_FAIL_NULL_V(mono_object, NULL);
 
-	gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE);
+	gchandle->set_handle(gdmono::MonoGCHandle::new_strong_handle(mono_object), gdmono::MonoGCHandle::STRONG_HANDLE);
 
 	// Tie managed to unmanaged
 	Reference *ref = Object::cast_to<Reference>(unmanaged);

+ 1 - 1
modules/mono/signal_awaiter_utils.cpp

@@ -117,7 +117,7 @@ void SignalAwaiterHandle::_bind_methods() {
 }
 
 SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) :
-		MonoGCHandle(MonoGCHandle::new_strong_handle(p_managed), STRONG_HANDLE) {
+		gdmono::MonoGCHandle(gdmono::MonoGCHandle::new_strong_handle(p_managed), STRONG_HANDLE) {
 #ifdef DEBUG_ENABLED
 	conn_target_id = 0;
 #endif

+ 2 - 2
modules/mono/signal_awaiter_utils.h

@@ -39,8 +39,8 @@ namespace SignalAwaiterUtils {
 Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p_target, MonoObject *p_awaiter);
 }
 
-class SignalAwaiterHandle : public MonoGCHandle {
-	GDCLASS(SignalAwaiterHandle, MonoGCHandle);
+class SignalAwaiterHandle : public gdmono::MonoGCHandle {
+	GDCLASS(SignalAwaiterHandle, gdmono::MonoGCHandle);
 
 	bool completed;
 

+ 14 - 1
scene/2d/physics_body_2d.cpp

@@ -60,6 +60,19 @@ void PhysicsBody2D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", 0), "_set_layers", "_get_layers"); //for backwards compat
 }
 
+String PhysicsBody2D::get_configuration_warning() const {
+	String warning = CollisionObject2D::get_configuration_warning();
+
+	if (!is_physics_interpolated()) {
+		if (!warning.empty()) {
+			warning += "\n\n";
+		}
+		warning += TTR("PhysicsBody2D will not work correctly on a non-interpolated branch of the SceneTree.\nCheck the node's inherited physics_interpolation_mode.");
+	}
+
+	return warning;
+}
+
 PhysicsBody2D::PhysicsBody2D(Physics2DServer::BodyMode p_mode) :
 		CollisionObject2D(RID_PRIME(Physics2DServer::get_singleton()->body_create()), false) {
 	Physics2DServer::get_singleton()->body_set_mode(get_rid(), p_mode);
@@ -808,7 +821,7 @@ void RigidBody2D::_notification(int p_what) {
 String RigidBody2D::get_configuration_warning() const {
 	Transform2D t = get_transform();
 
-	String warning = CollisionObject2D::get_configuration_warning();
+	String warning = PhysicsBody2D::get_configuration_warning();
 
 	if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) {
 		if (warning != String()) {

+ 2 - 0
scene/2d/physics_body_2d.h

@@ -51,6 +51,8 @@ protected:
 	static void _bind_methods();
 
 public:
+	virtual String get_configuration_warning() const;
+
 	Array get_collision_exceptions();
 	void add_collision_exception_with(Node *p_node); //must be physicsbody
 	void remove_collision_exception_with(Node *p_node);

+ 8 - 3
scene/3d/light.cpp

@@ -365,12 +365,17 @@ bool DirectionalLight::is_blend_splits_enabled() const {
 
 void DirectionalLight::_validate_property(PropertyInfo &property) const {
 	if (shadow_mode == SHADOW_ORTHOGONAL && (property.name == "directional_shadow_split_1" || property.name == "directional_shadow_blend_splits" || property.name == "directional_shadow_bias_split_scale")) {
-		// Split 2, split blending and bias split scale are only used with the PSSM 2 Splits and PSSM 4 Splits shadow modes.
+		// Splits 2/3/4, split blending and bias split scale are only used with the PSSM 2 Splits, PSSM 3 Splits and PSSM 4 Splits shadow modes.
 		property.usage = PROPERTY_USAGE_NOEDITOR;
 	}
 
-	if ((shadow_mode == SHADOW_ORTHOGONAL || shadow_mode == SHADOW_PARALLEL_2_SPLITS) && (property.name == "directional_shadow_split_2" || property.name == "directional_shadow_split_3")) {
-		// Splits 3 and 4 are only used with the PSSM 4 Splits shadow mode.
+	if ((shadow_mode == SHADOW_ORTHOGONAL || shadow_mode == SHADOW_PARALLEL_2_SPLITS) && (property.name == "directional_shadow_split_2")) {
+		// Splits 3/4 are only used with the PSSM 3 Splits and PSSM 4 Splits shadow modes.
+		property.usage = PROPERTY_USAGE_NOEDITOR;
+	}
+
+	if ((shadow_mode == SHADOW_ORTHOGONAL || shadow_mode == SHADOW_PARALLEL_2_SPLITS || shadow_mode == SHADOW_PARALLEL_3_SPLITS) && (property.name == "directional_shadow_split_3")) {
+		// Split 4 is only used with the PSSM 4 Splits shadow mode.
 		property.usage = PROPERTY_USAGE_NOEDITOR;
 	}
 

+ 20 - 2
scene/3d/physics_body.cpp

@@ -98,6 +98,19 @@ void PhysicsBody::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_get_layers"), &PhysicsBody::_get_layers);
 }
 
+String PhysicsBody::get_configuration_warning() const {
+	String warning = CollisionObject::get_configuration_warning();
+
+	if (!is_physics_interpolated()) {
+		if (!warning.empty()) {
+			warning += "\n\n";
+		}
+		warning += TTR("PhysicsBody will not work correctly on a non-interpolated branch of the SceneTree.\nCheck the node's inherited physics_interpolation_mode.");
+	}
+
+	return warning;
+}
+
 PhysicsBody::PhysicsBody(PhysicsServer::BodyMode p_mode) :
 		CollisionObject(RID_PRIME(PhysicsServer::get_singleton()->body_create(p_mode)), false) {
 }
@@ -783,7 +796,7 @@ Array RigidBody::get_colliding_bodies() const {
 String RigidBody::get_configuration_warning() const {
 	Transform t = get_transform();
 
-	String warning = CollisionObject::get_configuration_warning();
+	String warning = PhysicsBody::get_configuration_warning();
 
 	if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(2).length() - 1.0) > 0.05)) {
 		if (warning != String()) {
@@ -1083,7 +1096,12 @@ Vector3 KinematicBody::_move_and_slide_internal(const Vector3 &p_linear_velocity
 
 		// We need to check the on_floor_body still exists before accessing.
 		// A valid RID is no guarantee that the object has not been deleted.
-		if (ObjectDB::get_instance(on_floor_body_id)) {
+
+		// We can only perform the ObjectDB lifetime check on Object derived objects.
+		// Note that physics also creates RIDs for non-Object derived objects, these cannot
+		// be lifetime checked through ObjectDB, and therefore there is a still a vulnerability
+		// to dangling RIDs (access after free) in this scenario.
+		if (!on_floor_body_id || ObjectDB::get_instance(on_floor_body_id)) {
 			// This approach makes sure there is less delay between the actual body velocity and the one we saved.
 			bs = PhysicsServer::get_singleton()->body_get_direct_state(on_floor_body_rid);
 		}

+ 2 - 0
scene/3d/physics_body.h

@@ -49,6 +49,8 @@ protected:
 	PhysicsBody(PhysicsServer::BodyMode p_mode);
 
 public:
+	virtual String get_configuration_warning() const;
+
 	virtual Vector3 get_linear_velocity() const;
 	virtual Vector3 get_angular_velocity() const;
 	virtual float get_inverse_mass() const;

+ 6 - 14
scene/gui/popup_menu.cpp

@@ -32,8 +32,6 @@
 #include "core/os/input.h"
 #include "core/os/keyboard.h"
 #include "core/os/os.h"
-#include "core/print_string.h"
-#include "core/translation.h"
 
 String PopupMenu::_get_accel_text(int p_item) const {
 	ERR_FAIL_INDEX_V(p_item, items.size(), String());
@@ -119,8 +117,7 @@ int PopupMenu::_get_items_total_height() const {
 		items_total_height += MAX(items[i].get_icon_size().height, font_height) + vsep;
 	}
 
-	// Subtract a separator which is not needed for the last item.
-	return items_total_height - vsep;
+	return items_total_height;
 }
 
 void PopupMenu::_scroll_to_item(int p_item) {
@@ -149,16 +146,12 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
 	int vseparation = get_constant("vseparation");
 	float font_h = get_font("font")->get_height();
 
-	Point2 ofs = style->get_offset() + Point2(0, vseparation / 2);
+	real_t ofs = style->get_margin(MARGIN_TOP) + control->get_position().y;
 
 	for (int i = 0; i < items.size(); i++) {
-		if (i > 0) {
-			ofs.y += vseparation;
-		}
+		ofs += MAX(items[i].get_icon_size().height, font_h) + vseparation;
 
-		ofs.y += MAX(items[i].get_icon_size().height, font_h);
-
-		if (p_over.y - control->get_position().y < ofs.y) {
+		if (p_over.y < ofs) {
 			return i;
 		}
 	}
@@ -328,7 +321,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
 
 	// Make an area which does not include v scrollbar, so that items are not activated when dragging scrollbar.
 	Transform2D xform = get_global_transform_with_canvas();
-	Point2 item_origin = xform.get_origin();
+	Point2 item_origin = scroll_container->get_global_position();
 	float scroll_width = scroll_container->get_v_scrollbar()->is_visible_in_tree() ? scroll_container->get_v_scrollbar()->get_size().width : 0;
 	Size2 item_size = (control->get_global_rect().get_size() - Vector2(scroll_width, 0)) * xform.get_scale();
 	Rect2 item_clickable_area = Rect2(item_origin, item_size);
@@ -465,7 +458,6 @@ void PopupMenu::_draw_items() {
 	margin_size.width = margin_container->get_constant("margin_right") + margin_container->get_constant("margin_left");
 	margin_size.height = margin_container->get_constant("margin_top") + margin_container->get_constant("margin_bottom");
 
-	Ref<StyleBox> style = get_stylebox("panel");
 	Ref<StyleBox> hover = get_stylebox("hover");
 	Ref<Font> font = get_font("font");
 	select_font(font);
@@ -508,7 +500,7 @@ void PopupMenu::_draw_items() {
 		check_ofs = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()) + hseparation;
 	}
 
-	Point2 ofs = Point2();
+	Point2 ofs = Point2(0, vseparation / 2);
 
 	// Loop through all items and draw each.
 	for (int i = 0; i < items.size(); i++) {

+ 1 - 1
scene/gui/range.cpp

@@ -87,7 +87,7 @@ void Range::set_value(double p_val) {
 
 void Range::set_value_no_signal(double p_val) {
 	if (shared->step > 0) {
-		p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min;
+		p_val = Math::round(p_val / shared->step) * shared->step;
 	}
 
 	if (_rounded_values) {

+ 1 - 1
scene/gui/tree.cpp

@@ -1722,7 +1722,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
 				cache.click_id = c.buttons[j].id;
 				cache.click_item = p_item;
 				cache.click_column = col;
-				cache.click_pos = get_global_mouse_position() - get_global_position();
+				cache.click_pos = get_local_mouse_position();
 				update();
 				//emit_signal("button_pressed");
 				return -1;

+ 2 - 0
scene/main/node.cpp

@@ -225,6 +225,8 @@ void Node::_propagate_physics_interpolated(bool p_interpolated) {
 	// allow a call to the VisualServer etc in derived classes
 	_physics_interpolated_changed();
 
+	update_configuration_warning();
+
 	data.blocked++;
 	for (int i = 0; i < data.children.size(); i++) {
 		data.children[i]->_propagate_physics_interpolated(p_interpolated);

+ 5 - 5
scene/main/scene_tree.cpp

@@ -559,11 +559,6 @@ void SceneTree::iteration_prepare() {
 		// are flushed before pumping the interpolation prev and currents.
 		flush_transform_notifications();
 		VisualServer::get_singleton()->tick();
-
-		// Any objects performing client physics interpolation
-		// should be given an opportunity to keep their previous transforms
-		// up to date before each new physics tick.
-		_client_physics_interpolation.physics_process();
 	}
 }
 
@@ -572,6 +567,11 @@ void SceneTree::iteration_end() {
 	// to be flushed to the VisualServer before finishing a physics tick.
 	if (_physics_interpolation_enabled) {
 		flush_transform_notifications();
+
+		// Any objects performing client physics interpolation
+		// should be given an opportunity to keep their previous transforms
+		// up to date.
+		_client_physics_interpolation.physics_process();
 	}
 }
 

+ 1 - 0
scene/resources/default_theme/default_theme.cpp

@@ -230,6 +230,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	theme->set_stylebox("normal", "Button", sb_button_normal);
 	theme->set_stylebox("pressed", "Button", sb_button_pressed);
 	theme->set_stylebox("hover", "Button", sb_button_hover);
+	theme->set_stylebox("hover_pressed", "Button", sb_button_hover);
 	theme->set_stylebox("disabled", "Button", sb_button_disabled);
 	theme->set_stylebox("focus", "Button", sb_button_focus);