diff options
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); |