diff options
-rw-r--r-- | .github/workflows/javascript_builds.yml | 2 | ||||
-rw-r--r-- | core/local_vector.h | 2 | ||||
-rw-r--r-- | doc/classes/Button.xml | 12 | ||||
-rw-r--r-- | doc/classes/File.xml | 1 | ||||
-rw-r--r-- | doc/classes/JSON.xml | 3 | ||||
-rw-r--r-- | doc/classes/JSONParseResult.xml | 10 | ||||
-rw-r--r-- | doc/classes/TreeItem.xml | 12 | ||||
-rwxr-xr-x | doc/tools/makerst.py | 118 | ||||
-rw-r--r-- | editor/editor_help.cpp | 49 | ||||
-rw-r--r-- | editor/editor_settings.cpp | 2 | ||||
-rw-r--r-- | editor/plugins/script_editor_plugin.cpp | 8 | ||||
-rw-r--r-- | editor/plugins/visual_shader_editor_plugin.cpp | 8 | ||||
-rw-r--r-- | modules/gdscript/doc_classes/@GDScript.xml | 25 | ||||
-rw-r--r-- | platform/linuxbsd/display_server_x11.cpp | 214 | ||||
-rw-r--r-- | platform/linuxbsd/display_server_x11.h | 1 | ||||
-rw-r--r-- | platform/linuxbsd/joypad_linux.cpp | 11 | ||||
-rw-r--r-- | scene/main/window.cpp | 4 | ||||
-rw-r--r-- | scene/resources/visual_shader.cpp | 10 | ||||
-rw-r--r-- | scene/resources/visual_shader.h | 1 | ||||
-rw-r--r-- | tests/test_astar.cpp | 409 | ||||
-rw-r--r-- | tests/test_astar.h | 333 | ||||
-rw-r--r-- | tests/test_color.h | 213 | ||||
-rw-r--r-- | tests/test_main.cpp | 7 |
23 files changed, 812 insertions, 643 deletions
diff --git a/.github/workflows/javascript_builds.yml b/.github/workflows/javascript_builds.yml index 8167a48eae..421906c77f 100644 --- a/.github/workflows/javascript_builds.yml +++ b/.github/workflows/javascript_builds.yml @@ -5,7 +5,7 @@ on: [push, pull_request] env: GODOT_BASE_BRANCH: master SCONS_CACHE_LIMIT: 4096 - EM_VERSION: latest + EM_VERSION: 1.39.20 EM_CACHE_FOLDER: 'emsdk-cache' jobs: diff --git a/core/local_vector.h b/core/local_vector.h index d97f3330dc..b0dbd22b29 100644 --- a/core/local_vector.h +++ b/core/local_vector.h @@ -73,10 +73,10 @@ public: void remove(U p_index) { ERR_FAIL_UNSIGNED_INDEX(p_index, count); + count--; for (U i = p_index; i < count; i++) { data[i] = data[i + 1]; } - count--; if (!__has_trivial_destructor(T) && !force_trivial) { data[count].~T(); } diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml index 675441d842..de05cfcd13 100644 --- a/doc/classes/Button.xml +++ b/doc/classes/Button.xml @@ -5,6 +5,18 @@ </brief_description> <description> Button is the standard themed button. It can contain text and an icon, and will display them according to the current [Theme]. + [b]Example of creating a button and assigning an action when pressed by code:[/b] + [codeblock] + func _ready(): + var button = Button.new() + button.text = "Click me" + button.connect("pressed", self, "_button_pressed") + add_child(button) + + func _button_pressed(): + print("Hello world!") + [/codeblock] + Buttons (like all Control nodes) can also be created in the editor, but some situations may require creating them from code. </description> <tutorials> </tutorials> diff --git a/doc/classes/File.xml b/doc/classes/File.xml index 20bc39ef1f..d91203d91f 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -257,6 +257,7 @@ </argument> <description> Opens an encrypted file in write or read mode. You need to pass a binary key to encrypt/decrypt it. + [b]Note:[/b] The provided key must be 32 bytes long. </description> </method> <method name="open_encrypted_with_pass"> diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml index 7bd2edcb1c..2e837dea1d 100644 --- a/doc/classes/JSON.xml +++ b/doc/classes/JSON.xml @@ -15,7 +15,7 @@ <argument index="0" name="json" type="String"> </argument> <description> - Parses a JSON encoded string and returns a [JSONParseResult] containing the result. + Parses a JSON-encoded string and returns a [JSONParseResult] containing the result. </description> </method> <method name="print"> @@ -29,6 +29,7 @@ </argument> <description> Converts a [Variant] var to JSON text and returns the result. Useful for serializing data to store or send over the network. + [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, converting a Variant to JSON text will convert all numerical values to [float] types. </description> </method> </methods> diff --git a/doc/classes/JSONParseResult.xml b/doc/classes/JSONParseResult.xml index 4444e08593..4dbceb35e9 100644 --- a/doc/classes/JSONParseResult.xml +++ b/doc/classes/JSONParseResult.xml @@ -15,21 +15,21 @@ The error type if the JSON source was not successfully parsed. See the [enum Error] constants. </member> <member name="error_line" type="int" setter="set_error_line" getter="get_error_line" default="-1"> - The line number where the error occurred if JSON source was not successfully parsed. + The line number where the error occurred if the JSON source was not successfully parsed. </member> <member name="error_string" type="String" setter="set_error_string" getter="get_error_string" default=""""> - The error message if JSON source was not successfully parsed. See the [enum Error] constants. + The error message if the JSON source was not successfully parsed. See the [enum Error] constants. </member> <member name="result" type="Variant" setter="set_result" getter="get_result"> - A [Variant] containing the parsed JSON. Use [method @GDScript.typeof] or the [code]is[/code] keyword to check if it is what you expect. For example, if the JSON source starts with curly braces ([code]{}[/code]), a [Dictionary] will be returned. If the JSON source starts with braces ([code][][/code]), an [Array] will be returned. - [b]Note:[/b] The JSON specification does not define integer or float types, but only a number type. Therefore, parsing a JSON text will convert all numerical values to float types. + A [Variant] containing the parsed JSON. Use [method @GDScript.typeof] or the [code]is[/code] keyword to check if it is what you expect. For example, if the JSON source starts with curly braces ([code]{}[/code]), a [Dictionary] will be returned. If the JSON source starts with brackets ([code][][/code]), an [Array] will be returned. + [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, parsing a JSON text will convert all numerical values to [float] types. [b]Note:[/b] JSON objects do not preserve key order like Godot dictionaries, thus, you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements: [codeblock] var p = JSON.parse('["hello", "world", "!"]') if typeof(p.result) == TYPE_ARRAY: print(p.result[0]) # Prints "hello" else: - print("unexpected results") + push_error("Unexpected results.") [/codeblock] </member> </members> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 126d6b4180..22e643a51d 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -118,7 +118,7 @@ <return type="TreeItem"> </return> <description> - Returns the TreeItem's child items. + Returns the TreeItem's first child item or a null object if there is none. </description> </method> <method name="get_custom_bg_color" qualifiers="const"> @@ -196,7 +196,7 @@ <return type="TreeItem"> </return> <description> - Returns the next TreeItem in the tree. + Returns the next TreeItem in the tree or a null object if there is none. </description> </method> <method name="get_next_visible"> @@ -205,7 +205,7 @@ <argument index="0" name="wrap" type="bool" default="false"> </argument> <description> - Returns the next visible TreeItem in the tree. + Returns the next visible TreeItem in the tree or a null object if there is none. If [code]wrap[/code] is enabled, the method will wrap around to the first visible element in the tree when called on the last visible element, otherwise it returns [code]null[/code]. </description> </method> @@ -213,14 +213,14 @@ <return type="TreeItem"> </return> <description> - Returns the parent TreeItem. + Returns the parent TreeItem or a null object if there is none. </description> </method> <method name="get_prev"> <return type="TreeItem"> </return> <description> - Returns the previous TreeItem in the tree. + Returns the previous TreeItem in the tree or a null object if there is none. </description> </method> <method name="get_prev_visible"> @@ -229,7 +229,7 @@ <argument index="0" name="wrap" type="bool" default="false"> </argument> <description> - Returns the previous visible TreeItem in the tree. + Returns the previous visible TreeItem in the tree or a null object if there is none. If [code]wrap[/code] is enabled, the method will wrap around to the last visible element in the tree when called on the first visible element, otherwise it returns [code]null[/code]. </description> </method> diff --git a/doc/tools/makerst.py b/doc/tools/makerst.py index c0bba38799..b594f652b3 100755 --- a/doc/tools/makerst.py +++ b/doc/tools/makerst.py @@ -565,6 +565,8 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S index += 1 + f.write(make_footer()) + def escape_rst(text, until_pos=-1): # type: (str) -> str # Escape \ character, otherwise it ends up as an escape character in rst @@ -600,6 +602,43 @@ def escape_rst(text, until_pos=-1): # type: (str) -> str return text +def format_codeblock(code_type, post_text, indent_level, state): # types: str, str, int, state + end_pos = post_text.find("[/" + code_type + "]") + if end_pos == -1: + print_error("[" + code_type + "] without a closing tag, file: {}".format(state.current_class), state) + return None + + code_text = post_text[len("[" + code_type + "]") : end_pos] + post_text = post_text[end_pos:] + + # Remove extraneous tabs + code_pos = 0 + while True: + code_pos = code_text.find("\n", code_pos) + if code_pos == -1: + break + + to_skip = 0 + while code_pos + to_skip + 1 < len(code_text) and code_text[code_pos + to_skip + 1] == "\t": + to_skip += 1 + + if to_skip > indent_level: + print_error( + "Four spaces should be used for indentation within [" + + code_type + + "], file: {}".format(state.current_class), + state, + ) + + if len(code_text[code_pos + to_skip + 1 :]) == 0: + code_text = code_text[:code_pos] + "\n" + code_pos += 1 + else: + code_text = code_text[:code_pos] + "\n " + code_text[code_pos + to_skip + 1 :] + code_pos += 5 - to_skip + return ["\n[" + code_type + "]" + code_text + post_text, len("\n[" + code_type + "]" + code_text)] + + def rstize_text(text, state): # type: (str, State) -> str # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock" pos = 0 @@ -616,43 +655,17 @@ def rstize_text(text, state): # type: (str, State) -> str post_text = text[pos + 1 :] # Handle codeblocks - if post_text.startswith("[codeblock]"): - end_pos = post_text.find("[/codeblock]") - if end_pos == -1: - print_error("[codeblock] without a closing tag, file: {}".format(state.current_class), state) + if ( + post_text.startswith("[codeblock]") + or post_text.startswith("[gdscript]") + or post_text.startswith("[csharp]") + ): + block_type = post_text[1:].split("]")[0] + result = format_codeblock(block_type, post_text, indent_level, state) + if result is None: return "" - - code_text = post_text[len("[codeblock]") : end_pos] - post_text = post_text[end_pos:] - - # Remove extraneous tabs - code_pos = 0 - while True: - code_pos = code_text.find("\n", code_pos) - if code_pos == -1: - break - - to_skip = 0 - while code_pos + to_skip + 1 < len(code_text) and code_text[code_pos + to_skip + 1] == "\t": - to_skip += 1 - - if to_skip > indent_level: - print_error( - "Four spaces should be used for indentation within [codeblock], file: {}".format( - state.current_class - ), - state, - ) - - if len(code_text[code_pos + to_skip + 1 :]) == 0: - code_text = code_text[:code_pos] + "\n" - code_pos += 1 - else: - code_text = code_text[:code_pos] + "\n " + code_text[code_pos + to_skip + 1 :] - code_pos += 5 - to_skip - - text = pre_text + "\n[codeblock]" + code_text + post_text - pos += len("\n[codeblock]" + code_text) + text = pre_text + result[0] + pos += result[1] # Handle normal text else: @@ -697,7 +710,7 @@ def rstize_text(text, state): # type: (str, State) -> str else: # command cmd = tag_text space_pos = tag_text.find(" ") - if cmd == "/codeblock": + if cmd == "/codeblock" or cmd == "/gdscript" or cmd == "/csharp": tag_text = "" tag_depth -= 1 inside_code = False @@ -813,6 +826,20 @@ def rstize_text(text, state): # type: (str, State) -> str tag_depth += 1 tag_text = "\n::\n" inside_code = True + elif cmd == "gdscript": + tag_depth += 1 + tag_text = "\n .. code-tab:: gdscript GDScript\n" + inside_code = True + elif cmd == "csharp": + tag_depth += 1 + tag_text = "\n .. code-tab:: csharp\n" + inside_code = True + elif cmd == "codeblocks": + tag_depth += 1 + tag_text = "\n.. tabs::" + elif cmd == "/codeblocks": + tag_depth -= 1 + tag_text = "" elif cmd == "br": # Make a new paragraph instead of a linebreak, rst is not so linebreak friendly tag_text = "\n\n" @@ -995,7 +1022,10 @@ def make_method_signature( out += " **)**" if isinstance(method_def, MethodDef) and method_def.qualifiers is not None: - out += " " + method_def.qualifiers + # Use substitutions for abbreviations. This is used to display tooltips on hover. + # See `make_footer()` for descriptions. + for qualifier in method_def.qualifiers.split(): + out += " |" + qualifier + "|" return ret_type, out @@ -1004,6 +1034,18 @@ def make_heading(title, underline): # type: (str, str) -> str return title + "\n" + (underline * len(title)) + "\n\n" +def make_footer(): # type: () -> str + # Generate reusable abbreviation substitutions. + # This way, we avoid bloating the generated rST with duplicate abbreviations. + # fmt: off + return ( + ".. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`\n" + ".. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`\n" + ".. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`" + ) + # fmt: on + + def make_url(link): # type: (str) -> str match = GODOT_DOCS_PATTERN.search(link) if match: diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 2a59aadd61..bce34db740 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1252,6 +1252,55 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt) { String bbcode = p_bbcode.dedent().replace("\t", "").replace("\r", "").strip_edges(); + // Select the correct code examples + switch ((int)EDITOR_GET("text_editor/help/class_reference_examples")) { + case 0: // GDScript + bbcode = bbcode.replace("[gdscript]", "[codeblock]"); + bbcode = bbcode.replace("[/gdscript]", "[/codeblock]"); + + for (int pos = bbcode.find("[csharp]"); pos != -1; pos = bbcode.find("[csharp]")) { + if (bbcode.find("[/csharp]") == -1) { + WARN_PRINT("Unclosed [csharp] block or parse fail in code (search for tag errors)"); + break; + } + + bbcode.erase(pos, bbcode.find("[/csharp]") + 9 - pos); + while (bbcode[pos] == '\n') { + bbcode.erase(pos, 1); + } + } + break; + case 1: // C# + bbcode = bbcode.replace("[csharp]", "[codeblock]"); + bbcode = bbcode.replace("[/csharp]", "[/codeblock]"); + + for (int pos = bbcode.find("[gdscript]"); pos != -1; pos = bbcode.find("[gdscript]")) { + if (bbcode.find("[/gdscript]") == -1) { + WARN_PRINT("Unclosed [gdscript] block or parse fail in code (search for tag errors)"); + break; + } + + bbcode.erase(pos, bbcode.find("[/gdscript]") + 11 - pos); + while (bbcode[pos] == '\n') { + bbcode.erase(pos, 1); + } + } + break; + case 2: // GDScript and C# + bbcode = bbcode.replace("[csharp]", "[b]C#:[/b]\n[codeblock]"); + bbcode = bbcode.replace("[gdscript]", "[b]GDScript:[/b]\n[codeblock]"); + + bbcode = bbcode.replace("[/csharp]", "[/codeblock]"); + bbcode = bbcode.replace("[/gdscript]", "[/codeblock]"); + break; + } + + // Remove codeblocks (they would be printed otherwise) + bbcode = bbcode.replace("[codeblocks]\n", ""); + bbcode = bbcode.replace("\n[/codeblocks]", ""); + bbcode = bbcode.replace("[codeblocks]", ""); + bbcode = bbcode.replace("[/codeblocks]", ""); + // remove extra new lines around code blocks bbcode = bbcode.replace("[codeblock]\n", "[codeblock]"); bbcode = bbcode.replace("\n[/codeblock]", "[/codeblock]"); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 3a1a0d5b01..a3438b3601 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -495,6 +495,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { hints["text_editor/help/help_source_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_source_font_size", PROPERTY_HINT_RANGE, "8,48,1"); _initial_set("text_editor/help/help_title_font_size", 23); hints["text_editor/help/help_title_font_size"] = PropertyInfo(Variant::INT, "text_editor/help/help_title_font_size", PROPERTY_HINT_RANGE, "8,48,1"); + _initial_set("text_editor/help/class_reference_examples", 0); + hints["text_editor/help/class_reference_examples"] = PropertyInfo(Variant::INT, "text_editor/help/class_reference_examples", PROPERTY_HINT_ENUM, "GDScript,C#,GDScript and C#"); /* Editors */ diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 6c7f4eb908..20eef1cebd 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1922,14 +1922,18 @@ void ScriptEditor::_update_script_names() { Vector<String> disambiguated_script_names; Vector<String> full_script_paths; for (int j = 0; j < sedata.size(); j++) { - disambiguated_script_names.append(sedata[j].name); + disambiguated_script_names.append(sedata[j].name.replace("(*)", "")); full_script_paths.append(sedata[j].tooltip); } EditorNode::disambiguate_filenames(full_script_paths, disambiguated_script_names); for (int j = 0; j < sedata.size(); j++) { - sedata.write[j].name = disambiguated_script_names[j]; + if (sedata[j].name.ends_with("(*)")) { + sedata.write[j].name = disambiguated_script_names[j] + "(*)"; + } else { + sedata.write[j].name = disambiguated_script_names[j]; + } } EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 0af2b120bd..53bd1150ec 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -874,6 +874,8 @@ void VisualShaderEditor::_update_graph() { Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); Color function_color = EDITOR_GET("text_editor/highlighting/function_color"); + Color number_color = EDITOR_GET("text_editor/highlighting/number_color"); + Color members_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); expression_box->set_syntax_highlighter(expression_syntax_highlighter); expression_box->add_theme_color_override("background_color", background_color); @@ -884,8 +886,10 @@ void VisualShaderEditor::_update_graph() { expression_box->add_theme_font_override("font", get_theme_font("expression", "EditorFonts")); expression_box->add_theme_color_override("font_color", text_color); + expression_syntax_highlighter->set_number_color(number_color); expression_syntax_highlighter->set_symbol_color(symbol_color); expression_syntax_highlighter->set_function_color(function_color); + expression_syntax_highlighter->set_member_variable_color(members_color); expression_syntax_highlighter->add_color_region("/*", "*/", comment_color, false); expression_syntax_highlighter->add_color_region("//", "", comment_color, true); @@ -1743,6 +1747,8 @@ void VisualShaderEditor::_notification(int p_what) { Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); Color function_color = EDITOR_GET("text_editor/highlighting/function_color"); + Color number_color = EDITOR_GET("text_editor/highlighting/number_color"); + Color members_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); preview_text->add_theme_color_override("background_color", background_color); @@ -1752,8 +1758,10 @@ void VisualShaderEditor::_notification(int p_what) { preview_text->add_theme_font_override("font", get_theme_font("expression", "EditorFonts")); preview_text->add_theme_color_override("font_color", text_color); + syntax_highlighter->set_number_color(number_color); syntax_highlighter->set_symbol_color(symbol_color); syntax_highlighter->set_function_color(function_color); + syntax_highlighter->set_member_variable_color(members_color); syntax_highlighter->clear_color_regions(); syntax_highlighter->add_color_region("/*", "*/", comment_color, false); syntax_highlighter->add_color_region("//", "", comment_color, true); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 1ac47884c0..9e40a69712 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -735,16 +735,17 @@ <argument index="0" name="json" type="String"> </argument> <description> - Parse JSON text to a Variant (use [method typeof] to check if it is what you expect). - Be aware that the JSON specification does not define integer or float types, but only a number type. Therefore, parsing a JSON text will convert all numerical values to [float] types. - Note that JSON objects do not preserve key order like Godot dictionaries, thus you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements: + Parse JSON text to a Variant. (Use [method typeof] to check if the Variant's type is what you expect.) + [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, parsing a JSON text will convert all numerical values to [float] types. + [b]Note:[/b] JSON objects do not preserve key order like Godot dictionaries, thus, you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements: [codeblock] - p = parse_json('["a", "b", "c"]') - if typeof(p) == TYPE_ARRAY: - print(p[0]) # Prints a + var p = JSON.parse('["hello", "world", "!"]') + if typeof(p.result) == TYPE_ARRAY: + print(p.result[0]) # Prints "hello" else: - print("unexpected results") + push_error("Unexpected results.") [/codeblock] + See also [JSON] for an alternative way to parse JSON text. </description> </method> <method name="polar2cartesian"> @@ -1220,12 +1221,16 @@ <argument index="0" name="var" type="Variant"> </argument> <description> - Converts a Variant [code]var[/code] to JSON text and return the result. Useful for serializing data to store or send over the network. + Converts a [Variant] [code]var[/code] to JSON text and return the result. Useful for serializing data to store or send over the network. [codeblock] + # Both numbers below are integers. a = { "a": 1, "b": 2 } b = to_json(a) print(b) # {"a":1, "b":2} + # Both numbers above are floats, even if they display without any decimal places. [/codeblock] + [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, converting a [Variant] to JSON text will convert all numerical values to [float] types. + See also [JSON] for an alternative way to convert a [Variant] to JSON text. </description> </method> <method name="type_exists"> @@ -1268,9 +1273,9 @@ j = to_json([1, 2, 3]) v = validate_json(j) if not v: - print("valid") + print("Valid JSON.") else: - prints("invalid", v) + push_error("Invalid JSON: " + v) [/codeblock] </description> </method> diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 4aec6d256c..d35941bdcd 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -881,6 +881,46 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent } } +// Helper method. Assumes that the window id has already been checked and exists. +void DisplayServerX11::_update_size_hints(WindowID p_window) { + WindowData &wd = windows[p_window]; + WindowMode window_mode = window_get_mode(p_window); + XSizeHints *xsh = XAllocSizeHints(); + + // Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway + xsh->flags |= PPosition | PSize; + xsh->x = wd.position.x; + xsh->y = wd.position.y; + xsh->width = wd.size.width; + xsh->height = wd.size.height; + + if (window_mode == WINDOW_MODE_FULLSCREEN) { + // Do not set any other hints to prevent the window manager from ignoring the fullscreen flags + } else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + // If resizing is disabled, use the forced size + xsh->flags |= PMinSize | PMaxSize; + xsh->min_width = wd.size.x; + xsh->max_width = wd.size.x; + xsh->min_height = wd.size.y; + xsh->max_height = wd.size.y; + } else { + // Otherwise, just respect min_size and max_size + if (wd.min_size != Size2i()) { + xsh->flags |= PMinSize; + xsh->min_width = wd.min_size.x; + xsh->min_height = wd.min_size.y; + } + if (wd.max_size != Size2i()) { + xsh->flags |= PMaxSize; + xsh->max_width = wd.max_size.x; + xsh->max_height = wd.max_size.y; + } + } + + XSetWMNormalHints(x11_display, wd.x11_window, xsh); + XFree(xsh); +} + Point2i DisplayServerX11::window_get_position(WindowID p_window) const { _THREAD_SAFE_METHOD_ @@ -934,25 +974,8 @@ void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_windo } wd.max_size = p_size; - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - - XFlush(x11_display); - } + _update_size_hints(p_window); + XFlush(x11_display); } Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { @@ -976,25 +999,8 @@ void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_windo } wd.min_size = p_size; - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - - XFlush(x11_display); - } + _update_size_hints(p_window); + XFlush(x11_display); } Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { @@ -1027,37 +1033,15 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { int old_w = xwa.width; int old_h = xwa.height; - // If window resizable is disabled we need to update the attributes first - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); + // Update our videomode width and height + wd.size = size; + + // Update the size hints first to make sure the window size can be set + _update_size_hints(p_window); // Resize the window XResizeWindow(x11_display, wd.x11_window, size.x, size.y); - // Update our videomode width and height - wd.size = size; - for (int timeout = 0; timeout < 50; ++timeout) { XSync(x11_display, False); XGetWindowAttributes(x11_display, wd.x11_window, &xwa); @@ -1213,14 +1197,9 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); } - if (p_enabled && window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { + if (p_enabled) { // Set the window as resizable to prevent window managers to ignore the fullscreen state flag. - XSizeHints *xsh; - - xsh = XAllocSizeHints(); - xsh->flags = 0L; - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); + _update_size_hints(p_window); } // Using EWMH -- Extended Window Manager Hints @@ -1248,30 +1227,7 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { if (!p_enabled) { // Reset the non-resizable flags if we un-set these before. - Size2i size = window_get_size(p_window); - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - } - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); + _update_size_hints(p_window); // put back or remove decorations according to the last set borderless state Hints hints; @@ -1329,13 +1285,13 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_FULLSCREEN: { //Remove full-screen + wd.fullscreen = false; + _set_wm_fullscreen(p_window, false); //un-maximize required for always on top bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window); - wd.fullscreen = false; - window_set_position(wd.last_position_before_fs, p_window); if (on_top) { @@ -1381,15 +1337,16 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } break; case WINDOW_MODE_FULLSCREEN: { wd.last_position_before_fs = wd.position; + if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) { _set_wm_maximized(p_window, true); } - _set_wm_fullscreen(p_window, true); + wd.fullscreen = true; + _set_wm_fullscreen(p_window, true); } break; case WINDOW_MODE_MAXIMIZED: { _set_wm_maximized(p_window, true); - } break; } } @@ -1456,37 +1413,11 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - if (p_enabled) { - Size2i size = window_get_size(p_window); - - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = size.x; - xsh->max_width = size.x; - xsh->min_height = size.y; - xsh->max_height = size.y; - } else { - xsh->flags = 0L; - if (wd.min_size != Size2i()) { - xsh->flags |= PMinSize; - xsh->min_width = wd.min_size.x; - xsh->min_height = wd.min_size.y; - } - if (wd.max_size != Size2i()) { - xsh->flags |= PMaxSize; - xsh->max_width = wd.max_size.x; - xsh->max_height = wd.max_size.y; - } - } - - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - wd.resize_disabled = p_enabled; - XFlush(x11_display); + _update_size_hints(p_window); + XFlush(x11_display); } break; case WINDOW_FLAG_BORDERLESS: { Hints hints; @@ -3305,20 +3236,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u windows[id] = wd; { - if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) { - XSizeHints *xsh; - xsh = XAllocSizeHints(); - - xsh->flags = PMinSize | PMaxSize; - xsh->min_width = p_rect.size.width; - xsh->max_width = p_rect.size.width; - xsh->min_height = p_rect.size.height; - xsh->max_height = p_rect.size.height; - - XSetWMNormalHints(x11_display, wd.x11_window, xsh); - XFree(xsh); - } - bool make_utility = false; if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { @@ -3368,18 +3285,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u } } - if (id != MAIN_WINDOW_ID) { - XSizeHints my_hints = XSizeHints(); - - my_hints.flags = PPosition | PSize; /* I want to specify position and size */ - my_hints.x = p_rect.position.x; /* The origin and size coords I want */ - my_hints.y = p_rect.position.y; - my_hints.width = p_rect.size.width; - my_hints.height = p_rect.size.height; - - XSetNormalHints(x11_display, wd.x11_window, &my_hints); - XMoveWindow(x11_display, wd.x11_window, p_rect.position.x, p_rect.position.y); - } + _update_size_hints(id); #if defined(VULKAN_ENABLED) if (context_vulkan) { diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 0ba1359145..8e1f941bbf 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -235,6 +235,7 @@ class DisplayServerX11 : public DisplayServer { void _update_real_mouse_position(const WindowData &wd); bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; + void _update_size_hints(WindowID p_window); void _set_wm_fullscreen(WindowID p_window, bool p_enabled); void _set_wm_maximized(WindowID p_window, bool p_enabled); diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 5edaf35c50..fda1358dfd 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -311,16 +311,9 @@ void JoypadLinux::open_joypad(const char *p_path) { return; } - //check if the device supports basic gamepad events, prevents certain keyboards from - //being detected as joypads + // Check if the device supports basic gamepad events if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && - (test_bit(ABS_X, absbit) || test_bit(ABS_Y, absbit) || test_bit(ABS_HAT0X, absbit) || - test_bit(ABS_GAS, absbit) || test_bit(ABS_RUDDER, absbit)) && - (test_bit(BTN_A, keybit) || test_bit(BTN_THUMBL, keybit) || - test_bit(BTN_TRIGGER, keybit) || test_bit(BTN_1, keybit))) && - !(test_bit(EV_ABS, evbit) && - test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && - test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit))) { + test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) { close(fd); return; } diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 8c985242f1..a5c5be8a44 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -527,11 +527,11 @@ void Window::_update_window_size() { size.x = MAX(size_limit.x, size.x); size.y = MAX(size_limit.y, size.y); - if (max_size.x > 0 && max_size.x > min_size.x && max_size.x > size.x) { + if (max_size.x > 0 && max_size.x > min_size.x && size.x > max_size.x) { size.x = max_size.x; } - if (max_size.y > 0 && max_size.y > min_size.y && max_size.y > size.y) { + if (max_size.y > 0 && max_size.y > min_size.y && size.y > max_size.y) { size.y = max_size.y; } diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 8b84ce9e8c..e4851ad9f7 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -71,7 +71,15 @@ bool VisualShaderNode::is_output_port_connected(int p_port) const { } void VisualShaderNode::set_output_port_connected(int p_port, bool p_connected) { - connected_output_ports[p_port] = p_connected; + if (p_connected) { + connected_output_ports[p_port] = true; + ++connected_output_count; + } else { + --connected_output_count; + if (connected_output_count == 0) { + connected_output_ports[p_port] = false; + } + } } bool VisualShaderNode::is_input_port_connected(int p_port) const { diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index d74269cfc6..05d8950be9 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -184,6 +184,7 @@ class VisualShaderNode : public Resource { Map<int, Variant> default_input_values; Map<int, bool> connected_input_ports; Map<int, bool> connected_output_ports; + int connected_output_count = 0; protected: bool simple_decl; diff --git a/tests/test_astar.cpp b/tests/test_astar.cpp deleted file mode 100644 index cb5fcfe37b..0000000000 --- a/tests/test_astar.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/*************************************************************************/ -/* test_astar.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "test_astar.h" - -#include "core/math/a_star.h" -#include "core/math/math_funcs.h" -#include "core/os/os.h" - -#include <math.h> -#include <stdio.h> - -namespace TestAStar { - -class ABCX : public AStar { -public: - enum { A, - B, - C, - X }; - - ABCX() { - add_point(A, Vector3(0, 0, 0)); - add_point(B, Vector3(1, 0, 0)); - add_point(C, Vector3(0, 1, 0)); - add_point(X, Vector3(0, 0, 1)); - connect_points(A, B); - connect_points(A, C); - connect_points(B, C); - connect_points(X, A); - } - - // Disable heuristic completely - float _compute_cost(int p_from, int p_to) { - if (p_from == A && p_to == C) { - return 1000; - } - return 100; - } -}; - -bool test_abc() { - ABCX abcx; - Vector<int> path = abcx.get_id_path(ABCX::A, ABCX::C); - bool ok = path.size() == 3; - int i = 0; - ok = ok && path[i++] == ABCX::A; - ok = ok && path[i++] == ABCX::B; - ok = ok && path[i++] == ABCX::C; - return ok; -} - -bool test_abcx() { - ABCX abcx; - Vector<int> path = abcx.get_id_path(ABCX::X, ABCX::C); - bool ok = path.size() == 4; - int i = 0; - ok = ok && path[i++] == ABCX::X; - ok = ok && path[i++] == ABCX::A; - ok = ok && path[i++] == ABCX::B; - ok = ok && path[i++] == ABCX::C; - return ok; -} - -bool test_add_remove() { - AStar a; - bool ok = true; - - // Manual tests - a.add_point(1, Vector3(0, 0, 0)); - a.add_point(2, Vector3(0, 1, 0)); - a.add_point(3, Vector3(1, 1, 0)); - a.add_point(4, Vector3(2, 0, 0)); - a.connect_points(1, 2, true); - a.connect_points(1, 3, true); - a.connect_points(1, 4, false); - - ok = ok && (a.are_points_connected(2, 1)); - ok = ok && (a.are_points_connected(4, 1)); - ok = ok && (a.are_points_connected(2, 1, false)); - ok = ok && (a.are_points_connected(4, 1, false) == false); - - a.disconnect_points(1, 2, true); - ok = ok && (a.get_point_connections(1).size() == 2); // 3, 4 - ok = ok && (a.get_point_connections(2).size() == 0); - - a.disconnect_points(4, 1, false); - ok = ok && (a.get_point_connections(1).size() == 2); // 3, 4 - ok = ok && (a.get_point_connections(4).size() == 0); - - a.disconnect_points(4, 1, true); - ok = ok && (a.get_point_connections(1).size() == 1); // 3 - ok = ok && (a.get_point_connections(4).size() == 0); - - a.connect_points(2, 3, false); - ok = ok && (a.get_point_connections(2).size() == 1); // 3 - ok = ok && (a.get_point_connections(3).size() == 1); // 1 - - a.connect_points(2, 3, true); - ok = ok && (a.get_point_connections(2).size() == 1); // 3 - ok = ok && (a.get_point_connections(3).size() == 2); // 1, 2 - - a.disconnect_points(2, 3, false); - ok = ok && (a.get_point_connections(2).size() == 0); - ok = ok && (a.get_point_connections(3).size() == 2); // 1, 2 - - a.connect_points(4, 3, true); - ok = ok && (a.get_point_connections(3).size() == 3); // 1, 2, 4 - ok = ok && (a.get_point_connections(4).size() == 1); // 3 - - a.disconnect_points(3, 4, false); - ok = ok && (a.get_point_connections(3).size() == 2); // 1, 2 - ok = ok && (a.get_point_connections(4).size() == 1); // 3 - - a.remove_point(3); - ok = ok && (a.get_point_connections(1).size() == 0); - ok = ok && (a.get_point_connections(2).size() == 0); - ok = ok && (a.get_point_connections(4).size() == 0); - - a.add_point(0, Vector3(0, -1, 0)); - a.add_point(3, Vector3(2, 1, 0)); - // 0: (0, -1) - // 1: (0, 0) - // 2: (0, 1) - // 3: (2, 1) - // 4: (2, 0) - - // Tests for get_closest_position_in_segment - a.connect_points(2, 3); - ok = ok && (a.get_closest_position_in_segment(Vector3(0.5, 0.5, 0)) == Vector3(0.5, 1, 0)); - - a.connect_points(3, 4); - a.connect_points(0, 3); - a.connect_points(1, 4); - a.disconnect_points(1, 4, false); - a.disconnect_points(4, 3, false); - a.disconnect_points(3, 4, false); - // Remaining edges: <2, 3>, <0, 3>, <1, 4> (directed) - ok = ok && (a.get_closest_position_in_segment(Vector3(2, 0.5, 0)) == Vector3(1.75, 0.75, 0)); - ok = ok && (a.get_closest_position_in_segment(Vector3(-1, 0.2, 0)) == Vector3(0, 0, 0)); - ok = ok && (a.get_closest_position_in_segment(Vector3(3, 2, 0)) == Vector3(2, 1, 0)); - - Math::seed(0); - - // Random tests for connectivity checks - for (int i = 0; i < 20000; i++) { - int u = Math::rand() % 5; - int v = Math::rand() % 4; - if (u == v) { - v = 4; - } - if (Math::rand() % 2 == 1) { - // Add a (possibly existing) directed edge and confirm connectivity - a.connect_points(u, v, false); - ok = ok && (a.are_points_connected(u, v, false)); - } else { - // Remove a (possibly nonexistent) directed edge and confirm disconnectivity - a.disconnect_points(u, v, false); - ok = ok && (a.are_points_connected(u, v, false) == false); - } - } - - // Random tests for point removal - for (int i = 0; i < 20000; i++) { - a.clear(); - for (int j = 0; j < 5; j++) { - a.add_point(j, Vector3(0, 0, 0)); - } - - // Add or remove random edges - for (int j = 0; j < 10; j++) { - int u = Math::rand() % 5; - int v = Math::rand() % 4; - if (u == v) { - v = 4; - } - if (Math::rand() % 2 == 1) { - a.connect_points(u, v, false); - } else { - a.disconnect_points(u, v, false); - } - } - - // Remove point 0 - a.remove_point(0); - // White box: this will check all edges remaining in the segments set - for (int j = 1; j < 5; j++) { - ok = ok && (a.are_points_connected(0, j, true) == false); - } - } - - // It's been great work, cheers \(^ ^)/ - return ok; -} - -bool test_solutions() { - // Random stress tests with Floyd-Warshall - - const int N = 30; - Math::seed(0); - - for (int test = 0; test < 1000; test++) { - AStar a; - Vector3 p[N]; - bool adj[N][N] = { { false } }; - - // Assign initial coordinates - for (int u = 0; u < N; u++) { - p[u].x = Math::rand() % 100; - p[u].y = Math::rand() % 100; - p[u].z = Math::rand() % 100; - a.add_point(u, p[u]); - } - - // Generate a random sequence of operations - for (int i = 0; i < 1000; i++) { - // Pick two different vertices - int u, v; - u = Math::rand() % N; - v = Math::rand() % (N - 1); - if (u == v) { - v = N - 1; - } - - // Pick a random operation - int op = Math::rand(); - switch (op % 9) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - // Add edge (u, v); possibly bidirectional - a.connect_points(u, v, op % 2); - adj[u][v] = true; - if (op % 2) { - adj[v][u] = true; - } - break; - case 6: - case 7: - // Remove edge (u, v); possibly bidirectional - a.disconnect_points(u, v, op % 2); - adj[u][v] = false; - if (op % 2) { - adj[v][u] = false; - } - break; - case 8: - // Remove point u and add it back; clears adjacent edges and changes coordinates - a.remove_point(u); - p[u].x = Math::rand() % 100; - p[u].y = Math::rand() % 100; - p[u].z = Math::rand() % 100; - a.add_point(u, p[u]); - for (v = 0; v < N; v++) { - adj[u][v] = adj[v][u] = false; - } - break; - } - } - - // Floyd-Warshall - float d[N][N]; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - d[u][v] = (u == v || adj[u][v]) ? p[u].distance_to(p[v]) : INFINITY; - } - } - - for (int w = 0; w < N; w++) { - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (d[u][v] > d[u][w] + d[w][v]) { - d[u][v] = d[u][w] + d[w][v]; - } - } - } - } - - // Display statistics - int count = 0; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (adj[u][v]) { - count++; - } - } - } - printf("Test #%4d: %3d edges, ", test + 1, count); - count = 0; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (!Math::is_inf(d[u][v])) { - count++; - } - } - } - printf("%3d/%d pairs of reachable points\n", count - N, N * (N - 1)); - - // Check A*'s output - bool match = true; - for (int u = 0; u < N; u++) { - for (int v = 0; v < N; v++) { - if (u != v) { - Vector<int> route = a.get_id_path(u, v); - if (!Math::is_inf(d[u][v])) { - // Reachable - if (route.size() == 0) { - printf("From %d to %d: A* did not find a path\n", u, v); - match = false; - goto exit; - } - float astar_dist = 0; - for (int i = 1; i < route.size(); i++) { - if (!adj[route[i - 1]][route[i]]) { - printf("From %d to %d: edge (%d, %d) does not exist\n", - u, v, route[i - 1], route[i]); - match = false; - goto exit; - } - astar_dist += p[route[i - 1]].distance_to(p[route[i]]); - } - if (!Math::is_equal_approx(astar_dist, d[u][v])) { - printf("From %d to %d: Floyd-Warshall gives %.6f, A* gives %.6f\n", - u, v, d[u][v], astar_dist); - match = false; - goto exit; - } - } else { - // Unreachable - if (route.size() > 0) { - printf("From %d to %d: A* somehow found a nonexistent path\n", u, v); - match = false; - goto exit; - } - } - } - } - } - - exit: - if (!match) { - return false; - } - } - return true; -} - -typedef bool (*TestFunc)(); - -TestFunc test_funcs[] = { - test_abc, - test_abcx, - test_add_remove, - test_solutions, - 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"); - OS::get_singleton()->print("Passed %i of %i tests\n", passed, count); - return nullptr; -} - -} // namespace TestAStar diff --git a/tests/test_astar.h b/tests/test_astar.h index 0992812c18..bef6127471 100644 --- a/tests/test_astar.h +++ b/tests/test_astar.h @@ -31,11 +31,338 @@ #ifndef TEST_ASTAR_H #define TEST_ASTAR_H -#include "core/os/main_loop.h" +#include "core/math/a_star.h" +#include "core/math/math_funcs.h" +#include "core/os/os.h" + +#include <math.h> +#include <stdio.h> + +#include "tests/test_macros.h" namespace TestAStar { -MainLoop *test(); +class ABCX : public AStar { +public: + enum { + A, + B, + C, + X, + }; + + ABCX() { + add_point(A, Vector3(0, 0, 0)); + add_point(B, Vector3(1, 0, 0)); + add_point(C, Vector3(0, 1, 0)); + add_point(X, Vector3(0, 0, 1)); + connect_points(A, B); + connect_points(A, C); + connect_points(B, C); + connect_points(X, A); + } + + // Disable heuristic completely. + float _compute_cost(int p_from, int p_to) { + if (p_from == A && p_to == C) { + return 1000; + } + return 100; + } +}; + +TEST_CASE("[AStar] ABC path") { + ABCX abcx; + Vector<int> path = abcx.get_id_path(ABCX::A, ABCX::C); + REQUIRE(path.size() == 3); + CHECK(path[0] == ABCX::A); + CHECK(path[1] == ABCX::B); + CHECK(path[2] == ABCX::C); +} + +TEST_CASE("[AStar] ABCX path") { + ABCX abcx; + Vector<int> path = abcx.get_id_path(ABCX::X, ABCX::C); + REQUIRE(path.size() == 4); + CHECK(path[0] == ABCX::X); + CHECK(path[1] == ABCX::A); + CHECK(path[2] == ABCX::B); + CHECK(path[3] == ABCX::C); +} + +TEST_CASE("[AStar] Add/Remove") { + AStar a; + + // Manual tests. + a.add_point(1, Vector3(0, 0, 0)); + a.add_point(2, Vector3(0, 1, 0)); + a.add_point(3, Vector3(1, 1, 0)); + a.add_point(4, Vector3(2, 0, 0)); + a.connect_points(1, 2, true); + a.connect_points(1, 3, true); + a.connect_points(1, 4, false); + + CHECK(a.are_points_connected(2, 1)); + CHECK(a.are_points_connected(4, 1)); + CHECK(a.are_points_connected(2, 1, false)); + CHECK_FALSE(a.are_points_connected(4, 1, false)); + + a.disconnect_points(1, 2, true); + CHECK(a.get_point_connections(1).size() == 2); // 3, 4 + CHECK(a.get_point_connections(2).size() == 0); + + a.disconnect_points(4, 1, false); + CHECK(a.get_point_connections(1).size() == 2); // 3, 4 + CHECK(a.get_point_connections(4).size() == 0); + + a.disconnect_points(4, 1, true); + CHECK(a.get_point_connections(1).size() == 1); // 3 + CHECK(a.get_point_connections(4).size() == 0); + + a.connect_points(2, 3, false); + CHECK(a.get_point_connections(2).size() == 1); // 3 + CHECK(a.get_point_connections(3).size() == 1); // 1 + + a.connect_points(2, 3, true); + CHECK(a.get_point_connections(2).size() == 1); // 3 + CHECK(a.get_point_connections(3).size() == 2); // 1, 2 + + a.disconnect_points(2, 3, false); + CHECK(a.get_point_connections(2).size() == 0); + CHECK(a.get_point_connections(3).size() == 2); // 1, 2 + + a.connect_points(4, 3, true); + CHECK(a.get_point_connections(3).size() == 3); // 1, 2, 4 + CHECK(a.get_point_connections(4).size() == 1); // 3 + + a.disconnect_points(3, 4, false); + CHECK(a.get_point_connections(3).size() == 2); // 1, 2 + CHECK(a.get_point_connections(4).size() == 1); // 3 + + a.remove_point(3); + CHECK(a.get_point_connections(1).size() == 0); + CHECK(a.get_point_connections(2).size() == 0); + CHECK(a.get_point_connections(4).size() == 0); + + a.add_point(0, Vector3(0, -1, 0)); + a.add_point(3, Vector3(2, 1, 0)); + // 0: (0, -1) + // 1: (0, 0) + // 2: (0, 1) + // 3: (2, 1) + // 4: (2, 0) + + // Tests for get_closest_position_in_segment. + a.connect_points(2, 3); + CHECK(a.get_closest_position_in_segment(Vector3(0.5, 0.5, 0)) == Vector3(0.5, 1, 0)); + + a.connect_points(3, 4); + a.connect_points(0, 3); + a.connect_points(1, 4); + a.disconnect_points(1, 4, false); + a.disconnect_points(4, 3, false); + a.disconnect_points(3, 4, false); + // Remaining edges: <2, 3>, <0, 3>, <1, 4> (directed). + CHECK(a.get_closest_position_in_segment(Vector3(2, 0.5, 0)) == Vector3(1.75, 0.75, 0)); + CHECK(a.get_closest_position_in_segment(Vector3(-1, 0.2, 0)) == Vector3(0, 0, 0)); + CHECK(a.get_closest_position_in_segment(Vector3(3, 2, 0)) == Vector3(2, 1, 0)); + + Math::seed(0); + + // Random tests for connectivity checks + for (int i = 0; i < 20000; i++) { + int u = Math::rand() % 5; + int v = Math::rand() % 4; + if (u == v) { + v = 4; + } + if (Math::rand() % 2 == 1) { + // Add a (possibly existing) directed edge and confirm connectivity. + a.connect_points(u, v, false); + CHECK(a.are_points_connected(u, v, false)); + } else { + // Remove a (possibly nonexistent) directed edge and confirm disconnectivity. + a.disconnect_points(u, v, false); + CHECK_FALSE(a.are_points_connected(u, v, false)); + } + } + + // Random tests for point removal. + for (int i = 0; i < 20000; i++) { + a.clear(); + for (int j = 0; j < 5; j++) { + a.add_point(j, Vector3(0, 0, 0)); + } + + // Add or remove random edges. + for (int j = 0; j < 10; j++) { + int u = Math::rand() % 5; + int v = Math::rand() % 4; + if (u == v) { + v = 4; + } + if (Math::rand() % 2 == 1) { + a.connect_points(u, v, false); + } else { + a.disconnect_points(u, v, false); + } + } + + // Remove point 0. + a.remove_point(0); + // White box: this will check all edges remaining in the segments set. + for (int j = 1; j < 5; j++) { + CHECK_FALSE(a.are_points_connected(0, j, true)); + } + } + // It's been great work, cheers. \(^ ^)/ +} + +TEST_CASE("[Stress][AStar] Find paths") { + // Random stress tests with Floyd-Warshall. + const int N = 30; + Math::seed(0); + + for (int test = 0; test < 1000; test++) { + AStar a; + Vector3 p[N]; + bool adj[N][N] = { { false } }; + + // Assign initial coordinates. + for (int u = 0; u < N; u++) { + p[u].x = Math::rand() % 100; + p[u].y = Math::rand() % 100; + p[u].z = Math::rand() % 100; + a.add_point(u, p[u]); + } + // Generate a random sequence of operations. + for (int i = 0; i < 1000; i++) { + // Pick two different vertices. + int u, v; + u = Math::rand() % N; + v = Math::rand() % (N - 1); + if (u == v) { + v = N - 1; + } + // Pick a random operation. + int op = Math::rand(); + switch (op % 9) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + // Add edge (u, v); possibly bidirectional. + a.connect_points(u, v, op % 2); + adj[u][v] = true; + if (op % 2) { + adj[v][u] = true; + } + break; + case 6: + case 7: + // Remove edge (u, v); possibly bidirectional. + a.disconnect_points(u, v, op % 2); + adj[u][v] = false; + if (op % 2) { + adj[v][u] = false; + } + break; + case 8: + // Remove point u and add it back; clears adjacent edges and changes coordinates. + a.remove_point(u); + p[u].x = Math::rand() % 100; + p[u].y = Math::rand() % 100; + p[u].z = Math::rand() % 100; + a.add_point(u, p[u]); + for (v = 0; v < N; v++) { + adj[u][v] = adj[v][u] = false; + } + break; + } + } + // Floyd-Warshall. + float d[N][N]; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + d[u][v] = (u == v || adj[u][v]) ? p[u].distance_to(p[v]) : INFINITY; + } + } + for (int w = 0; w < N; w++) { + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (d[u][v] > d[u][w] + d[w][v]) { + d[u][v] = d[u][w] + d[w][v]; + } + } + } + } + // Display statistics. + int count = 0; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (adj[u][v]) { + count++; + } + } + } + print_verbose(vformat("Test #%4d: %3d edges, ", test + 1, count)); + count = 0; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (!Math::is_inf(d[u][v])) { + count++; + } + } + } + print_verbose(vformat("%3d/%d pairs of reachable points\n", count - N, N * (N - 1))); + + // Check A*'s output. + bool match = true; + for (int u = 0; u < N; u++) { + for (int v = 0; v < N; v++) { + if (u != v) { + Vector<int> route = a.get_id_path(u, v); + if (!Math::is_inf(d[u][v])) { + // Reachable. + if (route.size() == 0) { + print_verbose(vformat("From %d to %d: A* did not find a path\n", u, v)); + match = false; + goto exit; + } + float astar_dist = 0; + for (int i = 1; i < route.size(); i++) { + if (!adj[route[i - 1]][route[i]]) { + print_verbose(vformat("From %d to %d: edge (%d, %d) does not exist\n", + u, v, route[i - 1], route[i])); + match = false; + goto exit; + } + astar_dist += p[route[i - 1]].distance_to(p[route[i]]); + } + if (!Math::is_equal_approx(astar_dist, d[u][v])) { + print_verbose(vformat("From %d to %d: Floyd-Warshall gives %.6f, A* gives %.6f\n", + u, v, d[u][v], astar_dist)); + match = false; + goto exit; + } + } else { + // Unreachable. + if (route.size() > 0) { + print_verbose(vformat("From %d to %d: A* somehow found a nonexistent path\n", u, v)); + match = false; + goto exit; + } + } + } + } + } + exit: + CHECK_MESSAGE(match, "Found all paths."); + } } -#endif +} // namespace TestAStar + +#endif // TEST_ASTAR_H diff --git a/tests/test_color.h b/tests/test_color.h new file mode 100644 index 0000000000..3633f2746d --- /dev/null +++ b/tests/test_color.h @@ -0,0 +1,213 @@ +/*************************************************************************/ +/* test_color.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_COLOR_H +#define TEST_COLOR_H + +#include "core/color.h" + +#include "thirdparty/doctest/doctest.h" + +namespace TestColor { + +TEST_CASE("[Color] Constructor methods") { + const Color blue_rgba = Color(0.25098, 0.376471, 1, 0.501961); + // HTML currently uses ARGB notation, which is contrary to the CSS standard. + // This may be changed to RGBA in 4.0. + const Color blue_html = Color::html("#804060ff"); + const Color blue_hex = Color::hex(0x4060ff80); + const Color blue_hex64 = Color::hex64(0x4040'6060'ffff'8080); + + CHECK_MESSAGE( + blue_rgba.is_equal_approx(blue_html), + "Creation with HTML notation should result in components approximately equal to the default constructor."); + CHECK_MESSAGE( + blue_rgba.is_equal_approx(blue_hex), + "Creation with a 32-bit hexadecimal number should result in components approximately equal to the default constructor."); + CHECK_MESSAGE( + blue_rgba.is_equal_approx(blue_hex64), + "Creation with a 64-bit hexadecimal number should result in components approximately equal to the default constructor."); + + ERR_PRINT_OFF; + const Color html_invalid = Color::html("invalid"); + ERR_PRINT_ON; + + CHECK_MESSAGE( + html_invalid.is_equal_approx(Color()), + "Creation with invalid HTML notation should result in a Color with the default values."); + + const Color green_rgba = Color(0, 1, 0, 0.25); + const Color green_hsva = Color(0, 0, 0).from_hsv(120 / 360.0, 1, 1, 0.25); + + CHECK_MESSAGE( + green_rgba.is_equal_approx(green_hsva), + "Creation with HSV notation should result in components approximately equal to the default constructor."); +} + +TEST_CASE("[Color] Operators") { + const Color blue = Color(0.2, 0.2, 1); + const Color dark_red = Color(0.3, 0.1, 0.1); + + // Color components may be negative. Also, the alpha component may be greater than 1.0. + CHECK_MESSAGE( + (blue + dark_red).is_equal_approx(Color(0.5, 0.3, 1.1, 2)), + "Color addition should behave as expected."); + CHECK_MESSAGE( + (blue - dark_red).is_equal_approx(Color(-0.1, 0.1, 0.9, 0)), + "Color subtraction should behave as expected."); + CHECK_MESSAGE( + (blue * 2).is_equal_approx(Color(0.4, 0.4, 2, 2)), + "Color multiplication with a scalar should behave as expected."); + CHECK_MESSAGE( + (blue / 2).is_equal_approx(Color(0.1, 0.1, 0.5, 0.5)), + "Color division with a scalar should behave as expected."); + CHECK_MESSAGE( + (blue * dark_red).is_equal_approx(Color(0.06, 0.02, 0.1)), + "Color multiplication with another Color should behave as expected."); + CHECK_MESSAGE( + (blue / dark_red).is_equal_approx(Color(0.666667, 2, 10)), + "Color division with another Color should behave as expected."); + CHECK_MESSAGE( + (-blue).is_equal_approx(Color(0.8, 0.8, 0, 0)), + "Color negation should behave as expected (affecting the alpha channel, unlike `invert()`)."); +} + +TEST_CASE("[Color] Reading methods") { + const Color dark_blue = Color(0, 0, 0.5, 0.4); + + CHECK_MESSAGE( + Math::is_equal_approx(dark_blue.get_h(), 240 / 360.0), + "The returned HSV hue should match the expected value."); + CHECK_MESSAGE( + Math::is_equal_approx(dark_blue.get_s(), 1), + "The returned HSV saturation should match the expected value."); + CHECK_MESSAGE( + Math::is_equal_approx(dark_blue.get_v(), 0.5), + "The returned HSV value should match the expected value."); +} + +TEST_CASE("[Color] Conversion methods") { + const Color cyan = Color(0, 1, 1); + const Color cyan_transparent = Color(0, 1, 1, 0); + + CHECK_MESSAGE( + cyan.to_html() == "ff00ffff", + "The returned RGB HTML color code should match the expected value."); + CHECK_MESSAGE( + cyan_transparent.to_html() == "0000ffff", + "The returned RGBA HTML color code should match the expected value."); + CHECK_MESSAGE( + cyan.to_argb32() == 0xff00ffff, + "The returned 32-bit RGB number should match the expected value."); + CHECK_MESSAGE( + cyan.to_abgr32() == 0xffffff00, + "The returned 32-bit BGR number should match the expected value."); + CHECK_MESSAGE( + cyan.to_rgba32() == 0x00ffffff, + "The returned 32-bit BGR number should match the expected value."); + CHECK_MESSAGE( + cyan.to_argb64() == 0xffff'0000'ffff'ffff, + "The returned 64-bit RGB number should match the expected value."); + CHECK_MESSAGE( + cyan.to_abgr64() == 0xffff'ffff'ffff'0000, + "The returned 64-bit BGR number should match the expected value."); + CHECK_MESSAGE( + cyan.to_rgba64() == 0x0000'ffff'ffff'ffff, + "The returned 64-bit BGR number should match the expected value."); + CHECK_MESSAGE( + String(cyan) == "0, 1, 1, 1", + "The string representation should match the expected value."); +} + +TEST_CASE("[Color] Named colors") { + CHECK_MESSAGE( + Color::named("red").is_equal_approx(Color(1, 0, 0)), + "The named color \"red\" should match the expected value."); + + // Named colors have their names automatically normalized. + CHECK_MESSAGE( + Color::named("white_smoke").is_equal_approx(Color(0.96, 0.96, 0.96)), + "The named color \"white_smoke\" should match the expected value."); + CHECK_MESSAGE( + Color::named("Slate Blue").is_equal_approx(Color(0.42, 0.35, 0.80)), + "The named color \"Slate Blue\" should match the expected value."); + + ERR_PRINT_OFF; + CHECK_MESSAGE( + Color::named("doesn't exist").is_equal_approx(Color()), + "The invalid named color \"doesn't exist\" should result in a Color with the default values."); + ERR_PRINT_ON; +} + +TEST_CASE("[Color] Validation methods") { + CHECK_MESSAGE( + Color::html_is_valid("#4080ff"), + "Valid HTML color (with leading #) should be considered valid."); + CHECK_MESSAGE( + Color::html_is_valid("4080ff"), + "Valid HTML color (without leading #) should be considered valid."); + CHECK_MESSAGE( + !Color::html_is_valid("12345"), + "Invalid HTML color should be considered invalid."); + CHECK_MESSAGE( + !Color::html_is_valid("#fuf"), + "Invalid HTML color should be considered invalid."); +} + +TEST_CASE("[Color] Manipulation methods") { + const Color blue = Color(0, 0, 1, 0.4); + + CHECK_MESSAGE( + blue.inverted().is_equal_approx(Color(1, 1, 0, 0.4)), + "Inverted color should have its red, green and blue components inverted."); + CHECK_MESSAGE( + blue.contrasted().is_equal_approx(Color(0.5, 0.5, 0.5, 0.4)), + "Contrasted pure blue should be fully gray."); + + const Color purple = Color(0.5, 0.2, 0.5, 0.25); + + CHECK_MESSAGE( + purple.lightened(0.2).is_equal_approx(Color(0.6, 0.36, 0.6, 0.25)), + "Color should be lightened by the expected amount."); + CHECK_MESSAGE( + purple.darkened(0.2).is_equal_approx(Color(0.4, 0.16, 0.4, 0.25)), + "Color should be darkened by the expected amount."); + + const Color red = Color(1, 0, 0, 0.2); + const Color yellow = Color(1, 1, 0, 0.8); + + CHECK_MESSAGE( + red.lerp(yellow, 0.5).is_equal_approx(Color(1, 0.5, 0, 0.5)), + "Red interpolated with yellow should be orange (with interpolated alpha)."); +} + +} // namespace TestColor + +#endif // TEST_COLOR_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 0d83c3ced3..137882841c 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -35,6 +35,7 @@ #include "test_astar.h" #include "test_basis.h" #include "test_class_db.h" +#include "test_color.h" #include "test_gdscript.h" #include "test_gui.h" #include "test_math.h" @@ -69,7 +70,8 @@ int test_main(int argc, char *argv[]) { char **args = new char *[valid_arguments.size()]; for (int x = 0; x < valid_arguments.size(); x++) { // Operation to convert Godot string to non wchar string. - const char *str = valid_arguments[x].utf8().ptr(); + CharString cs = valid_arguments[x].utf8(); + const char *str = cs.get_data(); // Allocate the string copy. args[x] = new char[strlen(str) + 1]; // Copy this into memory. @@ -82,6 +84,9 @@ int test_main(int argc, char *argv[]) { test_context.setOption("abort-after", 5); test_context.setOption("no-breaks", true); + for (int x = 0; x < valid_arguments.size(); x++) { + delete[] args[x]; + } delete[] args; return test_context.run(); |