summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/classes/Vector2.xml2
-rw-r--r--doc/classes/Vector3.xml2
-rw-r--r--doc/classes/Vector4.xml2
-rw-r--r--editor/connections_dialog.cpp20
-rw-r--r--editor/editor_inspector.cpp2
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/gui/scene_tree_editor.cpp2
-rw-r--r--editor/plugins/control_editor_plugin.cpp6
-rw-r--r--editor/scene_tree_dock.cpp2
-rw-r--r--modules/gdscript/gdscript_parser.cpp36
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out4
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml17
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt (renamed from platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt)44
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt136
-rw-r--r--scene/property_utils.cpp20
-rw-r--r--scene/property_utils.h2
-rw-r--r--scene/resources/packed_scene.cpp2
-rw-r--r--tests/scene/test_node.h73
20 files changed, 251 insertions, 131 deletions
diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml
index 5fadfd0d95..a7df54e880 100644
--- a/doc/classes/Vector2.xml
+++ b/doc/classes/Vector2.xml
@@ -324,7 +324,7 @@
<method name="normalized" qualifiers="const">
<return type="Vector2" />
<description>
- Returns the result of scaling the vector to unit length. Equivalent to [code]v / v.length()[/code]. See also [method is_normalized].
+ Returns the result of scaling the vector to unit length. Equivalent to [code]v / v.length()[/code]. Returns [code](0, 0)[/code] if [code]v.length() == 0[/code]. See also [method is_normalized].
[b]Note:[/b] This function may return incorrect values if the input vector length is near zero.
</description>
</method>
diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml
index 387359a97f..1692ba3ece 100644
--- a/doc/classes/Vector3.xml
+++ b/doc/classes/Vector3.xml
@@ -293,7 +293,7 @@
<method name="normalized" qualifiers="const">
<return type="Vector3" />
<description>
- Returns the result of scaling the vector to unit length. Equivalent to [code]v / v.length()[/code]. See also [method is_normalized].
+ Returns the result of scaling the vector to unit length. Equivalent to [code]v / v.length()[/code]. Returns [code](0, 0, 0)[/code] if [code]v.length() == 0[/code]. See also [method is_normalized].
[b]Note:[/b] This function may return incorrect values if the input vector length is near zero.
</description>
</method>
diff --git a/doc/classes/Vector4.xml b/doc/classes/Vector4.xml
index 87af370462..f70c59fbef 100644
--- a/doc/classes/Vector4.xml
+++ b/doc/classes/Vector4.xml
@@ -227,7 +227,7 @@
<method name="normalized" qualifiers="const">
<return type="Vector4" />
<description>
- Returns the result of scaling the vector to unit length. Equivalent to [code]v / v.length()[/code]. See also [method is_normalized].
+ Returns the result of scaling the vector to unit length. Equivalent to [code]v / v.length()[/code]. Returns [code](0, 0, 0, 0)[/code] if [code]v.length() == 0[/code]. See also [method is_normalized].
[b]Note:[/b] This function may return incorrect values if the input vector length is near zero.
</description>
</method>
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 4f669c774b..1145a10f71 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -281,20 +281,32 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me
bool check_signal = compatible_methods_only->is_pressed();
List<MethodInfo> ret;
+ List<Pair<Variant::Type, StringName>> effective_args;
+ int unbind = get_unbinds();
+ for (int i = 0; i < p_signal.arguments.size() - unbind; i++) {
+ PropertyInfo pi = p_signal.arguments.get(i);
+ effective_args.push_back(Pair(pi.type, pi.class_name));
+ }
+ if (unbind == 0) {
+ for (const Variant &variant : get_binds()) {
+ effective_args.push_back(Pair(variant.get_type(), StringName()));
+ }
+ }
+
for (const MethodInfo &mi : p_methods) {
if (!p_search_string.is_empty() && !mi.name.containsn(p_search_string)) {
continue;
}
if (check_signal) {
- if (mi.arguments.size() != p_signal.arguments.size()) {
+ if (mi.arguments.size() != effective_args.size()) {
continue;
}
bool type_mismatch = false;
- const List<PropertyInfo>::Element *E = p_signal.arguments.front();
+ const List<Pair<Variant::Type, StringName>>::Element *E = effective_args.front();
for (const List<PropertyInfo>::Element *F = mi.arguments.front(); F; F = F->next(), E = E->next()) {
- Variant::Type stype = E->get().type;
+ Variant::Type stype = E->get().first;
Variant::Type mtype = F->get().type;
if (stype != Variant::NIL && mtype != Variant::NIL && stype != mtype) {
@@ -302,7 +314,7 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me
break;
}
- if (stype == Variant::OBJECT && mtype == Variant::OBJECT && !ClassDB::is_parent_class(E->get().class_name, F->get().class_name)) {
+ if (stype == Variant::OBJECT && mtype == Variant::OBJECT && !ClassDB::is_parent_class(E->get().second, F->get().class_name)) {
type_mismatch = true;
break;
}
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index c1ee2ef0e0..80e2302e91 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -480,7 +480,7 @@ bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringNam
return false;
}
Variant current_value = p_custom_current_value ? *p_custom_current_value : p_object->get(p_property);
- return PropertyUtils::is_property_value_different(current_value, revert_value);
+ return PropertyUtils::is_property_value_different(p_object, current_value, revert_value);
}
StringName EditorProperty::_get_revert_property() const {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 2570e19b48..ebdad467ff 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4118,7 +4118,7 @@ HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *
Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_node, E.name, &is_valid_revert);
Variant current_value = p_node->get(E.name);
if (is_valid_revert) {
- if (PropertyUtils::is_property_value_different(current_value, revert_value)) {
+ if (PropertyUtils::is_property_value_different(p_node, current_value, revert_value)) {
// If this property is a direct node reference, save a NodePath instead to prevent corrupted references.
if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
Node *target_node = Object::cast_to<Node>(current_value);
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index ddf22c46e6..7e2d6c9d1e 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -291,7 +291,7 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) {
const PackedStringArray warnings = p_node->get_configuration_warnings();
const int num_warnings = warnings.size();
if (num_warnings > 0) {
- String warning_icon;
+ StringName warning_icon;
if (num_warnings == 1) {
warning_icon = SNAME("NodeWarning");
} else if (num_warnings <= 3) {
diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp
index a3804eff00..3479d22267 100644
--- a/editor/plugins/control_editor_plugin.cpp
+++ b/editor/plugins/control_editor_plugin.cpp
@@ -360,9 +360,9 @@ void EditorPropertySizeFlags::setup(const Vector<String> &p_options, bool p_vert
}
Control *gui_base = EditorNode::get_singleton()->get_gui_base();
- String wide_preset_icon = SNAME("ControlAlignHCenterWide");
- String begin_preset_icon = SNAME("ControlAlignCenterLeft");
- String end_preset_icon = SNAME("ControlAlignCenterRight");
+ StringName wide_preset_icon = SNAME("ControlAlignHCenterWide");
+ StringName begin_preset_icon = SNAME("ControlAlignCenterLeft");
+ StringName end_preset_icon = SNAME("ControlAlignCenterRight");
if (vertical) {
wide_preset_icon = SNAME("ControlAlignVCenterWide");
begin_preset_icon = SNAME("ControlAlignCenterTop");
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 23588e0e6c..2f57dc5610 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -4171,7 +4171,7 @@ void SceneTreeDock::_create_remap_for_node(Node *p_node, HashMap<Ref<Resource>,
bool is_valid_default = false;
Variant orig = PropertyUtils::get_property_default_value(p_node, E.name, &is_valid_default, &states_stack);
- if (is_valid_default && !PropertyUtils::is_property_value_different(v, orig)) {
+ if (is_valid_default && !PropertyUtils::is_property_value_different(p_node, v, orig)) {
continue;
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 9a4c92f601..771ccf47b7 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -4315,39 +4315,55 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
return false;
}
break;
- case GDScriptParser::DataType::CLASS:
+ case GDScriptParser::DataType::CLASS: {
+ StringName class_name;
+ if (export_type.class_type) {
+ class_name = export_type.class_type->get_global_name();
+ }
+ if (class_name == StringName()) {
+ push_error(R"(Script export type must be a global class.)", p_annotation);
+ return false;
+ }
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
- variable->export_info.hint_string = export_type.to_string();
+ variable->export_info.hint_string = class_name;
} else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
- variable->export_info.hint_string = export_type.to_string();
+ variable->export_info.hint_string = class_name;
} else {
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
+ } break;
- break;
case GDScriptParser::DataType::SCRIPT: {
StringName class_name;
- StringName native_base;
if (export_type.script_type.is_valid()) {
- class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path());
- native_base = export_type.script_type->get_instance_base_type();
+ class_name = export_type.script_type->get_global_name();
}
if (class_name == StringName()) {
Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script"));
if (script.is_valid()) {
- class_name = script->get_language()->get_global_class_name(export_type.script_path);
- native_base = script->get_instance_base_type();
+ class_name = script->get_global_name();
}
}
- if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) {
+ if (class_name == StringName()) {
+ push_error(R"(Script export type must be a global class.)", p_annotation);
+ return false;
+ }
+ if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
variable->export_info.hint_string = class_name;
+ } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
+ variable->export_info.hint_string = class_name;
+ } else {
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
+ return false;
}
} break;
case GDScriptParser::DataType::ENUM: {
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 8b343de5ef..483e6cab0d 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -1,6 +1,8 @@
+class_name ExportVariableTest
extends Node
const Utils = preload("../../utils.notest.gd")
+const PreloadedScript = preload("./export_variable.notest.gd")
# Built-in types.
@export var test_weak_int = 1
@@ -20,6 +22,10 @@ const Utils = preload("../../utils.notest.gd")
@export var test_image: Image
@export var test_timer: Timer
+# Global custom classes.
+@export var test_global_class: ExportVariableTest
+@export var test_preloaded_script: PreloadedScript
+
# Arrays.
@export var test_array: Array
@export var test_array_bool: Array[bool]
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd
new file mode 100644
index 0000000000..6d064351c1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.notest.gd
@@ -0,0 +1,2 @@
+class_name ExportPreloadedClassTest
+extends Node
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index 99d7b27130..bb094e14b4 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -23,6 +23,10 @@ var test_image: Image = null
hint=RESOURCE_TYPE hint_string="Image" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Image"
var test_timer: Timer = null
hint=NODE_TYPE hint_string="Timer" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"Timer"
+var test_global_class: ExportVariableTest = null
+ hint=NODE_TYPE hint_string="ExportVariableTest" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportVariableTest"
+var test_preloaded_script: ExportPreloadedClassTest = null
+ hint=NODE_TYPE hint_string="ExportPreloadedClassTest" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"ExportPreloadedClassTest"
var test_array: Array = []
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_array_bool: Array = Array[bool]([])
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index 78dcddac0e..f646ef3f51 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -32,13 +32,11 @@
android:requestLegacyExternalStorage="true">
<activity
- android:name=".GodotProjectManager"
+ android:name=".GodotEditor"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:launchMode="singleTask"
android:screenOrientation="userLandscape"
- android:exported="true"
- android:process=":GodotProjectManager">
-
+ android:exported="true">
<layout android:defaultHeight="@dimen/editor_default_window_height"
android:defaultWidth="@dimen/editor_default_window_width" />
@@ -50,17 +48,6 @@
</activity>
<activity
- android:name=".GodotEditor"
- android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
- android:process=":GodotEditor"
- android:launchMode="singleTask"
- android:screenOrientation="userLandscape"
- android:exported="false">
- <layout android:defaultHeight="@dimen/editor_default_window_height"
- android:defaultWidth="@dimen/editor_default_window_width" />
- </activity>
-
- <activity
android:name=".GodotGame"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:label="@string/godot_project_name_string"
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
index 68ca5697f1..0da1d01aed 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* GodotProjectManager.kt */
+/* EditorWindowInfo.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -31,14 +31,38 @@
package org.godotengine.editor
/**
- * Launcher activity for the Godot Android Editor.
- *
- * It presents the user with the project manager interface.
- * Upon selection of a project, this activity (via its parent logic) starts the
- * [GodotEditor] activity.
+ * Specifies the policy for adjacent launches.
*/
-class GodotProjectManager : GodotEditor() {
- override fun checkForProjectPermissionsToEnable() {
- // Nothing to do here.. we have yet to select a project to load.
- }
+enum class LaunchAdjacentPolicy {
+ /**
+ * Adjacent launches are disabled.
+ */
+ DISABLED,
+
+ /**
+ * Adjacent launches are enabled / disabled based on the device and screen metrics.
+ */
+ AUTO,
+
+ /**
+ * Adjacent launches are enabled.
+ */
+ ENABLED
+}
+
+/**
+ * Describe the editor window to launch
+ */
+data class EditorWindowInfo(
+ val windowClassName: String,
+ val windowId: Int,
+ val processNameSuffix: String,
+ val launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
+) {
+ constructor(
+ windowClass: Class<*>,
+ windowId: Int,
+ processNameSuffix: String,
+ launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
+ ) : this(windowClass.name, windowId, processNameSuffix, launchAdjacentPolicy)
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index c9a62d24b7..52acd63674 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -32,12 +32,14 @@ package org.godotengine.editor
import android.Manifest
import android.app.ActivityManager
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.*
import android.util.Log
import android.widget.Toast
+import androidx.annotation.CallSuper
import androidx.window.layout.WindowMetricsCalculator
import org.godotengine.godot.GodotActivity
import org.godotengine.godot.GodotLib
@@ -64,18 +66,15 @@ open class GodotEditor : GodotActivity() {
private const val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
- private const val EDITOR_ID = 777
+ // Command line arguments
private const val EDITOR_ARG = "--editor"
private const val EDITOR_ARG_SHORT = "-e"
- private const val EDITOR_PROCESS_NAME_SUFFIX = ":GodotEditor"
+ private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
+ private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
- private const val GAME_ID = 667
- private const val GAME_PROCESS_NAME_SUFFIX = ":GodotGame"
-
- private const val PROJECT_MANAGER_ID = 555
- private const val PROJECT_MANAGER_ARG = "--project-manager"
- private const val PROJECT_MANAGER_ARG_SHORT = "-p"
- private const val PROJECT_MANAGER_PROCESS_NAME_SUFFIX = ":GodotProjectManager"
+ // Info for the various classes used by the editor
+ internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
+ internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchAdjacentPolicy.AUTO)
/**
* Sets of constants to specify the window to use to run the project.
@@ -96,8 +95,8 @@ open class GodotEditor : GodotActivity() {
PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
- Log.d(TAG, "Received parameters ${params.contentToString()}")
- updateCommandLineParams(params)
+ Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}")
+ updateCommandLineParams(params?.asList() ?: emptyList())
if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) {
Debug.waitForDebugger()
@@ -133,98 +132,85 @@ open class GodotEditor : GodotActivity() {
}
}
- private fun updateCommandLineParams(args: Array<String>?) {
+ @CallSuper
+ protected open fun updateCommandLineParams(args: List<String>) {
// Update the list of command line params with the new args
commandLineParams.clear()
- if (!args.isNullOrEmpty()) {
- commandLineParams.addAll(listOf(*args))
+ if (args.isNotEmpty()) {
+ commandLineParams.addAll(args)
}
if (BuildConfig.BUILD_TYPE == "dev") {
commandLineParams.add("--benchmark")
}
}
- override fun getCommandLine() = commandLineParams
+ final override fun getCommandLine() = commandLineParams
- override fun onNewGodotInstanceRequested(args: Array<String>): Int {
- // Parse the arguments to figure out which activity to start.
- var targetClass: Class<*> = GodotGame::class.java
- var instanceId = GAME_ID
-
- // Whether we should launch the new godot instance in an adjacent window
- // https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
- var launchAdjacent = shouldGameLaunchAdjacent()
-
- for (arg in args) {
- if (EDITOR_ARG == arg || EDITOR_ARG_SHORT == arg) {
- targetClass = GodotEditor::class.java
- launchAdjacent = false
- instanceId = EDITOR_ID
- break
- }
+ protected open fun getEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+ var hasEditor = false
- if (PROJECT_MANAGER_ARG == arg || PROJECT_MANAGER_ARG_SHORT == arg) {
- targetClass = GodotProjectManager::class.java
- launchAdjacent = false
- instanceId = PROJECT_MANAGER_ID
- break
+ var i = 0
+ while (i < args.size) {
+ when (args[i++]) {
+ EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true
}
}
+ return if (hasEditor) {
+ EDITOR_MAIN_INFO
+ } else {
+ RUN_GAME_INFO
+ }
+ }
+
+ protected open fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? {
+ return when (instanceId) {
+ RUN_GAME_INFO.windowId -> RUN_GAME_INFO
+ EDITOR_MAIN_INFO.windowId -> EDITOR_MAIN_INFO
+ else -> null
+ }
+ }
+
+ override fun onNewGodotInstanceRequested(args: Array<String>): Int {
+ val editorWindowInfo = getEditorWindowInfo(args)
+
// Launch a new activity
- val newInstance = Intent(this, targetClass)
+ val newInstance = Intent()
+ .setComponent(ComponentName(this, editorWindowInfo.windowClassName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_COMMAND_LINE_PARAMS, args)
- if (launchAdjacent) {
- newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.ENABLED ||
+ (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.AUTO && shouldGameLaunchAdjacent())) {
+ Log.v(TAG, "Adding flag for adjacent launch")
+ newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
+ }
}
- if (targetClass == javaClass) {
- Log.d(TAG, "Restarting $targetClass with parameters ${args.contentToString()}")
+ if (editorWindowInfo.windowClassName == javaClass.name) {
+ Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
ProcessPhoenix.triggerRebirth(this, newInstance)
} else {
- Log.d(TAG, "Starting $targetClass with parameters ${args.contentToString()}")
+ Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
newInstance.putExtra(EXTRA_NEW_LAUNCH, true)
startActivity(newInstance)
}
- return instanceId
+ return editorWindowInfo.windowId
}
- override fun onGodotForceQuit(godotInstanceId: Int): Boolean {
- val targetClass: Class<*>?
- val processNameSuffix: String
- when (godotInstanceId) {
- GAME_ID -> {
- processNameSuffix = GAME_PROCESS_NAME_SUFFIX
- targetClass = GodotGame::class.java
- }
- EDITOR_ID -> {
- processNameSuffix = EDITOR_PROCESS_NAME_SUFFIX
- targetClass = GodotEditor::class.java
- }
- PROJECT_MANAGER_ID -> {
- processNameSuffix = PROJECT_MANAGER_PROCESS_NAME_SUFFIX
- targetClass = GodotProjectManager::class.java
- }
- else -> {
- processNameSuffix = ""
- targetClass = null
- }
- }
+ final override fun onGodotForceQuit(godotInstanceId: Int): Boolean {
+ val editorWindowInfo = getEditorWindowInfoForInstanceId(godotInstanceId) ?: return super.onGodotForceQuit(godotInstanceId)
- if (targetClass == javaClass) {
- Log.d(TAG, "Force quitting $targetClass")
+ if (editorWindowInfo.windowClassName == javaClass.name) {
+ Log.d(TAG, "Force quitting ${editorWindowInfo.windowClassName}")
ProcessPhoenix.forceQuit(this)
return true
}
- if (processNameSuffix.isBlank()) {
- return false
- }
-
+ val processName = packageName + editorWindowInfo.processNameSuffix
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningProcesses = activityManager.runningAppProcesses
for (runningProcess in runningProcesses) {
- if (runningProcess.processName.endsWith(processNameSuffix)) {
+ if (runningProcess.processName == processName) {
// Killing process directly
Log.v(TAG, "Killing Godot process ${runningProcess.processName}")
Process.killProcess(runningProcess.pid)
@@ -232,11 +218,11 @@ open class GodotEditor : GodotActivity() {
}
}
- return false
+ return super.onGodotForceQuit(godotInstanceId)
}
// Get the screen's density scale
- protected val isLargeScreen: Boolean
+ private val isLargeScreen: Boolean
// Get the minimum window size // Correspond to the EXPANDED window size class.
get() {
val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
@@ -273,6 +259,10 @@ open class GodotEditor : GodotActivity() {
protected open fun enablePanAndScaleGestures() =
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures"))
+ /**
+ * Whether we should launch the new godot instance in an adjacent window
+ * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
+ */
private fun shouldGameLaunchAdjacent(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp
index be58f1c1e1..db17f9d643 100644
--- a/scene/property_utils.cpp
+++ b/scene/property_utils.cpp
@@ -39,16 +39,22 @@
#include "editor/editor_node.h"
#endif // TOOLS_ENABLED
-bool PropertyUtils::is_property_value_different(const Variant &p_a, const Variant &p_b) {
+bool PropertyUtils::is_property_value_different(const Object *p_object, const Variant &p_a, const Variant &p_b) {
if (p_a.get_type() == Variant::FLOAT && p_b.get_type() == Variant::FLOAT) {
- //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error
+ // This must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error.
return !Math::is_equal_approx((float)p_a, (float)p_b);
- } else {
- // For our purposes, treating null object as NIL is the right thing to do
- const Variant &a = p_a.get_type() == Variant::OBJECT && (Object *)p_a == nullptr ? Variant() : p_a;
- const Variant &b = p_b.get_type() == Variant::OBJECT && (Object *)p_b == nullptr ? Variant() : p_b;
- return a != b;
+ } else if (p_a.get_type() == Variant::NODE_PATH && p_b.get_type() == Variant::OBJECT) {
+ const Node *base_node = Object::cast_to<Node>(p_object);
+ const Node *target_node = Object::cast_to<Node>(p_b);
+ if (base_node && target_node) {
+ return p_a != base_node->get_path_to(target_node);
+ }
}
+
+ // For our purposes, treating null object as NIL is the right thing to do
+ const Variant &a = p_a.get_type() == Variant::OBJECT && (Object *)p_a == nullptr ? Variant() : p_a;
+ const Variant &b = p_b.get_type() == Variant::OBJECT && (Object *)p_b == nullptr ? Variant() : p_b;
+ return a != b;
}
Variant PropertyUtils::get_property_default_value(const Object *p_object, const StringName &p_property, bool *r_is_valid, const Vector<SceneState::PackState> *p_states_stack_cache, bool p_update_exports, const Node *p_owner, bool *r_is_class_default) {
diff --git a/scene/property_utils.h b/scene/property_utils.h
index e934792e34..0bf040ad26 100644
--- a/scene/property_utils.h
+++ b/scene/property_utils.h
@@ -36,7 +36,7 @@
class PropertyUtils {
public:
- static bool is_property_value_different(const Variant &p_a, const Variant &p_b);
+ static bool is_property_value_different(const Object *p_object, const Variant &p_a, const Variant &p_b);
// Gets the most pure default value, the one that would be set when the node has just been instantiated
static Variant get_property_default_value(const Object *p_object, const StringName &p_property, bool *r_is_valid = nullptr, const Vector<SceneState::PackState> *p_states_stack_cache = nullptr, bool p_update_exports = false, const Node *p_owner = nullptr, bool *r_is_class_default = nullptr);
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 0c57c6b7ba..b1742bd5a3 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -816,7 +816,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
bool is_valid_default = false;
Variant default_value = PropertyUtils::get_property_default_value(p_node, name, &is_valid_default, &states_stack, true);
- if (is_valid_default && !PropertyUtils::is_property_value_different(value, default_value)) {
+ if (is_valid_default && !PropertyUtils::is_property_value_different(p_node, value, default_value)) {
if (value.get_type() == Variant::ARRAY && has_local_resource(value)) {
// Save anyway
} else if (value.get_type() == Variant::DICTIONARY) {
diff --git a/tests/scene/test_node.h b/tests/scene/test_node.h
index b3362b02a8..2b14be76e2 100644
--- a/tests/scene/test_node.h
+++ b/tests/scene/test_node.h
@@ -31,7 +31,9 @@
#ifndef TEST_NODE_H
#define TEST_NODE_H
+#include "core/object/class_db.h"
#include "scene/main/node.h"
+#include "scene/resources/packed_scene.h"
#include "tests/test_macros.h"
@@ -62,6 +64,12 @@ protected:
}
}
+ static void _bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_exported_node", "node"), &TestNode::set_exported_node);
+ ClassDB::bind_method(D_METHOD("get_exported_node"), &TestNode::get_exported_node);
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "exported_node", PROPERTY_HINT_NODE_TYPE, "Node"), "set_exported_node", "get_exported_node");
+ }
+
private:
void push_self() {
if (callback_list) {
@@ -75,7 +83,12 @@ public:
int process_counter = 0;
int physics_process_counter = 0;
+ Node *exported_node = nullptr;
+
List<Node *> *callback_list = nullptr;
+
+ void set_exported_node(Node *p_node) { exported_node = p_node; }
+ Node *get_exported_node() const { return exported_node; }
};
TEST_CASE("[SceneTree][Node] Testing node operations with a very simple scene tree") {
@@ -478,6 +491,66 @@ TEST_CASE("[SceneTree][Node] Testing node operations with a more complex simple
memdelete(node2);
}
+TEST_CASE("[SceneTree][Node]Exported node checks") {
+ TestNode *node = memnew(TestNode);
+ SceneTree::get_singleton()->get_root()->add_child(node);
+
+ Node *child = memnew(Node);
+ child->set_name("Child");
+ node->add_child(child);
+ child->set_owner(node);
+
+ node->set("exported_node", child);
+
+ SUBCASE("Property of duplicated node should point to duplicated child") {
+ GDREGISTER_CLASS(TestNode);
+
+ TestNode *dup = Object::cast_to<TestNode>(node->duplicate());
+ Node *new_exported = Object::cast_to<Node>(dup->get("exported_node"));
+ CHECK(new_exported == dup->get_child(0));
+
+ memdelete(dup);
+ }
+
+ SUBCASE("Saving instance with exported node should not store the unchanged property") {
+ node->set_process_mode(Node::PROCESS_MODE_ALWAYS);
+ Ref<PackedScene> ps;
+ ps.instantiate();
+ ps->pack(node);
+
+ String scene_path = OS::get_singleton()->get_cache_path().path_join("test_scene.tscn");
+ ps->set_path(scene_path);
+
+ Node *root = memnew(Node);
+
+ Node *sub_child = ps->instantiate(PackedScene::GEN_EDIT_STATE_MAIN);
+ root->add_child(sub_child);
+ sub_child->set_owner(root);
+
+ Ref<PackedScene> ps2;
+ ps2.instantiate();
+ ps2->pack(root);
+
+ scene_path = OS::get_singleton()->get_cache_path().path_join("new_test_scene.tscn");
+ ResourceSaver::save(ps2, scene_path);
+ memdelete(root);
+
+ bool is_wrong = false;
+ Ref<FileAccess> fa = FileAccess::open(scene_path, FileAccess::READ);
+ while (!fa->eof_reached()) {
+ const String line = fa->get_line();
+ if (line.begins_with("exported_node")) {
+ // The property was saved, while it shouldn't.
+ is_wrong = true;
+ break;
+ }
+ }
+ CHECK_FALSE(is_wrong);
+ }
+
+ memdelete(node);
+}
+
TEST_CASE("[Node] Processing checks") {
Node *node = memnew(Node);