diff options
150 files changed, 12182 insertions, 4384 deletions
diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml new file mode 100644 index 0000000000..0657a6cb18 --- /dev/null +++ b/.github/workflows/ios_builds.yml @@ -0,0 +1,50 @@ +name: iOS Builds +on: [push, pull_request] + +# Global Cache Settings +env: +  GODOT_BASE_BRANCH: master +  SCONS_CACHE_LIMIT: 4096 + +jobs: +  ios-template: +    runs-on: "macos-latest" +    name: Template (target=release, tools=no) + +    steps: +      - uses: actions/checkout@v2 + +      # Upload cache on completion and check it out now +      - name: Load .scons_cache directory +        id: ios-template-cache +        uses: actions/cache@v2 +        with: +          path: ${{github.workspace}}/.scons_cache/ +          key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} +          restore-keys: | +            ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} +            ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} +            ${{github.job}}-${{env.GODOT_BASE_BRANCH}} + +      # Use python 3.x release (works cross platform) +      - name: Set up Python 3.x +        uses: actions/setup-python@v2 +        with: +          # Semantic version range syntax or exact version of a Python version +          python-version: '3.x' +          # Optional - x64 or x86 architecture, defaults to x64 +          architecture: 'x64' + +      # You can test your matrix by printing the current Python version +      - name: Configuring Python packages +        run: | +          python -c "import sys; print(sys.version)" +          python -m pip install scons +          python --version +          scons --version + +      - name: Compilation +        env: +          SCONS_CACHE: ${{github.workspace}}/.scons_cache/ +        run: | +          scons -j2 verbose=yes warnings=all werror=yes platform=iphone target=release tools=no diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index d91be544a3..a05bb09931 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -60,7 +60,12 @@ jobs:          env:            SCONS_CACHE: ${{github.workspace}}/.scons_cache/          run: | -          scons -j2 verbose=yes warnings=all werror=yes platform=linuxbsd tools=yes target=release_debug module_mono_enabled=yes mono_glue=no +          scons -j2 verbose=yes warnings=all werror=yes platform=linuxbsd tools=yes tests=yes target=release_debug module_mono_enabled=yes mono_glue=no + +      # Execute unit tests for the editor +      - name: Unit Tests +        run: | +          ./bin/godot.linuxbsd.opt.tools.64.mono --test    linux-template:      runs-on: "ubuntu-20.04" diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index f6d357e706..d9f41f09f7 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -1,4 +1,4 @@ -name: MacOS Builds +name: macOS Builds  on: [push, pull_request]  # Global Cache Settings @@ -49,7 +49,12 @@ jobs:          env:            SCONS_CACHE: ${{github.workspace}}/.scons_cache/          run: | -          scons -j2 verbose=yes warnings=all werror=yes platform=osx tools=yes target=release_debug +          scons -j2 verbose=yes warnings=all werror=yes platform=osx tools=yes tests=yes target=release_debug + +      # Execute unit tests for the editor +      - name: Unit Tests +        run: | +          ./bin/godot.osx.opt.tools.64 --test    macos-template:      runs-on: "macos-latest" diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 0c7f23036e..2bc3fcfdaa 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -54,7 +54,12 @@ jobs:        env:          SCONS_CACHE: /.scons_cache/        run: | -        scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=yes target=release_debug +        scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=yes tests=yes target=release_debug + +    # Execute unit tests for the editor +    - name: Unit Tests +      run: | +        ./bin/godot.windows.opt.tools.64.exe --test  # Build Product Upload (tested and working)  # sorry this is disabled until github can give us some more space as we would hit our limit very quickly diff --git a/.gitignore b/.gitignore index f4af79929c..6af581040c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,13 @@ doc/_build/  # CLion  cmake-build-debug +# clangd +.clangd/ +  # Android specific  .gradle  local.properties  *.iml -.idea  .gradletasknamecache  project.properties  platform/android/java/lib/.cxx/ diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index fc3079c361..e5d30e3328 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -126,6 +126,11 @@ Copyright: 2018, Eric Lasota   2018, Microsoft Corp.  License: Expat +Files: ./thirdparty/doctest/ +Comment: doctest +Copyright: 2016-2019, Viktor Kirilov +License: Expat +  Files: ./thirdparty/enet/  Comment: ENet  Copyright: 2002-2020, Lee Salzman @@ -66,9 +66,7 @@ There are also a number of other learning resources provided by the community,  such as text and video tutorials, demos, etc. Consult the [community channels](https://godotengine.org/community)  for more info. -[](https://travis-ci.org/godotengine/godot)  [](https://github.com/godotengine/godot/actions) -[](https://ci.appveyor.com/project/akien-mga/godot)  [](https://www.codetriage.com/godotengine/godot)  [](https://hosted.weblate.org/engage/godot-engine/?utm_source=widget)  [](https://lgtm.com/projects/g/godotengine/godot/alerts) diff --git a/SConstruct b/SConstruct index e7ca8b3030..857daf7b0e 100644 --- a/SConstruct +++ b/SConstruct @@ -115,6 +115,7 @@ opts.Add(EnumVariable("target", "Compilation target", "debug", ("debug", "releas  opts.Add(EnumVariable("optimize", "Optimization type", "speed", ("speed", "size")))  opts.Add(BoolVariable("tools", "Build the tools (a.k.a. the Godot editor)", True)) +opts.Add(BoolVariable("tests", "Build the unit tests", False))  opts.Add(BoolVariable("use_lto", "Use link-time optimization", False))  opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise epsilon (debug option)", False)) @@ -249,6 +250,10 @@ if env_base["target"] == "debug":      # http://scons.org/doc/production/HTML/scons-user/ch06s04.html      env_base.SetOption("implicit_cache", 1) +if not env_base["tools"]: +    # Export templates can't run unit test tool. +    env_base["tests"] = False +  if env_base["no_editor_splash"]:      env_base.Append(CPPDEFINES=["NO_EDITOR_SPLASH"]) @@ -312,6 +317,8 @@ if selected_platform in platform_list:          env["verbose"] = True          env["warnings"] = "extra"          env["werror"] = True +        if env["tools"]: +            env["tests"] = True      if env["vsproj"]:          env.vs_incs = [] @@ -648,8 +655,7 @@ if selected_platform in platform_list:      Export("env") -    # build subdirs, the build order is dependent on link order. - +    # Build subdirs, the build order is dependent on link order.      SConscript("core/SCsub")      SConscript("servers/SCsub")      SConscript("scene/SCsub") @@ -658,9 +664,11 @@ if selected_platform in platform_list:      SConscript("platform/SCsub")      SConscript("modules/SCsub") +    if env["tests"]: +        SConscript("tests/SCsub")      SConscript("main/SCsub") -    SConscript("platform/" + selected_platform + "/SCsub")  # build selected platform +    SConscript("platform/" + selected_platform + "/SCsub")  # Build selected platform.      # Microsoft Visual Studio Project Generation      if env["vsproj"]: diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 2f8b11652b..045d7d5872 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -781,6 +781,7 @@ void _OS::_bind_methods() {  	// Those default values need to be specified for the docs generator,  	// to avoid using values from the documentation writer's own OS instance. +	ADD_PROPERTY_DEFAULT("tablet_driver", "");  	ADD_PROPERTY_DEFAULT("exit_code", 0);  	ADD_PROPERTY_DEFAULT("low_processor_usage_mode", false);  	ADD_PROPERTY_DEFAULT("low_processor_usage_mode_sleep_usec", 6900); diff --git a/core/callable_method_pointer.h b/core/callable_method_pointer.h index 22db7d1c82..1bb89e53e1 100644 --- a/core/callable_method_pointer.h +++ b/core/callable_method_pointer.h @@ -131,7 +131,7 @@ void call_with_variant_args_helper(T *p_instance, void (T::*p_method)(P...), con  #ifdef DEBUG_METHODS_ENABLED  	(p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);  #else -	(p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...); +	(p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);  #endif  } @@ -228,7 +228,7 @@ void call_with_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), co  #ifdef DEBUG_METHODS_ENABLED  	r_ret = (p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);  #else -	(p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...); +	(p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);  #endif  } diff --git a/core/global_constants.cpp b/core/global_constants.cpp index 6281e56395..b30685539a 100644 --- a/core/global_constants.cpp +++ b/core/global_constants.cpp @@ -38,6 +38,7 @@  struct _GlobalConstant {  #ifdef DEBUG_METHODS_ENABLED  	StringName enum_name; +	bool ignore_value_in_docs;  #endif  	const char *name;  	int value; @@ -45,8 +46,9 @@ struct _GlobalConstant {  	_GlobalConstant() {}  #ifdef DEBUG_METHODS_ENABLED -	_GlobalConstant(const StringName &p_enum_name, const char *p_name, int p_value) : +	_GlobalConstant(const StringName &p_enum_name, const char *p_name, int p_value, bool p_ignore_value_in_docs = false) :  			enum_name(p_enum_name), +			ignore_value_in_docs(p_ignore_value_in_docs),  			name(p_name),  			value(p_value) {  	} @@ -71,6 +73,15 @@ static Vector<_GlobalConstant> _global_constants;  #define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \  	_global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant)); +#define BIND_GLOBAL_CONSTANT_NO_VAL(m_constant) \ +	_global_constants.push_back(_GlobalConstant(StringName(), #m_constant, m_constant, true)); + +#define BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(m_constant) \ +	_global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant, true)); + +#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \ +	_global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant, true)); +  #else  #define BIND_GLOBAL_CONSTANT(m_constant) \ @@ -82,6 +93,15 @@ static Vector<_GlobalConstant> _global_constants;  #define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \  	_global_constants.push_back(_GlobalConstant(m_custom_name, m_constant)); +#define BIND_GLOBAL_CONSTANT_NO_VAL(m_constant) \ +	_global_constants.push_back(_GlobalConstant(#m_constant, m_constant)); + +#define BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(m_constant) \ +	_global_constants.push_back(_GlobalConstant(#m_constant, m_constant)); + +#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \ +	_global_constants.push_back(_GlobalConstant(m_custom_name, m_constant)); +  #endif  VARIANT_ENUM_CAST(KeyList); @@ -368,7 +388,7 @@ void register_global_constants() {  	BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_ALT);  	BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_META);  	BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_CTRL); -	BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_CMD); +	BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(KEY_MASK_CMD);  	BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_KPAD);  	BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_GROUP_SWITCH); @@ -648,10 +668,18 @@ int GlobalConstants::get_global_constant_count() {  StringName GlobalConstants::get_global_constant_enum(int p_idx) {  	return _global_constants[p_idx].enum_name;  } + +bool GlobalConstants::get_ignore_value_in_docs(int p_idx) { +	return _global_constants[p_idx].ignore_value_in_docs; +}  #else  StringName GlobalConstants::get_global_constant_enum(int p_idx) {  	return StringName();  } + +bool GlobalConstants::get_ignore_value_in_docs(int p_idx) { +	return false; +}  #endif  const char *GlobalConstants::get_global_constant_name(int p_idx) { diff --git a/core/global_constants.h b/core/global_constants.h index a20b5ecd9a..989633a6fa 100644 --- a/core/global_constants.h +++ b/core/global_constants.h @@ -37,6 +37,7 @@ class GlobalConstants {  public:  	static int get_global_constant_count();  	static StringName get_global_constant_enum(int p_idx); +	static bool get_ignore_value_in_docs(int p_idx);  	static const char *get_global_constant_name(int p_idx);  	static int get_global_constant_value(int p_idx);  }; diff --git a/core/object.cpp b/core/object.cpp index 8abea9ca7e..ba002024e6 100644 --- a/core/object.cpp +++ b/core/object.cpp @@ -675,7 +675,7 @@ Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Call  	StringName method = *p_args[0]; -	MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1); +	MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1, true);  	return Variant();  } diff --git a/core/project_settings.cpp b/core/project_settings.cpp index 638987bb2f..e08a44d0c1 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -122,6 +122,22 @@ void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restar  	props[p_name].restart_if_changed = p_restart;  } +void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) { +	ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + "."); +#ifdef DEBUG_METHODS_ENABLED +	props[p_name].ignore_value_in_docs = p_ignore; +#endif +} + +bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const { +	ERR_FAIL_COND_V_MSG(!props.has(p_name), false, "Request for nonexistent project setting: " + p_name + "."); +#ifdef DEBUG_METHODS_ENABLED +	return props[p_name].ignore_value_in_docs; +#else +	return false; +#endif +} +  String ProjectSettings::globalize_path(const String &p_path) const {  	if (p_path.begins_with("res://")) {  		if (resource_path != "") { @@ -876,7 +892,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust  	}  } -Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed) { +Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed, bool p_ignore_value_in_docs) {  	Variant ret;  	if (!ProjectSettings::get_singleton()->has_setting(p_var)) {  		ProjectSettings::get_singleton()->set(p_var, p_default); @@ -886,6 +902,7 @@ Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restar  	ProjectSettings::get_singleton()->set_initial_value(p_var, p_default);  	ProjectSettings::get_singleton()->set_builtin_order(p_var);  	ProjectSettings::get_singleton()->set_restart_if_changed(p_var, p_restart_if_changed); +	ProjectSettings::get_singleton()->set_ignore_value_in_docs(p_var, p_ignore_value_in_docs);  	return ret;  } diff --git a/core/project_settings.h b/core/project_settings.h index 4aceafe3c0..659ee402b3 100644 --- a/core/project_settings.h +++ b/core/project_settings.h @@ -62,6 +62,9 @@ protected:  		bool hide_from_editor = false;  		bool overridden = false;  		bool restart_if_changed = false; +#ifdef DEBUG_METHODS_ENABLED +		bool ignore_value_in_docs = false; +#endif  		VariantContainer() {} @@ -125,6 +128,9 @@ public:  	void set_initial_value(const String &p_name, const Variant &p_value);  	void set_restart_if_changed(const String &p_name, bool p_restart); +	void set_ignore_value_in_docs(const String &p_name, bool p_ignore); +	bool get_ignore_value_in_docs(const String &p_name) const; +  	bool property_can_revert(const String &p_name);  	Variant property_get_revert(const String &p_name); @@ -167,9 +173,11 @@ public:  };  //not a macro any longer -Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false); +Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false, bool p_ignore_value_in_docs = false);  #define GLOBAL_DEF(m_var, m_value) _GLOBAL_DEF(m_var, m_value)  #define GLOBAL_DEF_RST(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true) +#define GLOBAL_DEF_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, true) +#define GLOBAL_DEF_RST_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true, true)  #define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var)  #endif // PROJECT_SETTINGS_H diff --git a/core/string_name.h b/core/string_name.h index df6b458581..886ddd0ee7 100644 --- a/core/string_name.h +++ b/core/string_name.h @@ -35,6 +35,8 @@  #include "core/safe_refcount.h"  #include "core/ustring.h" +class Main; +  struct StaticCString {  	const char *ptr;  	static StaticCString create(const char *p_ptr); @@ -73,7 +75,7 @@ class StringName {  	void unref();  	friend void register_core_types();  	friend void unregister_core_types(); - +	friend class Main;  	static Mutex mutex;  	static void setup();  	static void cleanup(); diff --git a/core/ustring.cpp b/core/ustring.cpp index 5d3cf5f1a4..572ad1af59 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -1025,6 +1025,9 @@ String String::chr(CharType p_char) {  }  String String::num(double p_num, int p_decimals) { +	if (Math::is_nan(p_num)) { +		return "nan"; +	}  #ifndef NO_USE_STDLIB  	if (p_decimals > 16) { @@ -1313,6 +1316,9 @@ String String::num_real(double p_num) {  }  String String::num_scientific(double p_num) { +	if (Math::is_nan(p_num)) { +		return "nan"; +	}  #ifndef NO_USE_STDLIB  	char buf[256]; diff --git a/core/variant.cpp b/core/variant.cpp index f6b7e2821a..afd01b3359 100644 --- a/core/variant.cpp +++ b/core/variant.cpp @@ -1454,7 +1454,7 @@ Variant::operator signed long() const {  		case INT:  			return _data._int;  		case FLOAT: -			return _data._real; +			return _data._float;  		case STRING:  			return operator String().to_int();  		default: { @@ -1474,7 +1474,7 @@ Variant::operator unsigned long() const {  		case INT:  			return _data._int;  		case FLOAT: -			return _data._real; +			return _data._float;  		case STRING:  			return operator String().to_int();  		default: { diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 9a28a0d085..7f7df33471 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -898,7 +898,7 @@  		<constant name="KEY_MASK_CTRL" value="268435456" enum="KeyModifierMask">  			Ctrl key mask.  		</constant> -		<constant name="KEY_MASK_CMD" value="268435456" enum="KeyModifierMask"> +		<constant name="KEY_MASK_CMD" value="platform-dependent" enum="KeyModifierMask">  			Command key mask. On macOS, this is equivalent to [constant KEY_MASK_META]. On other platforms, this is equivalent to [constant KEY_MASK_CTRL]. This mask should be preferred to [constant KEY_MASK_META] or [constant KEY_MASK_CTRL] for system shortcuts as it handles all platforms correctly.  		</constant>  		<constant name="KEY_MASK_KPAD" value="536870912" enum="KeyModifierMask"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index e255ce2e1a..733bb559cb 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -244,7 +244,7 @@  		<member name="audio/default_bus_layout" type="String" setter="" getter="" default=""res://default_bus_layout.tres"">  			Default [AudioBusLayout] resource file to use in the project, unless overridden by the scene.  		</member> -		<member name="audio/driver" type="String" setter="" getter="" default=""PulseAudio""> +		<member name="audio/driver" type="String" setter="" getter="">  			Specifies the audio driver to use. This setting is platform-dependent as each platform supports different audio drivers. If left empty, the default audio driver will be used.  		</member>  		<member name="audio/enable_audio_input" type="bool" setter="" getter="" default="false"> @@ -453,7 +453,7 @@  		<member name="display/window/size/width" type="int" setter="" getter="" default="1024">  			Sets the game's main viewport width. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled.  		</member> -		<member name="display/window/tablet_driver" type="String" setter="" getter="" default=""""> +		<member name="display/window/tablet_driver" type="String" setter="" getter="">  			Specifies the tablet driver to use. If left empty, the default driver will be used.  		</member>  		<member name="display/window/vsync/use_vsync" type="bool" setter="" getter="" default="true"> @@ -472,7 +472,7 @@  		<member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0">  			Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.  		</member> -		<member name="gui/common/swap_cancel_ok" type="bool" setter="" getter="" default="false"> +		<member name="gui/common/swap_cancel_ok" type="bool" setter="" getter="">  			If [code]true[/code], swaps Cancel and OK buttons in dialogs on Windows and UWP to follow interface conventions.  		</member>  		<member name="gui/common/text_edit_undo_stack_max_size" type="int" setter="" getter="" default="1024"> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 9a78e45d46..4991a1e58b 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -478,7 +478,17 @@  			<argument index="0" name="id" type="int">  			</argument>  			<description> -				Returns an array of the tile's shapes. +				Returns an array of dictionaries describing the tile's shapes. +				[b]Dictionary structure in the array returned by this method:[/b] +				[codeblock] +				{ +				    "autotile_coord": Vector2, +				    "one_way": bool, +				    "one_way_margin": int, +				    "shape": CollisionShape2D, +				    "shape_transform": Transform2D, +				} +				[/codeblock]  			</description>  		</method>  		<method name="tile_get_texture" qualifiers="const"> diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub index 91d0e42f80..61d91711da 100644 --- a/drivers/vulkan/SCsub +++ b/drivers/vulkan/SCsub @@ -4,6 +4,7 @@ Import("env")  env.add_source_files(env.drivers_sources, "*.cpp") +# FIXME: Refactor all this to reduce code duplication.  if env["platform"] == "android":      # Use NDK Vulkan headers      thirdparty_dir = env["ANDROID_NDK_ROOT"] + "/sources/third_party/vulkan/src" @@ -22,6 +23,17 @@ if env["platform"] == "android":      thirdparty_dir = "#thirdparty/vulkan"      vma_sources = [thirdparty_dir + "/android/vk_mem_alloc.cpp"]      env_thirdparty.add_source_files(env.drivers_sources, vma_sources) +elif env["platform"] == "iphone": +    # Use bundled Vulkan headers +    thirdparty_dir = "#thirdparty/vulkan" +    env.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include", thirdparty_dir + "/loader"]) + +    # Build Vulkan memory allocator +    env_thirdparty = env.Clone() +    env_thirdparty.disable_warnings() + +    vma_sources = [thirdparty_dir + "/vk_mem_alloc.cpp"] +    env_thirdparty.add_source_files(env.drivers_sources, vma_sources)  elif env["builtin_vulkan"]:      # Use bundled Vulkan headers      thirdparty_dir = "#thirdparty/vulkan" @@ -70,16 +82,6 @@ elif env["builtin_vulkan"]:                  'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg",              ]          ) -    elif env["platform"] == "iphone": -        env_thirdparty.AppendUnique( -            CPPDEFINES=[ -                "VK_USE_PLATFORM_IOS_MVK", -                "VULKAN_NON_CMAKE_BUILD", -                'SYSCONFDIR=\\"%s\\"' % "/etc", -                'FALLBACK_DATA_DIRS=\\"%s\\"' % "/usr/local/share:/usr/share", -                'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg", -            ] -        )      elif env["platform"] == "linuxbsd":          env_thirdparty.AppendUnique(              CPPDEFINES=[ @@ -99,3 +101,13 @@ elif env["builtin_vulkan"]:      loader_sources = [thirdparty_dir + "/loader/" + file for file in loader_sources]      env_thirdparty.add_source_files(env.drivers_sources, loader_sources)      env_thirdparty.add_source_files(env.drivers_sources, vma_sources) +else:  # Always build VMA. +    thirdparty_dir = "#thirdparty/vulkan" +    env.Prepend(CPPPATH=[thirdparty_dir]) + +    # Build Vulkan loader library +    env_thirdparty = env.Clone() +    env_thirdparty.disable_warnings() +    vma_sources = [thirdparty_dir + "/vk_mem_alloc.cpp"] + +    env_thirdparty.add_source_files(env.drivers_sources, vma_sources) diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index f3561dc03e..d569a2ca0a 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3363,9 +3363,9 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) {  			}  			if (num_tracks == 1) { -				insert_confirm_text->set_text(vformat(TTR("Create NEW track for %s and insert key?"), p_id.query)); +				insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?"), p_id.query));  			} else { -				insert_confirm_text->set_text(vformat(TTR("Create %d NEW tracks and insert keys?"), num_tracks)); +				insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?"), num_tracks));  			}  			insert_confirm_bezier->set_visible(all_bezier); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 9a87d6d38c..37db3ba780 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1456,8 +1456,6 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) {  			text_editor->set_line_as_bookmark(bookmarks[i], true);  		}  	} - -	text_editor->grab_focus();  }  void CodeTextEditor::set_error(const String &p_error) { diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index c0c3a73957..1e3dc01112 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -707,6 +707,7 @@ CreateDialog::CreateDialog() {  	favorite = memnew(Button);  	favorite->set_flat(true);  	favorite->set_toggle_mode(true); +	favorite->set_tooltip(TTR("(Un)favorite selected item."));  	favorite->connect("pressed", callable_mp(this, &CreateDialog::_favorite_toggled));  	search_hb->add_child(favorite);  	vbc->add_margin_child(TTR("Search:"), search_hb); diff --git a/editor/doc_data.cpp b/editor/doc_data.cpp index 54acbe9559..75b16b4510 100644 --- a/editor/doc_data.cpp +++ b/editor/doc_data.cpp @@ -321,12 +321,13 @@ void DocData::generate(bool p_basic_types) {  					continue;  				}  				if (E->get().usage & PROPERTY_USAGE_EDITOR) { -					default_value = ProjectSettings::get_singleton()->property_get_revert(E->get().name); -					default_value_valid = true; +					if (!ProjectSettings::get_singleton()->get_ignore_value_in_docs(E->get().name)) { +						default_value = ProjectSettings::get_singleton()->property_get_revert(E->get().name); +						default_value_valid = true; +					}  				}  			} else {  				default_value = get_documentation_default_value(name, E->get().name, default_value_valid); -  				if (inherited) {  					bool base_default_value_valid = false;  					Variant base_default_value = get_documentation_default_value(ClassDB::get_parent_class(name), E->get().name, base_default_value_valid); @@ -478,6 +479,7 @@ void DocData::generate(bool p_basic_types) {  			ConstantDoc constant;  			constant.name = E->get();  			constant.value = itos(ClassDB::get_integer_constant(name, E->get())); +			constant.is_value_valid = true;  			constant.enumeration = ClassDB::get_integer_constant_enum(name, E->get());  			c.constants.push_back(constant);  		} @@ -620,6 +622,7 @@ void DocData::generate(bool p_basic_types) {  			constant.name = E->get();  			Variant value = Variant::get_constant_value(Variant::Type(i), E->get());  			constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string(); +			constant.is_value_valid = true;  			c.constants.push_back(constant);  		}  	} @@ -635,7 +638,12 @@ void DocData::generate(bool p_basic_types) {  		for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {  			ConstantDoc cd;  			cd.name = GlobalConstants::get_global_constant_name(i); -			cd.value = itos(GlobalConstants::get_global_constant_value(i)); +			if (!GlobalConstants::get_ignore_value_in_docs(i)) { +				cd.value = itos(GlobalConstants::get_global_constant_value(i)); +				cd.is_value_valid = true; +			} else { +				cd.is_value_valid = false; +			}  			cd.enumeration = GlobalConstants::get_global_constant_enum(i);  			c.constants.push_back(cd);  		} @@ -715,6 +723,7 @@ void DocData::generate(bool p_basic_types) {  				ConstantDoc cd;  				cd.name = E->get().first;  				cd.value = E->get().second; +				cd.is_value_valid = true;  				c.constants.push_back(cd);  			} @@ -989,6 +998,7 @@ Error DocData::_load(Ref<XMLParser> parser) {  								constant2.name = parser->get_attribute_value("name");  								ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT);  								constant2.value = parser->get_attribute_value("value"); +								constant2.is_value_valid = true;  								if (parser->has_attribute("enum")) {  									constant2.enumeration = parser->get_attribute_value("enum");  								} @@ -1178,10 +1188,18 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri  		for (int i = 0; i < c.constants.size(); i++) {  			const ConstantDoc &k = c.constants[i]; -			if (k.enumeration != String()) { -				_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">"); +			if (k.is_value_valid) { +				if (k.enumeration != String()) { +					_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">"); +				} else { +					_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\">"); +				}  			} else { -				_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\">"); +				if (k.enumeration != String()) { +					_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\" enum=\"" + k.enumeration + "\">"); +				} else { +					_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\">"); +				}  			}  			_write_string(f, 3, k.description.strip_edges().xml_escape());  			_write_string(f, 2, "</constant>"); diff --git a/editor/doc_data.h b/editor/doc_data.h index 1880be81ed..a35cfb59c7 100644 --- a/editor/doc_data.h +++ b/editor/doc_data.h @@ -62,6 +62,7 @@ public:  	struct ConstantDoc {  		String name;  		String value; +		bool is_value_valid;  		String enumeration;  		String description;  		bool operator<(const ConstantDoc &p_const) const { diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 94887fb848..5d101ff2c2 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -688,12 +688,12 @@ bool EditorAutoloadSettings::autoload_add(const String &p_name, const String &p_  	const String &path = p_path;  	if (!FileAccess::exists(path)) { -		EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. File does not exist.", path))); +		EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + vformat(TTR("%s is an invalid path. File does not exist."), path));  		return false;  	}  	if (!path.begins_with("res://")) { -		EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. Not in resource path (res://).", path))); +		EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + vformat(TTR("%s is an invalid path. Not in resource path (res://)."), path));  		return false;  	} diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 85151c6d0a..0e851734a7 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -440,7 +440,7 @@ void EditorFileDialog::_action_pressed() {  		}  		if (dir_access->file_exists(f) && !disable_overwrite_warning) { -			confirm_save->set_text(TTR("File Exists, Overwrite?")); +			confirm_save->set_text(TTR("File exists, overwrite?"));  			confirm_save->popup_centered(Size2(200, 80));  		} else {  			_save_to_recent(); diff --git a/editor/input_map_editor.cpp b/editor/input_map_editor.cpp index 70c354e55a..52cf9c1869 100644 --- a/editor/input_map_editor.cpp +++ b/editor/input_map_editor.cpp @@ -36,44 +36,44 @@  #include "editor/editor_scale.h"  static const char *_button_descriptions[JOY_SDL_BUTTONS] = { -	"Face Bottom, DualShock Cross, Xbox A, Nintendo B", -	"Face Right, DualShock Circle, Xbox B, Nintendo A", -	"Face Left, DualShock Square, Xbox X, Nintendo Y", -	"Face Top, DualShock Triangle, Xbox Y, Nintendo X", -	"DualShock Select, Xbox Back, Nintendo -", -	"Home, DualShock PS, Guide", -	"Start, Nintendo +", -	"Left Stick, DualShock L3, Xbox L/LS", -	"Right Stick, DualShock R3, Xbox R/RS", -	"Left Shoulder, DualShock L1, Xbox LB", -	"Right Shoulder, DualShock R1, Xbox RB", -	"D-Pad Up", -	"D-Pad Down", -	"D-Pad Left", -	"D-Pad Right" +	TTRC("Face Bottom, DualShock Cross, Xbox A, Nintendo B"), +	TTRC("Face Right, DualShock Circle, Xbox B, Nintendo A"), +	TTRC("Face Left, DualShock Square, Xbox X, Nintendo Y"), +	TTRC("Face Top, DualShock Triangle, Xbox Y, Nintendo X"), +	TTRC("DualShock Select, Xbox Back, Nintendo -"), +	TTRC("Home, DualShock PS, Guide"), +	TTRC("Start, Nintendo +"), +	TTRC("Left Stick, DualShock L3, Xbox L/LS"), +	TTRC("Right Stick, DualShock R3, Xbox R/RS"), +	TTRC("Left Shoulder, DualShock L1, Xbox LB"), +	TTRC("Right Shoulder, DualShock R1, Xbox RB"), +	TTRC("D-Pad Up"), +	TTRC("D-Pad Down"), +	TTRC("D-Pad Left"), +	TTRC("D-Pad Right")  };  static const char *_axis_descriptions[JOY_AXIS_MAX * 2] = { -	"Left Stick Left", -	"Left Stick Right", -	"Left Stick Up", -	"Left Stick Down", -	"Right Stick Left", -	"Right Stick Right", -	"Right Stick Up", -	"Right Stick Down", -	"Joystick 2 Left", -	"Joystick 2 Right, Left Trigger, L2, LT", -	"Joystick 2 Up", -	"Joystick 2 Down, Right Trigger, R2, RT", -	"Joystick 3 Left", -	"Joystick 3 Right", -	"Joystick 3 Up", -	"Joystick 3 Down", -	"Joystick 4 Left", -	"Joystick 4 Right", -	"Joystick 4 Up", -	"Joystick 4 Down", +	TTRC("Left Stick Left"), +	TTRC("Left Stick Right"), +	TTRC("Left Stick Up"), +	TTRC("Left Stick Down"), +	TTRC("Right Stick Left"), +	TTRC("Right Stick Right"), +	TTRC("Right Stick Up"), +	TTRC("Right Stick Down"), +	TTRC("Joystick 2 Left"), +	TTRC("Joystick 2 Right, Left Trigger, L2, LT"), +	TTRC("Joystick 2 Up"), +	TTRC("Joystick 2 Down, Right Trigger, R2, RT"), +	TTRC("Joystick 3 Left"), +	TTRC("Joystick 3 Right"), +	TTRC("Joystick 3 Up"), +	TTRC("Joystick 3 Down"), +	TTRC("Joystick 4 Left"), +	TTRC("Joystick 4 Right"), +	TTRC("Joystick 4 Up"), +	TTRC("Joystick 4 Down"),  };  void InputMapEditor::_notification(int p_what) { diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index e2f35e29d8..af1d266832 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -411,7 +411,7 @@ void AnimationPlayerEditor::_animation_remove() {  	String current = animation->get_item_text(animation->get_selected()); -	delete_dialog->set_text(TTR("Delete Animation '" + current + "'?")); +	delete_dialog->set_text(vformat(TTR("Delete Animation '%s'?"), current));  	delete_dialog->popup_centered();  } diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index dc813896ff..269c54ba2b 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -79,7 +79,7 @@ void AnimationTreeEditor::_update_path() {  	group.instance();  	Button *b = memnew(Button); -	b->set_text("root"); +	b->set_text("Root");  	b->set_toggle_mode(true);  	b->set_button_group(group);  	b->set_pressed(true); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index ced0b9f984..6f209c512e 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -5407,7 +5407,7 @@ void Node3DEditor::_update_gizmos_menu() {  		}  		String plugin_name = gizmo_plugins_by_name[i]->get_name();  		const int plugin_state = gizmo_plugins_by_name[i]->get_state(); -		gizmos_menu->add_multistate_item(TTR(plugin_name), 3, plugin_state, i); +		gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i);  		const int idx = gizmos_menu->get_item_index(i);  		gizmos_menu->set_item_tooltip(  				idx, diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 71830d0464..6b844ec0d0 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -696,7 +696,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {  	Node *tselected = tab_container->get_child(selected); -	ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); +	ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected);  	if (current) {  		Ref<Script> script = current->get_edited_resource();  		if (p_save) { @@ -779,8 +779,10 @@ void ScriptEditor::_close_docs_tab() {  void ScriptEditor::_copy_script_path() {  	ScriptEditorBase *se = _get_current_editor(); -	RES script = se->get_edited_resource(); -	DisplayServer::get_singleton()->clipboard_set(script->get_path()); +	if (se) { +		RES script = se->get_edited_resource(); +		DisplayServer::get_singleton()->clipboard_set(script->get_path()); +	}  }  void ScriptEditor::_close_other_tabs() { @@ -1038,17 +1040,19 @@ void ScriptEditor::_file_dialog_action(String p_file) {  		} break;  		case FILE_SAVE_AS: {  			ScriptEditorBase *current = _get_current_editor(); +			if (current) { +				RES resource = current->get_edited_resource(); +				String path = ProjectSettings::get_singleton()->localize_path(p_file); +				Error err = _save_text_file(resource, path); -			String path = ProjectSettings::get_singleton()->localize_path(p_file); -			Error err = _save_text_file(current->get_edited_resource(), path); +				if (err != OK) { +					editor->show_accept(TTR("Error saving file!"), TTR("OK")); +					return; +				} -			if (err != OK) { -				editor->show_accept(TTR("Error saving file!"), TTR("OK")); -				return; +				resource->set_path(path); +				_update_script_names();  			} - -			((Resource *)current->get_edited_resource().ptr())->set_path(path); -			_update_script_names();  		} break;  		case THEME_SAVE_AS: {  			if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) { @@ -1240,13 +1244,14 @@ void ScriptEditor::_menu_option(int p_option) {  					}  				} -				Ref<TextFile> text_file = current->get_edited_resource(); +				RES resource = current->get_edited_resource(); +				Ref<TextFile> text_file = resource;  				if (text_file != nullptr) {  					current->apply_code();  					_save_text_file(text_file, text_file->get_path());  					break;  				} -				editor->save_resource(current->get_edited_resource()); +				editor->save_resource(resource);  			} break;  			case FILE_SAVE_AS: { @@ -1264,7 +1269,8 @@ void ScriptEditor::_menu_option(int p_option) {  					}  				} -				Ref<TextFile> text_file = current->get_edited_resource(); +				RES resource = current->get_edited_resource(); +				Ref<TextFile> text_file = resource;  				if (text_file != nullptr) {  					file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);  					file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); @@ -1280,8 +1286,8 @@ void ScriptEditor::_menu_option(int p_option) {  					break;  				} -				editor->push_item(Object::cast_to<Object>(current->get_edited_resource().ptr())); -				editor->save_resource_as(current->get_edited_resource()); +				editor->push_item(resource.ptr()); +				editor->save_resource_as(resource);  			} break; @@ -1577,7 +1583,9 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {  		List<int> bpoints;  		se->get_breakpoints(&bpoints);  		String base = script->get_path(); -		ERR_CONTINUE(base.begins_with("local://") || base == ""); +		if (base.begins_with("local://") || base == "") { +			continue; +		}  		for (List<int>::Element *E = bpoints.front(); E; E = E->next()) {  			p_breakpoints->push_back(base + ":" + itos(E->get() + 1)); @@ -1630,6 +1638,8 @@ void ScriptEditor::ensure_select_current() {  	if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) {  		ScriptEditorBase *se = _get_current_editor();  		if (se) { +			se->enable_editor(); +  			if (!grab_focus_block && is_visible_in_tree()) {  				se->ensure_focus();  			} @@ -1974,6 +1984,11 @@ void ScriptEditor::_update_script_names() {  			script_list->select(index);  			script_name_label->set_text(sedata_filtered[i].name);  			script_icon->set_texture(sedata_filtered[i].icon); +			ScriptEditorBase *se = _get_current_editor(); +			if (se) { +				se->enable_editor(); +				_update_selected_editor_menu(); +			}  		}  	} @@ -2148,6 +2163,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra  		if ((script != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {  			if (should_open) { +				se->enable_editor(); +  				if (tab_container->get_current_tab() != i) {  					_go_to_tab(i);  					_update_script_names(); @@ -2178,6 +2195,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra  	}  	ERR_FAIL_COND_V(!se, false); +	se->set_edited_resource(p_resource); +  	if (p_resource->get_class_name() != StringName("VisualScript")) {  		bool highlighter_set = false;  		for (int i = 0; i < syntax_highlighters.size(); i++) { @@ -2198,7 +2217,11 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra  	}  	tab_container->add_child(se); -	se->set_edited_resource(p_resource); + +	if (p_grab_focus) { +		se->enable_editor(); +	} +  	se->set_tooltip_request_func("_get_debug_tooltip", this);  	if (se->get_edit_menu()) {  		se->get_edit_menu()->hide(); @@ -2208,6 +2231,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra  	if (p_grab_focus) {  		_go_to_tab(tab_container->get_tab_count() - 1); +		_add_recent_script(p_resource->get_path());  	}  	_sort_list_on_update = true; @@ -2232,7 +2256,6 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra  	}  	notify_script_changed(p_resource); -	_add_recent_script(p_resource->get_path());  	return true;  } @@ -2704,7 +2727,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {  			if (!scr.is_valid()) {  				continue;  			} -			if (!edit(scr)) { +			if (!edit(scr, false)) {  				continue;  			}  		} else { @@ -2713,7 +2736,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {  			if (error != OK || !text_file.is_valid()) {  				continue;  			} -			if (!edit(text_file)) { +			if (!edit(text_file, false)) {  				continue;  			}  		} diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 3891af4091..74376d692f 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -134,6 +134,7 @@ public:  	virtual RES get_edited_resource() const = 0;  	virtual Vector<String> get_functions() = 0;  	virtual void set_edited_resource(const RES &p_res) = 0; +	virtual void enable_editor() = 0;  	virtual void reload_text() = 0;  	virtual String get_name() = 0;  	virtual Ref<Texture2D> get_theme_icon() = 0; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index f4fdf8ccb0..f728974dd7 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -140,10 +140,10 @@ RES ScriptTextEditor::get_edited_resource() const {  }  void ScriptTextEditor::set_edited_resource(const RES &p_res) { -	ERR_FAIL_COND(!script.is_null()); +	ERR_FAIL_COND(script.is_valid()); +	ERR_FAIL_COND(p_res.is_null());  	script = p_res; -	_set_theme_for_script();  	code_editor->get_text_edit()->set_text(script->get_source_code());  	code_editor->get_text_edit()->clear_undo_history(); @@ -151,6 +151,17 @@ void ScriptTextEditor::set_edited_resource(const RES &p_res) {  	emit_signal("name_changed");  	code_editor->update_line_and_column(); +} + +void ScriptTextEditor::enable_editor() { +	if (editor_enabled) { +		return; +	} + +	editor_enabled = true; + +	_enable_code_editor(); +	_set_theme_for_script();  	_validate_script();  } @@ -301,14 +312,6 @@ void ScriptTextEditor::reload_text() {  	code_editor->update_line_and_column();  } -void ScriptTextEditor::_notification(int p_what) { -	switch (p_what) { -		case NOTIFICATION_READY: -			_load_theme_settings(); -			break; -	} -} -  void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) {  	String code = code_editor->get_text_edit()->get_text();  	int pos = script->get_language()->find_function(p_function, code); @@ -352,6 +355,10 @@ void ScriptTextEditor::set_edit_state(const Variant &p_state) {  			_change_syntax_highlighter(idx);  		}  	} + +	if (editor_enabled) { +		ensure_focus(); +	}  }  void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { @@ -1274,23 +1281,28 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() {  }  void ScriptTextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { +	ERR_FAIL_COND(p_highlighter.is_null()); +  	highlighters[p_highlighter->_get_name()] = p_highlighter;  	highlighter_menu->add_radio_check_item(p_highlighter->_get_name());  }  void ScriptTextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { +	ERR_FAIL_COND(p_highlighter.is_null()); + +	Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front(); +	while (el != nullptr) { +		int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key()); +		highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter); +		el = el->next(); +	} +  	TextEdit *te = code_editor->get_text_edit();  	p_highlighter->_set_edited_resource(script);  	te->set_syntax_highlighter(p_highlighter); -	highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->_get_name()), true);  }  void ScriptTextEditor::_change_syntax_highlighter(int p_idx) { -	Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front(); -	while (el != nullptr) { -		highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false); -		el = el->next(); -	}  	set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);  } @@ -1606,64 +1618,40 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p  	context_menu->popup();  } -ScriptTextEditor::ScriptTextEditor() { -	theme_loaded = false; -	script_is_valid = false; +void ScriptTextEditor::_enable_code_editor() { +	ERR_FAIL_COND(code_editor->get_parent());  	VSplitContainer *editor_box = memnew(VSplitContainer);  	add_child(editor_box);  	editor_box->set_anchors_and_margins_preset(Control::PRESET_WIDE);  	editor_box->set_v_size_flags(SIZE_EXPAND_FILL); -	code_editor = memnew(CodeTextEditor);  	editor_box->add_child(code_editor); -	code_editor->add_theme_constant_override("separation", 2); -	code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE); +	code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));  	code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script));  	code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings)); -	code_editor->set_code_complete_func(_code_complete_scripts, this);  	code_editor->get_text_edit()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled));  	code_editor->get_text_edit()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol));  	code_editor->get_text_edit()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol)); -	code_editor->get_text_edit()->connect("info_clicked", callable_mp(this, &ScriptTextEditor::_lookup_connections)); -	code_editor->set_v_size_flags(SIZE_EXPAND_FILL); +	code_editor->get_text_edit()->connect("gui_input", callable_mp(this, &ScriptTextEditor::_text_edit_gui_input));  	code_editor->show_toggle_scripts_button(); -	warnings_panel = memnew(RichTextLabel);  	editor_box->add_child(warnings_panel);  	warnings_panel->add_theme_font_override(  			"normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts")); -	warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); -	warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); -	warnings_panel->set_meta_underline(true); -	warnings_panel->set_selection_enabled(true); -	warnings_panel->set_focus_mode(FOCUS_CLICK); -	warnings_panel->hide(); - -	code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));  	warnings_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_warning_clicked)); -	update_settings(); - -	code_editor->get_text_edit()->set_callhint_settings( -			EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"), -			EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset")); - -	code_editor->get_text_edit()->set_select_identifiers_on_hover(true); -	code_editor->get_text_edit()->set_context_menu_enabled(false); -	code_editor->get_text_edit()->connect("gui_input", callable_mp(this, &ScriptTextEditor::_text_edit_gui_input)); - -	context_menu = memnew(PopupMenu);  	add_child(context_menu);  	context_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); -	color_panel = memnew(PopupPanel);  	add_child(color_panel); +  	color_picker = memnew(ColorPicker);  	color_picker->set_deferred_mode(true); -	color_panel->add_child(color_picker);  	color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_color_changed)); +	color_panel->add_child(color_picker); +  	// get default color picker mode from editor settings  	int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode");  	if (default_color_mode == 1) { @@ -1672,12 +1660,27 @@ ScriptTextEditor::ScriptTextEditor() {  		color_picker->set_raw_mode(true);  	} -	edit_hb = memnew(HBoxContainer); +	quick_open = memnew(ScriptEditorQuickOpen); +	quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line)); +	add_child(quick_open); -	edit_menu = memnew(MenuButton); -	edit_menu->set_text(TTR("Edit")); -	edit_menu->set_switch_on_hover(true); +	goto_line_dialog = memnew(GotoLineDialog); +	add_child(goto_line_dialog); + +	add_child(connection_info_dialog); + +	edit_hb->add_child(search_menu); +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); +	search_menu->get_popup()->add_separator(); +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); +	search_menu->get_popup()->add_separator(); +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL); +	search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); +	edit_hb->add_child(edit_menu);  	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO);  	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO);  	edit_menu->get_popup()->add_separator(); @@ -1707,8 +1710,6 @@ ScriptTextEditor::ScriptTextEditor() {  	edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));  	edit_menu->get_popup()->add_separator(); -	PopupMenu *convert_case = memnew(PopupMenu); -	convert_case->set_name("convert_case");  	edit_menu->get_popup()->add_child(convert_case);  	edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case");  	convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KEY_MASK_SHIFT | KEY_F4), EDIT_TO_UPPERCASE); @@ -1716,12 +1717,73 @@ ScriptTextEditor::ScriptTextEditor() {  	convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KEY_MASK_SHIFT | KEY_F6), EDIT_CAPITALIZE);  	convert_case->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); -	highlighter_menu = memnew(PopupMenu); -	highlighter_menu->set_name("highlighter_menu");  	edit_menu->get_popup()->add_child(highlighter_menu);  	edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu");  	highlighter_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter)); +	_load_theme_settings(); + +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); +	edit_hb->add_child(goto_menu); +	goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION); +	goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); +	goto_menu->get_popup()->add_separator(); + +	goto_menu->get_popup()->add_child(bookmarks_menu); +	goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); +	_update_bookmark_list(); +	bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list)); +	bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed)); + +	goto_menu->get_popup()->add_child(breakpoints_menu); +	goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "Breakpoints"); +	_update_breakpoint_list(); +	breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list)); +	breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed)); + +	goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); +} + +ScriptTextEditor::ScriptTextEditor() { +	code_editor = memnew(CodeTextEditor); +	code_editor->add_theme_constant_override("separation", 2); +	code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE); +	code_editor->set_code_complete_func(_code_complete_scripts, this); +	code_editor->set_v_size_flags(SIZE_EXPAND_FILL); + +	warnings_panel = memnew(RichTextLabel); +	warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); +	warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); +	warnings_panel->set_meta_underline(true); +	warnings_panel->set_selection_enabled(true); +	warnings_panel->set_focus_mode(FOCUS_CLICK); +	warnings_panel->hide(); + +	update_settings(); + +	code_editor->get_text_edit()->set_callhint_settings( +			EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"), +			EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset")); + +	code_editor->get_text_edit()->set_select_identifiers_on_hover(true); +	code_editor->get_text_edit()->set_context_menu_enabled(false); + +	context_menu = memnew(PopupMenu); + +	color_panel = memnew(PopupPanel); + +	edit_hb = memnew(HBoxContainer); + +	edit_menu = memnew(MenuButton); +	edit_menu->set_text(TTR("Edit")); +	edit_menu->set_switch_on_hover(true); + +	convert_case = memnew(PopupMenu); +	convert_case->set_name("convert_case"); + +	highlighter_menu = memnew(PopupMenu); +	highlighter_menu->set_name("highlighter_menu"); +  	Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter;  	plain_highlighter.instance();  	add_syntax_highlighter(plain_highlighter); @@ -1732,64 +1794,42 @@ ScriptTextEditor::ScriptTextEditor() {  	set_syntax_highlighter(highlighter);  	search_menu = memnew(MenuButton); -	edit_hb->add_child(search_menu);  	search_menu->set_text(TTR("Search"));  	search_menu->set_switch_on_hover(true); -	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND); -	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT); -	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); -	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); -	search_menu->get_popup()->add_separator(); -	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); -	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES); -	search_menu->get_popup()->add_separator(); -	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL); -	search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); - -	edit_hb->add_child(edit_menu); - -	MenuButton *goto_menu = memnew(MenuButton); -	edit_hb->add_child(goto_menu); +	goto_menu = memnew(MenuButton);  	goto_menu->set_text(TTR("Go To"));  	goto_menu->set_switch_on_hover(true); -	goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); - -	goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION); -	goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); -	goto_menu->get_popup()->add_separator();  	bookmarks_menu = memnew(PopupMenu);  	bookmarks_menu->set_name("Bookmarks"); -	goto_menu->get_popup()->add_child(bookmarks_menu); -	goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); -	_update_bookmark_list(); -	bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list)); -	bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed));  	breakpoints_menu = memnew(PopupMenu);  	breakpoints_menu->set_name("Breakpoints"); -	goto_menu->get_popup()->add_child(breakpoints_menu); -	goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "Breakpoints"); -	_update_breakpoint_list(); -	breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list)); -	breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed)); - -	quick_open = memnew(ScriptEditorQuickOpen); -	add_child(quick_open); -	quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line)); - -	goto_line_dialog = memnew(GotoLineDialog); -	add_child(goto_line_dialog);  	connection_info_dialog = memnew(ConnectionInfoDialog); -	add_child(connection_info_dialog);  	code_editor->get_text_edit()->set_drag_forwarding(this);  }  ScriptTextEditor::~ScriptTextEditor() {  	highlighters.clear(); + +	if (!editor_enabled) { +		memdelete(code_editor); +		memdelete(warnings_panel); +		memdelete(context_menu); +		memdelete(color_panel); +		memdelete(edit_hb); +		memdelete(edit_menu); +		memdelete(convert_case); +		memdelete(highlighter_menu); +		memdelete(search_menu); +		memdelete(goto_menu); +		memdelete(bookmarks_menu); +		memdelete(breakpoints_menu); +		memdelete(connection_info_dialog); +	}  }  static ScriptEditorBase *create_editor(const RES &p_resource) { diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 6d7f84d746..e931c9fdc6 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -39,8 +39,8 @@  class ConnectionInfoDialog : public AcceptDialog {  	GDCLASS(ConnectionInfoDialog, AcceptDialog); -	Label *method; -	Tree *tree; +	Label *method = nullptr; +	Tree *tree = nullptr;  	virtual void ok_pressed() override; @@ -53,11 +53,12 @@ public:  class ScriptTextEditor : public ScriptEditorBase {  	GDCLASS(ScriptTextEditor, ScriptEditorBase); -	CodeTextEditor *code_editor; -	RichTextLabel *warnings_panel; +	CodeTextEditor *code_editor = nullptr; +	RichTextLabel *warnings_panel = nullptr;  	Ref<Script> script; -	bool script_is_valid; +	bool script_is_valid = false; +	bool editor_enabled = false;  	Vector<String> functions; @@ -65,25 +66,27 @@ class ScriptTextEditor : public ScriptEditorBase {  	Vector<String> member_keywords; -	HBoxContainer *edit_hb; +	HBoxContainer *edit_hb = nullptr; -	MenuButton *edit_menu; -	MenuButton *search_menu; -	PopupMenu *bookmarks_menu; -	PopupMenu *breakpoints_menu; -	PopupMenu *highlighter_menu; -	PopupMenu *context_menu; +	MenuButton *edit_menu = nullptr; +	MenuButton *search_menu = nullptr; +	MenuButton *goto_menu = nullptr; +	PopupMenu *bookmarks_menu = nullptr; +	PopupMenu *breakpoints_menu = nullptr; +	PopupMenu *highlighter_menu = nullptr; +	PopupMenu *context_menu = nullptr; +	PopupMenu *convert_case = nullptr; -	GotoLineDialog *goto_line_dialog; -	ScriptEditorQuickOpen *quick_open; -	ConnectionInfoDialog *connection_info_dialog; +	GotoLineDialog *goto_line_dialog = nullptr; +	ScriptEditorQuickOpen *quick_open = nullptr; +	ConnectionInfoDialog *connection_info_dialog = nullptr; -	PopupPanel *color_panel; -	ColorPicker *color_picker; +	PopupPanel *color_panel = nullptr; +	ColorPicker *color_picker = nullptr;  	Vector2 color_position;  	String color_args; -	bool theme_loaded; +	bool theme_loaded = false;  	enum {  		EDIT_UNDO, @@ -132,6 +135,8 @@ class ScriptTextEditor : public ScriptEditorBase {  		LOOKUP_SYMBOL,  	}; +	void _enable_code_editor(); +  protected:  	void _update_breakpoint_list();  	void _breakpoint_item_pressed(int p_idx); @@ -149,7 +154,6 @@ protected:  	void _show_warnings_panel(bool p_show);  	void _warning_clicked(Variant p_line); -	void _notification(int p_what);  	static void _bind_methods();  	Map<String, Ref<EditorSyntaxHighlighter>> highlighters; @@ -185,6 +189,7 @@ public:  	virtual void apply_code() override;  	virtual RES get_edited_resource() const override;  	virtual void set_edited_resource(const RES &p_res) override; +	virtual void enable_editor() override;  	virtual Vector<String> get_functions() override;  	virtual void reload_text() override;  	virtual String get_name() override; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 60ba3802fb..dc2abe15ee 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -136,28 +136,39 @@ void ShaderTextEditor::_load_theme_settings() {  	syntax_highlighter->set_function_color(EDITOR_GET("text_editor/highlighting/function_color"));  	syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/highlighting/member_variable_color")); +	syntax_highlighter->clear_keyword_colors(); +  	List<String> keywords;  	ShaderLanguage::get_keyword_list(&keywords); +	const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + +	for (List<String>::Element *E = keywords.front(); E; E = E->next()) { +		syntax_highlighter->add_keyword_color(E->get(), keyword_color); +	} +	// Colorize built-ins like `COLOR` differently to make them easier +	// to distinguish from keywords at a quick glance. + +	List<String> built_ins;  	if (shader.is_valid()) {  		for (const Map<StringName, ShaderLanguage::FunctionInfo>::Element *E = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())).front(); E; E = E->next()) {  			for (const Map<StringName, ShaderLanguage::BuiltInInfo>::Element *F = E->get().built_ins.front(); F; F = F->next()) { -				keywords.push_back(F->key()); +				built_ins.push_back(F->key());  			}  		}  		for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())).size(); i++) { -			keywords.push_back(ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()))[i]); +			built_ins.push_back(ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()))[i]);  		}  	} -	const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); -	syntax_highlighter->clear_keyword_colors(); -	for (List<String>::Element *E = keywords.front(); E; E = E->next()) { -		syntax_highlighter->add_keyword_color(E->get(), keyword_color); +	const Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + +	for (List<String>::Element *E = built_ins.front(); E; E = E->next()) { +		syntax_highlighter->add_keyword_color(E->get(), member_variable_color);  	} -	//colorize comments +	// Colorize comments.  	const Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color");  	syntax_highlighter->clear_color_regions();  	syntax_highlighter->add_color_region("/*", "*/", comment_color, false); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index d602d152fe..6d64e3b3cd 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -34,22 +34,27 @@  #include "editor/editor_node.h"  void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { +	ERR_FAIL_COND(p_highlighter.is_null()); +  	highlighters[p_highlighter->_get_name()] = p_highlighter;  	highlighter_menu->add_radio_check_item(p_highlighter->_get_name());  }  void TextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { -	TextEdit *te = code_editor->get_text_edit(); -	te->set_syntax_highlighter(p_highlighter); -	highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->_get_name()), true); -} +	ERR_FAIL_COND(p_highlighter.is_null()); -void TextEditor::_change_syntax_highlighter(int p_idx) {  	Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front();  	while (el != nullptr) { -		highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false); +		int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key()); +		highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter);  		el = el->next();  	} + +	TextEdit *te = code_editor->get_text_edit(); +	te->set_syntax_highlighter(p_highlighter); +} + +void TextEditor::_change_syntax_highlighter(int p_idx) {  	set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);  } @@ -126,7 +131,7 @@ String TextEditor::get_name() {  }  Ref<Texture2D> TextEditor::get_theme_icon() { -	return EditorNode::get_singleton()->get_object_icon(text_file.operator->(), ""); +	return EditorNode::get_singleton()->get_object_icon(text_file.ptr(), "");  }  RES TextEditor::get_edited_resource() const { @@ -134,7 +139,8 @@ RES TextEditor::get_edited_resource() const {  }  void TextEditor::set_edited_resource(const RES &p_res) { -	ERR_FAIL_COND(!text_file.is_null()); +	ERR_FAIL_COND(text_file.is_valid()); +	ERR_FAIL_COND(p_res.is_null());  	text_file = p_res; @@ -146,6 +152,16 @@ void TextEditor::set_edited_resource(const RES &p_res) {  	code_editor->update_line_and_column();  } +void TextEditor::enable_editor() { +	if (editor_enabled) { +		return; +	} + +	editor_enabled = true; + +	_load_theme_settings(); +} +  void TextEditor::add_callback(const String &p_function, PackedStringArray p_args) {  } @@ -237,6 +253,8 @@ void TextEditor::set_edit_state(const Variant &p_state) {  			_change_syntax_highlighter(idx);  		}  	} + +	ensure_focus();  }  void TextEditor::trim_trailing_whitespace() { @@ -303,14 +321,6 @@ void TextEditor::clear_edit_menu() {  	memdelete(edit_hb);  } -void TextEditor::_notification(int p_what) { -	switch (p_what) { -		case NOTIFICATION_READY: -			_load_theme_settings(); -			break; -	} -} -  void TextEditor::_edit_option(int p_op) {  	TextEdit *tx = code_editor->get_text_edit(); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 5299776b56..f3e9e599cf 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -37,18 +37,19 @@ class TextEditor : public ScriptEditorBase {  	GDCLASS(TextEditor, ScriptEditorBase);  private: -	CodeTextEditor *code_editor; +	CodeTextEditor *code_editor = nullptr;  	Ref<TextFile> text_file; +	bool editor_enabled = false; -	HBoxContainer *edit_hb; -	MenuButton *edit_menu; -	PopupMenu *highlighter_menu; -	MenuButton *search_menu; -	PopupMenu *bookmarks_menu; -	PopupMenu *context_menu; +	HBoxContainer *edit_hb = nullptr; +	MenuButton *edit_menu = nullptr; +	PopupMenu *highlighter_menu = nullptr; +	MenuButton *search_menu = nullptr; +	PopupMenu *bookmarks_menu = nullptr; +	PopupMenu *context_menu = nullptr; -	GotoLineDialog *goto_line_dialog; +	GotoLineDialog *goto_line_dialog = nullptr;  	enum {  		EDIT_UNDO, @@ -88,8 +89,6 @@ private:  protected:  	static void _bind_methods(); -	void _notification(int p_what); -  	void _edit_option(int p_op);  	void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position);  	void _text_edit_gui_input(const Ref<InputEvent> &ev); @@ -113,7 +112,7 @@ public:  	virtual Ref<Texture2D> get_theme_icon() override;  	virtual RES get_edited_resource() const override;  	virtual void set_edited_resource(const RES &p_res) override; -	void set_edited_file(const Ref<TextFile> &p_file); +	virtual void enable_editor() override;  	virtual void reload_text() override;  	virtual void apply_code() override;  	virtual bool is_unsaved() override; diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 43ace737c0..18a107ff75 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -828,7 +828,7 @@ ThemeEditor::ThemeEditor() {  	type_hbc->add_child(type_edit);  	type_menu = memnew(MenuButton);  	type_menu->set_flat(false); -	type_menu->set_text(".."); +	type_menu->set_text("...");  	type_hbc->add_child(type_menu);  	type_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_type_menu_cbk)); @@ -846,7 +846,7 @@ ThemeEditor::ThemeEditor() {  	name_hbc->add_child(name_edit);  	name_menu = memnew(MenuButton);  	type_menu->set_flat(false); -	name_menu->set_text(".."); +	name_menu->set_text("...");  	name_hbc->add_child(name_menu);  	name_menu->get_popup()->connect("about_to_popup", callable_mp(this, &ThemeEditor::_name_menu_about_to_show)); diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp index 7fb751e3ed..a613174ed9 100644 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ b/editor/plugins/tile_set_editor_plugin.cpp @@ -698,7 +698,7 @@ void TileSetEditor::_on_tileset_toolbar_confirm() {  			List<int> ids;  			tileset->get_tile_list(&ids); -			undo_redo->create_action(TTR(option == TOOL_TILESET_MERGE_SCENE ? "Merge Tileset from Scene" : "Create Tileset from Scene")); +			undo_redo->create_action(option == TOOL_TILESET_MERGE_SCENE ? TTR("Merge Tileset from Scene") : TTR("Create Tileset from Scene"));  			undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE);  			undo_redo->add_undo_method(tileset.ptr(), "clear");  			for (List<int>::Element *E = ids.front(); E; E = E->next()) { diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp index 6a54894f40..211e365454 100644 --- a/editor/rename_dialog.cpp +++ b/editor/rename_dialog.cpp @@ -141,7 +141,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und  	but_insert_name = memnew(Button);  	but_insert_name->set_text("NAME"); -	but_insert_name->set_tooltip(String("${NAME}\n") + TTR("Node name")); +	but_insert_name->set_tooltip(String("${NAME}\n") + TTR("Node name."));  	but_insert_name->set_focus_mode(Control::FOCUS_NONE);  	but_insert_name->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${NAME}"));  	but_insert_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -151,7 +151,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und  	but_insert_parent = memnew(Button);  	but_insert_parent->set_text("PARENT"); -	but_insert_parent->set_tooltip(String("${PARENT}\n") + TTR("Node's parent name, if available")); +	but_insert_parent->set_tooltip(String("${PARENT}\n") + TTR("Node's parent name, if available."));  	but_insert_parent->set_focus_mode(Control::FOCUS_NONE);  	but_insert_parent->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${PARENT}"));  	but_insert_parent->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -161,7 +161,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und  	but_insert_type = memnew(Button);  	but_insert_type->set_text("TYPE"); -	but_insert_type->set_tooltip(String("${TYPE}\n") + TTR("Node type")); +	but_insert_type->set_tooltip(String("${TYPE}\n") + TTR("Node type."));  	but_insert_type->set_focus_mode(Control::FOCUS_NONE);  	but_insert_type->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${TYPE}"));  	but_insert_type->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -171,7 +171,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und  	but_insert_scene = memnew(Button);  	but_insert_scene->set_text("SCENE"); -	but_insert_scene->set_tooltip(String("${SCENE}\n") + TTR("Current scene name")); +	but_insert_scene->set_tooltip(String("${SCENE}\n") + TTR("Current scene name."));  	but_insert_scene->set_focus_mode(Control::FOCUS_NONE);  	but_insert_scene->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${SCENE}"));  	but_insert_scene->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -181,7 +181,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und  	but_insert_root = memnew(Button);  	but_insert_root->set_text("ROOT"); -	but_insert_root->set_tooltip(String("${ROOT}\n") + TTR("Root node name")); +	but_insert_root->set_tooltip(String("${ROOT}\n") + TTR("Root node name."));  	but_insert_root->set_focus_mode(Control::FOCUS_NONE);  	but_insert_root->connect("pressed", callable_mp(this, &RenameDialog::_insert_text), make_binds("${ROOT}"));  	but_insert_root->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -199,7 +199,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und  	chk_per_level_counter = memnew(CheckBox);  	chk_per_level_counter->set_text(TTR("Per-level Counter")); -	chk_per_level_counter->set_tooltip(TTR("If set the counter restarts for each group of child nodes")); +	chk_per_level_counter->set_tooltip(TTR("If set the counter restarts for each group of child nodes."));  	vbc_substitute->add_child(chk_per_level_counter);  	HBoxContainer *hbc_count_options = memnew(HBoxContainer); @@ -207,22 +207,22 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_und  	Label *lbl_count_start = memnew(Label);  	lbl_count_start->set_text(TTR("Start")); -	lbl_count_start->set_tooltip(TTR("Initial value for the counter")); +	lbl_count_start->set_tooltip(TTR("Initial value for the counter."));  	hbc_count_options->add_child(lbl_count_start);  	spn_count_start = memnew(SpinBox); -	spn_count_start->set_tooltip(TTR("Initial value for the counter")); +	spn_count_start->set_tooltip(TTR("Initial value for the counter."));  	spn_count_start->set_step(1);  	spn_count_start->set_min(0);  	hbc_count_options->add_child(spn_count_start);  	Label *lbl_count_step = memnew(Label);  	lbl_count_step->set_text(TTR("Step")); -	lbl_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node")); +	lbl_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node."));  	hbc_count_options->add_child(lbl_count_step);  	spn_count_step = memnew(SpinBox); -	spn_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node")); +	spn_count_step->set_tooltip(TTR("Amount by which counter is incremented for each node."));  	spn_count_step->set_step(1);  	hbc_count_options->add_child(spn_count_step); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 13fb74987c..ce869feddd 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1100,6 +1100,7 @@ void SceneTreeDock::_notification(int p_what) {  			node_shortcuts_toggle->set_name("NodeShortcutsToggle");  			node_shortcuts_toggle->set_icon(get_theme_icon("Favorites", "EditorIcons"));  			node_shortcuts_toggle->set_toggle_mode(true); +			node_shortcuts_toggle->set_tooltip(TTR("Switch to Favorite Nodes"));  			node_shortcuts_toggle->set_pressed(EDITOR_GET("_use_favorites_root_selection"));  			node_shortcuts_toggle->set_anchors_and_margins_preset(Control::PRESET_CENTER_RIGHT);  			node_shortcuts_toggle->connect("pressed", callable_mp(this, &SceneTreeDock::_update_create_root_dialog)); @@ -2717,7 +2718,7 @@ void SceneTreeDock::_update_create_root_dialog() {  				if (l != String()) {  					Button *button = memnew(Button);  					favorite_nodes->add_child(button); -					button->set_text(TTR(l)); +					button->set_text(l);  					String name = l.get_slicec(' ', 0);  					if (ScriptServer::is_global_class(name)) {  						name = ScriptServer::get_global_class_native_base(name); diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index ffdf8208b8..628475bbc0 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -603,7 +603,7 @@ void ScriptCreateDialog::_path_entered(const String &p_path) {  }  void ScriptCreateDialog::_msg_script_valid(bool valid, const String &p_msg) { -	error_label->set_text("- " + TTR(p_msg)); +	error_label->set_text("- " + p_msg);  	if (valid) {  		error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor"));  	} else { @@ -612,7 +612,7 @@ void ScriptCreateDialog::_msg_script_valid(bool valid, const String &p_msg) {  }  void ScriptCreateDialog::_msg_path_valid(bool valid, const String &p_msg) { -	path_error_label->set_text("- " + TTR(p_msg)); +	path_error_label->set_text("- " + p_msg);  	if (valid) {  		path_error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor"));  	} else { diff --git a/main/SCsub b/main/SCsub index 7a301b82bc..97f77840f1 100644 --- a/main/SCsub +++ b/main/SCsub @@ -7,22 +7,23 @@ import main_builders  env.main_sources = [] -env.add_source_files(env.main_sources, "*.cpp") +env_main = env.Clone() +env_main.add_source_files(env.main_sources, "*.cpp") -env.Depends("#main/splash.gen.h", "#main/splash.png") -env.CommandNoCache("#main/splash.gen.h", "#main/splash.png", run_in_subprocess(main_builders.make_splash)) +if env["tests"]: +    env_main.Append(CPPDEFINES=["TESTS_ENABLED"]) -env.Depends("#main/splash_editor.gen.h", "#main/splash_editor.png") -env.CommandNoCache( +env_main.Depends("#main/splash.gen.h", "#main/splash.png") +env_main.CommandNoCache("#main/splash.gen.h", "#main/splash.png", run_in_subprocess(main_builders.make_splash)) + +env_main.Depends("#main/splash_editor.gen.h", "#main/splash_editor.png") +env_main.CommandNoCache(      "#main/splash_editor.gen.h", "#main/splash_editor.png", run_in_subprocess(main_builders.make_splash_editor)  ) -env.Depends("#main/app_icon.gen.h", "#main/app_icon.png") -env.CommandNoCache("#main/app_icon.gen.h", "#main/app_icon.png", run_in_subprocess(main_builders.make_app_icon)) - -if env["tools"]: -    SConscript("tests/SCsub") +env_main.Depends("#main/app_icon.gen.h", "#main/app_icon.png") +env_main.CommandNoCache("#main/app_icon.gen.h", "#main/app_icon.png", run_in_subprocess(main_builders.make_app_icon)) -lib = env.add_library("main", env.main_sources) +lib = env_main.add_library("main", env.main_sources)  env.Prepend(LIBS=[lib]) diff --git a/main/main.cpp b/main/main.cpp index a500e173a2..6965e5415a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -30,6 +30,7 @@  #include "main.h" +#include "core/core_string_names.h"  #include "core/crypto/crypto.h"  #include "core/debugger/engine_debugger.h"  #include "core/input/input.h" @@ -54,7 +55,6 @@  #include "main/performance.h"  #include "main/splash.gen.h"  #include "main/splash_editor.gen.h" -#include "main/tests/test_main.h"  #include "modules/modules_enabled.gen.h"  #include "modules/register_module_types.h"  #include "platform/register_platform_apis.h" @@ -74,13 +74,19 @@  #include "servers/rendering/rendering_server_wrap_mt.h"  #include "servers/xr_server.h" +#ifdef TESTS_ENABLED +#include "tests/test_main.h" +#endif +  #ifdef TOOLS_ENABLED +  #include "editor/doc_data.h"  #include "editor/doc_data_class_path.gen.h"  #include "editor/editor_node.h"  #include "editor/editor_settings.h"  #include "editor/progress_dialog.h"  #include "editor/project_manager.h" +  #endif  /* Static members */ @@ -186,7 +192,8 @@ static String get_full_version_string() {  // to have less code in main.cpp.  void initialize_physics() {  	/// 3D Physics Server -	physics_server = PhysicsServer3DManager::new_server(ProjectSettings::get_singleton()->get(PhysicsServer3DManager::setting_property_name)); +	physics_server = PhysicsServer3DManager::new_server( +			ProjectSettings::get_singleton()->get(PhysicsServer3DManager::setting_property_name));  	if (!physics_server) {  		// Physics server not found, Use the default physics  		physics_server = PhysicsServer3DManager::new_default_server(); @@ -195,7 +202,8 @@ void initialize_physics() {  	physics_server->init();  	/// 2D Physics server -	physics_2d_server = PhysicsServer2DManager::new_server(ProjectSettings::get_singleton()->get(PhysicsServer2DManager::setting_property_name)); +	physics_2d_server = PhysicsServer2DManager::new_server( +			ProjectSettings::get_singleton()->get(PhysicsServer2DManager::setting_property_name));  	if (!physics_2d_server) {  		// Physics server not found, Use the default physics  		physics_2d_server = PhysicsServer2DManager::new_default_server(); @@ -254,20 +262,25 @@ void Main::print_help(const char *p_binary) {  	OS::get_singleton()->print("  -h, --help                       Display this help message.\n");  	OS::get_singleton()->print("  --version                        Display the version string.\n");  	OS::get_singleton()->print("  -v, --verbose                    Use verbose stdout mode.\n"); -	OS::get_singleton()->print("  --quiet                          Quiet mode, silences stdout messages. Errors are still displayed.\n"); +	OS::get_singleton()->print( +			"  --quiet                          Quiet mode, silences stdout messages. Errors are still displayed.\n");  	OS::get_singleton()->print("\n");  	OS::get_singleton()->print("Run options:\n");  #ifdef TOOLS_ENABLED  	OS::get_singleton()->print("  -e, --editor                     Start the editor instead of running the scene.\n"); -	OS::get_singleton()->print("  -p, --project-manager            Start the project manager, even if a project is auto-detected.\n"); +	OS::get_singleton()->print( +			"  -p, --project-manager            Start the project manager, even if a project is auto-detected.\n");  #endif  	OS::get_singleton()->print("  -q, --quit                       Quit after the first iteration.\n"); -	OS::get_singleton()->print("  -l, --language <locale>          Use a specific locale (<locale> being a two-letter code).\n"); -	OS::get_singleton()->print("  --path <directory>               Path to a project (<directory> must contain a 'project.godot' file).\n"); +	OS::get_singleton()->print( +			"  -l, --language <locale>          Use a specific locale (<locale> being a two-letter code).\n"); +	OS::get_singleton()->print( +			"  --path <directory>               Path to a project (<directory> must contain a 'project.godot' file).\n");  	OS::get_singleton()->print("  -u, --upwards                    Scan folders upwards for project.godot file.\n");  	OS::get_singleton()->print("  --main-pack <file>               Path to a pack (.pck) file to load.\n"); -	OS::get_singleton()->print("  --render-thread <mode>           Render thread mode ('unsafe', 'safe', 'separate').\n"); +	OS::get_singleton()->print( +			"  --render-thread <mode>           Render thread mode ('unsafe', 'safe', 'separate').\n");  	OS::get_singleton()->print("  --remote-fs <address>            Remote filesystem (<host/IP>[:<port>] address).\n");  	OS::get_singleton()->print("  --remote-fs-password <password>  Password for remote filesystem.\n"); @@ -308,9 +321,12 @@ void Main::print_help(const char *p_binary) {  	OS::get_singleton()->print("  --resolution <W>x<H>             Request window resolution.\n");  	OS::get_singleton()->print("  --position <X>,<Y>               Request window position.\n");  	OS::get_singleton()->print("  --low-dpi                        Force low-DPI mode (macOS and Windows only).\n"); -	OS::get_singleton()->print("  --no-window                      Disable window creation (Windows only). Useful together with --script.\n"); -	OS::get_singleton()->print("  --enable-vsync-via-compositor    When vsync is enabled, vsync via the OS' window compositor (Windows only).\n"); -	OS::get_singleton()->print("  --disable-vsync-via-compositor   Disable vsync via the OS' window compositor (Windows only).\n"); +	OS::get_singleton()->print( +			"  --no-window                      Disable window creation (Windows only). Useful together with --script.\n"); +	OS::get_singleton()->print( +			"  --enable-vsync-via-compositor    When vsync is enabled, vsync via the OS' window compositor (Windows only).\n"); +	OS::get_singleton()->print( +			"  --disable-vsync-via-compositor   Disable vsync via the OS' window compositor (Windows only).\n");  	OS::get_singleton()->print("  --single-window                  Use a single window (no separate subwindows).\n");  	OS::get_singleton()->print("  --tablet-driver                  Tablet input driver (");  	for (int i = 0; i < OS::get_singleton()->get_tablet_driver_count(); i++) { @@ -324,36 +340,53 @@ void Main::print_help(const char *p_binary) {  	OS::get_singleton()->print("Debug options:\n");  	OS::get_singleton()->print("  -d, --debug                      Debug (local stdout debugger).\n"); -	OS::get_singleton()->print("  -b, --breakpoints                Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n"); +	OS::get_singleton()->print( +			"  -b, --breakpoints                Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n");  	OS::get_singleton()->print("  --profiling                      Enable profiling in the script debugger.\n"); -	OS::get_singleton()->print("  --gpu-abort                      Abort on GPU errors (usually validation layer errors), may help see the problem if your system freezes.\n"); -	OS::get_singleton()->print("  --remote-debug <uri>             Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n"); +	OS::get_singleton()->print( +			"  --gpu-abort                      Abort on GPU errors (usually validation layer errors), may help see the problem if your system freezes.\n"); +	OS::get_singleton()->print( +			"  --remote-debug <uri>             Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");  #if defined(DEBUG_ENABLED) && !defined(SERVER_ENABLED)  	OS::get_singleton()->print("  --debug-collisions               Show collision shapes when running the scene.\n");  	OS::get_singleton()->print("  --debug-navigation               Show navigation polygons when running the scene.\n");  #endif -	OS::get_singleton()->print("  --frame-delay <ms>               Simulate high CPU load (delay each frame by <ms> milliseconds).\n"); -	OS::get_singleton()->print("  --time-scale <scale>             Force time scale (higher values are faster, 1.0 is normal speed).\n"); -	OS::get_singleton()->print("  --disable-render-loop            Disable render loop so rendering only occurs when called explicitly from script.\n"); -	OS::get_singleton()->print("  --disable-crash-handler          Disable crash handler when supported by the platform code.\n"); -	OS::get_singleton()->print("  --fixed-fps <fps>                Force a fixed number of frames per second. This setting disables real-time synchronization.\n"); +	OS::get_singleton()->print( +			"  --frame-delay <ms>               Simulate high CPU load (delay each frame by <ms> milliseconds).\n"); +	OS::get_singleton()->print( +			"  --time-scale <scale>             Force time scale (higher values are faster, 1.0 is normal speed).\n"); +	OS::get_singleton()->print( +			"  --disable-render-loop            Disable render loop so rendering only occurs when called explicitly from script.\n"); +	OS::get_singleton()->print( +			"  --disable-crash-handler          Disable crash handler when supported by the platform code.\n"); +	OS::get_singleton()->print( +			"  --fixed-fps <fps>                Force a fixed number of frames per second. This setting disables real-time synchronization.\n");  	OS::get_singleton()->print("  --print-fps                      Print the frames per second to the stdout.\n");  	OS::get_singleton()->print("\n");  	OS::get_singleton()->print("Standalone tools:\n");  	OS::get_singleton()->print("  -s, --script <script>            Run a script.\n"); -	OS::get_singleton()->print("  --check-only                     Only parse for errors and quit (use with --script).\n"); +	OS::get_singleton()->print( +			"  --check-only                     Only parse for errors and quit (use with --script).\n");  #ifdef TOOLS_ENABLED -	OS::get_singleton()->print("  --export <preset> <path>         Export the project using the given preset and matching release template. The preset name should match one defined in export_presets.cfg.\n"); -	OS::get_singleton()->print("                                   <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe'). The target directory should exist.\n"); +	OS::get_singleton()->print( +			"  --export <preset> <path>         Export the project using the given preset and matching release template. The preset name should match one defined in export_presets.cfg.\n"); +	OS::get_singleton()->print( +			"                                   <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe'). The target directory should exist.\n");  	OS::get_singleton()->print("  --export-debug <preset> <path>   Same as --export, but using the debug template.\n"); -	OS::get_singleton()->print("  --export-pack <preset> <path>    Same as --export, but only export the game pack for the given preset. The <path> extension determines whether it will be in PCK or ZIP format.\n"); -	OS::get_singleton()->print("  --doctool <path>                 Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n"); -	OS::get_singleton()->print("  --no-docbase                     Disallow dumping the base types (used with --doctool).\n"); -	OS::get_singleton()->print("  --build-solutions                Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); +	OS::get_singleton()->print( +			"  --export-pack <preset> <path>    Same as --export, but only export the game pack for the given preset. The <path> extension determines whether it will be in PCK or ZIP format.\n"); +	OS::get_singleton()->print( +			"  --doctool <path>                 Dump the engine API reference to the given <path> in XML format, merging if existing files are found.\n"); +	OS::get_singleton()->print( +			"  --no-docbase                     Disallow dumping the base types (used with --doctool).\n"); +	OS::get_singleton()->print( +			"  --build-solutions                Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n");  #ifdef DEBUG_METHODS_ENABLED -	OS::get_singleton()->print("  --gdnative-generate-json-api     Generate JSON dump of the Godot API for GDNative bindings.\n"); +	OS::get_singleton()->print( +			"  --gdnative-generate-json-api     Generate JSON dump of the Godot API for GDNative bindings.\n");  #endif +#ifdef TESTS_ENABLED  	OS::get_singleton()->print("  --test <test>                    Run a unit test [");  	const char **test_names = tests_get_names();  	const char *comma = ""; @@ -364,6 +397,26 @@ void Main::print_help(const char *p_binary) {  	}  	OS::get_singleton()->print("].\n");  #endif +	OS::get_singleton()->print("\n"); +#endif +} + +int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) { +#ifdef TESTS_ENABLED +	for (int x = 0; x < argc; x++) { +		if (strncmp(argv[x], "--test", 6) == 0) { +			tests_need_run = true; +			OS::get_singleton()->initialize(); +			StringName::setup(); +			int status = test_main(argc, argv); +			StringName::cleanup(); +			// TODO: fix OS::singleton cleanup +			return status; +		} +	} +#endif +	tests_need_run = false; +	return 0;  }  /* Engine initialization @@ -418,7 +471,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	ClassDB::register_class<Performance>();  	engine->add_singleton(Engine::Singleton("Performance", performance)); -	GLOBAL_DEF("debug/settings/crash_handler/message", String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues")); +	GLOBAL_DEF("debug/settings/crash_handler/message", +			String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues"));  	MAIN_PRINT("Main: Parse CMDLine"); @@ -523,7 +577,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  				}  				if (!found) { -					OS::get_singleton()->print("Unknown audio driver '%s', aborting.\nValid options are ", audio_driver.utf8().get_data()); +					OS::get_singleton()->print("Unknown audio driver '%s', aborting.\nValid options are ", +							audio_driver.utf8().get_data());  					for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) {  						if (i == AudioDriverManager::get_driver_count() - 1) { @@ -559,7 +614,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  				}  				if (!found) { -					OS::get_singleton()->print("Unknown display driver '%s', aborting.\nValid options are ", display_driver.utf8().get_data()); +					OS::get_singleton()->print("Unknown display driver '%s', aborting.\nValid options are ", +							display_driver.utf8().get_data());  					for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {  						if (i == DisplayServer::get_create_function_count() - 1) { @@ -607,7 +663,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  				}  				if (!found) { -					OS::get_singleton()->print("Unknown tablet driver '%s', aborting.\n", tablet_driver.utf8().get_data()); +					OS::get_singleton()->print("Unknown tablet driver '%s', aborting.\n", +							tablet_driver.utf8().get_data());  					goto error;  				} @@ -629,7 +686,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  				if (vm.find("x") == -1) { // invalid parameter format -					OS::get_singleton()->print("Invalid resolution '%s', it should be e.g. '1280x720'.\n", vm.utf8().get_data()); +					OS::get_singleton()->print("Invalid resolution '%s', it should be e.g. '1280x720'.\n", +							vm.utf8().get_data());  					goto error;  				} @@ -637,7 +695,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  				int h = vm.get_slice("x", 1).to_int();  				if (w <= 0 || h <= 0) { -					OS::get_singleton()->print("Invalid resolution '%s', width and height must be above 0.\n", vm.utf8().get_data()); +					OS::get_singleton()->print("Invalid resolution '%s', width and height must be above 0.\n", +							vm.utf8().get_data());  					goto error;  				} @@ -658,7 +717,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  				if (vm.find(",") == -1) { // invalid parameter format -					OS::get_singleton()->print("Invalid position '%s', it should be e.g. '80,128'.\n", vm.utf8().get_data()); +					OS::get_singleton()->print("Invalid position '%s', it should be e.g. '80,128'.\n", +							vm.utf8().get_data());  					goto error;  				} @@ -754,7 +814,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  			// We still pass it to the main arguments since the argument handling itself is not done in this function  			main_args.push_back(I->get());  #endif -		} else if (I->get() == "--export" || I->get() == "--export-debug" || I->get() == "--export-pack") { // Export project +		} else if (I->get() == "--export" || I->get() == "--export-debug" || +				   I->get() == "--export-pack") { // Export project  			editor = true;  			main_args.push_back(I->get()); @@ -847,7 +908,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  			if (I->next()) {  				debug_uri = I->next()->get();  				if (debug_uri.find("://") == -1) { // wrong address -					OS::get_singleton()->print("Invalid debug host address, it should be of the form <protocol>://<host/IP>:<port>.\n"); +					OS::get_singleton()->print( +							"Invalid debug host address, it should be of the form <protocol>://<host/IP>:<port>.\n");  					goto error;  				}  				N = I->next()->next(); @@ -888,7 +950,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  #ifdef TOOLS_ENABLED  	if (editor && project_manager) { -		OS::get_singleton()->print("Error: Command line arguments implied opening both editor and project manager, which is not possible. Aborting.\n"); +		OS::get_singleton()->print( +				"Error: Command line arguments implied opening both editor and project manager, which is not possible. Aborting.\n");  		goto error;  	}  #endif @@ -935,15 +998,35 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	OS::get_singleton()->ensure_user_data_dir();  	GLOBAL_DEF("memory/limits/multithreaded_server/rid_pool_prealloc", 60); -	ProjectSettings::get_singleton()->set_custom_property_info("memory/limits/multithreaded_server/rid_pool_prealloc", PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1")); // No negative and limit to 500 due to crashes +	ProjectSettings::get_singleton()->set_custom_property_info("memory/limits/multithreaded_server/rid_pool_prealloc", +			PropertyInfo(Variant::INT, +					"memory/limits/multithreaded_server/rid_pool_prealloc", +					PROPERTY_HINT_RANGE, +					"0,500,1")); // No negative and limit to 500 due to crashes  	GLOBAL_DEF("network/limits/debugger/max_chars_per_second", 32768); -	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_chars_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_chars_per_second", PROPERTY_HINT_RANGE, "0, 4096, 1, or_greater")); +	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_chars_per_second", +			PropertyInfo(Variant::INT, +					"network/limits/debugger/max_chars_per_second", +					PROPERTY_HINT_RANGE, +					"0, 4096, 1, or_greater"));  	GLOBAL_DEF("network/limits/debugger/max_queued_messages", 2048); -	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_queued_messages", PropertyInfo(Variant::INT, "network/limits/debugger/max_queued_messages", PROPERTY_HINT_RANGE, "0, 8192, 1, or_greater")); +	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_queued_messages", +			PropertyInfo(Variant::INT, +					"network/limits/debugger/max_queued_messages", +					PROPERTY_HINT_RANGE, +					"0, 8192, 1, or_greater"));  	GLOBAL_DEF("network/limits/debugger/max_errors_per_second", 400); -	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_errors_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_errors_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater")); +	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_errors_per_second", +			PropertyInfo(Variant::INT, +					"network/limits/debugger/max_errors_per_second", +					PROPERTY_HINT_RANGE, +					"0, 200, 1, or_greater"));  	GLOBAL_DEF("network/limits/debugger/max_warnings_per_second", 400); -	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_warnings_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_warnings_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater")); +	ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_warnings_per_second", +			PropertyInfo(Variant::INT, +					"network/limits/debugger/max_warnings_per_second", +					PROPERTY_HINT_RANGE, +					"0, 200, 1, or_greater"));  	EngineDebugger::initialize(debug_uri, skip_breakpoints, breakpoints); @@ -978,8 +1061,13 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	GLOBAL_DEF("logging/file_logging/enable_file_logging.pc", true);  	GLOBAL_DEF("logging/file_logging/log_path", "user://logs/godot.log");  	GLOBAL_DEF("logging/file_logging/max_log_files", 5); -	ProjectSettings::get_singleton()->set_custom_property_info("logging/file_logging/max_log_files", PropertyInfo(Variant::INT, "logging/file_logging/max_log_files", PROPERTY_HINT_RANGE, "0,20,1,or_greater")); //no negative numbers -	if (!project_manager && !editor && FileAccess::get_create_func(FileAccess::ACCESS_USERDATA) && GLOBAL_GET("logging/file_logging/enable_file_logging")) { +	ProjectSettings::get_singleton()->set_custom_property_info("logging/file_logging/max_log_files", +			PropertyInfo(Variant::INT, +					"logging/file_logging/max_log_files", +					PROPERTY_HINT_RANGE, +					"0,20,1,or_greater")); //no negative numbers +	if (!project_manager && !editor && FileAccess::get_create_func(FileAccess::ACCESS_USERDATA) && +			GLOBAL_GET("logging/file_logging/enable_file_logging")) {  		// Don't create logs for the project manager as they would be written to  		// the current working directory, which is inconvenient.  		String base_path = GLOBAL_GET("logging/file_logging/log_path"); @@ -1020,7 +1108,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	OS::get_singleton()->set_cmdline(execpath, main_args);  	GLOBAL_DEF("rendering/quality/driver/driver_name", "Vulkan"); -	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name", PropertyInfo(Variant::STRING, "rendering/quality/driver/driver_name", PROPERTY_HINT_ENUM, "Vulkan,GLES2")); +	ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/driver/driver_name", +			PropertyInfo(Variant::STRING, +					"rendering/quality/driver/driver_name", +					PROPERTY_HINT_ENUM, "Vulkan,GLES2"));  	if (display_driver == "") {  		display_driver = GLOBAL_GET("rendering/quality/driver/driver_name");  	} @@ -1029,24 +1120,39 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	GLOBAL_DEF("rendering/quality/2d/gles2_use_nvidia_rect_flicker_workaround", false);  	GLOBAL_DEF("display/window/size/width", 1024); -	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/width", PropertyInfo(Variant::INT, "display/window/size/width", PROPERTY_HINT_RANGE, "0,7680,or_greater")); // 8K resolution +	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/width", +			PropertyInfo(Variant::INT, "display/window/size/width", +					PROPERTY_HINT_RANGE, +					"0,7680,or_greater")); // 8K resolution  	GLOBAL_DEF("display/window/size/height", 600); -	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/height", PropertyInfo(Variant::INT, "display/window/size/height", PROPERTY_HINT_RANGE, "0,4320,or_greater")); // 8K resolution +	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/height", +			PropertyInfo(Variant::INT, "display/window/size/height", +					PROPERTY_HINT_RANGE, +					"0,4320,or_greater")); // 8K resolution  	GLOBAL_DEF("display/window/size/resizable", true);  	GLOBAL_DEF("display/window/size/borderless", false);  	GLOBAL_DEF("display/window/size/fullscreen", false);  	GLOBAL_DEF("display/window/size/always_on_top", false);  	GLOBAL_DEF("display/window/size/test_width", 0); -	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_width", PropertyInfo(Variant::INT, "display/window/size/test_width", PROPERTY_HINT_RANGE, "0,7680,or_greater")); // 8K resolution +	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_width", +			PropertyInfo(Variant::INT, +					"display/window/size/test_width", +					PROPERTY_HINT_RANGE, +					"0,7680,or_greater")); // 8K resolution  	GLOBAL_DEF("display/window/size/test_height", 0); -	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_height", PropertyInfo(Variant::INT, "display/window/size/test_height", PROPERTY_HINT_RANGE, "0,4320,or_greater")); // 8K resolution +	ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/test_height", +			PropertyInfo(Variant::INT, +					"display/window/size/test_height", +					PROPERTY_HINT_RANGE, +					"0,4320,or_greater")); // 8K resolution  	if (use_custom_res) {  		if (!force_res) {  			window_size.width = GLOBAL_GET("display/window/size/width");  			window_size.height = GLOBAL_GET("display/window/size/height"); -			if (globals->has_setting("display/window/size/test_width") && globals->has_setting("display/window/size/test_height")) { +			if (globals->has_setting("display/window/size/test_width") && +					globals->has_setting("display/window/size/test_height")) {  				int tw = globals->get("display/window/size/test_width");  				if (tw > 0) {  					window_size.width = tw; @@ -1091,7 +1197,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	OS::get_singleton()->_vsync_via_compositor = window_vsync_via_compositor;  	if (tablet_driver == "") { // specified in project.godot -		tablet_driver = GLOBAL_DEF_RST("display/window/tablet_driver", OS::get_singleton()->get_tablet_driver_name(0)); +		tablet_driver = GLOBAL_DEF_RST_NOVAL("display/window/tablet_driver", OS::get_singleton()->get_tablet_driver_name(0));  	}  	for (int i = 0; i < OS::get_singleton()->get_tablet_driver_count(); i++) { @@ -1106,8 +1212,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	}  	/* todo restore -	OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/per_pixel_transparency/allowed", false); -	video_mode.layered = GLOBAL_DEF("display/window/per_pixel_transparency/enabled", false); +    OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/per_pixel_transparency/allowed", false); +    video_mode.layered = GLOBAL_DEF("display/window/per_pixel_transparency/enabled", false);  */  	GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_allocation", 2);  	GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_allocation.mobile", 3); @@ -1145,7 +1251,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	}  	if (audio_driver == "") { // specified in project.godot -		audio_driver = GLOBAL_DEF_RST("audio/driver", AudioDriverManager::get_driver(0)->get_name()); +		audio_driver = GLOBAL_DEF_RST_NOVAL("audio/driver", AudioDriverManager::get_driver(0)->get_name());  	}  	for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) { @@ -1180,10 +1286,15 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	}  	Engine::get_singleton()->set_iterations_per_second(GLOBAL_DEF("physics/common/physics_fps", 60)); -	ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", PropertyInfo(Variant::INT, "physics/common/physics_fps", PROPERTY_HINT_RANGE, "1,120,1,or_greater")); +	ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_fps", +			PropertyInfo(Variant::INT, "physics/common/physics_fps", +					PROPERTY_HINT_RANGE, "1,120,1,or_greater"));  	Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));  	Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0)); -	ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater")); +	ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", +			PropertyInfo(Variant::INT, +					"debug/settings/fps/force_fps", +					PROPERTY_HINT_RANGE, "0,120,1,or_greater"));  	GLOBAL_DEF("debug/settings/stdout/print_fps", false);  	GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false); @@ -1194,12 +1305,21 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph  	if (frame_delay == 0) {  		frame_delay = GLOBAL_DEF("application/run/frame_delay_msec", 0); -		ProjectSettings::get_singleton()->set_custom_property_info("application/run/frame_delay_msec", PropertyInfo(Variant::INT, "application/run/frame_delay_msec", PROPERTY_HINT_RANGE, "0,100,1,or_greater")); // No negative numbers +		ProjectSettings::get_singleton()->set_custom_property_info("application/run/frame_delay_msec", +				PropertyInfo(Variant::INT, +						"application/run/frame_delay_msec", +						PROPERTY_HINT_RANGE, +						"0,100,1,or_greater")); // No negative numbers  	}  	OS::get_singleton()->set_low_processor_usage_mode(GLOBAL_DEF("application/run/low_processor_mode", false)); -	OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(GLOBAL_DEF("application/run/low_processor_mode_sleep_usec", 6900)); // Roughly 144 FPS -	ProjectSettings::get_singleton()->set_custom_property_info("application/run/low_processor_mode_sleep_usec", PropertyInfo(Variant::INT, "application/run/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "0,33200,1,or_greater")); // No negative numbers +	OS::get_singleton()->set_low_processor_usage_mode_sleep_usec( +			GLOBAL_DEF("application/run/low_processor_mode_sleep_usec", 6900)); // Roughly 144 FPS +	ProjectSettings::get_singleton()->set_custom_property_info("application/run/low_processor_mode_sleep_usec", +			PropertyInfo(Variant::INT, +					"application/run/low_processor_mode_sleep_usec", +					PROPERTY_HINT_RANGE, +					"0,33200,1,or_greater")); // No negative numbers  	GLOBAL_DEF("display/window/ios/hide_home_indicator", true); @@ -1286,14 +1406,16 @@ Error Main::setup2(Thread::ID p_main_tid_override) {  		String rendering_driver; // temp broken  		Error err; -		display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags, window_size, err); +		display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags, +				window_size, err);  		if (err != OK) {  			//ok i guess we can't use this display server, try other ones  			for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {  				if (i == display_driver_idx) {  					continue; //don't try the same twice  				} -				display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags, window_size, err); +				display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_flags, +						window_size, err);  				if (err == OK) {  					break;  				} @@ -1314,7 +1436,9 @@ Error Main::setup2(Thread::ID p_main_tid_override) {  	rendering_server = memnew(RenderingServerRaster);  	if (OS::get_singleton()->get_render_thread_mode() != OS::RENDER_THREAD_UNSAFE) { -		rendering_server = memnew(RenderingServerWrapMT(rendering_server, OS::get_singleton()->get_render_thread_mode() == OS::RENDER_SEPARATE_THREAD)); +		rendering_server = memnew(RenderingServerWrapMT(rendering_server, +				OS::get_singleton()->get_render_thread_mode() == +						OS::RENDER_SEPARATE_THREAD));  	}  	rendering_server->init(); @@ -1379,7 +1503,10 @@ Error Main::setup2(Thread::ID p_main_tid_override) {  		String boot_logo_path = GLOBAL_DEF("application/boot_splash/image", String());  		bool boot_logo_scale = GLOBAL_DEF("application/boot_splash/fullsize", true);  		bool boot_logo_filter = GLOBAL_DEF("application/boot_splash/use_filter", true); -		ProjectSettings::get_singleton()->set_custom_property_info("application/boot_splash/image", PropertyInfo(Variant::STRING, "application/boot_splash/image", PROPERTY_HINT_FILE, "*.png")); +		ProjectSettings::get_singleton()->set_custom_property_info("application/boot_splash/image", +				PropertyInfo(Variant::STRING, +						"application/boot_splash/image", +						PROPERTY_HINT_FILE, "*.png"));  		Ref<Image> boot_logo; @@ -1396,7 +1523,8 @@ Error Main::setup2(Thread::ID p_main_tid_override) {  		Color boot_bg_color = GLOBAL_DEF("application/boot_splash/bg_color", boot_splash_bg_color);  		if (boot_logo.is_valid()) {  			OS::get_singleton()->_msec_splash = OS::get_singleton()->get_ticks_msec(); -			RenderingServer::get_singleton()->set_boot_image(boot_logo, boot_bg_color, boot_logo_scale, boot_logo_filter); +			RenderingServer::get_singleton()->set_boot_image(boot_logo, boot_bg_color, boot_logo_scale, +					boot_logo_filter);  		} else {  #ifndef NO_DEFAULT_BOOT_LOGO @@ -1421,21 +1549,31 @@ Error Main::setup2(Thread::ID p_main_tid_override) {  	}  	MAIN_PRINT("Main: DCC"); -	RenderingServer::get_singleton()->set_default_clear_color(GLOBAL_DEF("rendering/environment/default_clear_color", Color(0.3, 0.3, 0.3))); +	RenderingServer::get_singleton()->set_default_clear_color( +			GLOBAL_DEF("rendering/environment/default_clear_color", Color(0.3, 0.3, 0.3)));  	MAIN_PRINT("Main: END");  	GLOBAL_DEF("application/config/icon", String()); -	ProjectSettings::get_singleton()->set_custom_property_info("application/config/icon", PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.webp")); +	ProjectSettings::get_singleton()->set_custom_property_info("application/config/icon", +			PropertyInfo(Variant::STRING, "application/config/icon", +					PROPERTY_HINT_FILE, "*.png,*.webp"));  	GLOBAL_DEF("application/config/macos_native_icon", String()); -	ProjectSettings::get_singleton()->set_custom_property_info("application/config/macos_native_icon", PropertyInfo(Variant::STRING, "application/config/macos_native_icon", PROPERTY_HINT_FILE, "*.icns")); +	ProjectSettings::get_singleton()->set_custom_property_info("application/config/macos_native_icon", +			PropertyInfo(Variant::STRING, +					"application/config/macos_native_icon", +					PROPERTY_HINT_FILE, "*.icns"));  	GLOBAL_DEF("application/config/windows_native_icon", String()); -	ProjectSettings::get_singleton()->set_custom_property_info("application/config/windows_native_icon", PropertyInfo(Variant::STRING, "application/config/windows_native_icon", PROPERTY_HINT_FILE, "*.ico")); +	ProjectSettings::get_singleton()->set_custom_property_info("application/config/windows_native_icon", +			PropertyInfo(Variant::STRING, +					"application/config/windows_native_icon", +					PROPERTY_HINT_FILE, "*.ico"));  	Input *id = Input::get_singleton();  	if (id) { -		if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { +		if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && +				!(editor || project_manager)) {  			bool found_touchscreen = false;  			for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) {  				if (DisplayServer::get_singleton()->screen_is_touchscreen(i)) { @@ -1460,10 +1598,14 @@ Error Main::setup2(Thread::ID p_main_tid_override) {  	GLOBAL_DEF("display/mouse_cursor/custom_image", String());  	GLOBAL_DEF("display/mouse_cursor/custom_image_hotspot", Vector2());  	GLOBAL_DEF("display/mouse_cursor/tooltip_position_offset", Point2(10, 10)); -	ProjectSettings::get_singleton()->set_custom_property_info("display/mouse_cursor/custom_image", PropertyInfo(Variant::STRING, "display/mouse_cursor/custom_image", PROPERTY_HINT_FILE, "*.png,*.webp")); +	ProjectSettings::get_singleton()->set_custom_property_info("display/mouse_cursor/custom_image", +			PropertyInfo(Variant::STRING, +					"display/mouse_cursor/custom_image", +					PROPERTY_HINT_FILE, "*.png,*.webp"));  	if (String(ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image")) != String()) { -		Ref<Texture2D> cursor = ResourceLoader::load(ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image")); +		Ref<Texture2D> cursor = ResourceLoader::load( +				ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image"));  		if (cursor.is_valid()) {  			Vector2 hotspot = ProjectSettings::get_singleton()->get("display/mouse_cursor/custom_image_hotspot");  			Input::get_singleton()->set_custom_mouse_cursor(cursor, Input::CURSOR_ARROW, hotspot); @@ -1544,7 +1686,6 @@ bool Main::start() {  	String positional_arg;  	String game_path;  	String script; -	String test;  	bool check_only = false;  #ifdef TOOLS_ENABLED @@ -1555,10 +1696,12 @@ bool Main::start() {  #endif  	main_timer_sync.init(OS::get_singleton()->get_ticks_usec()); -  	List<String> args = OS::get_singleton()->get_cmdline_args(); + +	// parameters that do not have an argument to the right  	for (int i = 0; i < args.size(); i++) { -		//parameters that do not have an argument to the right +		// Doctest Unit Testing Handler +		// Designed to override and pass arguments to the unit test handler.  		if (args[i] == "--check-only") {  			check_only = true;  #ifdef TOOLS_ENABLED @@ -1591,8 +1734,6 @@ bool Main::start() {  			bool parsed_pair = true;  			if (args[i] == "-s" || args[i] == "--script") {  				script = args[i + 1]; -			} else if (args[i] == "--test") { -				test = args[i + 1];  #ifdef TOOLS_ENABLED  			} else if (args[i] == "--doctool") {  				doc_tool = args[i + 1]; @@ -1624,7 +1765,8 @@ bool Main::start() {  	String main_loop_type;  #ifdef TOOLS_ENABLED  	if (doc_tool != "") { -		Engine::get_singleton()->set_editor_hint(true); // Needed to instance editor-only classes for their default values +		Engine::get_singleton()->set_editor_hint( +				true); // Needed to instance editor-only classes for their default values  		{  			DirAccessRef da = DirAccess::open(doc_tool); @@ -1716,16 +1858,7 @@ bool Main::start() {  		main_loop = memnew(SceneTree);  	}; -	if (test != "") { -#ifdef TOOLS_ENABLED -		main_loop = test_main(test, args); - -		if (!main_loop) { -			return false; -		} -#endif - -	} else if (script != "") { +	if (script != "") {  		Ref<Script> script_res = ResourceLoader::load(script);  		ERR_FAIL_COND_V_MSG(script_res.is_null(), false, "Can't load script: " + script); @@ -1744,7 +1877,9 @@ bool Main::start() {  				if (obj) {  					memdelete(obj);  				} -				ERR_FAIL_V_MSG(false, vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script)); +				ERR_FAIL_V_MSG(false, +						vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", +								script));  			}  			script_loop->set_init_script(script_res); @@ -1832,7 +1967,9 @@ bool Main::start() {  						Object *obj = ClassDB::instance(ibt); -						ERR_CONTINUE_MSG(obj == nullptr, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt)); +						ERR_CONTINUE_MSG(obj == nullptr, +								"Cannot instance script for autoload, expected 'Node' inheritance, got: " + +										String(ibt));  						n = Object::cast_to<Node>(obj);  						n->set_script(script_res); @@ -1880,7 +2017,8 @@ bool Main::start() {  			String stretch_mode = GLOBAL_DEF("display/window/stretch/mode", "disabled");  			String stretch_aspect = GLOBAL_DEF("display/window/stretch/aspect", "ignore"); -			Size2i stretch_size = Size2(GLOBAL_DEF("display/window/size/width", 0), GLOBAL_DEF("display/window/size/height", 0)); +			Size2i stretch_size = Size2(GLOBAL_DEF("display/window/size/width", 0), +					GLOBAL_DEF("display/window/size/height", 0));  			Window::ContentScaleMode cs_sm = Window::CONTENT_SCALE_MODE_DISABLED;  			if (stretch_mode == "canvas_items") { @@ -1924,10 +2062,14 @@ bool Main::start() {  			int shadow_atlas_q3_subdiv = GLOBAL_GET("rendering/quality/shadow_atlas/quadrant_3_subdiv");  			sml->get_root()->set_shadow_atlas_size(shadow_atlas_size); -			sml->get_root()->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q0_subdiv)); -			sml->get_root()->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q1_subdiv)); -			sml->get_root()->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q2_subdiv)); -			sml->get_root()->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(shadow_atlas_q3_subdiv)); +			sml->get_root()->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv( +																		 shadow_atlas_q0_subdiv)); +			sml->get_root()->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv( +																		 shadow_atlas_q1_subdiv)); +			sml->get_root()->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv( +																		 shadow_atlas_q2_subdiv)); +			sml->get_root()->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv( +																		 shadow_atlas_q3_subdiv));  			bool snap_controls = GLOBAL_DEF("gui/common/snap_controls_to_pixels", true);  			sml->get_root()->set_snap_controls_to_pixels(snap_controls); @@ -1937,30 +2079,51 @@ bool Main::start() {  			int texture_filter = GLOBAL_DEF("rendering/canvas_textures/default_texture_filter", 1);  			int texture_repeat = GLOBAL_DEF("rendering/canvas_textures/default_texture_repeat", 0); -			sml->get_root()->set_default_canvas_item_texture_filter(Viewport::DefaultCanvasItemTextureFilter(texture_filter)); -			sml->get_root()->set_default_canvas_item_texture_repeat(Viewport::DefaultCanvasItemTextureRepeat(texture_repeat)); +			sml->get_root()->set_default_canvas_item_texture_filter( +					Viewport::DefaultCanvasItemTextureFilter(texture_filter)); +			sml->get_root()->set_default_canvas_item_texture_repeat( +					Viewport::DefaultCanvasItemTextureRepeat(texture_repeat));  		} else {  			GLOBAL_DEF("display/window/stretch/mode", "disabled"); -			ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/mode", PropertyInfo(Variant::STRING, "display/window/stretch/mode", PROPERTY_HINT_ENUM, "disabled,canvas_items,viewport")); +			ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/mode", +					PropertyInfo(Variant::STRING, +							"display/window/stretch/mode", +							PROPERTY_HINT_ENUM, +							"disabled,canvas_items,viewport"));  			GLOBAL_DEF("display/window/stretch/aspect", "ignore"); -			ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/aspect", PropertyInfo(Variant::STRING, "display/window/stretch/aspect", PROPERTY_HINT_ENUM, "ignore,keep,keep_width,keep_height,expand")); +			ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/aspect", +					PropertyInfo(Variant::STRING, +							"display/window/stretch/aspect", +							PROPERTY_HINT_ENUM, +							"ignore,keep,keep_width,keep_height,expand"));  			GLOBAL_DEF("display/window/stretch/shrink", 1.0); -			ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/shrink", PropertyInfo(Variant::FLOAT, "display/window/stretch/shrink", PROPERTY_HINT_RANGE, "1.0,8.0,0.1")); +			ProjectSettings::get_singleton()->set_custom_property_info("display/window/stretch/shrink", +					PropertyInfo(Variant::FLOAT, +							"display/window/stretch/shrink", +							PROPERTY_HINT_RANGE, +							"1.0,8.0,0.1"));  			sml->set_auto_accept_quit(GLOBAL_DEF("application/config/auto_accept_quit", true));  			sml->set_quit_on_go_back(GLOBAL_DEF("application/config/quit_on_go_back", true));  			GLOBAL_DEF("gui/common/snap_controls_to_pixels", true);  			GLOBAL_DEF("rendering/quality/dynamic_fonts/use_oversampling", true);  			GLOBAL_DEF("rendering/canvas_textures/default_texture_filter", 1); -			ProjectSettings::get_singleton()->set_custom_property_info("rendering/canvas_textures/default_texture_filter", PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,MipmapLinear,MipmapNearest")); +			ProjectSettings::get_singleton()->set_custom_property_info( +					"rendering/canvas_textures/default_texture_filter", +					PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, +							"Nearest,Linear,MipmapLinear,MipmapNearest"));  			GLOBAL_DEF("rendering/canvas_textures/default_texture_repeat", 0); -			ProjectSettings::get_singleton()->set_custom_property_info("rendering/canvas_textures/default_texture_repeat", PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM, "Disable,Enable,Mirror")); +			ProjectSettings::get_singleton()->set_custom_property_info( +					"rendering/canvas_textures/default_texture_repeat", +					PropertyInfo(Variant::INT, "rendering/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM, +							"Disable,Enable,Mirror"));  		}  #ifdef TOOLS_ENABLED  		if (editor) { -			bool editor_embed_subwindows = EditorSettings::get_singleton()->get_setting("interface/editor/single_window_mode"); +			bool editor_embed_subwindows = EditorSettings::get_singleton()->get_setting( +					"interface/editor/single_window_mode");  			if (editor_embed_subwindows) {  				sml->get_root()->set_embed_subwindows_hint(true); @@ -1973,7 +2136,8 @@ bool Main::start() {  			local_game_path = game_path.replace("\\", "/");  			if (!local_game_path.begins_with("res://")) { -				bool absolute = (local_game_path.size() > 1) && (local_game_path[0] == '/' || local_game_path[1] == ':'); +				bool absolute = +						(local_game_path.size() > 1) && (local_game_path[0] == '/' || local_game_path[1] == ':');  				if (!absolute) {  					if (ProjectSettings::get_singleton()->is_using_datapack()) { @@ -1989,7 +2153,8 @@ bool Main::start() {  						} else {  							DirAccess *da = DirAccess::open(local_game_path.substr(0, sep));  							if (da) { -								local_game_path = da->get_current_dir().plus_file(local_game_path.substr(sep + 1, local_game_path.length())); +								local_game_path = da->get_current_dir().plus_file( +										local_game_path.substr(sep + 1, local_game_path.length()));  								memdelete(da);  							}  						} @@ -2059,7 +2224,7 @@ bool Main::start() {  		}  #ifdef TOOLS_ENABLED -		if (project_manager || (script == "" && test == "" && game_path == "" && !editor)) { +		if (project_manager || (script == "" && game_path == "" && !editor)) {  			Engine::get_singleton()->set_editor_hint(true);  			ProjectManager *pmanager = memnew(ProjectManager);  			ProgressDialog *progress_dialog = memnew(ProgressDialog); @@ -2072,12 +2237,16 @@ bool Main::start() {  		if (project_manager || editor) {  			if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CONSOLE_WINDOW)) {  				// Hide console window if requested (Windows-only). -				bool hide_console = EditorSettings::get_singleton()->get_setting("interface/editor/hide_console_window"); +				bool hide_console = EditorSettings::get_singleton()->get_setting( +						"interface/editor/hide_console_window");  				DisplayServer::get_singleton()->console_set_visible(!hide_console);  			}  			// Load SSL Certificates from Editor Settings (or builtin) -			Crypto::load_default_certificates(EditorSettings::get_singleton()->get_setting("network/ssl/editor_ssl_certificates").operator String()); +			Crypto::load_default_certificates(EditorSettings::get_singleton()->get_setting( +																					 "network/ssl/editor_ssl_certificates") +													  . +													  operator String());  		}  #endif  	} @@ -2107,6 +2276,7 @@ uint32_t Main::frames = 0;  uint32_t Main::frame = 0;  bool Main::force_redraw_requested = false;  int Main::iterating = 0; +  bool Main::is_iterating() {  	return iterating > 0;  } @@ -2182,7 +2352,8 @@ bool Main::iteration() {  		message_queue->flush(); -		physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference +		physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - +																   physics_begin); // keep the largest one for reference  		physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max);  		Engine::get_singleton()->_physics_frames++;  	} @@ -2198,7 +2369,8 @@ bool Main::iteration() {  	RenderingServer::get_singleton()->sync(); //sync if still drawing from previous frames. -	if (DisplayServer::get_singleton()->can_any_window_draw() && RenderingServer::get_singleton()->is_render_loop_enabled()) { +	if (DisplayServer::get_singleton()->can_any_window_draw() && +			RenderingServer::get_singleton()->is_render_loop_enabled()) {  		if ((!force_redraw_requested) && OS::get_singleton()->is_in_low_processor_usage_mode()) {  			if (RenderingServer::get_singleton()->has_changed()) {  				RenderingServer::get_singleton()->draw(true, scaled_step); // flush visual commands @@ -2260,10 +2432,12 @@ bool Main::iteration() {  		auto_build_solutions = false;  		// Only relevant when running the editor.  		if (!editor) { -			ERR_FAIL_V_MSG(true, "Command line option --build-solutions was passed, but no project is being edited. Aborting."); +			ERR_FAIL_V_MSG(true, +					"Command line option --build-solutions was passed, but no project is being edited. Aborting.");  		}  		if (!EditorNode::get_singleton()->call_build()) { -			ERR_FAIL_V_MSG(true, "Command line option --build-solutions was passed, but the build callback failed. Aborting."); +			ERR_FAIL_V_MSG(true, +					"Command line option --build-solutions was passed, but the build callback failed. Aborting.");  		}  	}  #endif diff --git a/main/main.h b/main/main.h index 308128735c..20c0bebefa 100644 --- a/main/main.h +++ b/main/main.h @@ -45,7 +45,7 @@ class Main {  public:  	static bool is_project_manager(); - +	static int test_entrypoint(int argc, char *argv[], bool &tests_need_run);  	static Error setup(const char *execpath, int argc, char *argv[], bool p_second_phase = true);  	static Error setup2(Thread::ID p_main_tid_override = 0);  	static bool start(); @@ -58,4 +58,19 @@ public:  	static void cleanup();  }; +// Test main override is for the testing behaviour +#define TEST_MAIN_OVERRIDE                                         \ +	bool run_test = false;                                         \ +	int return_code = Main::test_entrypoint(argc, argv, run_test); \ +	if (run_test) {                                                \ +		return return_code;                                        \ +	} + +#define TEST_MAIN_PARAM_OVERRIDE(argc, argv)                       \ +	bool run_test = false;                                         \ +	int return_code = Main::test_entrypoint(argc, argv, run_test); \ +	if (run_test) {                                                \ +		return return_code;                                        \ +	} +  #endif // MAIN_H diff --git a/main/tests/SCsub b/main/tests/SCsub deleted file mode 100644 index cb1d35b12f..0000000000 --- a/main/tests/SCsub +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/python - -Import("env") - -env.tests_sources = [] -env.add_source_files(env.tests_sources, "*.cpp") - -lib = env.add_library("tests", env.tests_sources) -env.Prepend(LIBS=[lib]) diff --git a/main/tests/test_string.cpp b/main/tests/test_string.cpp deleted file mode 100644 index 73d59b0088..0000000000 --- a/main/tests/test_string.cpp +++ /dev/null @@ -1,1207 +0,0 @@ -/*************************************************************************/ -/*  test_string.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_string.h" - -#include "core/io/ip_address.h" -#include "core/os/os.h" -#include "core/ustring.h" - -#include "modules/modules_enabled.gen.h" -#ifdef MODULE_REGEX_ENABLED -#include "modules/regex/regex.h" -#endif - -#include <stdio.h> -#include <wchar.h> - -namespace TestString { - -bool test_1() { -	OS::get_singleton()->print("\n\nTest 1: Assign from cstr\n"); - -	String s = "Hello"; - -	OS::get_singleton()->print("\tExpected: Hello\n"); -	OS::get_singleton()->print("\tResulted: %ls\n", s.c_str()); - -	return (wcscmp(s.c_str(), L"Hello") == 0); -} - -bool test_2() { -	OS::get_singleton()->print("\n\nTest 2: Assign from string (operator=)\n"); - -	String s = "Dolly"; -	const String &t = s; - -	OS::get_singleton()->print("\tExpected: Dolly\n"); -	OS::get_singleton()->print("\tResulted: %ls\n", t.c_str()); - -	return (wcscmp(t.c_str(), L"Dolly") == 0); -} - -bool test_3() { -	OS::get_singleton()->print("\n\nTest 3: Assign from c-string (copycon)\n"); - -	String s("Sheep"); -	const String &t(s); - -	OS::get_singleton()->print("\tExpected: Sheep\n"); -	OS::get_singleton()->print("\tResulted: %ls\n", t.c_str()); - -	return (wcscmp(t.c_str(), L"Sheep") == 0); -} - -bool test_4() { -	OS::get_singleton()->print("\n\nTest 4: Assign from c-widechar (operator=)\n"); - -	String s(L"Give me"); - -	OS::get_singleton()->print("\tExpected: Give me\n"); -	OS::get_singleton()->print("\tResulted: %ls\n", s.c_str()); - -	return (wcscmp(s.c_str(), L"Give me") == 0); -} - -bool test_5() { -	OS::get_singleton()->print("\n\nTest 5: Assign from c-widechar (copycon)\n"); - -	String s(L"Wool"); - -	OS::get_singleton()->print("\tExpected: Wool\n"); -	OS::get_singleton()->print("\tResulted: %ls\n", s.c_str()); - -	return (wcscmp(s.c_str(), L"Wool") == 0); -} - -bool test_6() { -	OS::get_singleton()->print("\n\nTest 6: comparisons (equal)\n"); - -	String s = "Test Compare"; - -	OS::get_singleton()->print("\tComparing to \"Test Compare\"\n"); - -	if (!(s == "Test Compare")) { -		return false; -	} - -	if (!(s == L"Test Compare")) { -		return false; -	} - -	if (!(s == String("Test Compare"))) { -		return false; -	} - -	return true; -} - -bool test_7() { -	OS::get_singleton()->print("\n\nTest 7: comparisons (unequal)\n"); - -	String s = "Test Compare"; - -	OS::get_singleton()->print("\tComparing to \"Test Compare\"\n"); - -	if (!(s != "Peanut")) { -		return false; -	} - -	if (!(s != L"Coconut")) { -		return false; -	} - -	if (!(s != String("Butter"))) { -		return false; -	} - -	return true; -} - -bool test_8() { -	OS::get_singleton()->print("\n\nTest 8: comparisons (operator<)\n"); - -	String s = "Bees"; - -	OS::get_singleton()->print("\tComparing to \"Bees\"\n"); - -	if (!(s < "Elephant")) { -		return false; -	} - -	if (s < L"Amber") { -		return false; -	} - -	if (s < String("Beatrix")) { -		return false; -	} - -	return true; -} - -bool test_9() { -	OS::get_singleton()->print("\n\nTest 9: Concatenation\n"); - -	String s; - -	s += "Have"; -	s += ' '; -	s += 'a'; -	s += String(" "); -	s = s + L"Nice"; -	s = s + " "; -	s = s + String("Day"); - -	OS::get_singleton()->print("\tComparing to \"Have a Nice Day\"\n"); - -	return (s == "Have a Nice Day"); -} - -bool test_10() { -	OS::get_singleton()->print("\n\nTest 10: Misc funcs (size/length/empty/etc)\n"); - -	if (!String("").empty()) { -		return false; -	} - -	if (String("Mellon").size() != 7) { -		return false; -	} - -	if (String("Oranges").length() != 7) { -		return false; -	} - -	return true; -} - -bool test_11() { -	OS::get_singleton()->print("\n\nTest 11: Operator[]\n"); - -	String a = "Kugar Sane"; - -	a[0] = 'S'; -	a[6] = 'C'; - -	if (a != "Sugar Cane") { -		return false; -	} - -	if (a[1] != 'u') { -		return false; -	} - -	return true; -} - -bool test_12() { -	OS::get_singleton()->print("\n\nTest 12: case functions\n"); - -	String a = "MoMoNgA"; - -	if (a.to_upper() != "MOMONGA") { -		return false; -	} - -	if (a.nocasecmp_to("momonga") != 0) { -		return false; -	} - -	return true; -} - -bool test_13() { -	OS::get_singleton()->print("\n\nTest 13: UTF8\n"); - -	/* how can i embed UTF in here? */ - -	static const CharType ustr[] = { 0x304A, 0x360F, 0x3088, 0x3046, 0 }; -	//static const wchar_t ustr[] = { 'P', 0xCE, 'p',0xD3, 0 }; -	String s = ustr; - -	OS::get_singleton()->print("\tUnicode: %ls\n", ustr); -	s.parse_utf8(s.utf8().get_data()); -	OS::get_singleton()->print("\tConvert/Parse UTF8: %ls\n", s.c_str()); - -	return (s == ustr); -} - -bool test_14() { -	OS::get_singleton()->print("\n\nTest 14: ASCII\n"); - -	String s = L"Primero Leche"; -	OS::get_singleton()->print("\tAscii: %s\n", s.ascii().get_data()); - -	String t = s.ascii().get_data(); -	return (s == t); -} - -bool test_15() { -	OS::get_singleton()->print("\n\nTest 15: substr\n"); - -	String s = "Killer Baby"; -	OS::get_singleton()->print("\tsubstr(3,4) of \"%ls\" is \"%ls\"\n", s.c_str(), s.substr(3, 4).c_str()); - -	return (s.substr(3, 4) == "ler "); -} - -bool test_16() { -	OS::get_singleton()->print("\n\nTest 16: find\n"); - -	String s = "Pretty Woman"; -	OS::get_singleton()->print("\tString: %ls\n", s.c_str()); -	OS::get_singleton()->print("\t\"tty\" is at %i pos.\n", s.find("tty")); -	OS::get_singleton()->print("\t\"Revenge of the Monster Truck\" is at %i pos.\n", s.find("Revenge of the Monster Truck")); - -	if (s.find("tty") != 3) { -		return false; -	} - -	if (s.find("Revenge of the Monster Truck") != -1) { -		return false; -	} - -	return true; -} - -bool test_17() { -	OS::get_singleton()->print("\n\nTest 17: find no case\n"); - -	String s = "Pretty Whale"; -	OS::get_singleton()->print("\tString: %ls\n", s.c_str()); -	OS::get_singleton()->print("\t\"WHA\" is at %i pos.\n", s.findn("WHA")); -	OS::get_singleton()->print("\t\"Revenge of the Monster SawFish\" is at %i pos.\n", s.findn("Revenge of the Monster Truck")); - -	if (s.findn("WHA") != 7) { -		return false; -	} - -	if (s.findn("Revenge of the Monster SawFish") != -1) { -		return false; -	} - -	return true; -} - -bool test_18() { -	OS::get_singleton()->print("\n\nTest 18: find no case\n"); - -	String s = "Pretty Whale"; -	OS::get_singleton()->print("\tString: %ls\n", s.c_str()); -	OS::get_singleton()->print("\t\"WHA\" is at %i pos.\n", s.findn("WHA")); -	OS::get_singleton()->print("\t\"Revenge of the Monster SawFish\" is at %i pos.\n", s.findn("Revenge of the Monster Truck")); - -	if (s.findn("WHA") != 7) { -		return false; -	} - -	if (s.findn("Revenge of the Monster SawFish") != -1) { -		return false; -	} - -	return true; -} - -bool test_19() { -	OS::get_singleton()->print("\n\nTest 19: Search & replace\n"); - -	String s = "Happy Birthday, Anna!"; -	OS::get_singleton()->print("\tString: %ls\n", s.c_str()); - -	s = s.replace("Birthday", "Halloween"); -	OS::get_singleton()->print("\tReplaced Birthday/Halloween: %ls.\n", s.c_str()); - -	return (s == "Happy Halloween, Anna!"); -} - -bool test_20() { -	OS::get_singleton()->print("\n\nTest 20: Insertion\n"); - -	String s = "Who is Frederic?"; - -	OS::get_singleton()->print("\tString: %ls\n", s.c_str()); -	s = s.insert(s.find("?"), " Chopin"); -	OS::get_singleton()->print("\tInserted Chopin: %ls.\n", s.c_str()); - -	return (s == "Who is Frederic Chopin?"); -} - -bool test_21() { -	OS::get_singleton()->print("\n\nTest 21: Number -> String\n"); - -	OS::get_singleton()->print("\tPi is %f\n", 33.141593); -	OS::get_singleton()->print("\tPi String is %ls\n", String::num(3.141593).c_str()); - -	return String::num(3.141593) == "3.141593"; -} - -bool test_22() { -	OS::get_singleton()->print("\n\nTest 22: String -> Int\n"); - -	static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" }; -	static const int num[4] = { 1237461283, -22, 0, -1123412 }; - -	for (int i = 0; i < 4; i++) { -#ifdef __MINGW32__ // MinGW can't handle normal format specifiers for some reason. So we need special code just for MinGW. -		OS::get_singleton()->print("\tString: \"%s\" as Int is %I64i\n", nums[i], (long long)(String(nums[i]).to_int())); -#else -		OS::get_singleton()->print("\tString: \"%s\" as Int is %lli\n", nums[i], (long long)(String(nums[i]).to_int())); -#endif -		if (String(nums[i]).to_int() != num[i]) { -			return false; -		} -	} - -	return true; -} - -bool test_23() { -	OS::get_singleton()->print("\n\nTest 23: String -> Float\n"); - -	static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" }; -	static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 }; - -	for (int i = 0; i < 4; i++) { -		OS::get_singleton()->print("\tString: \"%s\" as Float is %f\n", nums[i], String(nums[i]).to_double()); - -		if (ABS(String(nums[i]).to_double() - num[i]) > 0.00001) { -			return false; -		} -	} - -	return true; -} - -bool test_24() { -	OS::get_singleton()->print("\n\nTest 24: Slicing\n"); - -	String s = "Mars,Jupiter,Saturn,Uranus"; - -	const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - -	OS::get_singleton()->print("\tSlicing \"%ls\" by \"%s\"..\n", s.c_str(), ","); - -	for (int i = 0; i < s.get_slice_count(","); i++) { -		OS::get_singleton()->print("\t\t%i- %ls\n", i + 1, s.get_slice(",", i).c_str()); - -		if (s.get_slice(",", i) != slices[i]) { -			return false; -		} -	} - -	return true; -} - -bool test_25() { -	OS::get_singleton()->print("\n\nTest 25: Erasing\n"); - -	String s = "Josephine is such a cute girl!"; - -	OS::get_singleton()->print("\tString: %ls\n", s.c_str()); -	OS::get_singleton()->print("\tRemoving \"cute\"\n"); - -	s.erase(s.find("cute "), String("cute ").length()); -	OS::get_singleton()->print("\tResult: %ls\n", s.c_str()); - -	return (s == "Josephine is such a girl!"); -} - -bool test_26() { -	OS::get_singleton()->print("\n\nTest 26: RegEx substitution\n"); - -#ifndef MODULE_REGEX_ENABLED -	OS::get_singleton()->print("\tRegEx module disabled, can't run test."); -	return false; -#else -	String s = "Double all the vowels."; - -	OS::get_singleton()->print("\tString: %ls\n", s.c_str()); -	OS::get_singleton()->print("\tRepeating instances of 'aeiou' once\n"); - -	RegEx re("(?<vowel>[aeiou])"); -	s = re.sub(s, "$0$vowel", true); - -	OS::get_singleton()->print("\tResult: %ls\n", s.c_str()); - -	return (s == "Doouublee aall thee vooweels."); -#endif -} - -struct test_27_data { -	char const *data; -	char const *begin; -	bool expected; -}; - -bool test_27() { -	OS::get_singleton()->print("\n\nTest 27: begins_with\n"); -	test_27_data tc[] = { -		{ "res://foobar", "res://", true }, -		{ "res", "res://", false }, -		{ "abc", "abc", true } -	}; -	size_t count = sizeof(tc) / sizeof(tc[0]); -	bool state = true; -	for (size_t i = 0; state && i < count; ++i) { -		String s = tc[i].data; -		state = s.begins_with(tc[i].begin) == tc[i].expected; -		if (state) { -			String sb = tc[i].begin; -			state = s.begins_with(sb) == tc[i].expected; -		} -		if (!state) { -			OS::get_singleton()->print("\n\t Failure on:\n\t\tstring: %s\n\t\tbegin: %s\n\t\texpected: %s\n", tc[i].data, tc[i].begin, tc[i].expected ? "true" : "false"); -			break; -		} -	}; -	return state; -}; - -bool test_28() { -	OS::get_singleton()->print("\n\nTest 28: sprintf\n"); - -	bool success, state = true; -	char output_format[] = "\tTest:\t%ls => %ls (%s)\n"; -	String format, output; -	Array args; -	bool error; - -	// %% -	format = "fish %% frog"; -	args.clear(); -	output = format.sprintf(args, &error); -	success = (output == String("fish % frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	//////// INTS - -	// Int -	format = "fish %d frog"; -	args.clear(); -	args.push_back(5); -	output = format.sprintf(args, &error); -	success = (output == String("fish 5 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Int left padded with zeroes. -	format = "fish %05d frog"; -	args.clear(); -	args.push_back(5); -	output = format.sprintf(args, &error); -	success = (output == String("fish 00005 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Int left padded with spaces. -	format = "fish %5d frog"; -	args.clear(); -	args.push_back(5); -	output = format.sprintf(args, &error); -	success = (output == String("fish     5 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Int right padded with spaces. -	format = "fish %-5d frog"; -	args.clear(); -	args.push_back(5); -	output = format.sprintf(args, &error); -	success = (output == String("fish 5     frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Int with sign (positive). -	format = "fish %+d frog"; -	args.clear(); -	args.push_back(5); -	output = format.sprintf(args, &error); -	success = (output == String("fish +5 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Negative int. -	format = "fish %d frog"; -	args.clear(); -	args.push_back(-5); -	output = format.sprintf(args, &error); -	success = (output == String("fish -5 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Hex (lower) -	format = "fish %x frog"; -	args.clear(); -	args.push_back(45); -	output = format.sprintf(args, &error); -	success = (output == String("fish 2d frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Hex (upper) -	format = "fish %X frog"; -	args.clear(); -	args.push_back(45); -	output = format.sprintf(args, &error); -	success = (output == String("fish 2D frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Octal -	format = "fish %o frog"; -	args.clear(); -	args.push_back(99); -	output = format.sprintf(args, &error); -	success = (output == String("fish 143 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	////// REALS - -	// Real -	format = "fish %f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish 99.990000 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Real left-padded -	format = "fish %11f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish   99.990000 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Real right-padded -	format = "fish %-11f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish 99.990000   frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Real given int. -	format = "fish %f frog"; -	args.clear(); -	args.push_back(99); -	output = format.sprintf(args, &error); -	success = (output == String("fish 99.000000 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Real with sign (positive). -	format = "fish %+f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish +99.990000 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Real with 1 decimals. -	format = "fish %.1f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish 100.0 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Real with 12 decimals. -	format = "fish %.12f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish 99.990000000000 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Real with no decimals. -	format = "fish %.f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish 100 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	/////// Strings. - -	// String -	format = "fish %s frog"; -	args.clear(); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == String("fish cheese frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// String left-padded -	format = "fish %10s frog"; -	args.clear(); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == String("fish     cheese frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// String right-padded -	format = "fish %-10s frog"; -	args.clear(); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == String("fish cheese     frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	///// Characters - -	// Character as string. -	format = "fish %c frog"; -	args.clear(); -	args.push_back("A"); -	output = format.sprintf(args, &error); -	success = (output == String("fish A frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Character as int. -	format = "fish %c frog"; -	args.clear(); -	args.push_back(65); -	output = format.sprintf(args, &error); -	success = (output == String("fish A frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	///// Dynamic width - -	// String dynamic width -	format = "fish %*s frog"; -	args.clear(); -	args.push_back(10); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == String("fish     cheese frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Int dynamic width -	format = "fish %*d frog"; -	args.clear(); -	args.push_back(10); -	args.push_back(99); -	output = format.sprintf(args, &error); -	success = (output == String("fish         99 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Float dynamic width -	format = "fish %*.*f frog"; -	args.clear(); -	args.push_back(10); -	args.push_back(3); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == String("fish     99.990 frog") && !error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	///// Errors - -	// More formats than arguments. -	format = "fish %s %s frog"; -	args.clear(); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == "not enough arguments for format string" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// More arguments than formats. -	format = "fish %s frog"; -	args.clear(); -	args.push_back("hello"); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == "not all arguments converted during string formatting" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Incomplete format. -	format = "fish %10"; -	args.clear(); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == "incomplete format" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Bad character in format string -	format = "fish %&f frog"; -	args.clear(); -	args.push_back("cheese"); -	output = format.sprintf(args, &error); -	success = (output == "unsupported format character" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Too many decimals. -	format = "fish %2.2.2f frog"; -	args.clear(); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == "too many decimal points in format" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// * not a number -	format = "fish %*f frog"; -	args.clear(); -	args.push_back("cheese"); -	args.push_back(99.99); -	output = format.sprintf(args, &error); -	success = (output == "* wants number" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Character too long. -	format = "fish %c frog"; -	args.clear(); -	args.push_back("sc"); -	output = format.sprintf(args, &error); -	success = (output == "%c requires number or single-character string" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	// Character bad type. -	format = "fish %c frog"; -	args.clear(); -	args.push_back(Array()); -	output = format.sprintf(args, &error); -	success = (output == "%c requires number or single-character string" && error); -	OS::get_singleton()->print(output_format, format.c_str(), output.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	return state; -} - -bool test_29() { -	bool state = true; - -	IP_Address ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); -	OS::get_singleton()->print("ip0 is %ls\n", String(ip0).c_str()); - -	IP_Address ip(0x0123, 0x4567, 0x89ab, 0xcdef, true); -	OS::get_singleton()->print("ip6 is %ls\n", String(ip).c_str()); - -	IP_Address ip2("fe80::52e5:49ff:fe93:1baf"); -	OS::get_singleton()->print("ip6 is %ls\n", String(ip2).c_str()); - -	IP_Address ip3("::ffff:192.168.0.1"); -	OS::get_singleton()->print("ip6 is %ls\n", String(ip3).c_str()); - -	String ip4 = "192.168.0.1"; -	bool success = ip4.is_valid_ip_address(); -	OS::get_singleton()->print("Is valid ipv4: %ls, %s\n", ip4.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	ip4 = "192.368.0.1"; -	success = (!ip4.is_valid_ip_address()); -	OS::get_singleton()->print("Is invalid ipv4: %ls, %s\n", ip4.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	String ip6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; -	success = ip6.is_valid_ip_address(); -	OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	ip6 = "2001:0db8:85j3:0000:0000:8a2e:0370:7334"; -	success = (!ip6.is_valid_ip_address()); -	OS::get_singleton()->print("Is invalid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	ip6 = "2001:0db8:85f345:0000:0000:8a2e:0370:7334"; -	success = (!ip6.is_valid_ip_address()); -	OS::get_singleton()->print("Is invalid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	ip6 = "2001:0db8::0:8a2e:370:7334"; -	success = (ip6.is_valid_ip_address()); -	OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	ip6 = "::ffff:192.168.0.1"; -	success = (ip6.is_valid_ip_address()); -	OS::get_singleton()->print("Is valid ipv6: %ls, %s\n", ip6.c_str(), success ? "OK" : "FAIL"); -	state = state && success; - -	return state; -}; - -bool test_30() { -	bool state = true; -	bool success = true; -	String input = "bytes2var"; -	String output = "Bytes 2 Var"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "linear2db"; -	output = "Linear 2 Db"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "vector3"; -	output = "Vector 3"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "sha256"; -	output = "Sha 256"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "2db"; -	output = "2 Db"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "PascalCase"; -	output = "Pascal Case"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "PascalPascalCase"; -	output = "Pascal Pascal Case"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "snake_case"; -	output = "Snake Case"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "snake_snake_case"; -	output = "Snake Snake Case"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "sha256sum"; -	output = "Sha 256 Sum"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "cat2dog"; -	output = "Cat 2 Dog"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "function(name)"; -	output = "Function(name)"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls (existing incorrect behavior): %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "snake_case_function(snake_case_arg)"; -	output = "Snake Case Function(snake Case Arg)"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls (existing incorrect behavior): %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	input = "snake_case_function( snake_case_arg )"; -	output = "Snake Case Function( Snake Case Arg )"; -	success = (input.capitalize() == output); -	state = state && success; -	OS::get_singleton()->print("Capitalize %ls: %ls, %s\n", input.c_str(), output.c_str(), success ? "OK" : "FAIL"); - -	return state; -} - -bool test_31() { -	bool state = true; -	bool success; - -	String a = ""; -	success = a[0] == 0; -	OS::get_singleton()->print("Is 0 String[0]:, %s\n", success ? "OK" : "FAIL"); -	if (!success) { -		state = false; -	} - -	String b = "Godot"; -	success = b[b.size()] == 0; -	OS::get_singleton()->print("Is 0 String[size()]:, %s\n", success ? "OK" : "FAIL"); -	if (!success) { -		state = false; -	} - -	const String c = ""; -	success = c[0] == 0; -	OS::get_singleton()->print("Is 0 const String[0]:, %s\n", success ? "OK" : "FAIL"); -	if (!success) { -		state = false; -	} - -	const String d = "Godot"; -	success = d[d.size()] == 0; -	OS::get_singleton()->print("Is 0 const String[size()]:, %s\n", success ? "OK" : "FAIL"); -	if (!success) { -		state = false; -	} - -	return state; -}; - -bool test_32() { -#define STRIP_TEST(x)                                            \ -	{                                                            \ -		bool success = x;                                        \ -		state = state && success;                                \ -		if (!success) {                                          \ -			OS::get_singleton()->print("\tfailed at: %s\n", #x); \ -		}                                                        \ -	} - -	OS::get_singleton()->print("\n\nTest 32: lstrip and rstrip\n"); -	bool state = true; - -	// strip none -	STRIP_TEST(String("abc").lstrip("") == "abc"); -	STRIP_TEST(String("abc").rstrip("") == "abc"); -	// strip one -	STRIP_TEST(String("abc").lstrip("a") == "bc"); -	STRIP_TEST(String("abc").rstrip("c") == "ab"); -	// strip lots -	STRIP_TEST(String("bababbababccc").lstrip("ab") == "ccc"); -	STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("cb") == "aaa"); -	// strip empty string -	STRIP_TEST(String("").lstrip("") == ""); -	STRIP_TEST(String("").rstrip("") == ""); -	// strip to empty string -	STRIP_TEST(String("abcabcabc").lstrip("bca") == ""); -	STRIP_TEST(String("abcabcabc").rstrip("bca") == ""); -	// don't strip wrong end -	STRIP_TEST(String("abc").lstrip("c") == "abc"); -	STRIP_TEST(String("abca").lstrip("a") == "bca"); -	STRIP_TEST(String("abc").rstrip("a") == "abc"); -	STRIP_TEST(String("abca").rstrip("a") == "abc"); -	// in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5) -	// and the same second as "ÿ" (\u00ff) -	STRIP_TEST(String::utf8("¿").lstrip(String::utf8("µÿ")) == String::utf8("¿")); -	STRIP_TEST(String::utf8("¿").rstrip(String::utf8("µÿ")) == String::utf8("¿")); -	STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("µÿ")) == String::utf8("¿ÿ")); -	STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("µÿ")) == String::utf8("µ¿")); - -	// the above tests repeated with additional superfluous strip chars - -	// strip none -	STRIP_TEST(String("abc").lstrip("qwjkl") == "abc"); -	STRIP_TEST(String("abc").rstrip("qwjkl") == "abc"); -	// strip one -	STRIP_TEST(String("abc").lstrip("qwajkl") == "bc"); -	STRIP_TEST(String("abc").rstrip("qwcjkl") == "ab"); -	// strip lots -	STRIP_TEST(String("bababbababccc").lstrip("qwabjkl") == "ccc"); -	STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("qwcbjkl") == "aaa"); -	// strip empty string -	STRIP_TEST(String("").lstrip("qwjkl") == ""); -	STRIP_TEST(String("").rstrip("qwjkl") == ""); -	// strip to empty string -	STRIP_TEST(String("abcabcabc").lstrip("qwbcajkl") == ""); -	STRIP_TEST(String("abcabcabc").rstrip("qwbcajkl") == ""); -	// don't strip wrong end -	STRIP_TEST(String("abc").lstrip("qwcjkl") == "abc"); -	STRIP_TEST(String("abca").lstrip("qwajkl") == "bca"); -	STRIP_TEST(String("abc").rstrip("qwajkl") == "abc"); -	STRIP_TEST(String("abca").rstrip("qwajkl") == "abc"); -	// in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5) -	// and the same second as "ÿ" (\u00ff) -	STRIP_TEST(String::utf8("¿").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿")); -	STRIP_TEST(String::utf8("¿").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿")); -	STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿ÿ")); -	STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("µ¿")); - -	return state; - -#undef STRIP_TEST -} - -bool test_33() { -	OS::get_singleton()->print("\n\nTest 33: parse_utf8(null, -1)\n"); - -	String empty; -	return empty.parse_utf8(nullptr, -1); -} - -bool test_34() { -	OS::get_singleton()->print("\n\nTest 34: Cyrillic to_lower()\n"); - -	String upper = String::utf8("АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"); -	String lower = String::utf8("абвгдеёжзийклмнопрстуфхцчшщъыьэюя"); - -	String test = upper.to_lower(); - -	bool state = test == lower; - -	return state; -} - -bool test_35() { -#define COUNT_TEST(x)                                            \ -	{                                                            \ -		bool success = x;                                        \ -		state = state && success;                                \ -		if (!success) {                                          \ -			OS::get_singleton()->print("\tfailed at: %s\n", #x); \ -		}                                                        \ -	} - -	OS::get_singleton()->print("\n\nTest 35: count and countn function\n"); -	bool state = true; - -	COUNT_TEST(String("").count("Test") == 0); -	COUNT_TEST(String("Test").count("") == 0); -	COUNT_TEST(String("Test").count("test") == 0); -	COUNT_TEST(String("Test").count("TEST") == 0); -	COUNT_TEST(String("TEST").count("TEST") == 1); -	COUNT_TEST(String("Test").count("Test") == 1); -	COUNT_TEST(String("aTest").count("Test") == 1); -	COUNT_TEST(String("Testa").count("Test") == 1); -	COUNT_TEST(String("TestTestTest").count("Test") == 3); -	COUNT_TEST(String("TestTestTest").count("TestTest") == 1); -	COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3); - -	COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1); -	COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2); -	COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3); -	COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3); - -	COUNT_TEST(String("Test").countn("test") == 1); -	COUNT_TEST(String("Test").countn("TEST") == 1); -	COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4); -	COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2); - -	return state; -} - -typedef bool (*TestFunc)(); - -TestFunc test_funcs[] = { - -	test_1, -	test_2, -	test_3, -	test_4, -	test_5, -	test_6, -	test_7, -	test_8, -	test_9, -	test_10, -	test_11, -	test_12, -	test_13, -	test_14, -	test_15, -	test_16, -	test_17, -	test_18, -	test_19, -	test_20, -	test_21, -	test_22, -	test_23, -	test_24, -	test_25, -	test_26, -	test_27, -	test_28, -	test_29, -	test_30, -	test_31, -	test_32, -	test_33, -	test_34, -	test_35, -	nullptr - -}; - -MainLoop *test() { -	/** A character length != wchar_t may be forced, so the tests won't work */ - -	static_assert(sizeof(CharType) == sizeof(wchar_t)); - -	int count = 0; -	int passed = 0; - -	while (true) { -		if (!test_funcs[count]) { -			break; -		} -		bool pass = test_funcs[count](); -		if (pass) { -			passed++; -		} -		OS::get_singleton()->print("\t%s\n", pass ? "PASS" : "FAILED"); - -		count++; -	} - -	OS::get_singleton()->print("\n\n\n"); -	OS::get_singleton()->print("*************\n"); -	OS::get_singleton()->print("***TOTALS!***\n"); -	OS::get_singleton()->print("*************\n"); - -	OS::get_singleton()->print("Passed %i of %i tests\n", passed, count); - -	return nullptr; -} - -} // namespace TestString diff --git a/main/tests/test_string.h b/main/tests/test_string.h deleted file mode 100644 index 96fa811126..0000000000 --- a/main/tests/test_string.h +++ /dev/null @@ -1,42 +0,0 @@ -/*************************************************************************/ -/*  test_string.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_STRING_H -#define TEST_STRING_H - -#include "core/os/main_loop.h" -#include "core/ustring.h" - -namespace TestString { - -MainLoop *test(); -} - -#endif diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj index 54dda2563d..9a1143a21a 100644 --- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj +++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@  		$modules_buildfile  		1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */; };  		D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D07CD44D1C5D589C00B7FB28 /* Images.xcassets */; }; +		9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9039D3BD24C093AC0020482C /* MoltenVK.a */; }; +		9073252C24BF980B0063BCD4 /* vulkan in Resources */ = {isa = PBXBuildFile; fileRef = 905036DC24BF932E00301046 /* vulkan */; };  		D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */; };  		D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7718AEBFEB004A7AAE /* $binary.pck */; };  		$pbx_launch_screen_build_reference @@ -37,6 +39,8 @@  		$modules_fileref  		1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "$binary.entitlements"; sourceTree = "<group>"; };  		1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = "<group>"; }; +		9039D3BD24C093AC0020482C /* MoltenVK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = MoltenVK; path = MoltenVK.a; sourceTree = "<group>"; }; +		905036DC24BF932E00301046 /* vulkan */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vulkan; path = "$binary/vulkan"; sourceTree = "<group>"; };  		D07CD44D1C5D589C00B7FB28 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };  		D0BCFE3418AEBDA2004A7AAE /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; };  		D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = "<group>"; }; @@ -52,6 +56,7 @@  			isa = PBXFrameworksBuildPhase;  			buildActionMask = 2147483647;  			files = ( +				9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */,  				DEADBEEF2F582BE20003B888 /* $binary.a */,  				$modules_buildphase  				$additional_pbx_frameworks_build @@ -64,6 +69,7 @@  		D0BCFE2B18AEBDA2004A7AAE = {  			isa = PBXGroup;  			children = ( +				905036DC24BF932E00301046 /* vulkan */,  				1F1575711F582BE20003B888 /* dylibs */,  				D0BCFE7718AEBFEB004A7AAE /* $binary.pck */,  				D0BCFE4118AEBDA2004A7AAE /* $binary */, @@ -84,6 +90,7 @@  		D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = {  			isa = PBXGroup;  			children = ( +				9039D3BD24C093AC0020482C /* MoltenVK.a */,  				DEADBEEF1F582BE20003B888 /* $binary.a */,  				$modules_buildgrp  				$additional_pbx_frameworks_refs @@ -248,6 +255,7 @@  			isa = PBXResourcesBuildPhase;  			buildActionMask = 2147483647;  			files = ( +				9073252C24BF980B0063BCD4 /* vulkan in Resources */,  				D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */,  				D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */,  				$pbx_launch_screen_build_phase @@ -319,7 +327,7 @@  				GCC_WARN_UNINITIALIZED_AUTOS = YES;  				GCC_WARN_UNUSED_FUNCTION = YES;  				GCC_WARN_UNUSED_VARIABLE = YES; -				IPHONEOS_DEPLOYMENT_TARGET = 9.0; +				IPHONEOS_DEPLOYMENT_TARGET = 11.0;  				OTHER_LDFLAGS = "$linker_flags";  				SDKROOT = iphoneos;  				TARGETED_DEVICE_FAMILY = "1,2"; @@ -358,7 +366,7 @@  				GCC_WARN_UNINITIALIZED_AUTOS = YES;  				GCC_WARN_UNUSED_FUNCTION = YES;  				GCC_WARN_UNUSED_VARIABLE = YES; -				IPHONEOS_DEPLOYMENT_TARGET = 9.0; +				IPHONEOS_DEPLOYMENT_TARGET = 11.0;  				OTHER_LDFLAGS = "$linker_flags";  				SDKROOT = iphoneos;  				TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json b/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json new file mode 100644 index 0000000000..7501cb548c --- /dev/null +++ b/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json @@ -0,0 +1,7 @@ +{ +    "file_format_version" : "1.0.0", +    "ICD": { +        "library_path": "./../../Frameworks/MoltenVK.framework/MoltenVK", +        "api_version" : "1.0.0" +    } +} diff --git a/modules/SCsub b/modules/SCsub index 9155a53eaf..2d774306e4 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -1,10 +1,10 @@  #!/usr/bin/env python -Import("env") -  import modules_builders  import os +Import("env") +  env_modules = env.Clone()  Export("env_modules") @@ -12,6 +12,15 @@ Export("env_modules")  # Header with MODULE_*_ENABLED defines.  env.CommandNoCache("modules_enabled.gen.h", Value(env.module_list), modules_builders.generate_modules_enabled) +# Header to be included in `tests/test_main.cpp` to run module-specific tests. +if env["tests"]: +    env.CommandNoCache( +        "modules_tests.gen.h", +        Value(env.module_list), +        Action(modules_builders.generate_modules_tests, "Generating modules tests header."), +    ) +    env.AlwaysBuild("modules_tests.gen.h") +  vs_sources = []  # libmodule_<name>.a for each active module.  for name, path in env.module_list.items(): diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h index 1044f3cf6f..5a2c50e213 100644 --- a/modules/arkit/arkit_interface.h +++ b/modules/arkit/arkit_interface.h @@ -60,8 +60,8 @@ private:  	float eye_height, z_near, z_far;  	Ref<CameraFeed> feed; -	int image_width[2]; -	int image_height[2]; +	size_t image_width[2]; +	size_t image_height[2];  	Vector<uint8_t> img_data[2];  	struct anchor_map { @@ -84,9 +84,9 @@ public:  	void start_session();  	void stop_session(); -	bool get_anchor_detection_is_enabled() const; -	void set_anchor_detection_is_enabled(bool p_enable); -	virtual int get_camera_feed_id(); +	bool get_anchor_detection_is_enabled() const override; +	void set_anchor_detection_is_enabled(bool p_enable) override; +	virtual int get_camera_feed_id() override;  	bool get_light_estimation_is_enabled() const;  	void set_light_estimation_is_enabled(bool p_enable); @@ -97,22 +97,22 @@ public:  	/* while Godot has its own raycast logic this takes ARKits camera into account and hits on any ARAnchor */  	Array raycast(Vector2 p_screen_coord); -	void notification(int p_what); +	virtual void notification(int p_what) override; -	virtual StringName get_name() const; -	virtual int get_capabilities() const; +	virtual StringName get_name() const override; +	virtual int get_capabilities() const override; -	virtual bool is_initialized() const; -	virtual bool initialize(); -	virtual void uninitialize(); +	virtual bool is_initialized() const override; +	virtual bool initialize() override; +	virtual void uninitialize() override; -	virtual Size2 get_render_targetsize(); -	virtual bool is_stereo(); -	virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform); -	virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far); -	virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect); +	virtual Size2 get_render_targetsize() override; +	virtual bool is_stereo() override; +	virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) override; +	virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far) override; +	virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override; -	virtual void process(); +	virtual void process() override;  	// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)  	void _add_or_update_anchor(void *p_anchor); diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm index 7de824815a..3fb2cc933d 100644 --- a/modules/arkit/arkit_interface.mm +++ b/modules/arkit/arkit_interface.mm @@ -42,7 +42,9 @@  #include "arkit_session_delegate.h"  // just a dirty workaround for now, declare these as globals. I'll probably encapsulate ARSession and associated logic into an mm object and change ARKitInterface to a normal cpp object that consumes it. +API_AVAILABLE(ios(11.0))  ARSession *ar_session; +  ARKitSessionDelegate *ar_delegate;  NSTimeInterval last_timestamp; @@ -55,22 +57,28 @@ void ARKitInterface::start_session() {  	if (initialized) {  		print_line("Starting ARKit session"); -		Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration"); -		ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new]; +		if (@available(iOS 11, *)) { +			Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration"); +			ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new]; -		configuration.lightEstimationEnabled = light_estimation_is_enabled; -		if (plane_detection_is_enabled) { -			configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal; -		} else { -			configuration.planeDetection = 0; -		} +			configuration.lightEstimationEnabled = light_estimation_is_enabled; +			if (plane_detection_is_enabled) { +				if (@available(iOS 11.3, *)) { +					configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal; +				} else { +					configuration.planeDetection = ARPlaneDetectionHorizontal; +				} +			} else { +				configuration.planeDetection = 0; +			} -		// make sure our camera is on -		if (feed.is_valid()) { -			feed->set_active(true); -		} +			// make sure our camera is on +			if (feed.is_valid()) { +				feed->set_active(true); +			} -		[ar_session runWithConfiguration:configuration]; +			[ar_session runWithConfiguration:configuration]; +		}  	}  } @@ -84,7 +92,9 @@ void ARKitInterface::stop_session() {  			feed->set_active(false);  		} -		[ar_session pause]; +		if (@available(iOS 11.0, *)) { +			[ar_session pause]; +		}  	}  } @@ -92,12 +102,12 @@ void ARKitInterface::notification(int p_what) {  	// TODO, this is not being called, need to find out why, possibly because this is not a node.  	// in that case we need to find a way to get these notifications!  	switch (p_what) { -		case MainLoop::NOTIFICATION_WM_FOCUS_IN: { +		case DisplayServer::WINDOW_EVENT_FOCUS_IN: {  			print_line("Focus in");  			start_session();  		}; break; -		case MainLoop::NOTIFICATION_WM_FOCUS_OUT: { +		case DisplayServer::WINDOW_EVENT_FOCUS_OUT: {  			print_line("Focus out");  			stop_session(); @@ -162,37 +172,42 @@ int ARKitInterface::get_capabilities() const {  }  Array ARKitInterface::raycast(Vector2 p_screen_coord) { -	Array arr; -	Size2 screen_size = OS::get_singleton()->get_window_size(); -	CGPoint point; -	point.x = p_screen_coord.x / screen_size.x; -	point.y = p_screen_coord.y / screen_size.y; - -	///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account -	NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hittest:point types:ARHitTestResultTypeExistingPlaneUsingExtent]; - -	for (ARHitTestResult *result in results) { -		Transform transform; - -		matrix_float4x4 m44 = result.worldTransform; -		transform.basis.elements[0].x = m44.columns[0][0]; -		transform.basis.elements[1].x = m44.columns[0][1]; -		transform.basis.elements[2].x = m44.columns[0][2]; -		transform.basis.elements[0].y = m44.columns[1][0]; -		transform.basis.elements[1].y = m44.columns[1][1]; -		transform.basis.elements[2].y = m44.columns[1][2]; -		transform.basis.elements[0].z = m44.columns[2][0]; -		transform.basis.elements[1].z = m44.columns[2][1]; -		transform.basis.elements[2].z = m44.columns[2][2]; -		transform.origin.x = m44.columns[3][0]; -		transform.origin.y = m44.columns[3][1]; -		transform.origin.z = m44.columns[3][2]; - -		/* important, NOT scaled to world_scale !! */ -		arr.push_back(transform); -	} +	if (@available(iOS 11, *)) { +		Array arr; +		Size2 screen_size = DisplayServer::get_singleton()->screen_get_size(); +		CGPoint point; +		point.x = p_screen_coord.x / screen_size.x; +		point.y = p_screen_coord.y / screen_size.y; + +		///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account + +		NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hitTest:point types:ARHitTestResultTypeExistingPlaneUsingExtent]; + +		for (ARHitTestResult *result in results) { +			Transform transform; + +			matrix_float4x4 m44 = result.worldTransform; +			transform.basis.elements[0].x = m44.columns[0][0]; +			transform.basis.elements[1].x = m44.columns[0][1]; +			transform.basis.elements[2].x = m44.columns[0][2]; +			transform.basis.elements[0].y = m44.columns[1][0]; +			transform.basis.elements[1].y = m44.columns[1][1]; +			transform.basis.elements[2].y = m44.columns[1][2]; +			transform.basis.elements[0].z = m44.columns[2][0]; +			transform.basis.elements[1].z = m44.columns[2][1]; +			transform.basis.elements[2].z = m44.columns[2][2]; +			transform.origin.x = m44.columns[3][0]; +			transform.origin.y = m44.columns[3][1]; +			transform.origin.z = m44.columns[3][2]; + +			/* important, NOT scaled to world_scale !! */ +			arr.push_back(transform); +		} -	return arr; +		return arr; +	} else { +		return Array(); +	}  }  void ARKitInterface::_bind_methods() { @@ -221,51 +236,55 @@ bool ARKitInterface::initialize() {  	XRServer *xr_server = XRServer::get_singleton();  	ERR_FAIL_NULL_V(xr_server, false); -	if (!initialized) { -		print_line("initializing ARKit"); +	if (@available(iOS 11, *)) { +		if (!initialized) { +			print_line("initializing ARKit"); -		// create our ar session and delegate -		Class ARSessionClass = NSClassFromString(@"ARSession"); -		if (ARSessionClass == Nil) { -			void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW); -			if (arkit_handle) { -				ARSessionClass = NSClassFromString(@"ARSession"); -			} else { -				print_line("ARKit init failed"); -				return false; +			// create our ar session and delegate +			Class ARSessionClass = NSClassFromString(@"ARSession"); +			if (ARSessionClass == Nil) { +				void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW); +				if (arkit_handle) { +					ARSessionClass = NSClassFromString(@"ARSession"); +				} else { +					print_line("ARKit init failed"); +					return false; +				}  			} -		} -		ar_session = [ARSessionClass new]; -		ar_delegate = [ARKitSessionDelegate new]; -		ar_delegate.arkit_interface = this; -		ar_session.delegate = ar_delegate; +			ar_session = [ARSessionClass new]; +			ar_delegate = [ARKitSessionDelegate new]; +			ar_delegate.arkit_interface = this; +			ar_session.delegate = ar_delegate; -		// reset our transform -		transform = Transform(); +			// reset our transform +			transform = Transform(); -		// make this our primary interface -		xr_server->set_primary_interface(this); +			// make this our primary interface +			xr_server->set_primary_interface(this); -		// make sure we have our feed setup -		if (feed.is_null()) { -			feed.instance(); -			feed->set_name("ARKit"); +			// make sure we have our feed setup +			if (feed.is_null()) { +				feed.instance(); +				feed->set_name("ARKit"); -			CameraServer *cs = CameraServer::get_singleton(); -			if (cs != NULL) { -				cs->add_feed(feed); +				CameraServer *cs = CameraServer::get_singleton(); +				if (cs != NULL) { +					cs->add_feed(feed); +				}  			} -		} -		feed->set_active(true); +			feed->set_active(true); -		// yeah! -		initialized = true; +			// yeah! +			initialized = true; -		// Start our session... -		start_session(); -	} +			// Start our session... +			start_session(); +		} -	return true; +		return true; +	} else { +		return false; +	}  }  void ARKitInterface::uninitialize() { @@ -286,9 +305,12 @@ void ARKitInterface::uninitialize() {  		remove_all_anchors(); -		[ar_session release]; +		if (@available(iOS 11.0, *)) { +			[ar_session release]; +			ar_session = NULL; +		}  		[ar_delegate release]; -		ar_session = NULL; +  		ar_delegate = NULL;  		initialized = false;  		session_was_started = false; @@ -296,15 +318,15 @@ void ARKitInterface::uninitialize() {  }  Size2 ARKitInterface::get_render_targetsize() { -	_THREAD_SAFE_METHOD_ +	// _THREAD_SAFE_METHOD_ -	Size2 target_size = OS::get_singleton()->get_window_size(); +	Size2 target_size = DisplayServer::get_singleton()->screen_get_size();  	return target_size;  }  Transform ARKitInterface::get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) { -	_THREAD_SAFE_METHOD_ +	// _THREAD_SAFE_METHOD_  	Transform transform_for_eye; @@ -336,7 +358,7 @@ CameraMatrix ARKitInterface::get_projection_for_eye(XRInterface::Eyes p_eye, rea  }  void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) { -	_THREAD_SAFE_METHOD_ +	// _THREAD_SAFE_METHOD_  	// We must have a valid render target  	ERR_FAIL_COND(!p_render_target.is_valid()); @@ -345,15 +367,15 @@ void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target  	ERR_FAIL_COND(p_screen_rect == Rect2());  	// get the size of our screen -	Rect2 screen_rect = p_screen_rect; +	//	Rect2 screen_rect = p_screen_rect;  	//		screen_rect.position.x += screen_rect.size.x;  	//		screen_rect.size.x = -screen_rect.size.x;  	//		screen_rect.position.y += screen_rect.size.y;  	//		screen_rect.size.y = -screen_rect.size.y; -	VSG::rasterizer->set_current_render_target(RID()); -	VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0); +	//	VSG::rasterizer->set_current_render_target(RID()); +	//	VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);  }  XRPositionalTracker *ARKitInterface::get_anchor_for_uuid(const unsigned char *p_uuid) { @@ -432,7 +454,7 @@ void ARKitInterface::remove_all_anchors() {  }  void ARKitInterface::process() { -	_THREAD_SAFE_METHOD_ +	// _THREAD_SAFE_METHOD_  	if (@available(iOS 11.0, *)) {  		if (initialized) { @@ -443,8 +465,16 @@ void ARKitInterface::process() {  				last_timestamp = current_frame.timestamp;  				// get some info about our screen and orientation -				Size2 screen_size = OS::get_singleton()->get_window_size(); -				UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; +				Size2 screen_size = DisplayServer::get_singleton()->screen_get_size(); +				UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown; + +				if (@available(iOS 13, *)) { +					orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +				} else { +					orientation = [[UIApplication sharedApplication] statusBarOrientation]; +#endif +				}  				// Grab our camera image for our backbuffer  				CVPixelBufferRef pixelBuffer = current_frame.capturedImage; @@ -475,27 +505,27 @@ void ARKitInterface::process() {  						{  							// do Y -							int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); -							int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); -							int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); +							size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); +							size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); +							size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);  							if ((image_width[0] != new_width) || (image_height[0] != new_height)) {  								printf("- Camera padding l:%lu r:%lu t:%lu b:%lu\n", extraLeft, extraRight, extraTop, extraBottom); -								printf("- Camera Y plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row); +								printf("- Camera Y plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);  								image_width[0] = new_width;  								image_height[0] = new_height;  								img_data[0].resize(new_width * new_height);  							} -							uint8_t *w = img_data[0].write(); +							uint8_t *w = img_data[0].ptrw();  							if (new_width == bytes_per_row) { -								memcpy(w.ptr(), dataY, new_width * new_height); +								memcpy(w, dataY, new_width * new_height);  							} else { -								int offset_a = 0; -								int offset_b = extraLeft + (extraTop * bytes_per_row); -								for (int r = 0; r < new_height; r++) { -									memcpy(w.ptr() + offset_a, dataY + offset_b, new_width); +								size_t offset_a = 0; +								size_t offset_b = extraLeft + (extraTop * bytes_per_row); +								for (size_t r = 0; r < new_height; r++) { +									memcpy(w + offset_a, dataY + offset_b, new_width);  									offset_a += new_width;  									offset_b += bytes_per_row;  								} @@ -507,26 +537,26 @@ void ARKitInterface::process() {  						{  							// do CbCr -							int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); -							int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); -							int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); +							size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); +							size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); +							size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);  							if ((image_width[1] != new_width) || (image_height[1] != new_height)) { -								printf("- Camera CbCr plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row); +								printf("- Camera CbCr plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);  								image_width[1] = new_width;  								image_height[1] = new_height;  								img_data[1].resize(2 * new_width * new_height);  							} -							uint8_t *w = img_data[1].write(); +							uint8_t *w = img_data[1].ptrw();  							if ((2 * new_width) == bytes_per_row) { -								memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height); +								memcpy(w, dataCbCr, 2 * new_width * new_height);  							} else { -								int offset_a = 0; -								int offset_b = extraLeft + (extraTop * bytes_per_row); -								for (int r = 0; r < new_height; r++) { -									memcpy(w.ptr() + offset_a, dataCbCr + offset_b, 2 * new_width); +								size_t offset_a = 0; +								size_t offset_b = extraLeft + (extraTop * bytes_per_row); +								for (size_t r = 0; r < new_height; r++) { +									memcpy(w + offset_a, dataCbCr + offset_b, 2 * new_width);  									offset_a += 2 * new_width;  									offset_b += bytes_per_row;  								} @@ -658,69 +688,78 @@ void ARKitInterface::process() {  }  void ARKitInterface::_add_or_update_anchor(void *p_anchor) { -	_THREAD_SAFE_METHOD_ - -	ARAnchor *anchor = (ARAnchor *)p_anchor; - -	unsigned char uuid[16]; -	[anchor.identifier getUUIDBytes:uuid]; - -	XRPositionalTracker *tracker = get_anchor_for_uuid(uuid); -	if (tracker != NULL) { -		// lets update our mesh! (using Arjens code as is for now) -		// we should also probably limit how often we do this... +	// _THREAD_SAFE_METHOD_ -		// can we safely cast this? -		ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor; - -		if (planeAnchor.geometry.triangleCount > 0) { -			Ref<SurfaceTool> surftool; -			surftool.instance(); -			surftool->begin(Mesh::PRIMITIVE_TRIANGLES); +	if (@available(iOS 11.0, *)) { +		ARAnchor *anchor = (ARAnchor *)p_anchor; + +		unsigned char uuid[16]; +		[anchor.identifier getUUIDBytes:uuid]; + +		XRPositionalTracker *tracker = get_anchor_for_uuid(uuid); +		if (tracker != NULL) { +			// lets update our mesh! (using Arjens code as is for now) +			// we should also probably limit how often we do this... + +			// can we safely cast this? +			ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor; + +			if (@available(iOS 11.3, *)) { +				if (planeAnchor.geometry.triangleCount > 0) { +					Ref<SurfaceTool> surftool; +					surftool.instance(); +					surftool->begin(Mesh::PRIMITIVE_TRIANGLES); + +					for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) { +						int16_t index = planeAnchor.geometry.triangleIndices[j]; +						simd_float3 vrtx = planeAnchor.geometry.vertices[index]; +						simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index]; +						surftool->add_uv(Vector2(textcoord[0], textcoord[1])); +						surftool->add_color(Color(0.8, 0.8, 0.8)); +						surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2])); +					} -			for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) { -				int16_t index = planeAnchor.geometry.triangleIndices[j]; -				simd_float3 vrtx = planeAnchor.geometry.vertices[index]; -				simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index]; -				surftool->add_uv(Vector2(textcoord[0], textcoord[1])); -				surftool->add_color(Color(0.8, 0.8, 0.8)); -				surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2])); +					surftool->generate_normals(); +					tracker->set_mesh(surftool->commit()); +				} else { +					Ref<Mesh> nomesh; +					tracker->set_mesh(nomesh); +				} +			} else { +				Ref<Mesh> nomesh; +				tracker->set_mesh(nomesh);  			} -			surftool->generate_normals(); -			tracker->set_mesh(surftool->commit()); -		} else { -			Ref<Mesh> nomesh; -			tracker->set_mesh(nomesh); +			// Note, this also contains a scale factor which gives us an idea of the size of the anchor +			// We may extract that in our XRAnchor class +			Basis b; +			matrix_float4x4 m44 = anchor.transform; +			b.elements[0].x = m44.columns[0][0]; +			b.elements[1].x = m44.columns[0][1]; +			b.elements[2].x = m44.columns[0][2]; +			b.elements[0].y = m44.columns[1][0]; +			b.elements[1].y = m44.columns[1][1]; +			b.elements[2].y = m44.columns[1][2]; +			b.elements[0].z = m44.columns[2][0]; +			b.elements[1].z = m44.columns[2][1]; +			b.elements[2].z = m44.columns[2][2]; +			tracker->set_orientation(b); +			tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));  		} - -		// Note, this also contains a scale factor which gives us an idea of the size of the anchor -		// We may extract that in our XRAnchor class -		Basis b; -		matrix_float4x4 m44 = anchor.transform; -		b.elements[0].x = m44.columns[0][0]; -		b.elements[1].x = m44.columns[0][1]; -		b.elements[2].x = m44.columns[0][2]; -		b.elements[0].y = m44.columns[1][0]; -		b.elements[1].y = m44.columns[1][1]; -		b.elements[2].y = m44.columns[1][2]; -		b.elements[0].z = m44.columns[2][0]; -		b.elements[1].z = m44.columns[2][1]; -		b.elements[2].z = m44.columns[2][2]; -		tracker->set_orientation(b); -		tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));  	}  }  void ARKitInterface::_remove_anchor(void *p_anchor) { -	_THREAD_SAFE_METHOD_ +	// _THREAD_SAFE_METHOD_ -	ARAnchor *anchor = (ARAnchor *)p_anchor; +	if (@available(iOS 11.0, *)) { +		ARAnchor *anchor = (ARAnchor *)p_anchor; -	unsigned char uuid[16]; -	[anchor.identifier getUUIDBytes:uuid]; +		unsigned char uuid[16]; +		[anchor.identifier getUUIDBytes:uuid]; -	remove_anchor_for_uuid(uuid); +		remove_anchor_for_uuid(uuid); +	}  }  ARKitInterface::ARKitInterface() { @@ -728,7 +767,9 @@ ARKitInterface::ARKitInterface() {  	session_was_started = false;  	plane_detection_is_enabled = false;  	light_estimation_is_enabled = false; -	ar_session = NULL; +	if (@available(iOS 11.0, *)) { +		ar_session = NULL; +	}  	z_near = 0.01;  	z_far = 1000.0;  	projection.set_perspective(60.0, 1.0, z_near, z_far, false); diff --git a/modules/arkit/arkit_session_delegate.h b/modules/arkit/arkit_session_delegate.h index 158b80a60a..df98bf506e 100644 --- a/modules/arkit/arkit_session_delegate.h +++ b/modules/arkit/arkit_session_delegate.h @@ -42,9 +42,9 @@ class ARKitInterface;  @property(nonatomic) ARKitInterface *arkit_interface; -- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors; -- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors; -- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors; +- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0)); +- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0)); +- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));  @end  #endif /* !ARKIT_SESSION_DELEGATE_H */ diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm index f01135f251..c10b13b2af 100644 --- a/modules/camera/camera_ios.mm +++ b/modules/camera/camera_ios.mm @@ -158,25 +158,31 @@  	} else if (dataCbCr == NULL) {  		print_line("Couldn't access CbCr pixel buffer data");  	} else { -		UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; +		UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown; + +		if (@available(iOS 13, *)) { +			orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +		} else { +			orientation = [[UIApplication sharedApplication] statusBarOrientation]; +#endif +		} +  		Ref<Image> img[2];  		{  			// do Y -			int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); -			int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); -			int _bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); +			size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); +			size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);  			if ((width[0] != new_width) || (height[0] != new_height)) { -				// printf("Camera Y plane %i, %i - %i\n", new_width, new_height, bytes_per_row); -  				width[0] = new_width;  				height[0] = new_height;  				img_data[0].resize(new_width * new_height);  			}  			uint8_t *w = img_data[0].ptrw(); -			memcpy(w.ptr(), dataY, new_width * new_height); +			memcpy(w, dataY, new_width * new_height);  			img[0].instance();  			img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]); @@ -184,20 +190,17 @@  		{  			// do CbCr -			int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); -			int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); -			int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); +			size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); +			size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);  			if ((width[1] != new_width) || (height[1] != new_height)) { -				// printf("Camera CbCr plane %i, %i - %i\n", new_width, new_height, bytes_per_row); -  				width[1] = new_width;  				height[1] = new_height;  				img_data[1].resize(2 * new_width * new_height);  			}  			uint8_t *w = img_data[1].ptrw(); -			memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height); +			memcpy(w, dataCbCr, 2 * new_width * new_height);  			///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion  			img[1].instance(); @@ -359,41 +362,59 @@ void CameraIOS::update_feeds() {  	// this way of doing things is deprecated but still works,  	// rewrite to using AVCaptureDeviceDiscoverySession -	AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInTrueDepthCamera, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; +	NSMutableArray *deviceTypes = [NSMutableArray array]; -	// remove devices that are gone.. -	for (int i = feeds.size() - 1; i >= 0; i--) { -		Ref<CameraFeedIOS> feed(feeds[i]); +	if (@available(iOS 10, *)) { +		[deviceTypes addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera]; +		[deviceTypes addObject:AVCaptureDeviceTypeBuiltInTelephotoCamera]; -		if (feed.is_null()) { -			// feed not managed by us -		} else if (![session.devices containsObject:feed->get_device()]) { -			// remove it from our array, this will also destroy it ;) -			remove_feed(feed); -		}; -	}; +		if (@available(iOS 10.2, *)) { +			[deviceTypes addObject:AVCaptureDeviceTypeBuiltInDualCamera]; +		} -	// add new devices.. -	for (AVCaptureDevice *device in session.devices) { -		bool found = false; +		if (@available(iOS 11.1, *)) { +			[deviceTypes addObject:AVCaptureDeviceTypeBuiltInTrueDepthCamera]; +		} + +		AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession +				discoverySessionWithDeviceTypes:deviceTypes +									  mediaType:AVMediaTypeVideo +									   position:AVCaptureDevicePositionUnspecified]; -		for (int i = 0; i < feeds.size() && !found; i++) { +		// remove devices that are gone.. +		for (int i = feeds.size() - 1; i >= 0; i--) {  			Ref<CameraFeedIOS> feed(feeds[i]);  			if (feed.is_null()) {  				// feed not managed by us -			} else if (feed->get_device() == device) { -				found = true; +			} else if (![session.devices containsObject:feed->get_device()]) { +				// remove it from our array, this will also destroy it ;) +				remove_feed(feed);  			};  		}; -		if (!found) { -			Ref<CameraFeedIOS> newfeed; -			newfeed.instance(); -			newfeed->set_device(device); -			add_feed(newfeed); +		// add new devices.. +		for (AVCaptureDevice *device in session.devices) { +			bool found = false; + +			for (int i = 0; i < feeds.size() && !found; i++) { +				Ref<CameraFeedIOS> feed(feeds[i]); + +				if (feed.is_null()) { +					// feed not managed by us +				} else if (feed->get_device() == device) { +					found = true; +				}; +			}; + +			if (!found) { +				Ref<CameraFeedIOS> newfeed; +				newfeed.instance(); +				newfeed->set_device(device); +				add_feed(newfeed); +			};  		}; -	}; +	}  };  CameraIOS::CameraIOS() { diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 3d37c7f803..e34d87f5cc 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -736,7 +736,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::  				} else {  					if (callee->type == GDScriptParser::Node::IDENTIFIER) {  						// Self function call. -						if (codegen.function_node && codegen.function_node->is_static) { +						if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {  							ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS);  						} else {  							ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); diff --git a/modules/modules_builders.py b/modules/modules_builders.py index e7be6380d1..2243162555 100644 --- a/modules/modules_builders.py +++ b/modules/modules_builders.py @@ -12,5 +12,16 @@ def generate_modules_enabled(target, source, env):              f.write("#define %s\n" % ("MODULE_" + module.upper() + "_ENABLED")) +def generate_modules_tests(target, source, env): +    import os +    import glob + +    with open(target[0].path, "w") as f: +        for name, path in env.module_list.items(): +            headers = glob.glob(os.path.join(path, "tests", "*.h")) +            for h in headers: +                f.write('#include "%s"\n' % (os.path.normpath(h))) + +  if __name__ == "__main__":      subprocess_main(globals()) diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 2fcd9332a1..a1c7dca9d2 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -2513,6 +2513,8 @@ RES VisualScriptEditor::get_edited_resource() const {  }  void VisualScriptEditor::set_edited_resource(const RES &p_res) { +	ERR_FAIL_COND(script.is_valid()); +	ERR_FAIL_COND(p_res.is_null());  	script = p_res;  	signal_editor->script = script;  	signal_editor->undo_redo = undo_redo; @@ -2533,6 +2535,9 @@ void VisualScriptEditor::set_edited_resource(const RES &p_res) {  	_update_members();  } +void VisualScriptEditor::enable_editor() { +} +  Vector<String> VisualScriptEditor::get_functions() {  	return Vector<String>();  } diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index e59618e120..0c5665cee8 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -294,6 +294,7 @@ public:  	virtual void apply_code() override;  	virtual RES get_edited_resource() const override;  	virtual void set_edited_resource(const RES &p_res) override; +	virtual void enable_editor() override;  	virtual Vector<String> get_functions() override;  	virtual void reload_text() override;  	virtual String get_name() override; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 0213094c60..8d3257a365 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -768,6 +768,30 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		}  	} +	void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { +		String manifest_text = +				"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +				"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +				"    xmlns:tools=\"http://schemas.android.com/tools\">\n"; + +		manifest_text += _get_screen_sizes_tag(p_preset); +		manifest_text += _get_gles_tag(); + +		Vector<String> perms; +		_get_permissions(p_preset, p_give_internet, perms); +		for (int i = 0; i < perms.size(); i++) { +			manifest_text += vformat("    <uses-permission android:name=\"%s\" />\n", perms.get(i)); +		} + +		manifest_text += _get_xr_features_tag(p_preset); +		manifest_text += _get_instrumentation_tag(p_preset); +		String plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); +		manifest_text += _get_application_tag(p_preset, plugins_names); +		manifest_text += "</manifest>\n"; +		String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); +		store_string_at_path(manifest_path, manifest_text); +	} +  	void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {  		// Leaving the unused types commented because looking these constants up  		// again later would be annoying @@ -2064,6 +2088,7 @@ public:  		EditorProgress ep("export", "Exporting for Android", 105, true);  		bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build")); +		bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);  		Ref<Image> main_image;  		Ref<Image> foreground; @@ -2093,9 +2118,10 @@ public:  			if (err != OK) {  				EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");  			} -			// Copies the project icon files into the appropriate Gradle project directory +			// Copies the project icon files into the appropriate Gradle project directory.  			_copy_icons_to_gradle_project(p_preset, main_image, foreground, background); - +			// Write an AndroidManifest.xml file into the Gradle project directory. +			_write_tmp_manifest(p_preset, p_give_internet, p_debug);  			//build project if custom build is enabled  			String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); @@ -2115,6 +2141,8 @@ public:  			build_command = build_path.plus_file(build_command);  			String package_name = get_package_name(p_preset->get("package/unique_name")); +			String version_code = itos(p_preset->get("version/code")); +			String version_name = p_preset->get("version/name");  			Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);  			String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins); @@ -2128,6 +2156,8 @@ public:  			}  			cmdline.push_back("build");  			cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. +			cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code. +			cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.  			cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.  			cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.  			cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies. @@ -2248,11 +2278,10 @@ public:  			//write -			if (file == "AndroidManifest.xml") { -				_fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG)); -			} -  			if (!use_custom_build) { +				if (file == "AndroidManifest.xml") { +					_fix_manifest(p_preset, data, p_give_internet); +				}  				if (file == "resources.arsc") {  					_fix_resources(p_preset, data);  				} diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 622860c307..209a664f8f 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -142,4 +142,104 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset  	return OK;  } +String bool_to_string(bool v) { +	return v ? "true" : "false"; +} + +String _get_gles_tag() { +	bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" && +					 !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2"); +	return min_gles3 ? "    <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : ""; +} + +String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) { +	String manifest_screen_sizes = "    <supports-screens \n        tools:node=\"replace\""; +	String sizes[] = { "small", "normal", "large", "xlarge" }; +	size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]); +	for (size_t i = 0; i < num_sizes; i++) { +		String feature_name = vformat("screen/support_%s", sizes[i]); +		String feature_support = bool_to_string(p_preset->get(feature_name)); +		String xml_entry = vformat("\n        android:%sScreens=\"%s\"", sizes[i], feature_support); +		manifest_screen_sizes += xml_entry; +	} +	manifest_screen_sizes += " />\n"; +	return manifest_screen_sizes; +} + +String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { +	String manifest_xr_features; +	bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; +	if (uses_xr) { +		int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof +		if (dof_index == 1) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n"; +		} else if (dof_index == 2) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n"; +		} +		int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required +		if (hand_tracking_index == 1) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; +		} else if (hand_tracking_index == 2) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n"; +		} +	} +	return manifest_xr_features; +} + +String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) { +	String package_name = p_preset->get("package/unique_name"); +	String manifest_instrumentation_text = vformat( +			"    <instrumentation\n" +			"        tools:node=\"replace\"\n" +			"        android:name=\".GodotInstrumentation\"\n" +			"        android:icon=\"@mipmap/icon\"\n" +			"        android:label=\"@string/godot_project_name_string\"\n" +			"        android:targetPackage=\"%s\" />\n", +			package_name); +	return manifest_instrumentation_text; +} + +String _get_plugins_tag(const String &plugins_names) { +	if (!plugins_names.empty()) { +		return vformat("    <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names); +	} else { +		return "    <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n"; +	} +} + +String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { +	bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; +	String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape"; +	String manifest_activity_text = vformat( +			"        <activity android:name=\"com.godot.game.GodotApp\" " +			"tools:replace=\"android:screenOrientation\" " +			"android:screenOrientation=\"%s\">\n", +			orientation); +	if (uses_xr) { +		String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness")); +		manifest_activity_text += vformat("            <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness); +	} else { +		manifest_activity_text += "            <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; +	} +	manifest_activity_text += "        </activity>\n"; +	return manifest_activity_text; +} + +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) { +	bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; +	String manifest_application_text = +			"    <application android:label=\"@string/godot_project_name_string\"\n" +			"        android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n" +			"        android:icon=\"@mipmap/icon\">)\n\n" +			"        <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"; + +	manifest_application_text += _get_plugins_tag(plugins_names); +	if (uses_xr) { +		manifest_application_text += "        <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; +	} +	manifest_application_text += _get_activity_tag(p_preset); +	manifest_application_text += "    </application>\n"; +	return manifest_application_text; +} +  #endif //GODOT_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 19202d2310..3f8d138e8f 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -82,6 +82,8 @@ android {          // Feel free to modify the application id to your own.          applicationId getExportPackageName() +        versionCode getExportVersionCode() +        versionName getExportVersionName()          minSdkVersion versions.minSdk          targetSdkVersion versions.targetSdk      } diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index acfdef531e..7d5d238100 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -28,6 +28,22 @@ ext.getExportPackageName = { ->      return appId  } +ext.getExportVersionCode = { -> +    String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : "" +    if (versionCode == null || versionCode.isEmpty()) { +        versionCode = "1" +    } +    return Integer.parseInt(versionCode) +} + +ext.getExportVersionName = { -> +    String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : "" +    if (versionName == null || versionName.isEmpty()) { +        versionName = "1.0" +    } +    return versionName +} +  final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"  /** diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index b72d29149c..49c77468ed 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -3,10 +3,8 @@  Import("env")  iphone_lib = [ -    "godot_iphone.cpp", -    "os_iphone.cpp", -    "semaphore_iphone.cpp", -    "gl_view.mm", +    "godot_iphone.mm", +    "os_iphone.mm",      "main.m",      "app_delegate.mm",      "view_controller.mm", @@ -15,6 +13,12 @@ iphone_lib = [      "icloud.mm",      "ios.mm",      "vulkan_context_iphone.mm", +    "display_server_iphone.mm", +    "joypad_iphone.mm", +    "godot_view.mm", +    "display_layer.mm", +    "godot_view_renderer.mm", +    "godot_view_gesture_recognizer.m",  ]  env_ios = env.Clone() diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index 27552d781a..2f082f1e07 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -28,29 +28,20 @@  /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */  /*************************************************************************/ -#if defined(OPENGL_ENABLED) -#import "gl_view.h" -#endif -#import "view_controller.h"  #import <UIKit/UIKit.h> -#import <CoreMotion/CoreMotion.h> +@class ViewController;  // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,  // so it can't be done with compilation time branching.  //#if defined(OPENGL_ENABLED)  //@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {  //#endif -#if defined(VULKAN_ENABLED) -@interface AppDelegate : NSObject <UIApplicationDelegate> { -#endif -	//@property (strong, nonatomic) UIWindow *window; -	ViewController *view_controller; -	bool is_focus_out; -}; +//#if defined(VULKAN_ENABLED) +@interface AppDelegate : NSObject <UIApplicationDelegate> +//#endif  @property(strong, nonatomic) UIWindow *window; - -+ (ViewController *)getViewController; +@property(strong, class, readonly, nonatomic) ViewController *viewController;  @end diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index c4ef185bf1..7edbcc4667 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -29,644 +29,60 @@  /*************************************************************************/  #import "app_delegate.h" -  #include "core/project_settings.h"  #include "drivers/coreaudio/audio_driver_coreaudio.h" -#if defined(OPENGL_ENABLED) -#import "gl_view.h" -#endif +#import "godot_view.h"  #include "main/main.h"  #include "os_iphone.h" +#import "view_controller.h" -#import "GameController/GameController.h"  #import <AudioToolbox/AudioServices.h> -#define kFilteringFactor 0.1  #define kRenderingFrequency 60 -#define kAccelerometerFrequency 100.0 // Hz - -Error _shell_open(String); -void _set_keep_screen_on(bool p_enabled); - -Error _shell_open(String p_uri) { -	NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; - -	if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]]) -		return ERR_CANT_OPEN; - -	printf("opening url %ls\n", p_uri.c_str()); -	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; -	[url release]; -	return OK; -}; - -void _set_keep_screen_on(bool p_enabled) { -	[[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled]; -}; - -void _vibrate() { -	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); -}; - -@implementation AppDelegate - -@synthesize window;  extern int gargc;  extern char **gargv; -extern int iphone_main(int, int, int, char **, String); + +extern int iphone_main(int, char **, String);  extern void iphone_finish(); -CMMotionManager *motionManager; -bool motionInitialised; +@implementation AppDelegate  static ViewController *mainViewController = nil; -+ (ViewController *)getViewController { -	return mainViewController; -} - -NSMutableDictionary *ios_joysticks = nil; -NSMutableArray *pending_ios_joysticks = nil; - -- (GCControllerPlayerIndex)getFreePlayerIndex { -	bool have_player_1 = false; -	bool have_player_2 = false; -	bool have_player_3 = false; -	bool have_player_4 = false; - -	if (ios_joysticks == nil) { -		NSArray *keys = [ios_joysticks allKeys]; -		for (NSNumber *key in keys) { -			GCController *controller = [ios_joysticks objectForKey:key]; -			if (controller.playerIndex == GCControllerPlayerIndex1) { -				have_player_1 = true; -			} else if (controller.playerIndex == GCControllerPlayerIndex2) { -				have_player_2 = true; -			} else if (controller.playerIndex == GCControllerPlayerIndex3) { -				have_player_3 = true; -			} else if (controller.playerIndex == GCControllerPlayerIndex4) { -				have_player_4 = true; -			}; -		}; -	}; - -	if (!have_player_1) { -		return GCControllerPlayerIndex1; -	} else if (!have_player_2) { -		return GCControllerPlayerIndex2; -	} else if (!have_player_3) { -		return GCControllerPlayerIndex3; -	} else if (!have_player_4) { -		return GCControllerPlayerIndex4; -	} else { -		return GCControllerPlayerIndexUnset; -	}; -}; - -void _ios_add_joystick(GCController *controller, AppDelegate *delegate) { -	// get a new id for our controller -	int joy_id = OSIPhone::get_singleton()->get_unused_joy_id(); -	if (joy_id != -1) { -		// assign our player index -		if (controller.playerIndex == GCControllerPlayerIndexUnset) { -			controller.playerIndex = [delegate getFreePlayerIndex]; -		}; - -		// tell Godot about our new controller -		OSIPhone::get_singleton()->joy_connection_changed( -				joy_id, true, [controller.vendorName UTF8String]); - -		// add it to our dictionary, this will retain our controllers -		[ios_joysticks setObject:controller -						  forKey:[NSNumber numberWithInt:joy_id]]; - -		// set our input handler -		[delegate setControllerInputHandler:controller]; -	} else { -		printf("Couldn't retrieve new joy id\n"); -	}; -} - -static void on_focus_out(ViewController *view_controller, bool *is_focus_out) { -	if (!*is_focus_out) { -		*is_focus_out = true; -		if (OS::get_singleton()->get_main_loop()) -			OS::get_singleton()->get_main_loop()->notification( -					MainLoop::NOTIFICATION_WM_FOCUS_OUT); - -		[view_controller.view stopAnimation]; -		if (OS::get_singleton()->native_video_is_playing()) { -			OSIPhone::get_singleton()->native_video_focus_out(); -		} -		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton()); -		if (audio) -			audio->stop(); -	} -} - -static void on_focus_in(ViewController *view_controller, bool *is_focus_out) { -	if (*is_focus_out) { -		*is_focus_out = false; -		if (OS::get_singleton()->get_main_loop()) -			OS::get_singleton()->get_main_loop()->notification( -					MainLoop::NOTIFICATION_WM_FOCUS_IN); - -		[view_controller.view startAnimation]; -		if (OSIPhone::get_singleton()->native_video_is_playing()) { -			OSIPhone::get_singleton()->native_video_unpause(); -		} - -		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton()); -		if (audio) -			audio->start(); -	} ++ (ViewController *)viewController { +	return mainViewController;  } -- (void)controllerWasConnected:(NSNotification *)notification { -	// create our dictionary if we don't have one yet -	if (ios_joysticks == nil) { -		ios_joysticks = [[NSMutableDictionary alloc] init]; -	}; - -	// get our controller -	GCController *controller = (GCController *)notification.object; -	if (controller == nil) { -		printf("Couldn't retrieve new controller\n"); -	} else if ([[ios_joysticks allKeysForObject:controller] count] != 0) { -		printf("Controller is already registered\n"); -	} else if (frame_count > 1) { -		_ios_add_joystick(controller, self); -	} else { -		if (pending_ios_joysticks == nil) -			pending_ios_joysticks = [[NSMutableArray alloc] init]; -		[pending_ios_joysticks addObject:controller]; -	}; -}; - -- (void)controllerWasDisconnected:(NSNotification *)notification { -	if (ios_joysticks != nil) { -		// find our joystick, there should be only one in our dictionary -		GCController *controller = (GCController *)notification.object; -		NSArray *keys = [ios_joysticks allKeysForObject:controller]; -		for (NSNumber *key in keys) { -			// tell Godot this joystick is no longer there -			int joy_id = [key intValue]; -			OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, ""); - -			// and remove it from our dictionary -			[ios_joysticks removeObjectForKey:key]; -		}; -	}; -}; - -- (int)getJoyIdForController:(GCController *)controller { -	if (ios_joysticks != nil) { -		// find our joystick, there should be only one in our dictionary -		NSArray *keys = [ios_joysticks allKeysForObject:controller]; -		for (NSNumber *key in keys) { -			int joy_id = [key intValue]; -			return joy_id; -		}; -	}; - -	return -1; -}; - -- (void)setControllerInputHandler:(GCController *)controller { -	// Hook in the callback handler for the correct gamepad profile. -	// This is a bit of a weird design choice on Apples part. -	// You need to select the most capable gamepad profile for the -	// gamepad attached. -	if (controller.extendedGamepad != nil) { -		// The extended gamepad profile has all the input you could possibly find on -		// a gamepad but will only be active if your gamepad actually has all of -		// these... -		controller.extendedGamepad.valueChangedHandler = ^( -				GCExtendedGamepad *gamepad, GCControllerElement *element) { -			int joy_id = [self getJoyIdForController:controller]; - -			if (element == gamepad.buttonA) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, -						gamepad.buttonA.isPressed); -			} else if (element == gamepad.buttonB) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, -						gamepad.buttonB.isPressed); -			} else if (element == gamepad.buttonX) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, -						gamepad.buttonX.isPressed); -			} else if (element == gamepad.buttonY) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, -						gamepad.buttonY.isPressed); -			} else if (element == gamepad.leftShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, -						gamepad.leftShoulder.isPressed); -			} else if (element == gamepad.rightShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, -						gamepad.rightShoulder.isPressed); -			} else if (element == gamepad.dpad) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, -						gamepad.dpad.up.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, -						gamepad.dpad.down.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, -						gamepad.dpad.left.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, -						gamepad.dpad.right.isPressed); -			}; - -			InputDefault::JoyAxis jx; -			jx.min = -1; -			if (element == gamepad.leftThumbstick) { -				jx.value = gamepad.leftThumbstick.xAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx); -				jx.value = -gamepad.leftThumbstick.yAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx); -			} else if (element == gamepad.rightThumbstick) { -				jx.value = gamepad.rightThumbstick.xAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx); -				jx.value = -gamepad.rightThumbstick.yAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx); -			} else if (element == gamepad.leftTrigger) { -				jx.value = gamepad.leftTrigger.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx); -			} else if (element == gamepad.rightTrigger) { -				jx.value = gamepad.rightTrigger.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); -			}; -		}; -	} else if (controller.gamepad != nil) { -		// gamepad is the standard profile with 4 buttons, shoulder buttons and a -		// D-pad -		controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, -				GCControllerElement *element) { -			int joy_id = [self getJoyIdForController:controller]; - -			if (element == gamepad.buttonA) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, -						gamepad.buttonA.isPressed); -			} else if (element == gamepad.buttonB) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, -						gamepad.buttonB.isPressed); -			} else if (element == gamepad.buttonX) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, -						gamepad.buttonX.isPressed); -			} else if (element == gamepad.buttonY) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, -						gamepad.buttonY.isPressed); -			} else if (element == gamepad.leftShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, -						gamepad.leftShoulder.isPressed); -			} else if (element == gamepad.rightShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, -						gamepad.rightShoulder.isPressed); -			} else if (element == gamepad.dpad) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, -						gamepad.dpad.up.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, -						gamepad.dpad.down.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, -						gamepad.dpad.left.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, -						gamepad.dpad.right.isPressed); -			}; -		}; -#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+, -		// while we are setting that as the minimum, seems our -		// build environment doesn't like it -	} else if (controller.microGamepad != nil) { -		// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad -		controller.microGamepad.valueChangedHandler = -				^(GCMicroGamepad *gamepad, GCControllerElement *element) { -					int joy_id = [self getJoyIdForController:controller]; - -					if (element == gamepad.buttonA) { -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, -								gamepad.buttonA.isPressed); -					} else if (element == gamepad.buttonX) { -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, -								gamepad.buttonX.isPressed); -					} else if (element == gamepad.dpad) { -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, -								gamepad.dpad.up.isPressed); -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, -								gamepad.dpad.down.isPressed); -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, -								gamepad.dpad.left.isPressed); -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, -								gamepad.dpad.right.isPressed); -					}; -				}; -#endif -	}; - -	///@TODO need to add support for controller.motion which gives us access to -	/// the orientation of the device (if supported) - -	///@TODO need to add support for controllerPausedHandler which should be a -	/// toggle -}; - -- (void)initGameControllers { -	// get told when controllers connect, this will be called right away for -	// already connected controllers -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(controllerWasConnected:) -				   name:GCControllerDidConnectNotification -				 object:nil]; - -	// get told when controllers disconnect -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(controllerWasDisconnected:) -				   name:GCControllerDidDisconnectNotification -				 object:nil]; -}; - -- (void)deinitGameControllers { -	[[NSNotificationCenter defaultCenter] -			removeObserver:self -					  name:GCControllerDidConnectNotification -					object:nil]; -	[[NSNotificationCenter defaultCenter] -			removeObserver:self -					  name:GCControllerDidDisconnectNotification -					object:nil]; - -	if (ios_joysticks != nil) { -		[ios_joysticks dealloc]; -		ios_joysticks = nil; -	}; - -	if (pending_ios_joysticks != nil) { -		[pending_ios_joysticks dealloc]; -		pending_ios_joysticks = nil; -	}; -}; - -OS::VideoMode _get_video_mode() { -	int backingWidth; -	int backingHeight; -#if defined(OPENGL_ENABLED) -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, -			GL_RENDERBUFFER_WIDTH_OES, &backingWidth); -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, -			GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); -#endif - -	OS::VideoMode vm; -	vm.fullscreen = true; -	vm.width = backingWidth; -	vm.height = backingHeight; -	vm.resizable = false; -	return vm; -}; - -static int frame_count = 0; -- (void)drawView:(UIView *)view; -{ -	switch (frame_count) { -		case 0: { -			OS::get_singleton()->set_video_mode(_get_video_mode()); - -			if (!OS::get_singleton()) { -				exit(0); -			}; -			++frame_count; - -			NSString *locale_code = [[NSLocale currentLocale] localeIdentifier]; -			OSIPhone::get_singleton()->set_locale( -					String::utf8([locale_code UTF8String])); - -			NSString *uuid; -			if ([[UIDevice currentDevice] -						respondsToSelector:@selector(identifierForVendor)]) { -				uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; -			} else { -				// before iOS 6, so just generate an identifier and store it -				uuid = [[NSUserDefaults standardUserDefaults] -						objectForKey:@"identiferForVendor"]; -				if (!uuid) { -					CFUUIDRef cfuuid = CFUUIDCreate(NULL); -					uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid); -					CFRelease(cfuuid); -					[[NSUserDefaults standardUserDefaults] -							setObject:uuid -							   forKey:@"identifierForVendor"]; -				} -			} - -			OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String])); - -		}; break; - -		case 1: { -			Main::setup2(); -			++frame_count; - -			if (pending_ios_joysticks != nil) { -				for (GCController *controller in pending_ios_joysticks) { -					_ios_add_joystick(controller, self); -				} -				[pending_ios_joysticks dealloc]; -				pending_ios_joysticks = nil; -			} - -			// this might be necessary before here -			NSDictionary *dict = [[NSBundle mainBundle] infoDictionary]; -			for (NSString *key in dict) { -				NSObject *value = [dict objectForKey:key]; -				String ukey = String::utf8([key UTF8String]); - -				// we need a NSObject to Variant conversor - -				if ([value isKindOfClass:[NSString class]]) { -					NSString *str = (NSString *)value; -					String uval = String::utf8([str UTF8String]); - -					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); - -				} else if ([value isKindOfClass:[NSNumber class]]) { -					NSNumber *n = (NSNumber *)value; -					double dval = [n doubleValue]; - -					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); -				}; -				// do stuff -			} - -		}; break; - -		case 2: { -			Main::start(); -			++frame_count; - -		}; break; // no fallthrough - -		default: { -			if (OSIPhone::get_singleton()) { -				// OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1], -				// accel[2]); -				if (motionInitialised) { -					// Just using polling approach for now, we can set this up so it sends -					// data to us in intervals, might be better. See Apple reference pages -					// for more details: -					// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc - -					// Apple splits our accelerometer date into a gravity and user movement -					// component. We add them back together -					CMAcceleration gravity = motionManager.deviceMotion.gravity; -					CMAcceleration acceleration = -							motionManager.deviceMotion.userAcceleration; - -					///@TODO We don't seem to be getting data here, is my device broken or -					/// is this code incorrect? -					CMMagneticField magnetic = -							motionManager.deviceMotion.magneticField.field; - -					///@TODO we can access rotationRate as a CMRotationRate variable -					///(processed date) or CMGyroData (raw data), have to see what works -					/// best -					CMRotationRate rotation = motionManager.deviceMotion.rotationRate; - -					// Adjust for screen orientation. -					// [[UIDevice currentDevice] orientation] changes even if we've fixed -					// our orientation which is not a good thing when you're trying to get -					// your user to move the screen in all directions and want consistent -					// output - -					///@TODO Using [[UIApplication sharedApplication] statusBarOrientation] -					/// is a bit of a hack. Godot obviously knows the orientation so maybe -					/// we -					// can use that instead? (note that left and right seem swapped) - -					switch ([[UIApplication sharedApplication] statusBarOrientation]) { -						case UIDeviceOrientationLandscapeLeft: { -							OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									-(acceleration.y + gravity.y), (acceleration.x + gravity.x), -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer( -									-magnetic.y, magnetic.x, magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, -									rotation.z); -						}; break; -						case UIDeviceOrientationLandscapeRight: { -							OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									(acceleration.y + gravity.y), -(acceleration.x + gravity.x), -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer( -									magnetic.y, -magnetic.x, magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, -									rotation.z); -						}; break; -						case UIDeviceOrientationPortraitUpsideDown: { -							OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									-(acceleration.x + gravity.x), (acceleration.y + gravity.y), -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer( -									-magnetic.x, magnetic.y, magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, -									rotation.z); -						}; break; -						default: { // assume portrait -							OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									acceleration.x + gravity.x, acceleration.y + gravity.y, -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, -									magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, -									rotation.z); -						}; break; -					}; -				} - -				bool quit_request = OSIPhone::get_singleton()->iterate(); -			}; - -		}; break; -	}; -}; - -- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { -	if (OS::get_singleton()->get_main_loop()) { -		OS::get_singleton()->get_main_loop()->notification( -				MainLoop::NOTIFICATION_OS_MEMORY_WARNING); -	} -}; -  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -	CGRect rect = [[UIScreen mainScreen] bounds]; +	// TODO: might be required to make an early return, so app wouldn't crash because of timeout. +	// TODO: logo screen is not displayed while shaders are compiling +	// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController -	is_focus_out = false; - -	[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; -	// disable idle timer -	// application.idleTimerDisabled = YES; +	CGRect windowBounds = [[UIScreen mainScreen] bounds];  	// Create a full-screen window -	window = [[UIWindow alloc] initWithFrame:rect]; - -	OS::VideoMode vm = _get_video_mode(); +	self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease]; -	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, -			NSUserDomainMask, YES); +	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  	NSString *documentsDirectory = [paths objectAtIndex:0]; -	int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String])); +	int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String])); +  	if (err != 0) {  		// bail, things did not go very well for us, should probably output a message on screen with our error code...  		exit(0); -		return FALSE; +		return NO;  	}; -#if defined(OPENGL_ENABLED) -	// WARNING: We must *always* create the GLView after we have constructed the -	// OS with iphone_main. This allows the GLView to access project settings so -	// it can properly initialize the OpenGL context -	GLView *glView = [[GLView alloc] initWithFrame:rect]; -	glView.delegate = self; - -	view_controller = [[ViewController alloc] init]; -	view_controller.view = glView; - -	_set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO); -	glView.useCADisplayLink = -			bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; -	printf("cadisaplylink: %d", glView.useCADisplayLink); -	glView.animationInterval = 1.0 / kRenderingFrequency; -	[glView startAnimation]; -#endif - -#if defined(VULKAN_ENABLED) -	view_controller = [[ViewController alloc] init]; -#endif +	ViewController *viewController = [[ViewController alloc] init]; +	viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; +	viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency; -	window.rootViewController = view_controller; +	self.window.rootViewController = viewController;  	// Show the window -	[window makeKeyAndVisible]; - -	// Configure and start accelerometer -	if (!motionInitialised) { -		motionManager = [[CMMotionManager alloc] init]; -		if (motionManager.deviceMotionAvailable) { -			motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; -			[motionManager startDeviceMotionUpdatesUsingReferenceFrame: -								   CMAttitudeReferenceFrameXMagneticNorthZVertical]; -			motionInitialised = YES; -		}; -	}; - -	[self initGameControllers]; +	[self.window makeKeyAndVisible];  	[[NSNotificationCenter defaultCenter]  			addObserver:self @@ -674,40 +90,33 @@ static int frame_count = 0;  				   name:AVAudioSessionInterruptionNotification  				 object:[AVAudioSession sharedInstance]]; -	// OSIPhone::screen_width = rect.size.width - rect.origin.x; -	// OSIPhone::screen_height = rect.size.height - rect.origin.y; - -	mainViewController = view_controller; +	mainViewController = viewController;  	// prevent to stop music in another background app  	[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; -	return TRUE; +	return YES;  };  - (void)onAudioInterruption:(NSNotification *)notification {  	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {  		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {  			NSLog(@"Audio interruption began"); -			on_focus_out(view_controller, &is_focus_out); +			OSIPhone::get_singleton()->on_focus_out();  		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {  			NSLog(@"Audio interruption ended"); -			on_focus_in(view_controller, &is_focus_out); +			OSIPhone::get_singleton()->on_focus_in();  		}  	}  }; -- (void)applicationWillTerminate:(UIApplication *)application { -	[self deinitGameControllers]; - -	if (motionInitialised) { -		///@TODO is this the right place to clean this up? -		[motionManager stopDeviceMotionUpdates]; -		[motionManager release]; -		motionManager = nil; -		motionInitialised = NO; -	}; +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { +	if (OS::get_singleton()->get_main_loop()) { +		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING); +	} +}; +- (void)applicationWillTerminate:(UIApplication *)application {  	iphone_finish();  }; @@ -722,15 +131,15 @@ static int frame_count = 0;  // notification panel by swiping from the upper part of the screen.  - (void)applicationWillResignActive:(UIApplication *)application { -	on_focus_out(view_controller, &is_focus_out); +	OSIPhone::get_singleton()->on_focus_out();  }  - (void)applicationDidBecomeActive:(UIApplication *)application { -	on_focus_in(view_controller, &is_focus_out); +	OSIPhone::get_singleton()->on_focus_in();  }  - (void)dealloc { -	[window release]; +	self.window = nil;  	[super dealloc];  } diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index c48eb7ff0e..6f67fc53c2 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -120,18 +120,18 @@ def configure(env):              CCFLAGS=(                  "-arch "                  + arch_flag -                + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0" +                + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0"              ).split()          )      elif env["arch"] == "arm":          detect_darwin_sdk_path("iphone", env)          env.Append( -            CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split() +            CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split()          )      elif env["arch"] == "arm64":          detect_darwin_sdk_path("iphone", env)          env.Append( -            CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split() +            CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0 -isysroot $IPHONESDK".split()          )          env.Append(CPPDEFINES=["NEED_LONG_INT"])          env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"]) @@ -143,6 +143,9 @@ def configure(env):          else:              env.Append(CCFLAGS=["-fno-exceptions"]) +    # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation +    env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) +      ## Link flags      if env["arch"] == "x86" or env["arch"] == "x86_64": @@ -151,7 +154,7 @@ def configure(env):              LINKFLAGS=[                  "-arch",                  arch_flag, -                "-mios-simulator-version-min=10.0", +                "-mios-simulator-version-min=13.0",                  "-isysroot",                  "$IPHONESDK",                  "-Xlinker", @@ -162,9 +165,9 @@ def configure(env):              ]          )      elif env["arch"] == "arm": -        env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"]) +        env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])      if env["arch"] == "arm64": -        env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"]) +        env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])      env.Append(          LINKFLAGS=[ @@ -228,8 +231,7 @@ def configure(env):      env.Append(CPPDEFINES=["VULKAN_ENABLED"])      env.Append(LINKFLAGS=["-framework", "IOSurface"]) -    if env["use_static_mvk"]: -        env.Append(LINKFLAGS=["-framework", "MoltenVK"]) -        env["builtin_vulkan"] = False -    elif not env["builtin_vulkan"]: -        env.Append(LIBS=["vulkan"]) + +    # Use Static Vulkan for iOS. Dynamic Framework works fine too. +    env.Append(LINKFLAGS=["-framework", "MoltenVK"]) +    env["builtin_vulkan"] = False diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h new file mode 100644 index 0000000000..bfde8f96a3 --- /dev/null +++ b/platform/iphone/display_layer.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/*  display_layer.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.                */ +/*************************************************************************/ + +#import <OpenGLES/EAGLDrawable.h> +#import <QuartzCore/QuartzCore.h> + +@protocol DisplayLayer <NSObject> + +- (void)renderDisplayLayer; +- (void)initializeDisplayLayer; +- (void)layoutDisplayLayer; + +@end + +// An ugly workaround for iOS simulator +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR +#if defined(__IPHONE_13_0) +API_AVAILABLE(ios(13.0)) +@interface GodotMetalLayer : CAMetalLayer <DisplayLayer> +#else +@interface GodotMetalLayer : CALayer <DisplayLayer> +#endif +#else +@interface GodotMetalLayer : CAMetalLayer <DisplayLayer> +#endif +@end + +API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0)) +@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer> + +@end diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm new file mode 100644 index 0000000000..5ec94fb471 --- /dev/null +++ b/platform/iphone/display_layer.mm @@ -0,0 +1,186 @@ +/*************************************************************************/ +/*  display_layer.mm                                                     */ +/*************************************************************************/ +/*                       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.                */ +/*************************************************************************/ + +#import "display_layer.h" +#include "core/os/keyboard.h" +#include "core/project_settings.h" +#include "display_server_iphone.h" +#include "main/main.h" +#include "os_iphone.h" +#include "servers/audio_server.h" + +#import <AudioToolbox/AudioServices.h> +#import <GameController/GameController.h> +#import <OpenGLES/EAGL.h> +#import <OpenGLES/ES1/gl.h> +#import <OpenGLES/ES1/glext.h> +#import <QuartzCore/QuartzCore.h> +#import <UIKit/UIKit.h> + +@implementation GodotMetalLayer + +- (void)initializeDisplayLayer { +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR +	if (@available(iOS 13, *)) { +		// Simulator supports Metal since iOS 13 +	} else { +		NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering."); +	} +#endif +} + +- (void)layoutDisplayLayer { +} + +- (void)renderDisplayLayer { +} + +@end + +@implementation GodotOpenGLLayer { +	// The pixel dimensions of the backbuffer +	GLint backingWidth; +	GLint backingHeight; + +	EAGLContext *context; +	GLuint viewRenderbuffer, viewFramebuffer; +	GLuint depthRenderbuffer; +} + +- (void)initializeDisplayLayer { +	// Get our backing layer + +	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color. +	self.opaque = YES; +	self.drawableProperties = [NSDictionary +			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], +			kEAGLDrawablePropertyRetainedBacking, +			kEAGLColorFormatRGBA8, +			kEAGLDrawablePropertyColorFormat, +			nil]; + +	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? + +	// Create GL ES 2 context +	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") { +		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; +		NSLog(@"Setting up an OpenGL ES 2.0 context."); +		if (!context) { +			NSLog(@"Failed to create OpenGL ES 2.0 context!"); +			return; +		} +	} + +	if (![EAGLContext setCurrentContext:context]) { +		NSLog(@"Failed to set EAGLContext!"); +		return; +	} +	if (![self createFramebuffer]) { +		NSLog(@"Failed to create frame buffer!"); +		return; +	} +} + +- (void)layoutDisplayLayer { +	[EAGLContext setCurrentContext:context]; +	[self destroyFramebuffer]; +	[self createFramebuffer]; +} + +- (void)renderDisplayLayer { +	[EAGLContext setCurrentContext:context]; +} + +- (void)dealloc { +	if ([EAGLContext currentContext] == context) { +		[EAGLContext setCurrentContext:nil]; +	} + +	if (context) { +		[context release]; +		context = nil; +	} + +	[super dealloc]; +} + +- (BOOL)createFramebuffer { +	glGenFramebuffersOES(1, &viewFramebuffer); +	glGenRenderbuffersOES(1, &viewRenderbuffer); + +	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); +	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); +	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAself) +	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view). +	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self]; +	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); + +	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); +	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); + +	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer. +	glGenRenderbuffersOES(1, &depthRenderbuffer); +	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); +	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); +	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); + +	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { +		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); +		return NO; +	} + +	//    if (OS::get_singleton()) { +	//        OS::VideoMode vm; +	//        vm.fullscreen = true; +	//        vm.width = backingWidth; +	//        vm.height = backingHeight; +	//        vm.resizable = false; +	//        OS::get_singleton()->set_video_mode(vm); +	//        OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer); +	//    }; +	//    gl_view_base_fb = viewFramebuffer; + +	return YES; +} + +// Clean up any buffers we have allocated. +- (void)destroyFramebuffer { +	glDeleteFramebuffersOES(1, &viewFramebuffer); +	viewFramebuffer = 0; +	glDeleteRenderbuffersOES(1, &viewRenderbuffer); +	viewRenderbuffer = 0; + +	if (depthRenderbuffer) { +		glDeleteRenderbuffersOES(1, &depthRenderbuffer); +		depthRenderbuffer = 0; +	} +} + +@end diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h new file mode 100644 index 0000000000..e82cef4122 --- /dev/null +++ b/platform/iphone/display_server_iphone.h @@ -0,0 +1,202 @@ +/*************************************************************************/ +/*  display_server_iphone.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 display_server_iphone_h +#define display_server_iphone_h + +#include "core/input/input.h" +#include "servers/display_server.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" + +#include "vulkan_context_iphone.h" + +#import <QuartzCore/CAMetalLayer.h> +#include <vulkan/vulkan_metal.h> +#endif + +class DisplayServerIPhone : public DisplayServer { +	GDCLASS(DisplayServerIPhone, DisplayServer) + +	_THREAD_SAFE_CLASS_ + +#if defined(VULKAN_ENABLED) +	VulkanContextIPhone *context_vulkan; +	RenderingDeviceVulkan *rendering_device_vulkan; +#endif + +	DisplayServer::ScreenOrientation screen_orientation; + +	ObjectID window_attached_instance_id; + +	Callable window_event_callback; +	Callable window_resize_callback; +	Callable input_event_callback; +	Callable input_text_callback; + +	int virtual_keyboard_height = 0; + +	void perform_event(const Ref<InputEvent> &p_event); + +	DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); +	~DisplayServerIPhone(); + +public: +	String rendering_driver; + +	static DisplayServerIPhone *get_singleton(); + +	static void register_iphone_driver(); +	static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); +	static Vector<String> get_rendering_drivers_func(); + +	// MARK: - Events + +	virtual void process_events() override; + +	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + +	static void _dispatch_input_events(const Ref<InputEvent> &p_event); +	void send_input_event(const Ref<InputEvent> &p_event) const; +	void send_input_text(const String &p_text) const; +	void send_window_event(DisplayServer::WindowEvent p_event) const; +	void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + +	// MARK: - Input + +	// MARK: Touches + +	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); +	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y); +	void touches_cancelled(int p_idx); + +	// MARK: Keyboard + +	void key(uint32_t p_key, bool p_pressed); + +	// MARK: Motion + +	void update_gravity(float p_x, float p_y, float p_z); +	void update_accelerometer(float p_x, float p_y, float p_z); +	void update_magnetometer(float p_x, float p_y, float p_z); +	void update_gyroscope(float p_x, float p_y, float p_z); + +	// MARK: - + +	virtual bool has_feature(Feature p_feature) const override; +	virtual String get_name() const override; + +	virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + +	virtual int get_screen_count() const override; +	virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + +	virtual Vector<DisplayServer::WindowID> get_window_list() const override; + +	virtual WindowID +	get_window_at_screen_position(const Point2i &p_position) const override; + +	virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; +	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; +	virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; + +	virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; +	virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual float screen_get_max_scale() const override; + +	virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override; +	virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override; + +	virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual bool can_any_window_draw() const override; + +	virtual bool screen_is_touchscreen(int p_screen) const override; + +	virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) override; +	virtual void virtual_keyboard_hide() override; + +	void virtual_keyboard_set_height(int height); +	virtual int virtual_keyboard_get_height() const override; + +	virtual void clipboard_set(const String &p_text) override; +	virtual String clipboard_get() const override; + +	virtual void screen_set_keep_on(bool p_enable) override; +	virtual bool screen_is_kept_on() const override; + +	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override; +	virtual bool native_video_is_playing() const override; +	virtual void native_video_pause() override; +	virtual void native_video_unpause() override; +	virtual void native_video_stop() override; + +	void resize_window(CGSize size); +}; + +#endif /* display_server_iphone_h */ diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm new file mode 100644 index 0000000000..498be89e48 --- /dev/null +++ b/platform/iphone/display_server_iphone.mm @@ -0,0 +1,751 @@ +/*************************************************************************/ +/*  display_server_iphone.mm                                             */ +/*************************************************************************/ +/*                       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 "display_server_iphone.h" +#import "app_delegate.h" +#include "core/io/file_access_pack.h" +#include "core/project_settings.h" +#import "godot_view.h" +#include "ios.h" +#include "os_iphone.h" +#import "view_controller.h" + +#import <Foundation/Foundation.h> +#import <sys/utsname.h> + +static const float kDisplayServerIPhoneAcceleration = 1; +static NSDictionary *iOSModelToDPI = @{ +	@[ +		@"iPad1,1", +		@"iPad2,1", +		@"iPad2,2", +		@"iPad2,3", +		@"iPad2,4", +	] : @132, +	@[ +		@"iPhone1,1", +		@"iPhone1,2", +		@"iPhone2,1", +		@"iPad2,5", +		@"iPad2,6", +		@"iPad2,7", +		@"iPod1,1", +		@"iPod2,1", +		@"iPod3,1", +	] : @163, +	@[ +		@"iPad3,1", +		@"iPad3,2", +		@"iPad3,3", +		@"iPad3,4", +		@"iPad3,5", +		@"iPad3,6", +		@"iPad4,1", +		@"iPad4,2", +		@"iPad4,3", +		@"iPad5,3", +		@"iPad5,4", +		@"iPad6,3", +		@"iPad6,4", +		@"iPad6,7", +		@"iPad6,8", +		@"iPad6,11", +		@"iPad6,12", +		@"iPad7,1", +		@"iPad7,2", +		@"iPad7,3", +		@"iPad7,4", +		@"iPad7,5", +		@"iPad7,6", +		@"iPad7,11", +		@"iPad7,12", +		@"iPad8,1", +		@"iPad8,2", +		@"iPad8,3", +		@"iPad8,4", +		@"iPad8,5", +		@"iPad8,6", +		@"iPad8,7", +		@"iPad8,8", +		@"iPad8,9", +		@"iPad8,10", +		@"iPad8,11", +		@"iPad8,12", +		@"iPad11,3", +		@"iPad11,4", +	] : @264, +	@[ +		@"iPhone3,1", +		@"iPhone3,2", +		@"iPhone3,3", +		@"iPhone4,1", +		@"iPhone5,1", +		@"iPhone5,2", +		@"iPhone5,3", +		@"iPhone5,4", +		@"iPhone6,1", +		@"iPhone6,2", +		@"iPhone7,2", +		@"iPhone8,1", +		@"iPhone8,4", +		@"iPhone9,1", +		@"iPhone9,3", +		@"iPhone10,1", +		@"iPhone10,4", +		@"iPhone11,8", +		@"iPhone12,1", +		@"iPhone12,8", +		@"iPad4,4", +		@"iPad4,5", +		@"iPad4,6", +		@"iPad4,7", +		@"iPad4,8", +		@"iPad4,9", +		@"iPad5,1", +		@"iPad5,2", +		@"iPad11,1", +		@"iPad11,2", +		@"iPod4,1", +		@"iPod5,1", +		@"iPod7,1", +		@"iPod9,1", +	] : @326, +	@[ +		@"iPhone7,1", +		@"iPhone8,2", +		@"iPhone9,2", +		@"iPhone9,4", +		@"iPhone10,2", +		@"iPhone10,5", +	] : @401, +	@[ +		@"iPhone10,3", +		@"iPhone10,6", +		@"iPhone11,2", +		@"iPhone11,4", +		@"iPhone11,6", +		@"iPhone12,3", +		@"iPhone12,5", +	] : @458, +}; + +DisplayServerIPhone *DisplayServerIPhone::get_singleton() { +	return (DisplayServerIPhone *)DisplayServer::get_singleton(); +} + +DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { +	rendering_driver = p_rendering_driver; + +#if defined(OPENGL_ENABLED) +	// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented +	// again, + +	if (rendering_driver == "opengl_es") { +		bool gl_initialization_error = false; + +		// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? + +		if (RasterizerGLES2::is_viable() == OK) { +			RasterizerGLES2::register_config(); +			RasterizerGLES2::make_current(); +		} else { +			gl_initialization_error = true; +		} + +		if (gl_initialization_error) { +			OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver"); +			//        return ERR_UNAVAILABLE; +		} + +		//    rendering_server = memnew(RenderingServerRaster); +		//    // FIXME: Reimplement threaded rendering +		//    if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { +		//        rendering_server = memnew(RenderingServerWrapMT(rendering_server, +		//        false)); +		//    } +		//    rendering_server->init(); +		// rendering_server->cursor_set_visible(false, 0); + +		// reset this to what it should be, it will have been set to 0 after +		// rendering_server->init() is called +		//    RasterizerStorageGLES2::system_fbo = gl_view_base_fb; +	} +#endif + +#if defined(VULKAN_ENABLED) +	rendering_driver = "vulkan"; + +	context_vulkan = nullptr; +	rendering_device_vulkan = nullptr; + +	if (rendering_driver == "vulkan") { +		context_vulkan = memnew(VulkanContextIPhone); +		if (context_vulkan->initialize() != OK) { +			memdelete(context_vulkan); +			context_vulkan = nullptr; +			ERR_FAIL_MSG("Failed to initialize Vulkan context"); +		} + +		CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"]; + +		if (!layer) { +			ERR_FAIL_MSG("Failed to create iOS rendering layer."); +		} + +		Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); +		if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) { +			memdelete(context_vulkan); +			context_vulkan = nullptr; +			ERR_FAIL_MSG("Failed to create Vulkan window."); +		} + +		rendering_device_vulkan = memnew(RenderingDeviceVulkan); +		rendering_device_vulkan->initialize(context_vulkan); + +		RasterizerRD::make_current(); +	} +#endif + +	bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)); +	screen_set_keep_on(keep_screen_on); + +	Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + +	r_error = OK; +} + +DisplayServerIPhone::~DisplayServerIPhone() { +#if defined(VULKAN_ENABLED) +	if (rendering_driver == "vulkan") { +		if (rendering_device_vulkan) { +			rendering_device_vulkan->finalize(); +			memdelete(rendering_device_vulkan); +			rendering_device_vulkan = NULL; +		} + +		if (context_vulkan) { +			context_vulkan->window_destroy(MAIN_WINDOW_ID); +			memdelete(context_vulkan); +			context_vulkan = NULL; +		} +	} +#endif +} + +DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { +	return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); +} + +Vector<String> DisplayServerIPhone::get_rendering_drivers_func() { +	Vector<String> drivers; + +#if defined(VULKAN_ENABLED) +	drivers.push_back("vulkan"); +#endif +#if defined(OPENGL_ENABLED) +	drivers.push_back("opengl_es"); +#endif + +	return drivers; +} + +void DisplayServerIPhone::register_iphone_driver() { +	register_create_function("iphone", create_func, get_rendering_drivers_func); +} + +// MARK: Events + +void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { +	window_resize_callback = p_callable; +} + +void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { +	window_event_callback = p_callable; +} +void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { +	input_event_callback = p_callable; +} + +void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { +	input_text_callback = p_callable; +} + +void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { +	// Probably not supported for iOS +} + +void DisplayServerIPhone::process_events() { +} + +void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) { +	DisplayServerIPhone::get_singleton()->send_input_event(p_event); +} + +void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const { +	_window_callback(input_event_callback, p_event); +} + +void DisplayServerIPhone::send_input_text(const String &p_text) const { +	_window_callback(input_text_callback, p_text); +} + +void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const { +	_window_callback(window_event_callback, int(p_event)); +} + +void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const { +	if (!p_callable.is_null()) { +		const Variant *argp = &p_arg; +		Variant ret; +		Callable::CallError ce; +		p_callable.call((const Variant **)&argp, 1, ret, ce); +	} +} + +// MARK: - Input + +// MARK: Touches + +void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) { +	if (!GLOBAL_DEF("debug/disable_touch", false)) { +		Ref<InputEventScreenTouch> ev; +		ev.instance(); + +		ev->set_index(p_idx); +		ev->set_pressed(p_pressed); +		ev->set_position(Vector2(p_x, p_y)); +		perform_event(ev); +	} +}; + +void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { +	if (!GLOBAL_DEF("debug/disable_touch", false)) { +		Ref<InputEventScreenDrag> ev; +		ev.instance(); +		ev->set_index(p_idx); +		ev->set_position(Vector2(p_x, p_y)); +		ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); +		perform_event(ev); +	}; +}; + +void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) { +	Input::get_singleton()->parse_input_event(p_event); +}; + +void DisplayServerIPhone::touches_cancelled(int p_idx) { +	touch_press(p_idx, -1, -1, false, false); +}; + +// MARK: Keyboard + +void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) { +	Ref<InputEventKey> ev; +	ev.instance(); +	ev->set_echo(false); +	ev->set_pressed(p_pressed); +	ev->set_keycode(p_key); +	ev->set_physical_keycode(p_key); +	ev->set_unicode(p_key); +	perform_event(ev); +}; + +// MARK: Motion + +void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) { +	Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, +		float p_z) { +	// Found out the Z should not be negated! Pass as is! +	Vector3 v_accelerometer = Vector3( +			p_x / kDisplayServerIPhoneAcceleration, +			p_y / kDisplayServerIPhoneAcceleration, +			p_z / kDisplayServerIPhoneAcceleration); + +	Input::get_singleton()->set_accelerometer(v_accelerometer); + +	/* +  if (p_x != last_accel.x) { +      //printf("updating accel x %f\n", p_x); +      InputEvent ev; +      ev.type = InputEvent::JOYPAD_MOTION; +      ev.device = 0; +      ev.joy_motion.axis = JOY_ANALOG_0; +      ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE); +      last_accel.x = p_x; +      queue_event(ev); +  }; +  if (p_y != last_accel.y) { +      //printf("updating accel y %f\n", p_y); +      InputEvent ev; +      ev.type = InputEvent::JOYPAD_MOTION; +      ev.device = 0; +      ev.joy_motion.axis = JOY_ANALOG_1; +      ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE); +      last_accel.y = p_y; +      queue_event(ev); +  }; +  if (p_z != last_accel.z) { +      //printf("updating accel z %f\n", p_z); +      InputEvent ev; +      ev.type = InputEvent::JOYPAD_MOTION; +      ev.device = 0; +      ev.joy_motion.axis = JOY_ANALOG_2; +      ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE); +      last_accel.z = p_z; +      queue_event(ev); +  }; +  */ +}; + +void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) { +	Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) { +	Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z)); +}; + +// MARK: - + +bool DisplayServerIPhone::has_feature(Feature p_feature) const { +	switch (p_feature) { +		// case FEATURE_CONSOLE_WINDOW: +		// case FEATURE_CURSOR_SHAPE: +		// case FEATURE_CUSTOM_CURSOR_SHAPE: +		// case FEATURE_GLOBAL_MENU: +		// case FEATURE_HIDPI: +		// case FEATURE_ICON: +		// case FEATURE_IME: +		// case FEATURE_MOUSE: +		// case FEATURE_MOUSE_WARP: +		// case FEATURE_NATIVE_DIALOG: +		// case FEATURE_NATIVE_ICON: +		// case FEATURE_NATIVE_VIDEO: +		// case FEATURE_WINDOW_TRANSPARENCY: +		case FEATURE_CLIPBOARD: +		case FEATURE_KEEP_SCREEN_ON: +		case FEATURE_ORIENTATION: +		case FEATURE_TOUCHSCREEN: +		case FEATURE_VIRTUAL_KEYBOARD: +			return true; +		default: +			return false; +	} +} + +String DisplayServerIPhone::get_name() const { +	return "iPhone"; +} + +void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) { +	const CharString utf8_alert = p_alert.utf8(); +	const CharString utf8_title = p_title.utf8(); +	iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +int DisplayServerIPhone::get_screen_count() const { +	return 1; +} + +Point2i DisplayServerIPhone::screen_get_position(int p_screen) const { +	return Size2i(); +} + +Size2i DisplayServerIPhone::screen_get_size(int p_screen) const { +	CALayer *layer = AppDelegate.viewController.godotView.renderingLayer; + +	if (!layer) { +		return Size2i(); +	} + +	return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen); +} + +Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const { +	return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); +} + +int DisplayServerIPhone::screen_get_dpi(int p_screen) const { +	struct utsname systemInfo; +	uname(&systemInfo); + +	NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + +	for (NSArray *keyArray in iOSModelToDPI) { +		if ([keyArray containsObject:string]) { +			NSNumber *value = iOSModelToDPI[keyArray]; +			return [value intValue]; +		} +	} + +	return 163; +} + +float DisplayServerIPhone::screen_get_scale(int p_screen) const { +	return [UIScreen mainScreen].nativeScale; +} + +Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const { +	Vector<DisplayServer::WindowID> list; +	list.push_back(MAIN_WINDOW_ID); +	return list; +} + +DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const { +	return MAIN_WINDOW_ID; +} + +void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { +	window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const { +	return window_attached_instance_id; +} + +void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) { +	// Probably not supported for iOS +} + +int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const { +	return SCREEN_OF_MAIN_WINDOW; +} + +void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) { +	// Probably not supported for iOS +} + +Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const { +	return Point2i(); +} + +void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) { +	// Probably not supported for single window iOS app +} + +void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) { +	// Probably not supported for iOS +} + +void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) { +	// Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const { +	return Size2i(); +} + +void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) { +	// Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const { +	return Size2i(); +} + +void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) { +	// Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const { +	CGRect screenBounds = [UIScreen mainScreen].bounds; +	return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale(); +} + +Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const { +	return window_get_size(p_window); +} + +void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) { +	// Probably not supported for iOS +} + +DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const { +	return WindowMode::WINDOW_MODE_FULLSCREEN; +} + +bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const { +	return false; +} + +void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { +	// Probably not supported for iOS +} + +bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const { +	return false; +} + +void DisplayServerIPhone::window_request_attention(WindowID p_window) { +	// Probably not supported for iOS +} + +void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) { +	// Probably not supported for iOS +} + +float DisplayServerIPhone::screen_get_max_scale() const { +	return screen_get_scale(SCREEN_OF_MAIN_WINDOW); +}; + +void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { +	screen_orientation = p_orientation; +} + +DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const { +	return screen_orientation; +} + +bool DisplayServerIPhone::window_can_draw(WindowID p_window) const { +	return true; +} + +bool DisplayServerIPhone::can_any_window_draw() const { +	return true; +} + +bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const { +	return true; +} + +void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) { +	[AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text]; +} + +void DisplayServerIPhone::virtual_keyboard_hide() { +	[AppDelegate.viewController.godotView resignFirstResponder]; +} + +void DisplayServerIPhone::virtual_keyboard_set_height(int height) { +	virtual_keyboard_height = height * screen_get_max_scale(); +} + +int DisplayServerIPhone::virtual_keyboard_get_height() const { +	return virtual_keyboard_height; +} + +void DisplayServerIPhone::clipboard_set(const String &p_text) { +	[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()]; +} + +String DisplayServerIPhone::clipboard_get() const { +	NSString *text = [UIPasteboard generalPasteboard].string; + +	return String::utf8([text UTF8String]); +} + +void DisplayServerIPhone::screen_set_keep_on(bool p_enable) { +	[UIApplication sharedApplication].idleTimerDisabled = p_enable; +} + +bool DisplayServerIPhone::screen_is_kept_on() const { +	return [UIApplication sharedApplication].idleTimerDisabled; +} + +Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) { +	FileAccess *f = FileAccess::open(p_path, FileAccess::READ); +	bool exists = f && f->is_open(); + +	String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir(); + +	if (!exists) { +		return FAILED; +	} + +	String tempFile = OSIPhone::get_singleton()->get_user_data_dir(); + +	if (p_path.begins_with("res://")) { +		if (PackedData::get_singleton()->has_path(p_path)) { +			printf("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str()); +			return ERR_INVALID_PARAMETER; +		} else { +			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path()); +		} +	} else if (p_path.begins_with("user://")) { +		p_path = p_path.replace("user:/", user_data_dir); +	} + +	memdelete(f); + +	printf("Playing video: %S\n", p_path.c_str()); + +	String file_path = ProjectSettings::get_singleton()->globalize_path(p_path); + +	NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease]; +	NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()]; +	NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()]; + +	if (![AppDelegate.viewController playVideoAtPath:filePath +											  volume:p_volume +											   audio:audioTrack +											subtitle:subtitleTrack]) { +		return OK; +	} + +	return FAILED; +} + +bool DisplayServerIPhone::native_video_is_playing() const { +	return [AppDelegate.viewController isVideoPlaying]; +} + +void DisplayServerIPhone::native_video_pause() { +	if (native_video_is_playing()) { +		[AppDelegate.viewController pauseVideo]; +	} +} + +void DisplayServerIPhone::native_video_unpause() { +	[AppDelegate.viewController unpauseVideo]; +}; + +void DisplayServerIPhone::native_video_stop() { +	if (native_video_is_playing()) { +		[AppDelegate.viewController stopVideo]; +	} +} + +void DisplayServerIPhone::resize_window(CGSize viewSize) { +	Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); + +#if defined(VULKAN_ENABLED) +	if (rendering_driver == "vulkan") { +		if (context_vulkan) { +			context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y); +		} +	} +#endif + +	Variant resize_rect = Rect2i(Point2i(), size); +	_window_callback(window_resize_callback, resize_rect); +} diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h index 0d3ef5b696..1e9a68fe48 100644 --- a/platform/iphone/game_center.h +++ b/platform/iphone/game_center.h @@ -51,12 +51,12 @@ public:  	void connect();  	bool is_authenticated(); -	Error post_score(Variant p_score); -	Error award_achievement(Variant p_params); +	Error post_score(Dictionary p_score); +	Error award_achievement(Dictionary p_params);  	void reset_achievements();  	void request_achievements();  	void request_achievement_descriptions(); -	Error show_game_center(Variant p_params); +	Error show_game_center(Dictionary p_params);  	Error request_identity_verification_signature();  	void game_center_closed(); diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index 8d470da1a8..4481775c32 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -47,13 +47,15 @@ extern "C" {  #import "app_delegate.h"  }; +#import "view_controller.h" +  GameCenter *GameCenter::instance = NULL;  void GameCenter::_bind_methods() {  	ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);  	ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score); -	ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement); +	ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);  	ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);  	ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);  	ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions); @@ -105,7 +107,14 @@ void GameCenter::connect() {  			ret["type"] = "authentication";  			if (player.isAuthenticated) {  				ret["result"] = "ok"; -				ret["player_id"] = [player.playerID UTF8String]; +				if (@available(iOS 13, *)) { +					ret["player_id"] = [player.teamPlayerID UTF8String]; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +				} else { +					ret["player_id"] = [player.playerID UTF8String]; +#endif +				} +  				GameCenter::get_singleton()->authenticated = true;  			} else {  				ret["result"] = "error"; @@ -123,11 +132,10 @@ bool GameCenter::is_authenticated() {  	return authenticated;  }; -Error GameCenter::post_score(Variant p_score) { -	Dictionary params = p_score; -	ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER); -	float score = params["score"]; -	String category = params["category"]; +Error GameCenter::post_score(Dictionary p_score) { +	ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER); +	float score = p_score["score"]; +	String category = p_score["category"];  	NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];  	GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease]; @@ -153,11 +161,10 @@ Error GameCenter::post_score(Variant p_score) {  	return OK;  }; -Error GameCenter::award_achievement(Variant p_params) { -	Dictionary params = p_params; -	ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER); -	String name = params["name"]; -	float progress = params["progress"]; +Error GameCenter::award_achievement(Dictionary p_params) { +	ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER); +	String name = p_params["name"]; +	float progress = p_params["progress"];  	NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];  	GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease]; @@ -167,8 +174,8 @@ Error GameCenter::award_achievement(Variant p_params) {  	achievement.percentComplete = progress;  	achievement.showsCompletionBanner = NO; -	if (params.has("show_completion_banner")) { -		achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO; +	if (p_params.has("show_completion_banner")) { +		achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;  	}  	[GKAchievement reportAchievements:@[ achievement ] @@ -202,7 +209,7 @@ void GameCenter::request_achievement_descriptions() {  			Array hidden;  			Array replayable; -			for (int i = 0; i < [descriptions count]; i++) { +			for (NSUInteger i = 0; i < [descriptions count]; i++) {  				GKAchievementDescription *description = [descriptions objectAtIndex:i];  				const char *str = [description.identifier UTF8String]; @@ -250,7 +257,7 @@ void GameCenter::request_achievements() {  			PackedStringArray names;  			PackedFloat32Array percentages; -			for (int i = 0; i < [achievements count]; i++) { +			for (NSUInteger i = 0; i < [achievements count]; i++) {  				GKAchievement *achievement = [achievements objectAtIndex:i];  				const char *str = [achievement.identifier UTF8String];  				names.push_back(String::utf8(str != NULL ? str : "")); @@ -285,14 +292,12 @@ void GameCenter::reset_achievements() {  	}];  }; -Error GameCenter::show_game_center(Variant p_params) { +Error GameCenter::show_game_center(Dictionary p_params) {  	ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED); -	Dictionary params = p_params; -  	GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault; -	if (params.has("view")) { -		String view_name = params["view"]; +	if (p_params.has("view")) { +		String view_name = p_params["view"];  		if (view_name == "default") {  			view_state = GKGameCenterViewControllerStateDefault;  		} else if (view_name == "leaderboards") { @@ -306,7 +311,7 @@ Error GameCenter::show_game_center(Variant p_params) {  		}  	} -	GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init]; +	GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease];  	ERR_FAIL_COND_V(!controller, FAILED);  	ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; @@ -316,8 +321,8 @@ Error GameCenter::show_game_center(Variant p_params) {  	controller.viewState = view_state;  	if (view_state == GKGameCenterViewControllerStateLeaderboards) {  		controller.leaderboardIdentifier = nil; -		if (params.has("leaderboard_name")) { -			String name = params["leaderboard_name"]; +		if (p_params.has("leaderboard_name")) { +			String name = p_params["leaderboard_name"];  			NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];  			controller.leaderboardIdentifier = name_str;  		} @@ -341,7 +346,13 @@ Error GameCenter::request_identity_verification_signature() {  			ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];  			ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];  			ret["timestamp"] = timestamp; -			ret["player_id"] = [player.playerID UTF8String]; +			if (@available(iOS 13, *)) { +				ret["player_id"] = [player.teamPlayerID UTF8String]; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +			} else { +				ret["player_id"] = [player.playerID UTF8String]; +#endif +			}  		} else {  			ret["result"] = "error";  			ret["error_code"] = (int64_t)error.code; diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h deleted file mode 100644 index 975aa4b70a..0000000000 --- a/platform/iphone/gl_view.h +++ /dev/null @@ -1,123 +0,0 @@ -/*************************************************************************/ -/*  gl_view.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.                */ -/*************************************************************************/ - -#import <AVFoundation/AVFoundation.h> -#import <MediaPlayer/MediaPlayer.h> -#import <OpenGLES/EAGL.h> -#import <OpenGLES/ES1/gl.h> -#import <OpenGLES/ES1/glext.h> -#import <UIKit/UIKit.h> - -@protocol GLViewDelegate; - -@interface GLView : UIView <UIKeyInput> { -@private -	// The pixel dimensions of the backbuffer -	GLint backingWidth; -	GLint backingHeight; - -	EAGLContext *context; - -	// OpenGL names for the renderbuffer and framebuffers used to render to this view -	GLuint viewRenderbuffer, viewFramebuffer; - -	// OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) -	GLuint depthRenderbuffer; - -	BOOL useCADisplayLink; -	// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15 -	CADisplayLink *displayLink; - -	// An animation timer that, when animation is started, will periodically call -drawView at the given rate. -	// Only used if CADisplayLink is not -	NSTimer *animationTimer; - -	NSTimeInterval animationInterval; - -	// Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer. -	id<GLViewDelegate> delegate; - -	// Flag to denote that the -setupView method of a delegate has been called. -	// Resets to NO whenever the delegate changes. -	BOOL delegateSetup; -	BOOL active; -	float screen_scale; -} - -@property(nonatomic, assign) id<GLViewDelegate> delegate; - -// AVPlayer-related properties -@property(strong, nonatomic) AVAsset *avAsset; -@property(strong, nonatomic) AVPlayerItem *avPlayerItem; -@property(strong, nonatomic) AVPlayer *avPlayer; -@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; - -@property(strong, nonatomic) UIWindow *backgroundWindow; - -@property(nonatomic) UITextAutocorrectionType autocorrectionType; - -- (void)startAnimation; -- (void)stopAnimation; -- (void)drawView; - -- (BOOL)canBecomeFirstResponder; - -- (void)open_keyboard; -- (void)hide_keyboard; -- (void)deleteBackward; -- (BOOL)hasText; -- (void)insertText:(NSString *)p_text; - -- (id)initGLES; -- (BOOL)createFramebuffer; -- (void)destroyFramebuffer; - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification; -- (void)keyboardOnScreen:(NSNotification *)notification; -- (void)keyboardHidden:(NSNotification *)notification; - -@property NSTimeInterval animationInterval; -@property(nonatomic, assign) BOOL useCADisplayLink; - -@end - -@protocol GLViewDelegate <NSObject> - -@required - -// Draw with OpenGL ES -- (void)drawView:(GLView *)view; - -@optional - -// Called whenever you need to do some initialization before rendering. -- (void)setupView:(GLView *)view; - -@end diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm deleted file mode 100644 index 1169ebc6b4..0000000000 --- a/platform/iphone/gl_view.mm +++ /dev/null @@ -1,702 +0,0 @@ -/*************************************************************************/ -/*  gl_view.mm                                                           */ -/*************************************************************************/ -/*                       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.                */ -/*************************************************************************/ - -#import "gl_view.h" - -#include "core/os/keyboard.h" -#include "core/project_settings.h" -#include "os_iphone.h" -#include "servers/audio_server.h" - -#import <OpenGLES/EAGLDrawable.h> -#import <QuartzCore/QuartzCore.h> - -/* -@interface GLView (private) - -- (id)initGLES; -- (BOOL)createFramebuffer; -- (void)destroyFramebuffer; -@end -*/ - -int gl_view_base_fb; -static String keyboard_text; -static GLView *_instance = NULL; - -static bool video_found_error = false; -static bool video_playing = false; -static CMTime video_current_time; - -void _show_keyboard(String); -void _hide_keyboard(); -bool _play_video(String, float, String, String); -bool _is_video_playing(); -void _pause_video(); -void _focus_out_video(); -void _unpause_video(); -void _stop_video(); -CGFloat _points_to_pixels(CGFloat); - -void _show_keyboard(String p_existing) { -	keyboard_text = p_existing; -	printf("instance on show is %p\n", _instance); -	[_instance open_keyboard]; -}; - -void _hide_keyboard() { -	printf("instance on hide is %p\n", _instance); -	[_instance hide_keyboard]; -	keyboard_text = ""; -}; - -Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) { -	UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0); -	if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) { -		insets = [_instance safeAreaInsets]; -	} -	ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0, -			Rect2(0, 0, p_window_width, p_window_height)); -	UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right)); -	return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top); -} - -bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { -	p_path = ProjectSettings::get_singleton()->globalize_path(p_path); - -	NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease]; - -	_instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]]; - -	_instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset]; -	[_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil]; - -	_instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem]; -	_instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer]; - -	[_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil]; -	[[NSNotificationCenter defaultCenter] -			addObserver:_instance -			   selector:@selector(playerItemDidReachEnd:) -				   name:AVPlayerItemDidPlayToEndTimeNotification -				 object:[_instance.avPlayer currentItem]]; - -	[_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; - -	[_instance.avPlayerLayer setFrame:_instance.bounds]; -	[_instance.layer addSublayer:_instance.avPlayerLayer]; -	[_instance.avPlayer play]; - -	AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - -	NSMutableArray *allAudioParams = [NSMutableArray array]; -	for (id track in audioGroup.options) { -		NSString *language = [[track locale] localeIdentifier]; -		NSLog(@"subtitle lang: %@", language); - -		if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) { -			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; -			[audioInputParams setVolume:p_volume atTime:kCMTimeZero]; -			[audioInputParams setTrackID:[track trackID]]; -			[allAudioParams addObject:audioInputParams]; - -			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; -			[audioMix setInputParameters:allAudioParams]; - -			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; -			[_instance.avPlayer.currentItem setAudioMix:audioMix]; - -			break; -		} -	} - -	AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; -	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; - -	for (id track in useableTracks) { -		NSString *language = [[track locale] localeIdentifier]; -		NSLog(@"subtitle lang: %@", language); - -		if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) { -			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; -			break; -		} -	} - -	video_playing = true; - -	return true; -} - -bool _is_video_playing() { -	if (_instance.avPlayer.error) { -		printf("Error during playback\n"); -	} -	return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error); -} - -void _pause_video() { -	video_current_time = _instance.avPlayer.currentTime; -	[_instance.avPlayer pause]; -	video_playing = false; -} - -void _focus_out_video() { -	printf("focus out pausing video\n"); -	[_instance.avPlayer pause]; -}; - -void _unpause_video() { -	[_instance.avPlayer play]; -	video_playing = true; -}; - -void _stop_video() { -	[_instance.avPlayer pause]; -	[_instance.avPlayerLayer removeFromSuperlayer]; -	_instance.avPlayer = nil; -	video_playing = false; -} - -CGFloat _points_to_pixels(CGFloat points) { -	float pixelPerInch; -	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { -		pixelPerInch = 132; -	} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { -		pixelPerInch = 163; -	} else { -		pixelPerInch = 160; -	} -	CGFloat pointsPerInch = 72.0; -	return (points / pointsPerInch * pixelPerInch); -} - -@implementation GLView - -@synthesize animationInterval; - -static const int max_touches = 8; -static UITouch *touches[max_touches]; - -static void init_touches() { -	for (int i = 0; i < max_touches; i++) { -		touches[i] = NULL; -	}; -}; - -static int get_touch_id(UITouch *p_touch) { -	int first = -1; -	for (int i = 0; i < max_touches; i++) { -		if (first == -1 && touches[i] == NULL) { -			first = i; -			continue; -		}; -		if (touches[i] == p_touch) -			return i; -	}; - -	if (first != -1) { -		touches[first] = p_touch; -		return first; -	}; - -	return -1; -}; - -static int remove_touch(UITouch *p_touch) { -	int remaining = 0; -	for (int i = 0; i < max_touches; i++) { -		if (touches[i] == NULL) -			continue; -		if (touches[i] == p_touch) -			touches[i] = NULL; -		else -			++remaining; -	}; -	return remaining; -}; - -static void clear_touches() { -	for (int i = 0; i < max_touches; i++) { -		touches[i] = NULL; -	}; -}; - -// Implement this to override the default layer class (which is [CALayer class]). -// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering. -+ (Class)layerClass { -	return [CAEAGLLayer class]; -} - -//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder: -- (id)initWithCoder:(NSCoder *)coder { -	active = FALSE; -	if ((self = [super initWithCoder:coder])) { -		self = [self initGLES]; -	} -	return self; -} - -- (id)initGLES { -	// Get our backing layer -	CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; - -	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color. -	eaglLayer.opaque = YES; -	eaglLayer.drawableProperties = [NSDictionary -			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], -			kEAGLDrawablePropertyRetainedBacking, -			kEAGLColorFormatRGBA8, -			kEAGLDrawablePropertyColorFormat, -			nil]; - -	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? - -	// Create GL ES 2 context -	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") { -		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; -		NSLog(@"Setting up an OpenGL ES 2.0 context."); -		if (!context) { -			NSLog(@"Failed to create OpenGL ES 2.0 context!"); -			return nil; -		} -	} - -	if (![EAGLContext setCurrentContext:context]) { -		NSLog(@"Failed to set EAGLContext!"); -		return nil; -	} -	if (![self createFramebuffer]) { -		NSLog(@"Failed to create frame buffer!"); -		return nil; -	} - -	// Default the animation interval to 1/60th of a second. -	animationInterval = 1.0 / 60.0; -	return self; -} - -- (id<GLViewDelegate>)delegate { -	return delegate; -} - -// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called. -- (void)setDelegate:(id<GLViewDelegate>)d { -	delegate = d; -	delegateSetup = ![delegate respondsToSelector:@selector(setupView:)]; -} - -@synthesize useCADisplayLink; - -// If our view is resized, we'll be asked to layout subviews. -// This is the perfect opportunity to also update the framebuffer so that it is -// the same size as our display area. - -- (void)layoutSubviews { -	[EAGLContext setCurrentContext:context]; -	[self destroyFramebuffer]; -	[self createFramebuffer]; -	[self drawView]; -} - -- (BOOL)createFramebuffer { -	// Generate IDs for a framebuffer object and a color renderbuffer -	UIScreen *mainscr = [UIScreen mainScreen]; -	printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height); -	self.contentScaleFactor = mainscr.nativeScale; - -	glGenFramebuffersOES(1, &viewFramebuffer); -	glGenRenderbuffersOES(1, &viewRenderbuffer); - -	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); -	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); -	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer) -	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view). -	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer]; -	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); - -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); - -	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer. -	glGenRenderbuffersOES(1, &depthRenderbuffer); -	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); -	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); -	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); - -	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { -		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); -		return NO; -	} - -	if (OS::get_singleton()) { -		OS::VideoMode vm; -		vm.fullscreen = true; -		vm.width = backingWidth; -		vm.height = backingHeight; -		vm.resizable = false; -		OS::get_singleton()->set_video_mode(vm); -		OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer); -	}; -	gl_view_base_fb = viewFramebuffer; - -	return YES; -} - -// Clean up any buffers we have allocated. -- (void)destroyFramebuffer { -	glDeleteFramebuffersOES(1, &viewFramebuffer); -	viewFramebuffer = 0; -	glDeleteRenderbuffersOES(1, &viewRenderbuffer); -	viewRenderbuffer = 0; - -	if (depthRenderbuffer) { -		glDeleteRenderbuffersOES(1, &depthRenderbuffer); -		depthRenderbuffer = 0; -	} -} - -- (void)startAnimation { -	if (active) -		return; -	active = TRUE; -	printf("start animation!\n"); -	if (useCADisplayLink) { -		// Approximate frame rate -		// assumes device refreshes at 60 fps -		int frameInterval = (int)floor(animationInterval * 60.0f); - -		displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; -		[displayLink setFrameInterval:frameInterval]; - -		// Setup DisplayLink in main thread -		[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; -	} else { -		animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; -	} - -	if (video_playing) { -		_unpause_video(); -	} -} - -- (void)stopAnimation { -	if (!active) -		return; -	active = FALSE; -	printf("******** stop animation!\n"); - -	if (useCADisplayLink) { -		[displayLink invalidate]; -		displayLink = nil; -	} else { -		[animationTimer invalidate]; -		animationTimer = nil; -	} - -	clear_touches(); - -	if (video_playing) { -		// save position -	} -} - -- (void)setAnimationInterval:(NSTimeInterval)interval { -	animationInterval = interval; -	if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) { -		[self stopAnimation]; -		[self startAnimation]; -	} -} - -// Updates the OpenGL view when the timer fires -- (void)drawView { -	if (!active) { -		printf("draw view not active!\n"); -		return; -	}; -	if (useCADisplayLink) { -		// Pause the CADisplayLink to avoid recursion -		[displayLink setPaused:YES]; - -		// Process all input events -		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) -			; - -		// We are good to go, resume the CADisplayLink -		[displayLink setPaused:NO]; -	} - -	// Make sure that you are drawing to the current context -	[EAGLContext setCurrentContext:context]; - -	// If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again. -	if (!delegateSetup) { -		[delegate setupView:self]; -		delegateSetup = YES; -	} - -	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); - -	[delegate drawView:self]; - -	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); -	[context presentRenderbuffer:GL_RENDERBUFFER_OES]; - -#ifdef DEBUG_ENABLED -	GLenum err = glGetError(); -	if (err) -		NSLog(@"DrawView: %x error", err); -#endif -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { -	NSArray *tlist = [[event allTouches] allObjects]; -	for (unsigned int i = 0; i < [tlist count]; i++) { -		if ([touches containsObject:[tlist objectAtIndex:i]]) { -			UITouch *touch = [tlist objectAtIndex:i]; -			if (touch.phase != UITouchPhaseBegan) -				continue; -			int tid = get_touch_id(touch); -			ERR_FAIL_COND(tid == -1); -			CGPoint touchPoint = [touch locationInView:self]; -			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); -		}; -	}; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { -	NSArray *tlist = [[event allTouches] allObjects]; -	for (unsigned int i = 0; i < [tlist count]; i++) { -		if ([touches containsObject:[tlist objectAtIndex:i]]) { -			UITouch *touch = [tlist objectAtIndex:i]; -			if (touch.phase != UITouchPhaseMoved) -				continue; -			int tid = get_touch_id(touch); -			ERR_FAIL_COND(tid == -1); -			CGPoint touchPoint = [touch locationInView:self]; -			CGPoint prev_point = [touch previousLocationInView:self]; -			OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor); -		}; -	}; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { -	NSArray *tlist = [[event allTouches] allObjects]; -	for (unsigned int i = 0; i < [tlist count]; i++) { -		if ([touches containsObject:[tlist objectAtIndex:i]]) { -			UITouch *touch = [tlist objectAtIndex:i]; -			if (touch.phase != UITouchPhaseEnded) -				continue; -			int tid = get_touch_id(touch); -			ERR_FAIL_COND(tid == -1); -			remove_touch(touch); -			CGPoint touchPoint = [touch locationInView:self]; -			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); -		}; -	}; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { -	OSIPhone::get_singleton()->touches_cancelled(); -	clear_touches(); -}; - -- (BOOL)canBecomeFirstResponder { -	return YES; -}; - -- (void)open_keyboard { -	//keyboard_text = p_existing; -	[self becomeFirstResponder]; -}; - -- (void)hide_keyboard { -	//keyboard_text = p_existing; -	[self resignFirstResponder]; -}; - -- (void)keyboardOnScreen:(NSNotification *)notification { -	NSDictionary *info = notification.userInfo; -	NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; - -	CGRect rawFrame = [value CGRectValue]; -	CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil]; - -	OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height)); -} - -- (void)keyboardHidden:(NSNotification *)notification { -	OSIPhone::get_singleton()->set_virtual_keyboard_height(0); -} - -- (void)deleteBackward { -	if (keyboard_text.length()) -		keyboard_text.erase(keyboard_text.length() - 1, 1); -	OSIPhone::get_singleton()->key(KEY_BACKSPACE, true); -}; - -- (BOOL)hasText { -	return keyboard_text.length() ? YES : NO; -}; - -- (void)insertText:(NSString *)p_text { -	String character; -	character.parse_utf8([p_text UTF8String]); -	keyboard_text = keyboard_text + character; -	OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true); -	printf("inserting text with character %lc\n", (CharType)character[0]); -}; - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { -	printf("*********** route changed!\n"); -	NSDictionary *interuptionDict = notification.userInfo; - -	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; - -	switch (routeChangeReason) { -		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { -			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); -			NSLog(@"Headphone/Line plugged in"); -		}; break; - -		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { -			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); -			NSLog(@"Headphone/Line was pulled. Resuming video play...."); -			if (_is_video_playing()) { -				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ -					[_instance.avPlayer play]; // NOTE: change this line according your current player implementation -					NSLog(@"resumed play"); -				}); -			}; -		}; break; - -		case AVAudioSessionRouteChangeReasonCategoryChange: { -			// called at start - also when other audio wants to play -			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); -		}; break; -	} -} - -// When created via code however, we get initWithFrame -- (id)initWithFrame:(CGRect)frame { -	self = [super initWithFrame:frame]; -	_instance = self; -	printf("after init super %p\n", self); -	if (self != nil) { -		self = [self initGLES]; -		printf("after init gles %p\n", self); -	} -	init_touches(); -	self.multipleTouchEnabled = YES; -	self.autocorrectionType = UITextAutocorrectionTypeNo; - -	printf("******** adding observer for sound routing changes\n"); -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(audioRouteChangeListenerCallback:) -				   name:AVAudioSessionRouteChangeNotification -				 object:nil]; - -	printf("******** adding observer for keyboard show/hide\n"); -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(keyboardOnScreen:) -				   name:UIKeyboardDidShowNotification -				 object:nil]; -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(keyboardHidden:) -				   name:UIKeyboardDidHideNotification -				 object:nil]; - -	//self.autoresizesSubviews = YES; -	//[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth]; - -	return self; -} - -//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers { -//	return YES; -//} - -//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ -//	return YES; -//} - -// Stop animating and release resources when they are no longer needed. -- (void)dealloc { -	[self stopAnimation]; - -	if ([EAGLContext currentContext] == context) { -		[EAGLContext setCurrentContext:nil]; -	} - -	[context release]; -	context = nil; - -	[super dealloc]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { -	if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) { -		if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) { -			_stop_video(); -			video_found_error = true; -		} - -		if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay && -				_instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && -				CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) { -			//NSLog(@"time: %@", video_current_time); - -			[_instance.avPlayer seekToTime:video_current_time]; -			video_current_time = kCMTimeZero; -		} -	} - -	if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) { -		NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate); -		if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) { -			dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ -				[_instance.avPlayer play]; // NOTE: change this line according your current player implementation -				NSLog(@"resumed play"); -			}); - -			NSLog(@" . . . PAUSED (or just started)"); -		} -	} -} - -- (void)playerItemDidReachEnd:(NSNotification *)notification { -	_stop_video(); -} - -@end diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm index b9d217c9d2..090b772947 100644 --- a/platform/iphone/godot_iphone.cpp +++ b/platform/iphone/godot_iphone.mm @@ -1,5 +1,5 @@  /*************************************************************************/ -/*  godot_iphone.cpp                                                     */ +/*  godot_iphone.mm                                                      */  /*************************************************************************/  /*                       This file is part of:                           */  /*                           GODOT ENGINE                                */ @@ -38,19 +38,53 @@  static OSIPhone *os = nullptr; -extern "C" { -int add_path(int p_argc, char **p_args); -int add_cmdline(int p_argc, char **p_args); +int add_path(int, char **); +int add_cmdline(int, char **); +int iphone_main(int, char **, String); + +int add_path(int p_argc, char **p_args) { +	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; +	if (!str) { +		return p_argc; +	} + +	p_args[p_argc++] = (char *)"--path"; +	[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo +	p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; +	p_args[p_argc] = NULL; +	[str release]; + +	return p_argc;  }; -int iphone_main(int, int, int, char **, String); +int add_cmdline(int p_argc, char **p_args) { +	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; +	if (!arr) { +		return p_argc; +	} + +	for (NSUInteger i = 0; i < [arr count]; i++) { +		NSString *str = [arr objectAtIndex:i]; +		if (!str) { +			continue; +		} +		[str retain]; // @todo delete these at some point +		p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; +		[str release]; +	}; + +	p_args[p_argc] = NULL; + +	return p_argc; +}; -int iphone_main(int width, int height, int argc, char **argv, String data_dir) { +int iphone_main(int argc, char **argv, String data_dir) {  	size_t len = strlen(argv[0]);  	while (len--) { -		if (argv[0][len] == '/') +		if (argv[0][len] == '/') {  			break; +		}  	}  	if (len >= 0) { @@ -65,7 +99,10 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {  	char cwd[512];  	getcwd(cwd, sizeof(cwd));  	printf("cwd %s\n", cwd); -	os = new OSIPhone(width, height, data_dir); +	os = new OSIPhone(data_dir); + +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE  	char *fargv[64];  	for (int i = 0; i < argc; i++) { @@ -76,10 +113,14 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {  	argc = add_cmdline(argc, fargv);  	printf("os created\n"); +  	Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);  	printf("setup %i\n", err); -	if (err != OK) +	if (err != OK) {  		return 255; +	} + +	os->initialize_modules();  	return 0;  }; diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h new file mode 100644 index 0000000000..62fa2f5a32 --- /dev/null +++ b/platform/iphone/godot_view.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/*  godot_view.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.                */ +/*************************************************************************/ + +#import <UIKit/UIKit.h> + +class String; + +@protocol DisplayLayer; +@protocol GodotViewRendererProtocol; + +@interface GodotView : UIView <UIKeyInput> + +@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer; + +@property(assign, readonly, nonatomic) BOOL isActive; + +@property(assign, nonatomic) BOOL useCADisplayLink; +@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer; +@property(assign, readonly, nonatomic) BOOL canRender; + +@property(assign, nonatomic) NSTimeInterval renderingInterval; + +- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName; +- (void)stopRendering; +- (void)startRendering; + +- (BOOL)becomeFirstResponderWithString:(String)p_existing; + +@end diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm new file mode 100644 index 0000000000..c0a31549c4 --- /dev/null +++ b/platform/iphone/godot_view.mm @@ -0,0 +1,499 @@ +/*************************************************************************/ +/*  godot_view.mm                                                        */ +/*************************************************************************/ +/*                       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.                */ +/*************************************************************************/ + +#import "godot_view.h" +#include "core/os/keyboard.h" +#include "core/ustring.h" +#import "display_layer.h" +#include "display_server_iphone.h" +#import "godot_view_gesture_recognizer.h" +#import "godot_view_renderer.h" + +#import <CoreMotion/CoreMotion.h> + +static const int max_touches = 8; + +@interface GodotView () { +	UITouch *godot_touches[max_touches]; +	String keyboard_text; +} + +@property(assign, nonatomic) BOOL isActive; + +// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15 +@property(strong, nonatomic) CADisplayLink *displayLink; + +// An animation timer that, when animation is started, will periodically call -drawView at the given rate. +// Only used if CADisplayLink is not +@property(strong, nonatomic) NSTimer *animationTimer; + +@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer; + +@property(strong, nonatomic) CMMotionManager *motionManager; + +@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer; + +@end + +@implementation GodotView + +- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName { +	if (self.renderingLayer) { +		return self.renderingLayer; +	} + +	CALayer<DisplayLayer> *layer; + +	if ([driverName isEqualToString:@"vulkan"]) { +		layer = [GodotMetalLayer layer]; +	} else if ([driverName isEqualToString:@"opengl_es"]) { +		if (@available(iOS 13, *)) { +			NSLog(@"OpenGL ES is deprecated on iOS 13"); +		} +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR +		return nil; +#else +		layer = [GodotOpenGLLayer layer]; +#endif +	} else { +		return nil; +	} + +	layer.frame = self.bounds; +	layer.contentsScale = self.contentScaleFactor; + +	[self.layer addSublayer:layer]; +	self.renderingLayer = layer; + +	[layer initializeDisplayLayer]; + +	return self.renderingLayer; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { +	self = [super initWithCoder:coder]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { +	self = [super initWithFrame:frame]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (void)dealloc { +	[self stopRendering]; + +	self.renderer = nil; + +	if (self.renderingLayer) { +		[self.renderingLayer removeFromSuperlayer]; +		self.renderingLayer = nil; +	} + +	if (self.motionManager) { +		[self.motionManager stopDeviceMotionUpdates]; +		self.motionManager = nil; +	} + +	if (self.displayLink) { +		[self.displayLink invalidate]; +		self.displayLink = nil; +	} + +	if (self.animationTimer) { +		[self.animationTimer invalidate]; +		self.animationTimer = nil; +	} + +	if (self.delayGestureRecognizer) { +		self.delayGestureRecognizer = nil; +	} + +	[super dealloc]; +} + +- (void)godot_commonInit { +	self.contentScaleFactor = [UIScreen mainScreen].nativeScale; + +	[self initTouches]; + +	// Configure and start accelerometer +	if (!self.motionManager) { +		self.motionManager = [[[CMMotionManager alloc] init] autorelease]; +		if (self.motionManager.deviceMotionAvailable) { +			self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; +			[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical]; +		} else { +			self.motionManager = nil; +		} +	} + +	// Initialize delay gesture recognizer +	GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init]; +	self.delayGestureRecognizer = gestureRecognizer; +	[self addGestureRecognizer:self.delayGestureRecognizer]; +	[gestureRecognizer release]; +} + +- (void)stopRendering { +	if (!self.isActive) { +		return; +	} + +	self.isActive = NO; + +	printf("******** stop animation!\n"); + +	if (self.useCADisplayLink) { +		[self.displayLink invalidate]; +		self.displayLink = nil; +	} else { +		[self.animationTimer invalidate]; +		self.animationTimer = nil; +	} + +	[self clearTouches]; +} + +- (void)startRendering { +	if (self.isActive) { +		return; +	} + +	self.isActive = YES; + +	printf("start animation!\n"); + +	if (self.useCADisplayLink) { +		self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; + +		//        if (@available(iOS 10, *)) { +		self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval); +		//        } else { +		//            // Approximate frame rate +		//            // assumes device refreshes at 60 fps +		//            int frameInterval = (int)floor(self.renderingInterval * 60.0f); +		//            [self.displayLink setFrameInterval:frameInterval]; +		//        } + +		// Setup DisplayLink in main thread +		[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; +	} else { +		self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; +	} +} + +- (void)drawView { +	if (!self.isActive) { +		printf("draw view not active!\n"); +		return; +	} + +	if (self.useCADisplayLink) { +		// Pause the CADisplayLink to avoid recursion +		[self.displayLink setPaused:YES]; + +		// Process all input events +		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) +			; + +		// We are good to go, resume the CADisplayLink +		[self.displayLink setPaused:NO]; +	} + +	[self.renderingLayer renderDisplayLayer]; + +	if (!self.renderer) { +		return; +	} + +	if ([self.renderer setupView:self]) { +		return; +	} + +	[self handleMotion]; +	[self.renderer renderOnView:self]; +} + +- (BOOL)canRender { +	if (self.useCADisplayLink) { +		return self.displayLink != nil; +	} else { +		return self.animationTimer != nil; +	} +} + +- (void)setRenderingInterval:(NSTimeInterval)renderingInterval { +	_renderingInterval = renderingInterval; + +	if (self.canRender) { +		[self stopRendering]; +		[self startRendering]; +	} +} + +- (void)layoutSubviews { +	if (self.renderingLayer) { +		self.renderingLayer.frame = self.bounds; +		[self.renderingLayer layoutDisplayLayer]; + +		if (DisplayServerIPhone::get_singleton()) { +			DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size); +		} +	} + +	[super layoutSubviews]; +} + +// MARK: - Input + +// MARK: Keyboard + +- (BOOL)canBecomeFirstResponder { +	return YES; +} + +- (BOOL)becomeFirstResponderWithString:(String)p_existing { +	keyboard_text = p_existing; +	return [self becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder { +	keyboard_text = String(); +	return [super resignFirstResponder]; +} + +- (void)deleteBackward { +	if (keyboard_text.length()) { +		keyboard_text.erase(keyboard_text.length() - 1, 1); +	} +	DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true); +} + +- (BOOL)hasText { +	return keyboard_text.length() > 0; +} + +- (void)insertText:(NSString *)p_text { +	String character; +	character.parse_utf8([p_text UTF8String]); +	keyboard_text = keyboard_text + character; +	DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true); +} + +// MARK: Touches + +- (void)initTouches { +	for (int i = 0; i < max_touches; i++) { +		godot_touches[i] = NULL; +	} +} + +- (int)getTouchIDForTouch:(UITouch *)p_touch { +	int first = -1; +	for (int i = 0; i < max_touches; i++) { +		if (first == -1 && godot_touches[i] == NULL) { +			first = i; +			continue; +		} +		if (godot_touches[i] == p_touch) { +			return i; +		} +	} + +	if (first != -1) { +		godot_touches[first] = p_touch; +		return first; +	} + +	return -1; +} + +- (int)removeTouch:(UITouch *)p_touch { +	int remaining = 0; +	for (int i = 0; i < max_touches; i++) { +		if (godot_touches[i] == NULL) { +			continue; +		} +		if (godot_touches[i] == p_touch) { +			godot_touches[i] = NULL; +		} else { +			++remaining; +		} +	} +	return remaining; +} + +- (void)clearTouches { +	for (int i = 0; i < max_touches; i++) { +		godot_touches[i] = NULL; +	} +} + +- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touchesSet containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			CGPoint touchPoint = [touch locationInView:self]; +			DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); +		} +	} +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touches containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			CGPoint touchPoint = [touch locationInView:self]; +			CGPoint prev_point = [touch previousLocationInView:self]; +			DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor); +		} +	} +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touches containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			[self removeTouch:touch]; +			CGPoint touchPoint = [touch locationInView:self]; +			DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); +		} +	} +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touches containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			DisplayServerIPhone::get_singleton()->touches_cancelled(tid); +		} +	} +	[self clearTouches]; +} + +// MARK: Motion + +- (void)handleMotion { +	if (!self.motionManager) { +		return; +	} + +	// Just using polling approach for now, we can set this up so it sends +	// data to us in intervals, might be better. See Apple reference pages +	// for more details: +	// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc + +	// Apple splits our accelerometer date into a gravity and user movement +	// component. We add them back together +	CMAcceleration gravity = self.motionManager.deviceMotion.gravity; +	CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration; + +	///@TODO We don't seem to be getting data here, is my device broken or +	/// is this code incorrect? +	CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field; + +	///@TODO we can access rotationRate as a CMRotationRate variable +	///(processed date) or CMGyroData (raw data), have to see what works +	/// best +	CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate; + +	// Adjust for screen orientation. +	// [[UIDevice currentDevice] orientation] changes even if we've fixed +	// our orientation which is not a good thing when you're trying to get +	// your user to move the screen in all directions and want consistent +	// output + +	///@TODO Using [[UIApplication sharedApplication] statusBarOrientation] +	/// is a bit of a hack. Godot obviously knows the orientation so maybe +	/// we +	// can use that instead? (note that left and right seem swapped) + +	UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown; + +	if (@available(iOS 13, *)) { +		interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +	} else { +		interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; +#endif +	} + +	switch (interfaceOrientation) { +		case UIInterfaceOrientationLandscapeLeft: { +			DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z); +		} break; +		case UIInterfaceOrientationLandscapeRight: { +			DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z); +		} break; +		case UIInterfaceOrientationPortraitUpsideDown: { +			DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z); +		} break; +		default: { // assume portrait +			DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z); +		} break; +	} +} + +@end diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h new file mode 100644 index 0000000000..ca3bd808d1 --- /dev/null +++ b/platform/iphone/godot_view_gesture_recognizer.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/*  godot_view_gesture_recognizer.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.                */ +/*************************************************************************/ + +// GLViewGestureRecognizer allows iOS gestures to work currectly by +// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer. +// It catches all gestures incoming to UIView and delays them for 150ms +// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer) +// If touch cancelation or end message is fired it fires delayed +// begin touch immediately as well as last touch signal + +#import <UIKit/UIKit.h> + +@interface GodotViewGestureRecognizer : UIGestureRecognizer + +- (instancetype)init; + +@end diff --git a/platform/iphone/godot_view_gesture_recognizer.m b/platform/iphone/godot_view_gesture_recognizer.m new file mode 100644 index 0000000000..377ccd52a5 --- /dev/null +++ b/platform/iphone/godot_view_gesture_recognizer.m @@ -0,0 +1,171 @@ +/*************************************************************************/ +/*  godot_view_gesture_recognizer.m                                      */ +/*************************************************************************/ +/*                       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.                */ +/*************************************************************************/ + +#import "godot_view_gesture_recognizer.h" + +// Using same delay interval that is used for `UIScrollView` +const NSTimeInterval kGLGestureDelayInterval = 0.150; + +// Minimum distance for touches to move to fire +// a delay timer before scheduled time. +// Should be the low enough to not cause issues with dragging +// but big enough to allow click to work. +const CGFloat kGLGestureMovementDistance = 0.5; + +@interface GodotViewGestureRecognizer () + +// Timer used to delay begin touch message. +// Should work as simple emulation of UIDelayedAction +@property(strong, nonatomic) NSTimer *delayTimer; + +// Delayed touch parameters +@property(strong, nonatomic) NSSet *delayedTouches; +@property(strong, nonatomic) UIEvent *delayedEvent; + +@end + +@implementation GodotViewGestureRecognizer + +- (instancetype)init { +	self = [super init]; + +	self.cancelsTouchesInView = YES; +	self.delaysTouchesBegan = YES; +	self.delaysTouchesEnded = YES; + +	return self; +} + +- (void)dealloc { +	if (self.delayTimer) { +		[self.delayTimer invalidate]; +		self.delayTimer = nil; +	} + +	if (self.delayedTouches) { +		self.delayedTouches = nil; +	} + +	if (self.delayedEvent) { +		self.delayedEvent = nil; +	} + +	[super dealloc]; +} + +- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { +	[self.delayTimer fire]; + +	self.delayedTouches = touches; +	self.delayedEvent = event; + +	self.delayTimer = [NSTimer +			scheduledTimerWithTimeInterval:kGLGestureDelayInterval +									target:self +								  selector:@selector(fireDelayedTouches:) +								  userInfo:nil +								   repeats:NO]; +} + +- (void)fireDelayedTouches:(id)timer { +	[self.delayTimer invalidate]; +	self.delayTimer = nil; + +	if (self.delayedTouches) { +		[self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent]; +	} + +	self.delayedTouches = nil; +	self.delayedEvent = nil; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { +	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan]; +	[self delayTouches:cleared andEvent:event]; +	[cleared release]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved]; + +	if (self.delayTimer) { +		// We should check if movement was significant enough to fire an event +		// for dragging to work correctly. +		for (UITouch *touch in cleared) { +			CGPoint from = [touch locationInView:self.view]; +			CGPoint to = [touch previousLocationInView:self.view]; +			CGFloat xDistance = from.x - to.x; +			CGFloat yDistance = from.y - to.y; + +			CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance); + +			// Early exit, since one of touches has moved enough to fire a drag event. +			if (distance > kGLGestureMovementDistance) { +				[self.delayTimer fire]; +				[self.view touchesMoved:cleared withEvent:event]; +				[cleared release]; +				return; +			} +		} + +		[cleared release]; +		return; +	} + +	[self.view touchesMoved:cleared withEvent:event]; +	[cleared release]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { +	[self.delayTimer fire]; + +	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; +	[self.view touchesEnded:cleared withEvent:event]; +	[cleared release]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { +	[self.delayTimer fire]; +	[self.view touchesCancelled:touches withEvent:event]; +}; + +- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { +	NSMutableSet *cleared = [touches mutableCopy]; + +	for (UITouch *touch in touches) { +		if (touch.phase != phaseToSave) { +			[cleared removeObject:touch]; +		} +	} + +	return cleared; +} + +@end diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h new file mode 100644 index 0000000000..ea8998c808 --- /dev/null +++ b/platform/iphone/godot_view_renderer.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/*  godot_view_renderer.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.                */ +/*************************************************************************/ + +#import <UIKit/UIKit.h> + +@protocol GodotViewRendererProtocol <NSObject> + +@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; + +- (BOOL)setupView:(UIView *)view; +- (void)renderOnView:(UIView *)view; + +@end + +@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol> + +@end diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm new file mode 100644 index 0000000000..1fc822b457 --- /dev/null +++ b/platform/iphone/godot_view_renderer.mm @@ -0,0 +1,146 @@ +/*************************************************************************/ +/*  godot_view_renderer.mm                                               */ +/*************************************************************************/ +/*                       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.                */ +/*************************************************************************/ + +#import "godot_view_renderer.h" +#include "core/os/keyboard.h" +#include "core/project_settings.h" +#import "display_server_iphone.h" +#include "main/main.h" +#include "os_iphone.h" +#include "servers/audio_server.h" + +#import <AudioToolbox/AudioServices.h> +#import <CoreMotion/CoreMotion.h> +#import <GameController/GameController.h> +#import <QuartzCore/QuartzCore.h> +#import <UIKit/UIKit.h> + +@interface GodotViewRenderer () + +@property(assign, nonatomic) BOOL hasFinishedLocaleSetup; +@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup; +@property(assign, nonatomic) BOOL hasStartedMain; +@property(assign, nonatomic) BOOL hasFinishedSetup; + +@end + +@implementation GodotViewRenderer + +- (BOOL)setupView:(UIView *)view { +	if (self.hasFinishedSetup) { +		return NO; +	} + +	if (!self.hasFinishedLocaleSetup) { +		[self setupLocaleAndUUID]; +		return YES; +	} + +	if (!self.hasFinishedProjectDataSetup) { +		[self setupProjectData]; +		return YES; +	} + +	if (!self.hasStartedMain) { +		self.hasStartedMain = YES; +		OSIPhone::get_singleton()->start(); +		return YES; +	} + +	self.hasFinishedSetup = YES; + +	return NO; +} + +- (void)setupLocaleAndUUID { +	self.hasFinishedLocaleSetup = YES; + +	if (!OS::get_singleton()) { +		exit(0); +	} + +	NSString *locale_code = [[NSLocale currentLocale] localeIdentifier]; +	OSIPhone::get_singleton()->set_locale(String::utf8([locale_code UTF8String])); + +	NSString *uuid; +	if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) { +		uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; +	} else { +		// before iOS 6, so just generate an identifier and store it +		uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"identiferForVendor"]; +		if (!uuid) { +			CFUUIDRef cfuuid = CFUUIDCreate(NULL); +			uuid = [(NSString *)CFUUIDCreateString(NULL, cfuuid) autorelease]; +			CFRelease(cfuuid); +			[[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"identifierForVendor"]; +		} +	} + +	OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String])); +} + +- (void)setupProjectData { +	self.hasFinishedProjectDataSetup = YES; + +	Main::setup2(); + +	// this might be necessary before here +	NSDictionary *dict = [[NSBundle mainBundle] infoDictionary]; +	for (NSString *key in dict) { +		NSObject *value = [dict objectForKey:key]; +		String ukey = String::utf8([key UTF8String]); + +		// we need a NSObject to Variant conversor + +		if ([value isKindOfClass:[NSString class]]) { +			NSString *str = (NSString *)value; +			String uval = String::utf8([str UTF8String]); + +			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); + +		} else if ([value isKindOfClass:[NSNumber class]]) { +			NSNumber *n = (NSNumber *)value; +			double dval = [n doubleValue]; + +			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); +		}; +		// do stuff +	} +} + +- (void)renderOnView:(UIView *)view { +	if (!OSIPhone::get_singleton()) { +		return; +	} + +	OSIPhone::get_singleton()->iterate(); +} + +@end diff --git a/platform/iphone/icloud.h b/platform/iphone/icloud.h index b11e22fec6..381edfa718 100644 --- a/platform/iphone/icloud.h +++ b/platform/iphone/icloud.h @@ -44,9 +44,9 @@ class ICloud : public Object {  	List<Variant> pending_events;  public: -	Error remove_key(Variant p_param); -	Variant set_key_values(Variant p_param); -	Variant get_key_value(Variant p_param); +	Error remove_key(String p_param); +	Array set_key_values(Dictionary p_params); +	Variant get_key_value(String p_param);  	Error synchronize_key_values();  	Variant get_all_key_values(); diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index c768274b1f..d3086e6cea 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -48,8 +48,10 @@ ICloud *ICloud::instance = NULL;  void ICloud::_bind_methods() {  	ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key); +  	ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);  	ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value); +  	ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);  	ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values); @@ -91,7 +93,7 @@ Variant nsobject_to_variant(NSObject *object) {  	} else if ([object isKindOfClass:[NSArray class]]) {  		Array result;  		NSArray *array = (NSArray *)object; -		for (unsigned int i = 0; i < [array count]; ++i) { +		for (NSUInteger i = 0; i < [array count]; ++i) {  			NSObject *value = [array objectAtIndex:i];  			result.push_back(nsobject_to_variant(value));  		} @@ -149,7 +151,7 @@ Variant nsobject_to_variant(NSObject *object) {  NSObject *variant_to_nsobject(Variant v) {  	if (v.get_type() == Variant::STRING) {  		return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease]; -	} else if (v.get_type() == Variant::REAL) { +	} else if (v.get_type() == Variant::FLOAT) {  		return [NSNumber numberWithDouble:(double)v];  	} else if (v.get_type() == Variant::INT) {  		return [NSNumber numberWithLongLong:(long)(int)v]; @@ -159,7 +161,7 @@ NSObject *variant_to_nsobject(Variant v) {  		NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];  		Dictionary dic = v;  		Array keys = dic.keys(); -		for (unsigned int i = 0; i < keys.size(); ++i) { +		for (int i = 0; i < keys.size(); ++i) {  			NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];  			NSObject *value = variant_to_nsobject(dic[keys[i]]); @@ -173,7 +175,7 @@ NSObject *variant_to_nsobject(Variant v) {  	} else if (v.get_type() == Variant::ARRAY) {  		NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];  		Array arr = v; -		for (unsigned int i = 0; i < arr.size(); ++i) { +		for (int i = 0; i < arr.size(); ++i) {  			NSObject *value = variant_to_nsobject(arr[i]);  			if (value == NULL) {  				//trying to add something unsupported to the array. cancel the whole array @@ -192,9 +194,8 @@ NSObject *variant_to_nsobject(Variant v) {  	return NULL;  } -Error ICloud::remove_key(Variant p_param) { -	String param = p_param; -	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease]; +Error ICloud::remove_key(String p_param) { +	NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];  	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; @@ -207,15 +208,14 @@ Error ICloud::remove_key(Variant p_param) {  }  //return an array of the keys that could not be set -Variant ICloud::set_key_values(Variant p_params) { -	Dictionary params = p_params; -	Array keys = params.keys(); +Array ICloud::set_key_values(Dictionary p_params) { +	Array keys = p_params.keys();  	Array error_keys; -	for (unsigned int i = 0; i < keys.size(); ++i) { +	for (int i = 0; i < keys.size(); ++i) {  		String variant_key = keys[i]; -		Variant variant_value = params[variant_key]; +		Variant variant_value = p_params[variant_key];  		NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];  		if (key == NULL) { @@ -237,10 +237,8 @@ Variant ICloud::set_key_values(Variant p_params) {  	return error_keys;  } -Variant ICloud::get_key_value(Variant p_param) { -	String param = p_param; - -	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease]; +Variant ICloud::get_key_value(String p_param) { +	NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];  	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];  	if (![[store dictionaryRepresentation] objectForKey:key]) { diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h index 44e65e77ed..beb58af2c7 100644 --- a/platform/iphone/in_app_store.h +++ b/platform/iphone/in_app_store.h @@ -44,9 +44,9 @@ class InAppStore : public Object {  	List<Variant> pending_events;  public: -	Error request_product_info(Variant p_params); +	Error request_product_info(Dictionary p_params);  	Error restore_purchases(); -	Error purchase(Variant p_params); +	Error purchase(Dictionary p_params);  	int get_pending_event_count();  	Variant pop_pending_event(); diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 548dcc549d..dfec5d7634 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -39,8 +39,10 @@ extern "C" {  bool auto_finish_transactions = true;  NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary]; +static NSArray *latestProducts;  @interface SKProduct (LocalizedPrice) +  @property(nonatomic, readonly) NSString *localizedPrice;  @end @@ -82,6 +84,8 @@ void InAppStore::_bind_methods() {  - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {  	NSArray *products = response.products; +	latestProducts = products; +  	Dictionary ret;  	ret["type"] = "product_info";  	ret["result"] = "ok"; @@ -126,11 +130,10 @@ void InAppStore::_bind_methods() {  @end -Error InAppStore::request_product_info(Variant p_params) { -	Dictionary params = p_params; -	ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER); +Error InAppStore::request_product_info(Dictionary p_params) { +	ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER); -	PackedStringArray pids = params["product_ids"]; +	PackedStringArray pids = p_params["product_ids"];  	printf("************ request product info! %i\n", pids.size());  	NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease]; @@ -198,11 +201,11 @@ Error InAppStore::restore_purchases() {  						// which is still available in iOS 7.  						// Use SKPaymentTransaction's transactionReceipt. -						receipt = transaction.transactionReceipt; +						receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];  					}  				} else { -					receipt = transaction.transactionReceipt; +					receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];  				}  				NSString *receipt_to_send = nil; @@ -254,17 +257,32 @@ Error InAppStore::restore_purchases() {  @end -Error InAppStore::purchase(Variant p_params) { +Error InAppStore::purchase(Dictionary p_params) {  	ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);  	if (![SKPaymentQueue canMakePayments])  		return ERR_UNAVAILABLE;  	printf("purchasing!\n"); -	Dictionary params = p_params; -	ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER); +	ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER); + +	NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease]; + +	SKProduct *product = nil; + +	if (latestProducts) { +		for (SKProduct *storedProduct in latestProducts) { +			if ([storedProduct.productIdentifier isEqualToString:pid]) { +				product = storedProduct; +				break; +			} +		} +	} + +	if (!product) { +		return ERR_INVALID_PARAMETER; +	} -	NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease]; -	SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid]; +	SKPayment *payment = [SKPayment paymentWithProduct:product];  	SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];  	[defq addPayment:payment];  	printf("purchase sent!\n"); diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 5923f558a5..ad26d0ada3 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -29,17 +29,27 @@  /*************************************************************************/  #include "ios.h" -#include <sys/sysctl.h> - +#import "app_delegate.h"  #import <UIKit/UIKit.h> +#include <sys/sysctl.h>  void iOS::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);  };  void iOS::alert(const char *p_alert, const char *p_title) { -	UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease]; -	[alert show]; +	NSString *title = [NSString stringWithUTF8String:p_title]; +	NSString *message = [NSString stringWithUTF8String:p_alert]; + +	UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; +	UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK" +													 style:UIAlertActionStyleCancel +												   handler:^(id){ +												   }]; + +	[alert addAction:button]; + +	[AppDelegate.viewController presentViewController:alert animated:YES completion:nil];  }  String iOS::get_model() const { diff --git a/platform/iphone/joypad_iphone.h b/platform/iphone/joypad_iphone.h new file mode 100644 index 0000000000..85e26e1dc8 --- /dev/null +++ b/platform/iphone/joypad_iphone.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/*  joypad_iphone.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.                */ +/*************************************************************************/ + +#import <GameController/GameController.h> + +@interface JoypadIPhoneObserver : NSObject + +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; + +@end + +class JoypadIPhone { +private: +	JoypadIPhoneObserver *observer; + +public: +	JoypadIPhone(); +	~JoypadIPhone(); + +	void start_processing(); +}; diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm new file mode 100644 index 0000000000..6088f1c25c --- /dev/null +++ b/platform/iphone/joypad_iphone.mm @@ -0,0 +1,380 @@ +/*************************************************************************/ +/*  joypad_iphone.mm                                                     */ +/*************************************************************************/ +/*                       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.                */ +/*************************************************************************/ + +#import "joypad_iphone.h" +#include "core/project_settings.h" +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "main/main.h" + +#import "godot_view.h" + +#include "os_iphone.h" + +JoypadIPhone::JoypadIPhone() { +	observer = [[JoypadIPhoneObserver alloc] init]; +	[observer startObserving]; +} + +JoypadIPhone::~JoypadIPhone() { +	if (observer) { +		[observer finishObserving]; +		observer = nil; +	} +} + +void JoypadIPhone::start_processing() { +	if (observer) { +		[observer startProcessing]; +	} +} + +@interface JoypadIPhoneObserver () + +@property(assign, nonatomic) BOOL isObserving; +@property(assign, nonatomic) BOOL isProcessing; +@property(strong, nonatomic) NSMutableDictionary *connectedJoypads; +@property(strong, nonatomic) NSMutableArray *joypadsQueue; + +@end + +@implementation JoypadIPhoneObserver + +- (instancetype)init { +	self = [super init]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (void)godot_commonInit { +	self.isObserving = NO; +	self.isProcessing = NO; +} + +- (void)startProcessing { +	self.isProcessing = YES; + +	for (GCController *controller in self.joypadsQueue) { +		[self addiOSJoypad:controller]; +	} + +	[self.joypadsQueue removeAllObjects]; +} + +- (void)startObserving { +	if (self.isObserving) { +		return; +	} + +	self.isObserving = YES; + +	self.connectedJoypads = [NSMutableDictionary dictionary]; +	self.joypadsQueue = [NSMutableArray array]; + +	// get told when controllers connect, this will be called right away for +	// already connected controllers +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(controllerWasConnected:) +				   name:GCControllerDidConnectNotification +				 object:nil]; + +	// get told when controllers disconnect +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(controllerWasDisconnected:) +				   name:GCControllerDidDisconnectNotification +				 object:nil]; +} + +- (void)finishObserving { +	if (self.isObserving) { +		[[NSNotificationCenter defaultCenter] removeObserver:self]; +	} + +	self.isObserving = NO; +	self.isProcessing = NO; + +	self.connectedJoypads = nil; +	self.joypadsQueue = nil; +} + +- (void)dealloc { +	[self finishObserving]; + +	[super dealloc]; +} + +- (int)getJoyIdForController:(GCController *)controller { +	NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; + +	for (NSNumber *key in keys) { +		int joy_id = [key intValue]; +		return joy_id; +	}; + +	return -1; +}; + +- (void)addiOSJoypad:(GCController *)controller { +	//     get a new id for our controller +	int joy_id = Input::get_singleton()->get_unused_joy_id(); + +	if (joy_id == -1) { +		printf("Couldn't retrieve new joy id\n"); +		return; +	} + +	// assign our player index +	if (controller.playerIndex == GCControllerPlayerIndexUnset) { +		controller.playerIndex = [self getFreePlayerIndex]; +	}; + +	// tell Godot about our new controller +	Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]); + +	// add it to our dictionary, this will retain our controllers +	[self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]]; + +	// set our input handler +	[self setControllerInputHandler:controller]; +} + +- (void)controllerWasConnected:(NSNotification *)notification { +	// get our controller +	GCController *controller = (GCController *)notification.object; + +	if (!controller) { +		printf("Couldn't retrieve new controller\n"); +		return; +	} + +	if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) { +		printf("Controller is already registered\n"); +	} else if (!self.isProcessing) { +		[self.joypadsQueue addObject:controller]; +	} else { +		[self addiOSJoypad:controller]; +	} +} + +- (void)controllerWasDisconnected:(NSNotification *)notification { +	// find our joystick, there should be only one in our dictionary +	GCController *controller = (GCController *)notification.object; + +	if (!controller) { +		return; +	} + +	NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; +	for (NSNumber *key in keys) { +		// tell Godot this joystick is no longer there +		int joy_id = [key intValue]; +		Input::get_singleton()->joy_connection_changed(joy_id, false, ""); + +		// and remove it from our dictionary +		[self.connectedJoypads removeObjectForKey:key]; +	}; +}; + +- (GCControllerPlayerIndex)getFreePlayerIndex { +	bool have_player_1 = false; +	bool have_player_2 = false; +	bool have_player_3 = false; +	bool have_player_4 = false; + +	if (self.connectedJoypads == nil) { +		NSArray *keys = [self.connectedJoypads allKeys]; +		for (NSNumber *key in keys) { +			GCController *controller = [self.connectedJoypads objectForKey:key]; +			if (controller.playerIndex == GCControllerPlayerIndex1) { +				have_player_1 = true; +			} else if (controller.playerIndex == GCControllerPlayerIndex2) { +				have_player_2 = true; +			} else if (controller.playerIndex == GCControllerPlayerIndex3) { +				have_player_3 = true; +			} else if (controller.playerIndex == GCControllerPlayerIndex4) { +				have_player_4 = true; +			}; +		}; +	}; + +	if (!have_player_1) { +		return GCControllerPlayerIndex1; +	} else if (!have_player_2) { +		return GCControllerPlayerIndex2; +	} else if (!have_player_3) { +		return GCControllerPlayerIndex3; +	} else if (!have_player_4) { +		return GCControllerPlayerIndex4; +	} else { +		return GCControllerPlayerIndexUnset; +	}; +} + +- (void)setControllerInputHandler:(GCController *)controller { +	// Hook in the callback handler for the correct gamepad profile. +	// This is a bit of a weird design choice on Apples part. +	// You need to select the most capable gamepad profile for the +	// gamepad attached. +	if (controller.extendedGamepad != nil) { +		// The extended gamepad profile has all the input you could possibly find on +		// a gamepad but will only be active if your gamepad actually has all of +		// these... +		controller.extendedGamepad.valueChangedHandler = ^( +				GCExtendedGamepad *gamepad, GCControllerElement *element) { +			int joy_id = [self getJoyIdForController:controller]; + +			if (element == gamepad.buttonA) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, +						gamepad.buttonA.isPressed); +			} else if (element == gamepad.buttonB) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, +						gamepad.buttonB.isPressed); +			} else if (element == gamepad.buttonX) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, +						gamepad.buttonX.isPressed); +			} else if (element == gamepad.buttonY) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, +						gamepad.buttonY.isPressed); +			} else if (element == gamepad.leftShoulder) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, +						gamepad.leftShoulder.isPressed); +			} else if (element == gamepad.rightShoulder) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, +						gamepad.rightShoulder.isPressed); +			} else if (element == gamepad.dpad) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, +						gamepad.dpad.up.isPressed); +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, +						gamepad.dpad.down.isPressed); +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, +						gamepad.dpad.left.isPressed); +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, +						gamepad.dpad.right.isPressed); +			}; + +			Input::JoyAxis jx; +			jx.min = -1; +			if (element == gamepad.leftThumbstick) { +				jx.value = gamepad.leftThumbstick.xAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx); +				jx.value = -gamepad.leftThumbstick.yAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx); +			} else if (element == gamepad.rightThumbstick) { +				jx.value = gamepad.rightThumbstick.xAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx); +				jx.value = -gamepad.rightThumbstick.yAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx); +			} else if (element == gamepad.leftTrigger) { +				jx.value = gamepad.leftTrigger.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx); +			} else if (element == gamepad.rightTrigger) { +				jx.value = gamepad.rightTrigger.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); +			}; +		}; +	} +	//    else if (controller.gamepad != nil) { +	//        // gamepad is the standard profile with 4 buttons, shoulder buttons and a +	//        // D-pad +	//        controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, +	//                GCControllerElement *element) { +	//            int joy_id = [self getJoyIdForController:controller]; +	// +	//            if (element == gamepad.buttonA) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, +	//                        gamepad.buttonA.isPressed); +	//            } else if (element == gamepad.buttonB) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, +	//                        gamepad.buttonB.isPressed); +	//            } else if (element == gamepad.buttonX) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, +	//                        gamepad.buttonX.isPressed); +	//            } else if (element == gamepad.buttonY) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, +	//                        gamepad.buttonY.isPressed); +	//            } else if (element == gamepad.leftShoulder) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, +	//                        gamepad.leftShoulder.isPressed); +	//            } else if (element == gamepad.rightShoulder) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, +	//                        gamepad.rightShoulder.isPressed); +	//            } else if (element == gamepad.dpad) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, +	//                        gamepad.dpad.up.isPressed); +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, +	//                        gamepad.dpad.down.isPressed); +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, +	//                        gamepad.dpad.left.isPressed); +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, +	//                        gamepad.dpad.right.isPressed); +	//            }; +	//        }; +	//#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+, +	//        // while we are setting that as the minimum, seems our +	//        // build environment doesn't like it +	//    } else if (controller.microGamepad != nil) { +	//        // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad +	//        controller.microGamepad.valueChangedHandler = +	//                ^(GCMicroGamepad *gamepad, GCControllerElement *element) { +	//                    int joy_id = [self getJoyIdForController:controller]; +	// +	//                    if (element == gamepad.buttonA) { +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, +	//                                gamepad.buttonA.isPressed); +	//                    } else if (element == gamepad.buttonX) { +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, +	//                                gamepad.buttonX.isPressed); +	//                    } else if (element == gamepad.dpad) { +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, +	//                                gamepad.dpad.up.isPressed); +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, +	//                                gamepad.dpad.down.isPressed); +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, +	//                                gamepad.dpad.left.isPressed); +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, +	//                                gamepad.dpad.right.isPressed); +	//                    }; +	//                }; +	//#endif +	//    }; + +	///@TODO need to add support for controller.motion which gives us access to +	/// the orientation of the device (if supported) + +	///@TODO need to add support for controllerPausedHandler which should be a +	/// toggle +}; + +@end diff --git a/platform/iphone/main.m b/platform/iphone/main.m index 164db2a74b..c292f02822 100644 --- a/platform/iphone/main.m +++ b/platform/iphone/main.m @@ -32,20 +32,25 @@  #import <UIKit/UIKit.h>  #include <stdio.h> +#include <vulkan/vulkan.h>  int gargc;  char **gargv;  int main(int argc, char *argv[]) { +#if defined(VULKAN_ENABLED) +	//MoltenVK - enable full component swizzling support +	setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#endif +  	printf("*********** main.m\n");  	gargc = argc;  	gargv = argv; -	NSAutoreleasePool *pool = [NSAutoreleasePool new]; -	AppDelegate *app = [AppDelegate alloc];  	printf("running app main\n"); -	UIApplicationMain(argc, argv, nil, @"AppDelegate"); -	printf("main done, pool release\n"); -	[pool release]; +	@autoreleasepool { +		UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); +	} +	printf("main done\n");  	return 0;  } diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp deleted file mode 100644 index 41dd623e69..0000000000 --- a/platform/iphone/os_iphone.cpp +++ /dev/null @@ -1,632 +0,0 @@ -/*************************************************************************/ -/*  os_iphone.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.                */ -/*************************************************************************/ - -#ifdef IPHONE_ENABLED - -#include "os_iphone.h" - -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" -// #import <QuartzCore/CAMetalLayer.h> -#include <vulkan/vulkan_metal.h> -#endif - -#include "servers/rendering/rendering_server_raster.h" -#include "servers/rendering/rendering_server_wrap_mt.h" - -#include "main/main.h" - -#include "core/io/file_access_pack.h" -#include "core/os/dir_access.h" -#include "core/os/file_access.h" -#include "core/project_settings.h" -#include "drivers/unix/syslog_logger.h" - -#include "semaphore_iphone.h" - -#include <dlfcn.h> - -int OSIPhone::get_video_driver_count() const { -	return 2; -}; - -const char *OSIPhone::get_video_driver_name(int p_driver) const { -	switch (p_driver) { -		case VIDEO_DRIVER_GLES2: -			return "GLES2"; -	} -	ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + "."); -}; - -OSIPhone *OSIPhone::get_singleton() { -	return (OSIPhone *)OS::get_singleton(); -}; - -extern int gl_view_base_fb; // from gl_view.mm - -void OSIPhone::set_data_dir(String p_dir) { -	DirAccess *da = DirAccess::open(p_dir); - -	data_dir = da->get_current_dir(); -	printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str()); -	memdelete(da); -}; - -void OSIPhone::set_unique_id(String p_id) { -	unique_id = p_id; -}; - -String OSIPhone::get_unique_id() const { -	return unique_id; -}; - -void OSIPhone::initialize_core() { -	OS_Unix::initialize_core(); - -	set_data_dir(data_dir); -}; - -int OSIPhone::get_current_video_driver() const { -	return video_driver_index; -} - -Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { -	video_driver_index = p_video_driver; - -#if defined(OPENGL_ENABLED) -	bool gl_initialization_error = false; - -	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? - -	if (RasterizerGLES2::is_viable() == OK) { -		RasterizerGLES2::register_config(); -		RasterizerGLES2::make_current(); -	} else { -		gl_initialization_error = true; -	} - -	if (gl_initialization_error) { -		OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", -				"Unable to initialize video driver"); -		return ERR_UNAVAILABLE; -	} -#endif - -#if defined(VULKAN_ENABLED) -	RasterizerRD::make_current(); -#endif - -	rendering_server = memnew(RenderingServerRaster); -	// FIXME: Reimplement threaded rendering -	if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { -		rendering_server = memnew(RenderingServerWrapMT(rendering_server, false)); -	} -	rendering_server->init(); -	//rendering_server->cursor_set_visible(false, 0); - -#if defined(OPENGL_ENABLED) -	// reset this to what it should be, it will have been set to 0 after rendering_server->init() is called -	RasterizerStorageGLES2::system_fbo = gl_view_base_fb; -#endif - -	AudioDriverManager::initialize(p_audio_driver); - -	input = memnew(InputDefault); - -#ifdef GAME_CENTER_ENABLED -	game_center = memnew(GameCenter); -	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center)); -	game_center->connect(); -#endif - -#ifdef STOREKIT_ENABLED -	store_kit = memnew(InAppStore); -	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit)); -#endif - -#ifdef ICLOUD_ENABLED -	icloud = memnew(ICloud); -	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud)); -	//icloud->connect(); -#endif -	ios = memnew(iOS); -	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); - -	return OK; -}; - -MainLoop *OSIPhone::get_main_loop() const { -	return main_loop; -}; - -void OSIPhone::set_main_loop(MainLoop *p_main_loop) { -	main_loop = p_main_loop; - -	if (main_loop) { -		input->set_main_loop(p_main_loop); -		main_loop->init(); -	} -}; - -bool OSIPhone::iterate() { -	if (!main_loop) -		return true; - -	if (main_loop) { -		for (int i = 0; i < event_count; i++) { -			input->parse_input_event(event_queue[i]); -		}; -	}; -	event_count = 0; - -	return Main::iteration(); -}; - -void OSIPhone::key(uint32_t p_key, bool p_pressed) { -	Ref<InputEventKey> ev; -	ev.instance(); -	ev->set_echo(false); -	ev->set_pressed(p_pressed); -	ev->set_keycode(p_key); -	ev->set_physical_keycode(p_key); -	ev->set_unicode(p_key); -	queue_event(ev); -}; - -void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) { -	if (!GLOBAL_DEF("debug/disable_touch", false)) { -		Ref<InputEventScreenTouch> ev; -		ev.instance(); - -		ev->set_index(p_idx); -		ev->set_pressed(p_pressed); -		ev->set_position(Vector2(p_x, p_y)); -		queue_event(ev); -	}; - -	touch_list.pressed[p_idx] = p_pressed; -}; - -void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { -	if (!GLOBAL_DEF("debug/disable_touch", false)) { -		Ref<InputEventScreenDrag> ev; -		ev.instance(); -		ev->set_index(p_idx); -		ev->set_position(Vector2(p_x, p_y)); -		ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); -		queue_event(ev); -	}; -}; - -void OSIPhone::queue_event(const Ref<InputEvent> &p_event) { -	ERR_FAIL_INDEX(event_count, MAX_EVENTS); - -	event_queue[event_count++] = p_event; -}; - -void OSIPhone::touches_cancelled() { -	for (int i = 0; i < MAX_MOUSE_COUNT; i++) { -		if (touch_list.pressed[i]) { -			// send a mouse_up outside the screen -			touch_press(i, -1, -1, false, false); -		}; -	}; -}; - -static const float ACCEL_RANGE = 1; - -void OSIPhone::update_gravity(float p_x, float p_y, float p_z) { -	input->set_gravity(Vector3(p_x, p_y, p_z)); -}; - -void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) { -	// Found out the Z should not be negated! Pass as is! -	input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE)); - -	/* -	if (p_x != last_accel.x) { -		//printf("updating accel x %f\n", p_x); -		InputEvent ev; -		ev.type = InputEvent::JOYPAD_MOTION; -		ev.device = 0; -		ev.joy_motion.axis = JOY_ANALOG_0; -		ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE); -		last_accel.x = p_x; -		queue_event(ev); -	}; -	if (p_y != last_accel.y) { -		//printf("updating accel y %f\n", p_y); -		InputEvent ev; -		ev.type = InputEvent::JOYPAD_MOTION; -		ev.device = 0; -		ev.joy_motion.axis = JOY_ANALOG_1; -		ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE); -		last_accel.y = p_y; -		queue_event(ev); -	}; -	if (p_z != last_accel.z) { -		//printf("updating accel z %f\n", p_z); -		InputEvent ev; -		ev.type = InputEvent::JOYPAD_MOTION; -		ev.device = 0; -		ev.joy_motion.axis = JOY_ANALOG_2; -		ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE); -		last_accel.z = p_z; -		queue_event(ev); -	}; -	*/ -}; - -void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) { -	input->set_magnetometer(Vector3(p_x, p_y, p_z)); -}; - -void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) { -	input->set_gyroscope(Vector3(p_x, p_y, p_z)); -}; - -int OSIPhone::get_unused_joy_id() { -	return input->get_unused_joy_id(); -}; - -void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) { -	input->joy_connection_changed(p_idx, p_connected, p_name); -}; - -void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) { -	input->joy_button(p_device, p_button, p_pressed); -}; - -void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) { -	input->joy_axis(p_device, p_axis, p_value); -}; - -void OSIPhone::delete_main_loop() { -	if (main_loop) { -		main_loop->finish(); -		memdelete(main_loop); -	}; - -	main_loop = nullptr; -}; - -void OSIPhone::finalize() { -	delete_main_loop(); - -	memdelete(input); -	memdelete(ios); - -#ifdef GAME_CENTER_ENABLED -	memdelete(game_center); -#endif - -#ifdef STOREKIT_ENABLED -	memdelete(store_kit); -#endif - -#ifdef ICLOUD_ENABLED -	memdelete(icloud); -#endif - -	rendering_server->finish(); -	memdelete(rendering_server); -	//	memdelete(rasterizer); - -	// Free unhandled events before close -	for (int i = 0; i < MAX_EVENTS; i++) { -		event_queue[i].unref(); -	}; -	event_count = 0; -}; - -void OSIPhone::set_mouse_show(bool p_show) {} -void OSIPhone::set_mouse_grab(bool p_grab) {} - -bool OSIPhone::is_mouse_grab_enabled() const { -	return true; -}; - -Point2 OSIPhone::get_mouse_position() const { -	return Point2(); -}; - -int OSIPhone::get_mouse_button_state() const { -	return 0; -}; - -void OSIPhone::set_window_title(const String &p_title) {} - -void OSIPhone::alert(const String &p_alert, const String &p_title) { -	const CharString utf8_alert = p_alert.utf8(); -	const CharString utf8_title = p_title.utf8(); -	iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); -} - -Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { -	if (p_path.length() == 0) { -		p_library_handle = RTLD_SELF; -		return OK; -	} -	return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); -} - -Error OSIPhone::close_dynamic_library(void *p_library_handle) { -	if (p_library_handle == RTLD_SELF) { -		return OK; -	} -	return OS_Unix::close_dynamic_library(p_library_handle); -} - -HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table; -void register_dynamic_symbol(char *name, void *address) { -	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address; -} - -Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { -	if (p_library_handle == RTLD_SELF) { -		void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name); -		if (ptr) { -			p_symbol_handle = *ptr; -			return OK; -		} -	} -	return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); -} - -void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -	video_mode = p_video_mode; -}; - -OS::VideoMode OSIPhone::get_video_mode(int p_screen) const { -	return video_mode; -}; - -void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { -	p_list->push_back(video_mode); -}; - -bool OSIPhone::can_draw() const { -	if (native_video_is_playing()) -		return false; -	return true; -}; - -int OSIPhone::set_base_framebuffer(int p_fb) { -#if defined(OPENGL_ENABLED) -	// gl_view_base_fb has not been updated yet -	RasterizerStorageGLES2::system_fbo = p_fb; -#endif - -	return 0; -}; - -bool OSIPhone::has_virtual_keyboard() const { -	return true; -}; - -extern void _show_keyboard(String p_existing); -extern void _hide_keyboard(); -extern Error _shell_open(String p_uri); -extern void _set_keep_screen_on(bool p_enabled); -extern void _vibrate(); - -void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { -	_show_keyboard(p_existing_text); -}; - -void OSIPhone::hide_virtual_keyboard() { -	_hide_keyboard(); -}; - -void OSIPhone::set_virtual_keyboard_height(int p_height) { -	virtual_keyboard_height = p_height; -} - -int OSIPhone::get_virtual_keyboard_height() const { -	return virtual_keyboard_height; -} - -Error OSIPhone::shell_open(String p_uri) { -	return _shell_open(p_uri); -}; - -void OSIPhone::set_keep_screen_on(bool p_enabled) { -	OS::set_keep_screen_on(p_enabled); -	_set_keep_screen_on(p_enabled); -}; - -String OSIPhone::get_user_data_dir() const { -	return data_dir; -}; - -String OSIPhone::get_name() const { -	return "iOS"; -}; - -String OSIPhone::get_model_name() const { -	String model = ios->get_model(); -	if (model != "") -		return model; - -	return OS_Unix::get_model_name(); -} - -Size2 OSIPhone::get_window_size() const { -	return Vector2(video_mode.width, video_mode.height); -} - -extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height); - -Rect2 OSIPhone::get_window_safe_area() const { -	return _get_ios_window_safe_area(video_mode.width, video_mode.height); -} - -bool OSIPhone::has_touchscreen_ui_hint() const { -	return true; -} - -void OSIPhone::set_locale(String p_locale) { -	locale_code = p_locale; -} - -String OSIPhone::get_locale() const { -	return locale_code; -} - -extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); -extern bool _is_video_playing(); -extern void _pause_video(); -extern void _unpause_video(); -extern void _stop_video(); -extern void _focus_out_video(); - -Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { -	FileAccess *f = FileAccess::open(p_path, FileAccess::READ); -	bool exists = f && f->is_open(); - -	String tempFile = get_user_data_dir(); -	if (!exists) -		return FAILED; - -	if (p_path.begins_with("res://")) { -		if (PackedData::get_singleton()->has_path(p_path)) { -			print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str()); -			return ERR_INVALID_PARAMETER; -		} else { -			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path()); -		} -	} else if (p_path.begins_with("user://")) -		p_path = p_path.replace("user:/", get_user_data_dir()); - -	memdelete(f); - -	print("Playing video: %S\n", p_path.c_str()); -	if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track)) -		return OK; -	return FAILED; -} - -bool OSIPhone::native_video_is_playing() const { -	return _is_video_playing(); -} - -void OSIPhone::native_video_pause() { -	if (native_video_is_playing()) -		_pause_video(); -} - -void OSIPhone::native_video_unpause() { -	_unpause_video(); -}; - -void OSIPhone::native_video_focus_out() { -	_focus_out_video(); -}; - -void OSIPhone::native_video_stop() { -	if (native_video_is_playing()) -		_stop_video(); -} - -void OSIPhone::vibrate_handheld(int p_duration_ms) { -	// iOS does not support duration for vibration -	_vibrate(); -} - -bool OSIPhone::_check_internal_feature_support(const String &p_feature) { -	return p_feature == "mobile"; -} - -// Initialization order between compilation units is not guaranteed, -// so we use this as a hack to ensure certain code is called before -// everything else, but after all units are initialized. -typedef void (*init_callback)(); -static init_callback *ios_init_callbacks = nullptr; -static int ios_init_callbacks_count = 0; -static int ios_init_callbacks_capacity = 0; - -void add_ios_init_callback(init_callback cb) { -	if (ios_init_callbacks_count == ios_init_callbacks_capacity) { -		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32); -		if (new_ptr) { -			ios_init_callbacks = (init_callback *)(new_ptr); -			ios_init_callbacks_capacity += 32; -		} -	} -	if (ios_init_callbacks_capacity > ios_init_callbacks_count) { -		ios_init_callbacks[ios_init_callbacks_count] = cb; -		++ios_init_callbacks_count; -	} -} - -OSIPhone::OSIPhone(int width, int height, String p_data_dir) { -	for (int i = 0; i < ios_init_callbacks_count; ++i) { -		ios_init_callbacks[i](); -	} -	free(ios_init_callbacks); -	ios_init_callbacks = nullptr; -	ios_init_callbacks_count = 0; -	ios_init_callbacks_capacity = 0; - -	main_loop = nullptr; -	rendering_server = nullptr; - -	VideoMode vm; -	vm.fullscreen = true; -	vm.width = width; -	vm.height = height; -	vm.resizable = false; -	set_video_mode(vm); -	event_count = 0; -	virtual_keyboard_height = 0; - -	// can't call set_data_dir from here, since it requires DirAccess -	// which is initialized in initialize_core -	data_dir = p_data_dir; - -	Vector<Logger *> loggers; -	loggers.push_back(memnew(SyslogLogger)); -#ifdef DEBUG_ENABLED -	// it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode -	loggers.push_back(memnew(StdLogger)); -#endif -	_set_logger(memnew(CompositeLogger(loggers))); - -	AudioDriverManager::add_driver(&audio_driver); -}; - -OSIPhone::~OSIPhone() { -} - -#endif diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 955eb15d57..f3bde46717 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -33,16 +33,15 @@  #ifndef OS_IPHONE_H  #define OS_IPHONE_H -#include "core/input/input.h"  #include "drivers/coreaudio/audio_driver_coreaudio.h"  #include "drivers/unix/os_unix.h"  #include "game_center.h"  #include "icloud.h"  #include "in_app_store.h"  #include "ios.h" +#include "joypad_iphone.h"  #include "servers/audio_server.h"  #include "servers/rendering/rasterizer.h" -#include "servers/rendering_server.h"  #if defined(VULKAN_ENABLED)  #include "drivers/vulkan/rendering_device_vulkan.h" @@ -51,16 +50,9 @@  class OSIPhone : public OS_Unix {  private: -	enum { -		MAX_MOUSE_COUNT = 8, -		MAX_EVENTS = 64, -	}; -  	static HashMap<String, void *> dynamic_symbol_lookup_table;  	friend void register_dynamic_symbol(char *name, void *address); -	RenderingServer *rendering_server; -  	AudioDriverCoreAudio audio_driver;  #ifdef GAME_CENTER_ENABLED @@ -74,139 +66,72 @@ private:  #endif  	iOS *ios; -	MainLoop *main_loop; - -#if defined(VULKAN_ENABLED) -	VulkanContextIPhone *context_vulkan; -	RenderingDeviceVulkan *rendering_device_vulkan; -#endif -	VideoMode video_mode; - -	virtual int get_video_driver_count() const; -	virtual const char *get_video_driver_name(int p_driver) const; +	JoypadIPhone *joypad_iphone; -	virtual int get_current_video_driver() const; - -	virtual void initialize_core(); -	virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); - -	virtual void set_main_loop(MainLoop *p_main_loop); -	virtual MainLoop *get_main_loop() const; - -	virtual void delete_main_loop(); +	MainLoop *main_loop; -	virtual void finalize(); +	virtual void initialize_core() override; +	virtual void initialize() override; -	struct MouseList { -		bool pressed[MAX_MOUSE_COUNT]; -		MouseList() { -			for (int i = 0; i < MAX_MOUSE_COUNT; i++) -				pressed[i] = false; -		}; -	}; +	virtual void initialize_joypads() override { +	} -	MouseList touch_list; +	virtual void set_main_loop(MainLoop *p_main_loop) override; +	virtual MainLoop *get_main_loop() const override; -	Vector3 last_accel; +	virtual void delete_main_loop() override; -	Ref<InputEvent> event_queue[MAX_EVENTS]; -	int event_count; -	void queue_event(const Ref<InputEvent> &p_event); +	virtual void finalize() override; -	String data_dir; +	String user_data_dir;  	String unique_id;  	String locale_code; -	InputDefault *input; +	bool is_focused = false; -	int virtual_keyboard_height; - -	int video_driver_index; +	void deinitialize_modules();  public: -	bool iterate(); - -	uint8_t get_orientations() const; - -	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); -	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y); -	void touches_cancelled(); -	void key(uint32_t p_key, bool p_pressed); -	void set_virtual_keyboard_height(int p_height); - -	int set_base_framebuffer(int p_fb); - -	void update_gravity(float p_x, float p_y, float p_z); -	void update_accelerometer(float p_x, float p_y, float p_z); -	void update_magnetometer(float p_x, float p_y, float p_z); -	void update_gyroscope(float p_x, float p_y, float p_z); - -	int get_unused_joy_id(); -	void joy_connection_changed(int p_idx, bool p_connected, String p_name); -	void joy_button(int p_device, int p_button, bool p_pressed); -	void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value); -  	static OSIPhone *get_singleton(); -	virtual void set_mouse_show(bool p_show); -	virtual void set_mouse_grab(bool p_grab); -	virtual bool is_mouse_grab_enabled() const; -	virtual Point2 get_mouse_position() const; -	virtual int get_mouse_button_state() const; -	virtual void set_window_title(const String &p_title); - -	virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); +	OSIPhone(String p_data_dir); +	~OSIPhone(); -	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); -	virtual Error close_dynamic_library(void *p_library_handle); -	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false); +	void initialize_modules(); -	virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); -	virtual VideoMode get_video_mode(int p_screen = 0) const; -	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; +	bool iterate(); -	virtual void set_keep_screen_on(bool p_enabled); +	void start(); -	virtual bool can_draw() const; +	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; +	virtual Error close_dynamic_library(void *p_library_handle) override; +	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override; -	virtual bool has_virtual_keyboard() const; -	virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); -	virtual void hide_virtual_keyboard(); -	virtual int get_virtual_keyboard_height() const; +	virtual void alert(const String &p_alert, +			const String &p_title = "ALERT!") override; -	virtual Size2 get_window_size() const; -	virtual Rect2 get_window_safe_area() const; +	virtual String get_name() const override; +	virtual String get_model_name() const override; -	virtual bool has_touchscreen_ui_hint() const; +	virtual Error shell_open(String p_uri) override; -	void set_data_dir(String p_dir); +	void set_user_data_dir(String p_dir); +	virtual String get_user_data_dir() const override; -	virtual String get_name() const; -	virtual String get_model_name() const; +	void set_locale(String p_locale); +	virtual String get_locale() const override; -	Error shell_open(String p_uri); +	void set_unique_id(String p_id); +	virtual String get_unique_id() const override; -	String get_user_data_dir() const; +	virtual void vibrate_handheld(int p_duration_ms = 500) override; -	void set_locale(String p_locale); -	String get_locale() const; +	virtual bool _check_internal_feature_support(const String &p_feature) override; -	void set_unique_id(String p_id); -	String get_unique_id() const; - -	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); -	virtual bool native_video_is_playing() const; -	virtual void native_video_pause(); -	virtual void native_video_unpause(); -	virtual void native_video_focus_out(); -	virtual void native_video_stop(); -	virtual void vibrate_handheld(int p_duration_ms = 500); - -	virtual bool _check_internal_feature_support(const String &p_feature); -	OSIPhone(int width, int height, String p_data_dir); -	~OSIPhone(); +	void on_focus_out(); +	void on_focus_in();  };  #endif // OS_IPHONE_H -#endif +#endif // IPHONE_ENABLED diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm new file mode 100644 index 0000000000..f0bbbd39ca --- /dev/null +++ b/platform/iphone/os_iphone.mm @@ -0,0 +1,369 @@ +/*************************************************************************/ +/*  os_iphone.mm                                                         */ +/*************************************************************************/ +/*                       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.                */ +/*************************************************************************/ + +#ifdef IPHONE_ENABLED + +#include "os_iphone.h" +#import "app_delegate.h" +#include "core/io/file_access_pack.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/project_settings.h" +#include "display_server_iphone.h" +#include "drivers/unix/syslog_logger.h" +#import "godot_view.h" +#include "main/main.h" +#import "view_controller.h" + +#import <AudioToolbox/AudioServices.h> +#import <UIKit/UIKit.h> +#import <dlfcn.h> + +#if defined(OPENGL_ENABLED) +#include "drivers/gles2/rasterizer_gles2.h" +#endif + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#import <QuartzCore/CAMetalLayer.h> +#include <vulkan/vulkan_metal.h> +#endif + +// Initialization order between compilation units is not guaranteed, +// so we use this as a hack to ensure certain code is called before +// everything else, but after all units are initialized. +typedef void (*init_callback)(); +static init_callback *ios_init_callbacks = nullptr; +static int ios_init_callbacks_count = 0; +static int ios_init_callbacks_capacity = 0; +HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table; + +void add_ios_init_callback(init_callback cb) { +	if (ios_init_callbacks_count == ios_init_callbacks_capacity) { +		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32); +		if (new_ptr) { +			ios_init_callbacks = (init_callback *)(new_ptr); +			ios_init_callbacks_capacity += 32; +		} +	} +	if (ios_init_callbacks_capacity > ios_init_callbacks_count) { +		ios_init_callbacks[ios_init_callbacks_count] = cb; +		++ios_init_callbacks_count; +	} +} + +void register_dynamic_symbol(char *name, void *address) { +	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address; +} + +OSIPhone *OSIPhone::get_singleton() { +	return (OSIPhone *)OS::get_singleton(); +} + +OSIPhone::OSIPhone(String p_data_dir) { +	for (int i = 0; i < ios_init_callbacks_count; ++i) { +		ios_init_callbacks[i](); +	} +	free(ios_init_callbacks); +	ios_init_callbacks = nullptr; +	ios_init_callbacks_count = 0; +	ios_init_callbacks_capacity = 0; + +	main_loop = nullptr; + +	// can't call set_data_dir from here, since it requires DirAccess +	// which is initialized in initialize_core +	user_data_dir = p_data_dir; + +	Vector<Logger *> loggers; +	loggers.push_back(memnew(SyslogLogger)); +#ifdef DEBUG_ENABLED +	// it seems iOS app's stdout/stderr is only obtainable if you launch it from +	// Xcode +	loggers.push_back(memnew(StdLogger)); +#endif +	_set_logger(memnew(CompositeLogger(loggers))); + +	AudioDriverManager::add_driver(&audio_driver); + +	DisplayServerIPhone::register_iphone_driver(); +} + +OSIPhone::~OSIPhone() {} + +void OSIPhone::initialize_core() { +	OS_Unix::initialize_core(); + +	set_user_data_dir(user_data_dir); +} + +void OSIPhone::initialize() { +	initialize_core(); +} + +void OSIPhone::initialize_modules() { +#ifdef GAME_CENTER_ENABLED +	game_center = memnew(GameCenter); +	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center)); +	game_center->connect(); +#endif + +#ifdef STOREKIT_ENABLED +	store_kit = memnew(InAppStore); +	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit)); +#endif + +#ifdef ICLOUD_ENABLED +	icloud = memnew(ICloud); +	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud)); +#endif + +	ios = memnew(iOS); +	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); + +	joypad_iphone = memnew(JoypadIPhone); +} + +void OSIPhone::deinitialize_modules() { +	if (joypad_iphone) { +		memdelete(joypad_iphone); +	} + +	if (ios) { +		memdelete(ios); +	} + +#ifdef GAME_CENTER_ENABLED +	if (game_center) { +		memdelete(game_center); +	} +#endif + +#ifdef STOREKIT_ENABLED +	if (store_kit) { +		memdelete(store_kit); +	} +#endif + +#ifdef ICLOUD_ENABLED +	if (icloud) { +		memdelete(icloud); +	} +#endif +} + +void OSIPhone::set_main_loop(MainLoop *p_main_loop) { +	main_loop = p_main_loop; + +	if (main_loop) { +		main_loop->init(); +	} +} + +MainLoop *OSIPhone::get_main_loop() const { +	return main_loop; +} + +void OSIPhone::delete_main_loop() { +	if (main_loop) { +		main_loop->finish(); +		memdelete(main_loop); +	}; + +	main_loop = nullptr; +} + +bool OSIPhone::iterate() { +	if (!main_loop) { +		return true; +	} + +	if (DisplayServer::get_singleton()) { +		DisplayServer::get_singleton()->process_events(); +	} + +	return Main::iteration(); +} + +void OSIPhone::start() { +	Main::start(); + +	if (joypad_iphone) { +		joypad_iphone->start_processing(); +	} +} + +void OSIPhone::finalize() { +	deinitialize_modules(); + +	// Already gets called +	//    delete_main_loop(); +} + +// MARK: Dynamic Libraries + +Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { +	if (p_path.length() == 0) { +		p_library_handle = RTLD_SELF; +		return OK; +	} +	return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); +} + +Error OSIPhone::close_dynamic_library(void *p_library_handle) { +	if (p_library_handle == RTLD_SELF) { +		return OK; +	} +	return OS_Unix::close_dynamic_library(p_library_handle); +} + +Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { +	if (p_library_handle == RTLD_SELF) { +		void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name); +		if (ptr) { +			p_symbol_handle = *ptr; +			return OK; +		} +	} +	return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); +} + +void OSIPhone::alert(const String &p_alert, const String &p_title) { +	const CharString utf8_alert = p_alert.utf8(); +	const CharString utf8_title = p_title.utf8(); +	iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +String OSIPhone::get_name() const { +	return "iOS"; +}; + +String OSIPhone::get_model_name() const { +	String model = ios->get_model(); +	if (model != "") +		return model; + +	return OS_Unix::get_model_name(); +} + +Error OSIPhone::shell_open(String p_uri) { +	NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; +	NSURL *url = [NSURL URLWithString:urlPath]; +	[urlPath release]; + +	if (![[UIApplication sharedApplication] canOpenURL:url]) { +		return ERR_CANT_OPEN; +	} + +	printf("opening url %ls\n", p_uri.c_str()); + +	//    if (@available(iOS 10, *)) { +	[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; +	//    } else { +	//        [[UIApplication sharedApplication] openURL:url]; +	//    } + +	return OK; +}; + +void OSIPhone::set_user_data_dir(String p_dir) { +	DirAccess *da = DirAccess::open(p_dir); + +	user_data_dir = da->get_current_dir(); +	printf("setting data dir to %ls from %ls\n", user_data_dir.c_str(), p_dir.c_str()); +	memdelete(da); +} + +String OSIPhone::get_user_data_dir() const { +	return user_data_dir; +} + +void OSIPhone::set_locale(String p_locale) { +	locale_code = p_locale; +} + +String OSIPhone::get_locale() const { +	return locale_code; +} + +void OSIPhone::set_unique_id(String p_id) { +	unique_id = p_id; +} + +String OSIPhone::get_unique_id() const { +	return unique_id; +} + +void OSIPhone::vibrate_handheld(int p_duration_ms) { +	// iOS does not support duration for vibration +	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); +} + +bool OSIPhone::_check_internal_feature_support(const String &p_feature) { +	return p_feature == "mobile"; +} + +void OSIPhone::on_focus_out() { +	if (is_focused) { +		is_focused = false; + +		if (DisplayServerIPhone::get_singleton()) { +			DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); +		} + +		[AppDelegate.viewController.godotView stopRendering]; + +		if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { +			DisplayServerIPhone::get_singleton()->native_video_pause(); +		} + +		audio_driver.stop(); +	} +} + +void OSIPhone::on_focus_in() { +	if (!is_focused) { +		is_focused = true; + +		if (DisplayServerIPhone::get_singleton()) { +			DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); +		} + +		[AppDelegate.viewController.godotView startRendering]; + +		if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { +			DisplayServerIPhone::get_singleton()->native_video_unpause(); +		} + +		audio_driver.start(); +	} +} + +#endif // IPHONE_ENABLED diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index f6bbe11d97..dffdc01d4a 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -31,20 +31,18 @@  #import <GameKit/GameKit.h>  #import <UIKit/UIKit.h> -@interface ViewController : UIViewController <GKGameCenterControllerDelegate> { -}; +@class GodotView; -- (BOOL)shouldAutorotateToInterfaceOrientation: -		(UIInterfaceOrientation)p_orientation; +@interface ViewController : UIViewController <GKGameCenterControllerDelegate> -- (void)didReceiveMemoryWarning; +- (GodotView *)godotView; -- (void)viewDidLoad; +// MARK: Native Video Player -- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures; - -- (BOOL)prefersStatusBarHidden; - -- (BOOL)prefersHomeIndicatorAutoHidden; +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; +- (BOOL)isVideoPlaying; +- (void)pauseVideo; +- (void)unpauseVideo; +- (void)stopVideo;  @end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 279bcc1226..31597f7856 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -29,96 +29,174 @@  /*************************************************************************/  #import "view_controller.h" - +#include "core/project_settings.h" +#include "display_server_iphone.h" +#import "godot_view.h" +#import "godot_view_renderer.h"  #include "os_iphone.h" -#include "core/project_settings.h" +#import <GameController/GameController.h> -extern "C" { +@interface ViewController () -int add_path(int, char **); -int add_cmdline(int, char **); +@property(strong, nonatomic) GodotViewRenderer *renderer; -int add_path(int p_argc, char **p_args) { -	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; -	if (!str) -		return p_argc; +// TODO: separate view to handle video +// AVPlayer-related properties +@property(strong, nonatomic) AVAsset *avAsset; +@property(strong, nonatomic) AVPlayerItem *avPlayerItem; +@property(strong, nonatomic) AVPlayer *avPlayer; +@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; +@property(assign, nonatomic) CMTime videoCurrentTime; +@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; +@property(assign, nonatomic) BOOL videoHasFoundError; -	p_args[p_argc++] = "--path"; -	[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo -	p_args[p_argc++] = (char *)[str cString]; -	p_args[p_argc] = NULL; +@end -	return p_argc; -}; +@implementation ViewController -int add_cmdline(int p_argc, char **p_args) { -	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; -	if (!arr) -		return p_argc; +- (GodotView *)godotView { +	return (GodotView *)self.view; +} -	for (int i = 0; i < [arr count]; i++) { -		NSString *str = [arr objectAtIndex:i]; -		if (!str) -			continue; -		[str retain]; // @todo delete these at some point -		p_args[p_argc++] = (char *)[str cString]; -	}; +- (void)loadView { +	GodotView *view = [[GodotView alloc] init]; +	GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init]; -	p_args[p_argc] = NULL; +	self.renderer = renderer; +	self.view = view; -	return p_argc; -}; -}; // extern "C" +	view.renderer = self.renderer; -@interface ViewController () +	[renderer release]; +	[view release]; +} -@end +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { +	self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; -@implementation ViewController +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { +	self = [super initWithCoder:coder]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (void)godot_commonInit { +	self.isVideoCurrentlyPlaying = NO; +	self.videoCurrentTime = kCMTimeZero; +	self.videoHasFoundError = false; +}  - (void)didReceiveMemoryWarning { +	[super didReceiveMemoryWarning];  	printf("*********** did receive memory warning!\n"); -}; +}  - (void)viewDidLoad {  	[super viewDidLoad]; +	[self observeKeyboard]; +	[self observeAudio]; +  	if (@available(iOS 11.0, *)) {  		[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];  	}  } +- (void)observeKeyboard { +	printf("******** adding observer for keyboard show/hide\n"); +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(keyboardOnScreen:) +				   name:UIKeyboardDidShowNotification +				 object:nil]; +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(keyboardHidden:) +				   name:UIKeyboardDidHideNotification +				 object:nil]; +} + +- (void)observeAudio { +	printf("******** adding observer for sound routing changes\n"); +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(audioRouteChangeListenerCallback:) +				   name:AVAudioSessionRouteChangeNotification +				 object:nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { +	if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { +		[self handleVideoOrPlayerStatus]; +	} + +	if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { +		[self handleVideoPlayRate]; +	} +} + +- (void)dealloc { +	[self stopVideo]; + +	self.renderer = nil; + +	[[NSNotificationCenter defaultCenter] removeObserver:self]; + +	[super dealloc]; +} + +// MARK: Orientation +  - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {  	return UIRectEdgeAll;  }  - (BOOL)shouldAutorotate { -	switch (OS::get_singleton()->get_screen_orientation()) { -		case OS::SCREEN_SENSOR: -		case OS::SCREEN_SENSOR_LANDSCAPE: -		case OS::SCREEN_SENSOR_PORTRAIT: +	if (!DisplayServerIPhone::get_singleton()) { +		return NO; +	} + +	switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { +		case DisplayServer::SCREEN_SENSOR: +		case DisplayServer::SCREEN_SENSOR_LANDSCAPE: +		case DisplayServer::SCREEN_SENSOR_PORTRAIT:  			return YES;  		default:  			return NO;  	} -}; +}  - (UIInterfaceOrientationMask)supportedInterfaceOrientations { -	switch (OS::get_singleton()->get_screen_orientation()) { -		case OS::SCREEN_PORTRAIT: +	if (!DisplayServerIPhone::get_singleton()) { +		return UIInterfaceOrientationMaskAll; +	} + +	switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { +		case DisplayServer::SCREEN_PORTRAIT:  			return UIInterfaceOrientationMaskPortrait; -		case OS::SCREEN_REVERSE_LANDSCAPE: +		case DisplayServer::SCREEN_REVERSE_LANDSCAPE:  			return UIInterfaceOrientationMaskLandscapeRight; -		case OS::SCREEN_REVERSE_PORTRAIT: +		case DisplayServer::SCREEN_REVERSE_PORTRAIT:  			return UIInterfaceOrientationMaskPortraitUpsideDown; -		case OS::SCREEN_SENSOR_LANDSCAPE: +		case DisplayServer::SCREEN_SENSOR_LANDSCAPE:  			return UIInterfaceOrientationMaskLandscape; -		case OS::SCREEN_SENSOR_PORTRAIT: +		case DisplayServer::SCREEN_SENSOR_PORTRAIT:  			return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; -		case OS::SCREEN_SENSOR: +		case DisplayServer::SCREEN_SENSOR:  			return UIInterfaceOrientationMaskAll; -		case OS::SCREEN_LANDSCAPE: +		case DisplayServer::SCREEN_LANDSCAPE:  			return UIInterfaceOrientationMaskLandscapeLeft;  	}  }; @@ -135,6 +213,190 @@ int add_cmdline(int p_argc, char **p_args) {  	}  } +// MARK: Keyboard + +- (void)keyboardOnScreen:(NSNotification *)notification { +	NSDictionary *info = notification.userInfo; +	NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; + +	CGRect rawFrame = [value CGRectValue]; +	CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil]; + +	if (DisplayServerIPhone::get_singleton()) { +		DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height); +	} +} + +- (void)keyboardHidden:(NSNotification *)notification { +	if (DisplayServerIPhone::get_singleton()) { +		DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0); +	} +} + +// MARK: Audio + +- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { +	printf("*********** route changed!\n"); +	NSDictionary *interuptionDict = notification.userInfo; + +	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; + +	switch (routeChangeReason) { +		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { +			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); +			NSLog(@"Headphone/Line plugged in"); +		} break; +		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { +			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); +			NSLog(@"Headphone/Line was pulled. Resuming video play...."); +			if ([self isVideoPlaying]) { +				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ +					[self.avPlayer play]; // NOTE: change this line according your current player implementation +					NSLog(@"resumed play"); +				}); +			} +		} break; +		case AVAudioSessionRouteChangeReasonCategoryChange: { +			// called at start - also when other audio wants to play +			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); +		} break; +	} +} + +// MARK: Native Video Player + +- (void)handleVideoOrPlayerStatus { +	if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { +		[self stopVideo]; +		self.videoHasFoundError = true; +	} + +	if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { +		//        NSLog(@"time: %@", self.video_current_time); +		[self.avPlayer seekToTime:self.videoCurrentTime]; +		self.videoCurrentTime = kCMTimeZero; +	} +} + +- (void)handleVideoPlayRate { +	NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); +	if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { +		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ +			[self.avPlayer play]; // NOTE: change this line according your current player implementation +			NSLog(@"resumed play"); +		}); + +		NSLog(@" . . . PAUSED (or just started)"); +	} +} + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { +	self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; + +	self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; +	[self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; + +	self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; +	self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; + +	[self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(playerItemDidReachEnd:) +				   name:AVPlayerItemDidPlayToEndTimeNotification +				 object:[self.avPlayer currentItem]]; + +	[self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; + +	[self.avPlayerLayer setFrame:self.view.bounds]; +	[self.view.layer addSublayer:self.avPlayerLayer]; +	[self.avPlayer play]; + +	AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + +	NSMutableArray *allAudioParams = [NSMutableArray array]; +	for (id track in audioGroup.options) { +		NSString *language = [[track locale] localeIdentifier]; +		NSLog(@"subtitle lang: %@", language); + +		if ([language isEqualToString:audioTrack]) { +			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; +			[audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; +			[audioInputParams setTrackID:[track trackID]]; +			[allAudioParams addObject:audioInputParams]; + +			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; +			[audioMix setInputParameters:allAudioParams]; + +			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; +			[self.avPlayer.currentItem setAudioMix:audioMix]; + +			break; +		} +	} + +	AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; +	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; + +	for (id track in useableTracks) { +		NSString *language = [[track locale] localeIdentifier]; +		NSLog(@"subtitle lang: %@", language); + +		if ([language isEqualToString:subtitleTrack]) { +			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; +			break; +		} +	} + +	self.isVideoCurrentlyPlaying = YES; + +	return true; +} + +- (BOOL)isVideoPlaying { +	if (self.avPlayer.error) { +		printf("Error during playback\n"); +	} +	return (self.avPlayer.rate > 0 && !self.avPlayer.error); +} + +- (void)pauseVideo { +	self.videoCurrentTime = self.avPlayer.currentTime; +	[self.avPlayer pause]; +	self.isVideoCurrentlyPlaying = NO; +} + +- (void)unpauseVideo { +	[self.avPlayer play]; +	self.isVideoCurrentlyPlaying = YES; +} + +- (void)playerItemDidReachEnd:(NSNotification *)notification { +	[self stopVideo]; +} + +- (void)stopVideo { +	[self.avPlayer pause]; +	[self.avPlayerLayer removeFromSuperlayer]; +	self.avPlayerLayer = nil; + +	if (self.avPlayerItem) { +		[self.avPlayerItem removeObserver:self forKeyPath:@"status"]; +		self.avPlayerItem = nil; +	} + +	if (self.avPlayer) { +		[self.avPlayer removeObserver:self forKeyPath:@"status"]; +		self.avPlayer = nil; +	} + +	self.avAsset = nil; + +	self.isVideoCurrentlyPlaying = NO; +} + +// MARK: Delegates +  #ifdef GAME_CENTER_ENABLED  - (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {  	//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h index cadd701636..5c3d5fe33e 100644 --- a/platform/iphone/vulkan_context_iphone.h +++ b/platform/iphone/vulkan_context_iphone.h @@ -32,13 +32,14 @@  #define VULKAN_CONTEXT_IPHONE_H  #include "drivers/vulkan/vulkan_context.h" -// #import <UIKit/UIKit.h> + +#import <UIKit/UIKit.h>  class VulkanContextIPhone : public VulkanContext {  	virtual const char *_get_platform_surface_extension() const;  public: -	int window_create(void *p_window, int p_width, int p_height); +	Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);  	VulkanContextIPhone();  	~VulkanContextIPhone(); diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index 44c940dc3a..cb4dbe7f85 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -35,21 +35,23 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {  	return VK_MVK_IOS_SURFACE_EXTENSION_NAME;  } -int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) { +Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, +		CALayer *p_metal_layer, int p_width, +		int p_height) {  	VkIOSSurfaceCreateInfoMVK createInfo; -	createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; +	createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;  	createInfo.pNext = NULL;  	createInfo.flags = 0; -	createInfo.pView = p_window; +	createInfo.pView = p_metal_layer;  	VkSurfaceKHR surface; -	VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); -	ERR_FAIL_COND_V(err, -1); -	return _window_create(surface, p_width, p_height); -} +	VkResult err = +			vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); +	ERR_FAIL_COND_V(err, ERR_CANT_CREATE); -VulkanContextIPhone::VulkanContextIPhone() { +	return _window_create(p_window_id, surface, p_width, p_height);  } -VulkanContextIPhone::~VulkanContextIPhone() { -} +VulkanContextIPhone::VulkanContextIPhone() {} + +VulkanContextIPhone::~VulkanContextIPhone() {} diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 99672745e7..f697887f08 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -131,6 +131,10 @@ int main(int argc, char *argv[]) {  	/* clang-format on */  	os = new OS_JavaScript(); + +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE +  	Main::setup(argv[0], argc - 1, &argv[1], false);  	emscripten_set_main_loop(main_loop_callback, -1, false);  	emscripten_pause_main_loop(); // Will need to wait for FS sync. diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index 3ed64e9d46..e1796ccefe 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -41,6 +41,9 @@ int main(int argc, char *argv[]) {  	setlocale(LC_CTYPE, ""); +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE +  	char *cwd = (char *)malloc(PATH_MAX);  	ERR_FAIL_COND_V(!cwd, ERR_OUT_OF_MEMORY);  	char *ret = getcwd(cwd, PATH_MAX); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 25d230fc89..d700bcd7f6 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -50,9 +50,11 @@ def configure(env):      if env["target"] == "release":          if env["optimize"] == "speed":  # optimize for speed (default) -            env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize", "-msse2"]) +            env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"])          else:  # optimize for size -            env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize", "-msse2"]) +            env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) +        if env["arch"] != "arm64": +            env.Prepend(CCFLAGS=["-msse2"])          if env["debug_symbols"] == "yes":              env.Prepend(CCFLAGS=["-g1"]) diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 5e209a6e6b..ffe60ad582 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -2793,7 +2793,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo  		} break;  		case WINDOW_FLAG_BORDERLESS: {  			// OrderOut prevents a lose focus bug with the window -			[wd.window_object orderOut:nil]; +			if ([wd.window_object isVisible]) { +				[wd.window_object orderOut:nil]; +			}  			wd.borderless = p_enabled;  			if (p_enabled) {  				[wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; @@ -2807,7 +2809,13 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo  				[wd.window_object setFrame:frameRect display:NO];  			}  			_update_window(wd); -			[wd.window_object makeKeyAndOrderFront:nil]; +			if ([wd.window_object isVisible]) { +				if (wd.no_focus) { +					[wd.window_object orderFront:nil]; +				} else { +					[wd.window_object makeKeyAndOrderFront:nil]; +				} +			}  		} break;  		case WINDOW_FLAG_ALWAYS_ON_TOP: {  			wd.on_top = p_enabled; @@ -2875,7 +2883,11 @@ void DisplayServerOSX::window_move_to_foreground(WindowID p_window) {  	const WindowData &wd = windows[p_window];  	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; -	[wd.window_object makeKeyAndOrderFront:nil]; +	if (wd.no_focus) { +		[wd.window_object orderFront:nil]; +	} else { +		[wd.window_object makeKeyAndOrderFront:nil]; +	}  }  bool DisplayServerOSX::window_can_draw(WindowID p_window) const { diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index 93d0d6168c..4e73d5441c 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -37,7 +37,7 @@  int main(int argc, char **argv) {  #if defined(VULKAN_ENABLED) -	//MoltenVK - enable full component swizzling support +	// MoltenVK - enable full component swizzling support  	setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);  #endif @@ -60,6 +60,9 @@ int main(int argc, char **argv) {  	OS_OSX os;  	Error err; +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE +  	if (os.open_with_filename != "") {  		char *argv_c = (char *)malloc(os.open_with_filename.utf8().size());  		memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size()); diff --git a/platform/server/godot_server.cpp b/platform/server/godot_server.cpp index 32bd943ac3..9f22240a80 100644 --- a/platform/server/godot_server.cpp +++ b/platform/server/godot_server.cpp @@ -34,6 +34,9 @@  int main(int argc, char *argv[]) {  	OS_Server os; +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE +  	Error err = Main::setup(argv[0], argc - 1, &argv[1]);  	if (err != OK)  		return 255; diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 910059a9fc..add559a717 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -146,6 +146,8 @@ int widechar_main(int argc, wchar_t **argv) {  		argv_utf8[i] = wc_to_utf8(argv[i]);  	} +	TEST_MAIN_PARAM_OVERRIDE(argc, argv_utf8) +  	Error err = Main::setup(argv_utf8[0], argc - 1, &argv_utf8[1]);  	if (err != OK) { @@ -186,10 +188,12 @@ int _main() {  	return result;  } -int main(int _argc, char **_argv) { +int main(int argc, char **argv) { +	// override the arguments for the test handler / if symbol is provided +	// TEST_MAIN_OVERRIDE +  	// _argc and _argv are ignored  	// we are going to use the WideChar version of them instead -  #ifdef CRASH_HANDLER_EXCEPTION  	__try {  		return _main(); diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 191110a669..f130726837 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -104,6 +104,7 @@ void BoxContainer::_resort() {  		has_stretched = true;  		bool refit_successful = true; //assume refit-test will go well +		float error = 0; // Keep track of accumulated error in pixels  		for (int i = 0; i < get_child_count(); i++) {  			Control *c = Object::cast_to<Control>(get_child(i)); @@ -119,8 +120,9 @@ void BoxContainer::_resort() {  			if (msc.will_stretch) { //wants to stretch  				//let's see if it can really stretch - -				int final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total; +				float final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total; +				// Add leftover fractional pixels to error accumulator +				error += final_pixel_size - (int)final_pixel_size;  				if (final_pixel_size < msc.min_size) {  					//if available stretching area is too small for widget,  					//then remove it from stretching area @@ -132,6 +134,11 @@ void BoxContainer::_resort() {  					break;  				} else {  					msc.final_size = final_pixel_size; +					// Dump accumulated error if one pixel or more +					if (error >= 1) { +						msc.final_size += 1; +						error -= 1; +					}  				}  			}  		} diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 21eb3d558c..4aea2928f4 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -272,7 +272,7 @@ void FileDialog::_action_pressed() {  		}  		if (dir_access->file_exists(f)) { -			confirm_save->set_text(RTR("File Exists, Overwrite?")); +			confirm_save->set_text(RTR("File exists, overwrite?"));  			confirm_save->popup_centered(Size2(200, 80));  		} else {  			emit_signal("file_selected", f); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 4db6ca2949..0e9ef71892 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -522,7 +522,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {  			drag_node_accum = Vector2();  			last_drag_node_accum = Vector2();  			drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0); -			drag_node_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); +			drag_node_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));  			drag_node_touching_deaccel = false;  			time_since_motion = 0; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 3cbc64c075..db80dbe814 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -372,7 +372,7 @@ void register_scene_types() {  	OS::get_singleton()->yield(); //may take time to init -	AcceptDialog::set_swap_cancel_ok(GLOBAL_DEF("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok()))); +	AcceptDialog::set_swap_cancel_ok(GLOBAL_DEF_NOVAL("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok())));  #endif  	/* REGISTER 3D */ diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 8236f9a9e3..792e1ac2d7 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -63,6 +63,21 @@ bool VisualShaderNode::is_port_separator(int p_index) const {  	return false;  } +bool VisualShaderNode::is_output_port_connected(int p_port) const { +	if (connected_output_ports.has(p_port)) { +		return connected_output_ports[p_port]; +	} +	return false; +} + +void VisualShaderNode::set_output_port_connected(int p_port, bool p_connected) { +	connected_output_ports[p_port] = p_connected; +} + +bool VisualShaderNode::is_code_generated() const { +	return true; +} +  Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {  	return Vector<VisualShader::DefaultTextureParam>();  } @@ -526,6 +541,7 @@ void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from  	c.to_port = p_to_port;  	g->connections.push_back(c);  	g->nodes[p_to_node].prev_connected_nodes.push_back(p_from_node); +	g->nodes[p_from_node].node->set_output_port_connected(p_from_port, true);  	_queue_update();  } @@ -557,6 +573,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port,  	c.to_port = p_to_port;  	g->connections.push_back(c);  	g->nodes[p_to_node].prev_connected_nodes.push_back(p_from_node); +	g->nodes[p_from_node].node->set_output_port_connected(p_from_port, true);  	_queue_update();  	return OK; @@ -570,6 +587,7 @@ void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_por  		if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {  			g->connections.erase(E);  			g->nodes[p_to_node].prev_connected_nodes.erase(p_from_node); +			g->nodes[p_from_node].node->set_output_port_connected(p_from_port, false);  			_queue_update();  			return;  		} @@ -1105,6 +1123,35 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui  	// then this node +	Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node); +	for (int i = 0; i < params.size(); i++) { +		def_tex_params.push_back(params[i]); +	} + +	Ref<VisualShaderNodeInput> input = vsnode; +	bool skip_global = input.is_valid() && for_preview; + +	if (!skip_global) { +		global_code += vsnode->generate_global(get_mode(), type, node); + +		String class_name = vsnode->get_class_name(); +		if (class_name == "VisualShaderNodeCustom") { +			class_name = vsnode->get_script_instance()->get_script()->get_path(); +		} +		if (!r_classes.has(class_name)) { +			global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node); +			for (int i = 0; i < TYPE_MAX; i++) { +				global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node); +			} +			r_classes.insert(class_name); +		} +	} + +	if (!vsnode->is_code_generated()) { // just generate globals and ignore locals +		processed.insert(node); +		return OK; +	} +  	code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n";  	Vector<String> input_vars; @@ -1255,30 +1302,6 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui  		}  	} -	Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node); -	for (int i = 0; i < params.size(); i++) { -		def_tex_params.push_back(params[i]); -	} - -	Ref<VisualShaderNodeInput> input = vsnode; -	bool skip_global = input.is_valid() && for_preview; - -	if (!skip_global) { -		global_code += vsnode->generate_global(get_mode(), type, node); - -		String class_name = vsnode->get_class_name(); -		if (class_name == "VisualShaderNodeCustom") { -			class_name = vsnode->get_script_instance()->get_script()->get_path(); -		} -		if (!r_classes.has(class_name)) { -			global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node); -			for (int i = 0; i < TYPE_MAX; i++) { -				global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node); -			} -			r_classes.insert(class_name); -		} -	} -  	code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview);  	code += "\n"; // diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index dbb8d1d28c..cad567d32f 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -182,6 +182,7 @@ class VisualShaderNode : public Resource {  	int port_preview;  	Map<int, Variant> default_input_values; +	Map<int, bool> connected_output_ports;  protected:  	bool simple_decl; @@ -222,6 +223,11 @@ public:  	virtual bool is_port_separator(int p_index) const; +	bool is_output_port_connected(int p_port) const; +	void set_output_port_connected(int p_port, bool p_connected); + +	virtual bool is_code_generated() const; +  	virtual Vector<StringName> get_editable_properties() const;  	virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 88f5287831..4cf382a933 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -3915,6 +3915,10 @@ String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, Visu  	return code;  } +bool VisualShaderNodeTextureUniform::is_code_generated() const { +	return is_output_port_connected(0) || is_output_port_connected(1); // rgb or alpha +} +  String VisualShaderNodeTextureUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {  	String id = get_uniform_name();  	String code = "\t{\n"; diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 13a132c60e..b82d5c65c9 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -1715,6 +1715,8 @@ public:  	virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;  	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty +	virtual bool is_code_generated() const override; +  	Vector<StringName> get_editable_properties() const override;  	void set_texture_type(TextureType p_type); diff --git a/tests/SCsub b/tests/SCsub new file mode 100644 index 0000000000..9c32eb8ec3 --- /dev/null +++ b/tests/SCsub @@ -0,0 +1,15 @@ +#!/usr/bin/python + +Import("env") + +env.tests_sources = [] + +env_tests = env.Clone() + +# Enable test framework and inform it of configuration method. +env_tests.Append(CPPDEFINES=["DOCTEST_CONFIG_IMPLEMENT"]) + +env_tests.add_source_files(env.tests_sources, "*.cpp") + +lib = env_tests.add_library("tests", env.tests_sources) +env.Prepend(LIBS=[lib]) diff --git a/main/tests/test_astar.cpp b/tests/test_astar.cpp index cb5fcfe37b..cb5fcfe37b 100644 --- a/main/tests/test_astar.cpp +++ b/tests/test_astar.cpp diff --git a/main/tests/test_astar.h b/tests/test_astar.h index 0992812c18..0992812c18 100644 --- a/main/tests/test_astar.h +++ b/tests/test_astar.h diff --git a/main/tests/test_basis.cpp b/tests/test_basis.cpp index 5904fc386a..5904fc386a 100644 --- a/main/tests/test_basis.cpp +++ b/tests/test_basis.cpp diff --git a/main/tests/test_basis.h b/tests/test_basis.h index 63297bd3b8..63297bd3b8 100644 --- a/main/tests/test_basis.h +++ b/tests/test_basis.h diff --git a/main/tests/test_class_db.cpp b/tests/test_class_db.cpp index 3171091402..3171091402 100644 --- a/main/tests/test_class_db.cpp +++ b/tests/test_class_db.cpp diff --git a/main/tests/test_class_db.h b/tests/test_class_db.h index 1a31cfb01b..1a31cfb01b 100644 --- a/main/tests/test_class_db.h +++ b/tests/test_class_db.h diff --git a/main/tests/test_gdscript.cpp b/tests/test_gdscript.cpp index a50311972f..a50311972f 100644 --- a/main/tests/test_gdscript.cpp +++ b/tests/test_gdscript.cpp diff --git a/main/tests/test_gdscript.h b/tests/test_gdscript.h index 6595da1430..6595da1430 100644 --- a/main/tests/test_gdscript.h +++ b/tests/test_gdscript.h diff --git a/main/tests/test_gui.cpp b/tests/test_gui.cpp index d46a13d2c0..d46a13d2c0 100644 --- a/main/tests/test_gui.cpp +++ b/tests/test_gui.cpp diff --git a/main/tests/test_gui.h b/tests/test_gui.h index 5a23179eee..5a23179eee 100644 --- a/main/tests/test_gui.h +++ b/tests/test_gui.h diff --git a/main/tests/test_main.cpp b/tests/test_main.cpp index 5ebdaf1741..0fb9f2fcda 100644 --- a/main/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -47,10 +47,16 @@  #include "test_render.h"  #include "test_shader_lang.h"  #include "test_string.h" +#include "test_validate_testing.h" + +#include "modules/modules_tests.gen.h" + +#include "thirdparty/doctest/doctest.h"  const char **tests_get_names() {  	static const char *test_names[] = { -		"string", +		"*", +		"all",  		"math",  		"basis",  		"physics_2d", @@ -72,75 +78,38 @@ const char **tests_get_names() {  	return test_names;  } -MainLoop *test_main(String p_test, const List<String> &p_args) { -	if (p_test == "string") { -		return TestString::test(); -	} - -	if (p_test == "math") { -		return TestMath::test(); -	} - -	if (p_test == "basis") { -		return TestBasis::test(); -	} - -	if (p_test == "physics_2d") { -		return TestPhysics2D::test(); -	} - -	if (p_test == "physics_3d") { -		return TestPhysics3D::test(); -	} - -	if (p_test == "render") { -		return TestRender::test(); -	} - -	if (p_test == "oa_hash_map") { -		return TestOAHashMap::test(); -	} - -	if (p_test == "class_db") { -		return TestClassDB::test(); -	} - -#ifndef _3D_DISABLED -	if (p_test == "gui") { -		return TestGUI::test(); -	} -#endif - -	if (p_test == "shaderlang") { -		return TestShaderLang::test(); -	} - -	if (p_test == "gd_tokenizer") { -		return TestGDScript::test(TestGDScript::TEST_TOKENIZER); -	} - -	if (p_test == "gd_parser") { -		return TestGDScript::test(TestGDScript::TEST_PARSER); -	} - -	if (p_test == "gd_compiler") { -		return TestGDScript::test(TestGDScript::TEST_COMPILER); -	} - -	if (p_test == "gd_bytecode") { -		return TestGDScript::test(TestGDScript::TEST_BYTECODE); -	} - -	if (p_test == "ordered_hash_map") { -		return TestOrderedHashMap::test(); -	} - -	if (p_test == "astar") { -		return TestAStar::test(); -	} - -	print_line("Unknown test: " + p_test); -	return nullptr; +int test_main(int argc, char *argv[]) { +	// doctest runner for when legacy unit tests are no  found +	doctest::Context test_context; +	List<String> valid_arguments; + +	// clean arguments of --test from the args +	int argument_count = 0; +	for (int x = 0; x < argc; x++) { +		if (strncmp(argv[x], "--test", 6) != 0) { +			valid_arguments.push_back(String(argv[x])); +			argument_count++; +		} +	} + +	// convert godot command line arguments back to standard arguments. +	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(); +		// allocate the string copy +		args[x] = new char[strlen(str) + 1]; +		// copy this into memory +		std::memcpy(args[x], str, strlen(str) + 1); +	} + +	test_context.applyCommandLine(valid_arguments.size(), args); + +	test_context.setOption("order-by", "name"); +	test_context.setOption("abort-after", 5); +	test_context.setOption("no-breaks", true); +	delete[] args; +	return test_context.run();  }  #else @@ -153,8 +122,8 @@ const char **tests_get_names() {  	return test_names;  } -MainLoop *test_main(String p_test, const List<String> &p_args) { -	return nullptr; +int test_main(int argc, char *argv[]) { +	return 0;  }  #endif diff --git a/main/tests/test_main.h b/tests/test_main.h index bdb1668d21..8273b74eac 100644 --- a/main/tests/test_main.h +++ b/tests/test_main.h @@ -36,6 +36,6 @@  #include "core/ustring.h"  const char **tests_get_names(); -MainLoop *test_main(String p_test, const List<String> &p_args); +int test_main(int argc, char *argv[]);  #endif // TEST_MAIN_H diff --git a/main/tests/test_math.cpp b/tests/test_math.cpp index 5f84bad4e9..5f84bad4e9 100644 --- a/main/tests/test_math.cpp +++ b/tests/test_math.cpp diff --git a/main/tests/test_math.h b/tests/test_math.h index 77bce8dd66..77bce8dd66 100644 --- a/main/tests/test_math.h +++ b/tests/test_math.h diff --git a/main/tests/test_oa_hash_map.cpp b/tests/test_oa_hash_map.cpp index 9182f66b61..9182f66b61 100644 --- a/main/tests/test_oa_hash_map.cpp +++ b/tests/test_oa_hash_map.cpp diff --git a/main/tests/test_oa_hash_map.h b/tests/test_oa_hash_map.h index eb2b3d1e99..eb2b3d1e99 100644 --- a/main/tests/test_oa_hash_map.h +++ b/tests/test_oa_hash_map.h diff --git a/main/tests/test_ordered_hash_map.cpp b/tests/test_ordered_hash_map.cpp index d18a3784be..d18a3784be 100644 --- a/main/tests/test_ordered_hash_map.cpp +++ b/tests/test_ordered_hash_map.cpp diff --git a/main/tests/test_ordered_hash_map.h b/tests/test_ordered_hash_map.h index f251da0ba2..f251da0ba2 100644 --- a/main/tests/test_ordered_hash_map.h +++ b/tests/test_ordered_hash_map.h diff --git a/main/tests/test_physics_2d.cpp b/tests/test_physics_2d.cpp index c82ae920bc..c82ae920bc 100644 --- a/main/tests/test_physics_2d.cpp +++ b/tests/test_physics_2d.cpp diff --git a/main/tests/test_physics_2d.h b/tests/test_physics_2d.h index 517d324f3b..517d324f3b 100644 --- a/main/tests/test_physics_2d.h +++ b/tests/test_physics_2d.h diff --git a/main/tests/test_physics_3d.cpp b/tests/test_physics_3d.cpp index 72de2041e4..72de2041e4 100644 --- a/main/tests/test_physics_3d.cpp +++ b/tests/test_physics_3d.cpp diff --git a/main/tests/test_physics_3d.h b/tests/test_physics_3d.h index d03f2c6573..d03f2c6573 100644 --- a/main/tests/test_physics_3d.h +++ b/tests/test_physics_3d.h diff --git a/main/tests/test_render.cpp b/tests/test_render.cpp index d936dd72e7..d936dd72e7 100644 --- a/main/tests/test_render.cpp +++ b/tests/test_render.cpp diff --git a/main/tests/test_render.h b/tests/test_render.h index 4a6340c443..4a6340c443 100644 --- a/main/tests/test_render.h +++ b/tests/test_render.h diff --git a/main/tests/test_shader_lang.cpp b/tests/test_shader_lang.cpp index 34ee3e3210..34ee3e3210 100644 --- a/main/tests/test_shader_lang.cpp +++ b/tests/test_shader_lang.cpp diff --git a/main/tests/test_shader_lang.h b/tests/test_shader_lang.h index 2811c5f46e..2811c5f46e 100644 --- a/main/tests/test_shader_lang.h +++ b/tests/test_shader_lang.h diff --git a/tests/test_string.h b/tests/test_string.h new file mode 100644 index 0000000000..25fd513a1a --- /dev/null +++ b/tests/test_string.h @@ -0,0 +1,798 @@ +/*************************************************************************/ +/*  test_string.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_STRING_H +#define TEST_STRING_H + +#include <inttypes.h> +#include <stdio.h> +#include <wchar.h> + +#include "core/io/ip_address.h" +#include "core/os/main_loop.h" +#include "core/os/os.h" +#include "core/ustring.h" + +#ifdef MODULE_REGEX_ENABLED +#include "modules/regex/regex.h" +#endif + +#include "thirdparty/doctest/doctest.h" + +namespace TestString { + +TEST_CASE("[String] Assign from cstr") { +	String s = "Hello"; +	CHECK(wcscmp(s.c_str(), L"Hello") == 0); +} + +TEST_CASE("[String] Assign from string (operator=)") { +	String s = "Dolly"; +	const String &t = s; +	CHECK(wcscmp(t.c_str(), L"Dolly") == 0); +} + +TEST_CASE("[String] Assign from c-string (copycon)") { +	String s("Sheep"); +	const String &t(s); +	CHECK(wcscmp(t.c_str(), L"Sheep") == 0); +} + +TEST_CASE("[String] Assign from c-widechar (operator=)") { +	String s(L"Give me"); +	CHECK(wcscmp(s.c_str(), L"Give me") == 0); +} + +TEST_CASE("[String] Assign from c-widechar (copycon)") { +	String s(L"Wool"); +	CHECK(wcscmp(s.c_str(), L"Wool") == 0); +} + +TEST_CASE("[String] Comparisons (equal)") { +	String s = "Test Compare"; +	CHECK(s == "Test Compare"); +	CHECK(s == L"Test Compare"); +	CHECK(s == String("Test Compare")); +} + +TEST_CASE("[String] Comparisons (not equal)") { +	String s = "Test Compare"; +	CHECK(s != "Peanut"); +	CHECK(s != L"Coconut"); +	CHECK(s != String("Butter")); +} + +TEST_CASE("[String] Comparisons (operator <)") { +	String s = "Bees"; +	CHECK(s < "Elephant"); +	CHECK(!(s < L"Amber")); +	CHECK(!(s < String("Beatrix"))); +} + +TEST_CASE("[String] Concatenation") { +	String s; + +	s += "Have"; +	s += ' '; +	s += 'a'; +	s += String(" "); +	s = s + L"Nice"; +	s = s + " "; +	s = s + String("Day"); + +	CHECK(s == "Have a Nice Day"); +} + +TEST_CASE("[String] Testing size and length of string") { +	// todo: expand this test to do more tests on size() as it is complicated under the hood. +	CHECK(String("Mellon").size() == 7); +	CHECK(String("Mellon1").size() == 8); + +	// length works fine and is easier to test +	CHECK(String("Mellon").length() == 6); +	CHECK(String("Mellon1").length() == 7); +	CHECK(String("Mellon2").length() == 7); +	CHECK(String("Mellon3").length() == 7); +} + +TEST_CASE("[String] Testing for empty string") { +	CHECK(!String("Mellon").empty()); +	// do this more than once, to check for string corruption +	CHECK(String("").empty()); +	CHECK(String("").empty()); +	CHECK(String("").empty()); +} + +TEST_CASE("[String] Operator []") { +	String a = "Kugar Sane"; +	a[0] = 'S'; +	a[6] = 'C'; +	CHECK(a == "Sugar Cane"); +	CHECK(a[1] == 'u'); +} + +TEST_CASE("[String] Case function test") { +	String a = "MoMoNgA"; + +	CHECK(a.to_upper() == "MOMONGA"); +	CHECK(a.nocasecmp_to("momonga") == 0); +} + +TEST_CASE("[String] UTF8") { +	/* how can i embed UTF in here? */ +	static const CharType ustr[] = { 0x304A, 0x360F, 0x3088, 0x3046, 0 }; +	//static const wchar_t ustr[] = { 'P', 0xCE, 'p',0xD3, 0 }; +	String s = ustr; +	s.parse_utf8(s.utf8().get_data()); +	CHECK(s == ustr); +} + +TEST_CASE("[String] ASCII") { +	String s = L"Primero Leche"; +	String t = s.ascii().get_data(); +	CHECK(s == t); +} + +TEST_CASE("[String] Substr") { +	String s = "Killer Baby"; +	CHECK(s.substr(3, 4) == "ler "); +} + +TEST_CASE("[string] Find") { +	String s = "Pretty Woman"; +	s.find("Revenge of the Monster Truck"); + +	CHECK(s.find("tty") == 3); +	CHECK(s.find("Revenge of the Monster Truck") == -1); +} + +TEST_CASE("[String] find no case") { +	String s = "Pretty Whale"; +	CHECK(s.findn("WHA") == 7); +	CHECK(s.findn("Revenge of the Monster SawFish") == -1); +} + +TEST_CASE("[String] Find and replace") { +	String s = "Happy Birthday, Anna!"; +	s = s.replace("Birthday", "Halloween"); +	CHECK(s == "Happy Halloween, Anna!"); +} + +TEST_CASE("[String] Insertion") { +	String s = "Who is Frederic?"; +	s = s.insert(s.find("?"), " Chopin"); +	CHECK(s == "Who is Frederic Chopin?"); +} + +TEST_CASE("[String] Number to string") { +	CHECK(String::num(3.141593) == "3.141593"); +} + +TEST_CASE("[String] String to integer") { +	static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" }; +	static const int num[4] = { 1237461283, -22, 0, -1123412 }; + +	for (int i = 0; i < 4; i++) { +		CHECK(String(nums[i]).to_int() == num[i]); +	} +} + +TEST_CASE("[String] String to float") { +	static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" }; +	static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 }; + +	for (int i = 0; i < 4; i++) { +		CHECK(!(ABS(String(nums[i]).to_double() - num[i]) > 0.00001)); +	} +} + +TEST_CASE("[String] Slicing") { +	String s = "Mars,Jupiter,Saturn,Uranus"; + +	const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; +	for (int i = 0; i < s.get_slice_count(","); i++) { +		CHECK(s.get_slice(",", i) == slices[i]); +	} +} + +TEST_CASE("[String] Erasing") { +	String s = "Josephine is such a cute girl!"; +	s.erase(s.find("cute "), String("cute ").length()); +	CHECK(s == "Josephine is such a girl!"); +} + +#ifdef MODULE_REGEX_ENABLED +TEST_CASE("[String] Regex substitution") { +	String s = "Double all the vowels."; +	RegEx re("(?<vowel>[aeiou])"); +	s = re.sub(s, "$0$vowel", true); +	CHECK(s == "Doouublee aall thee vooweels."); +} +#endif + +struct test_27_data { +	char const *data; +	char const *begin; +	bool expected; +}; + +TEST_CASE("[String] Begins with") { +	test_27_data tc[] = { +		{ "res://foobar", "res://", true }, +		{ "res", "res://", false }, +		{ "abc", "abc", true } +	}; +	size_t count = sizeof(tc) / sizeof(tc[0]); +	bool state = true; +	for (size_t i = 0; state && i < count; ++i) { +		String s = tc[i].data; +		state = s.begins_with(tc[i].begin) == tc[i].expected; +		if (state) { +			String sb = tc[i].begin; +			state = s.begins_with(sb) == tc[i].expected; +		} +		CHECK(state); +		if (!state) { +			break; +		} +	}; +	CHECK(state); +} + +TEST_CASE("[String] sprintf") { +	String format, output; +	Array args; +	bool error; + +	// %% +	format = "fish %% frog"; +	args.clear(); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish % frog")); +	//////// INTS + +	// Int +	format = "fish %d frog"; +	args.clear(); +	args.push_back(5); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 5 frog")); + +	// Int left padded with zeroes. +	format = "fish %05d frog"; +	args.clear(); +	args.push_back(5); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 00005 frog")); + +	// Int left padded with spaces. +	format = "fish %5d frog"; +	args.clear(); +	args.push_back(5); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish     5 frog")); + +	// Int right padded with spaces. +	format = "fish %-5d frog"; +	args.clear(); +	args.push_back(5); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 5     frog")); + +	// Int with sign (positive). +	format = "fish %+d frog"; +	args.clear(); +	args.push_back(5); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish +5 frog")); + +	// Negative int. +	format = "fish %d frog"; +	args.clear(); +	args.push_back(-5); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish -5 frog")); + +	// Hex (lower) +	format = "fish %x frog"; +	args.clear(); +	args.push_back(45); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 2d frog")); + +	// Hex (upper) +	format = "fish %X frog"; +	args.clear(); +	args.push_back(45); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 2D frog")); + +	// Octal +	format = "fish %o frog"; +	args.clear(); +	args.push_back(99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 143 frog")); + +	////// REALS + +	// Real +	format = "fish %f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 99.990000 frog")); + +	// Real left-padded +	format = "fish %11f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish   99.990000 frog")); + +	// Real right-padded +	format = "fish %-11f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 99.990000   frog")); + +	// Real given int. +	format = "fish %f frog"; +	args.clear(); +	args.push_back(99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 99.000000 frog")); + +	// Real with sign (positive). +	format = "fish %+f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish +99.990000 frog")); + +	// Real with 1 decimals. +	format = "fish %.1f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 100.0 frog")); + +	// Real with 12 decimals. +	format = "fish %.12f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 99.990000000000 frog")); + +	// Real with no decimals. +	format = "fish %.f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish 100 frog")); + +	/////// Strings. + +	// String +	format = "fish %s frog"; +	args.clear(); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish cheese frog")); + +	// String left-padded +	format = "fish %10s frog"; +	args.clear(); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish     cheese frog")); + +	// String right-padded +	format = "fish %-10s frog"; +	args.clear(); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish cheese     frog")); + +	///// Characters + +	// Character as string. +	format = "fish %c frog"; +	args.clear(); +	args.push_back("A"); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish A frog")); + +	// Character as int. +	format = "fish %c frog"; +	args.clear(); +	args.push_back(65); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish A frog")); + +	///// Dynamic width + +	// String dynamic width +	format = "fish %*s frog"; +	args.clear(); +	args.push_back(10); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	REQUIRE(output == String("fish     cheese frog")); + +	// Int dynamic width +	format = "fish %*d frog"; +	args.clear(); +	args.push_back(10); +	args.push_back(99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	REQUIRE(output == String("fish         99 frog")); + +	// Float dynamic width +	format = "fish %*.*f frog"; +	args.clear(); +	args.push_back(10); +	args.push_back(3); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error == false); +	CHECK(output == String("fish     99.990 frog")); + +	///// Errors + +	// More formats than arguments. +	format = "fish %s %s frog"; +	args.clear(); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "not enough arguments for format string"); + +	// More arguments than formats. +	format = "fish %s frog"; +	args.clear(); +	args.push_back("hello"); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "not all arguments converted during string formatting"); + +	// Incomplete format. +	format = "fish %10"; +	args.clear(); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "incomplete format"); + +	// Bad character in format string +	format = "fish %&f frog"; +	args.clear(); +	args.push_back("cheese"); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "unsupported format character"); + +	// Too many decimals. +	format = "fish %2.2.2f frog"; +	args.clear(); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "too many decimal points in format"); + +	// * not a number +	format = "fish %*f frog"; +	args.clear(); +	args.push_back("cheese"); +	args.push_back(99.99); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "* wants number"); + +	// Character too long. +	format = "fish %c frog"; +	args.clear(); +	args.push_back("sc"); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "%c requires number or single-character string"); + +	// Character bad type. +	format = "fish %c frog"; +	args.clear(); +	args.push_back(Array()); +	output = format.sprintf(args, &error); +	REQUIRE(error); +	CHECK(output == "%c requires number or single-character string"); +} + +TEST_CASE("[String] IPVX address to string") { +	IP_Address ip0("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); +	IP_Address ip(0x0123, 0x4567, 0x89ab, 0xcdef, true); +	IP_Address ip2("fe80::52e5:49ff:fe93:1baf"); +	IP_Address ip3("::ffff:192.168.0.1"); +	String ip4 = "192.168.0.1"; +	CHECK(ip4.is_valid_ip_address()); + +	ip4 = "192.368.0.1"; +	CHECK(!ip4.is_valid_ip_address()); + +	String ip6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; +	CHECK(ip6.is_valid_ip_address()); + +	ip6 = "2001:0db8:85j3:0000:0000:8a2e:0370:7334"; +	CHECK(!ip6.is_valid_ip_address()); + +	ip6 = "2001:0db8:85f345:0000:0000:8a2e:0370:7334"; +	CHECK(!ip6.is_valid_ip_address()); + +	ip6 = "2001:0db8::0:8a2e:370:7334"; +	CHECK(ip6.is_valid_ip_address()); + +	ip6 = "::ffff:192.168.0.1"; +	CHECK(ip6.is_valid_ip_address()); +} + +TEST_CASE("[String] Capitalize against many strings") { +	String input = "bytes2var"; +	String output = "Bytes 2 Var"; +	CHECK(input.capitalize() == output); + +	input = "linear2db"; +	output = "Linear 2 Db"; +	CHECK(input.capitalize() == output); + +	input = "vector3"; +	output = "Vector 3"; +	CHECK(input.capitalize() == output); + +	input = "sha256"; +	output = "Sha 256"; +	CHECK(input.capitalize() == output); + +	input = "2db"; +	output = "2 Db"; +	CHECK(input.capitalize() == output); + +	input = "PascalCase"; +	output = "Pascal Case"; +	CHECK(input.capitalize() == output); + +	input = "PascalPascalCase"; +	output = "Pascal Pascal Case"; +	CHECK(input.capitalize() == output); + +	input = "snake_case"; +	output = "Snake Case"; +	CHECK(input.capitalize() == output); + +	input = "snake_snake_case"; +	output = "Snake Snake Case"; +	CHECK(input.capitalize() == output); + +	input = "sha256sum"; +	output = "Sha 256 Sum"; +	CHECK(input.capitalize() == output); + +	input = "cat2dog"; +	output = "Cat 2 Dog"; +	CHECK(input.capitalize() == output); + +	input = "function(name)"; +	output = "Function(name)"; +	CHECK(input.capitalize() == output); + +	input = "snake_case_function(snake_case_arg)"; +	output = "Snake Case Function(snake Case Arg)"; +	CHECK(input.capitalize() == output); + +	input = "snake_case_function( snake_case_arg )"; +	output = "Snake Case Function( Snake Case Arg )"; +	CHECK(input.capitalize() == output); +} + +TEST_CASE("[String] Checking string is empty when it should be") { +	bool state = true; +	bool success; + +	String a = ""; +	success = a[0] == 0; +	if (!success) { +		state = false; +	} +	String b = "Godot"; +	success = b[b.size()] == 0; +	if (!success) { +		state = false; +	} +	const String c = ""; +	success = c[0] == 0; +	if (!success) { +		state = false; +	} + +	const String d = "Godot"; +	success = d[d.size()] == 0; +	if (!success) { +		state = false; +	} + +	CHECK(state); +} + +TEST_CASE("[String] lstrip and rstrip") { +#define STRIP_TEST(x)             \ +	{                             \ +		bool success = x;         \ +		state = state && success; \ +	} + +	bool state = true; + +	// strip none +	STRIP_TEST(String("abc").lstrip("") == "abc"); +	STRIP_TEST(String("abc").rstrip("") == "abc"); +	// strip one +	STRIP_TEST(String("abc").lstrip("a") == "bc"); +	STRIP_TEST(String("abc").rstrip("c") == "ab"); +	// strip lots +	STRIP_TEST(String("bababbababccc").lstrip("ab") == "ccc"); +	STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("cb") == "aaa"); +	// strip empty string +	STRIP_TEST(String("").lstrip("") == ""); +	STRIP_TEST(String("").rstrip("") == ""); +	// strip to empty string +	STRIP_TEST(String("abcabcabc").lstrip("bca") == ""); +	STRIP_TEST(String("abcabcabc").rstrip("bca") == ""); +	// don't strip wrong end +	STRIP_TEST(String("abc").lstrip("c") == "abc"); +	STRIP_TEST(String("abca").lstrip("a") == "bca"); +	STRIP_TEST(String("abc").rstrip("a") == "abc"); +	STRIP_TEST(String("abca").rstrip("a") == "abc"); +	// in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5) +	// and the same second as "ÿ" (\u00ff) +	STRIP_TEST(String::utf8("¿").lstrip(String::utf8("µÿ")) == String::utf8("¿")); +	STRIP_TEST(String::utf8("¿").rstrip(String::utf8("µÿ")) == String::utf8("¿")); +	STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("µÿ")) == String::utf8("¿ÿ")); +	STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("µÿ")) == String::utf8("µ¿")); + +	// the above tests repeated with additional superfluous strip chars + +	// strip none +	STRIP_TEST(String("abc").lstrip("qwjkl") == "abc"); +	STRIP_TEST(String("abc").rstrip("qwjkl") == "abc"); +	// strip one +	STRIP_TEST(String("abc").lstrip("qwajkl") == "bc"); +	STRIP_TEST(String("abc").rstrip("qwcjkl") == "ab"); +	// strip lots +	STRIP_TEST(String("bababbababccc").lstrip("qwabjkl") == "ccc"); +	STRIP_TEST(String("aaabcbcbcbbcbbc").rstrip("qwcbjkl") == "aaa"); +	// strip empty string +	STRIP_TEST(String("").lstrip("qwjkl") == ""); +	STRIP_TEST(String("").rstrip("qwjkl") == ""); +	// strip to empty string +	STRIP_TEST(String("abcabcabc").lstrip("qwbcajkl") == ""); +	STRIP_TEST(String("abcabcabc").rstrip("qwbcajkl") == ""); +	// don't strip wrong end +	STRIP_TEST(String("abc").lstrip("qwcjkl") == "abc"); +	STRIP_TEST(String("abca").lstrip("qwajkl") == "bca"); +	STRIP_TEST(String("abc").rstrip("qwajkl") == "abc"); +	STRIP_TEST(String("abca").rstrip("qwajkl") == "abc"); +	// in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5) +	// and the same second as "ÿ" (\u00ff) +	STRIP_TEST(String::utf8("¿").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿")); +	STRIP_TEST(String::utf8("¿").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿")); +	STRIP_TEST(String::utf8("µ¿ÿ").lstrip(String::utf8("qwaµÿjkl")) == String::utf8("¿ÿ")); +	STRIP_TEST(String::utf8("µ¿ÿ").rstrip(String::utf8("qwaµÿjkl")) == String::utf8("µ¿")); + +	CHECK(state); + +#undef STRIP_TEST +} + +TEST_CASE("[String] ensuring empty string into parse_utf8 passes empty string") { +	String empty; +	CHECK(empty.parse_utf8(NULL, -1)); +} + +TEST_CASE("[String] Cyrillic to_lower()") { +	String upper = String::utf8("АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"); +	String lower = String::utf8("абвгдеёжзийклмнопрстуфхцчшщъыьэюя"); + +	String test = upper.to_lower(); + +	bool state = test == lower; + +	CHECK(state); +} + +TEST_CASE("[String] Count and countn functionality") { +#define COUNT_TEST(x)             \ +	{                             \ +		bool success = x;         \ +		state = state && success; \ +	} + +	bool state = true; + +	COUNT_TEST(String("").count("Test") == 0); +	COUNT_TEST(String("Test").count("") == 0); +	COUNT_TEST(String("Test").count("test") == 0); +	COUNT_TEST(String("Test").count("TEST") == 0); +	COUNT_TEST(String("TEST").count("TEST") == 1); +	COUNT_TEST(String("Test").count("Test") == 1); +	COUNT_TEST(String("aTest").count("Test") == 1); +	COUNT_TEST(String("Testa").count("Test") == 1); +	COUNT_TEST(String("TestTestTest").count("Test") == 3); +	COUNT_TEST(String("TestTestTest").count("TestTest") == 1); +	COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3); + +	COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1); +	COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2); +	COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3); +	COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3); + +	COUNT_TEST(String("Test").countn("test") == 1); +	COUNT_TEST(String("Test").countn("TEST") == 1); +	COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4); +	COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2); + +	CHECK(state); +} +} // namespace TestString + +#endif // TEST_STRING_H diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h new file mode 100644 index 0000000000..5be7d45185 --- /dev/null +++ b/tests/test_validate_testing.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/*  test_validate_testing.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_VALIDATE_TESTING_H +#define TEST_VALIDATE_TESTING_H + +#include "core/os/os.h" + +#include "thirdparty/doctest/doctest.h" + +TEST_CASE("Validate Test will always pass") { +	CHECK(true); +} + +#endif // TEST_VALIDATE_TESTING_H diff --git a/thirdparty/README.md b/thirdparty/README.md index 392abea85e..c1b230cfb7 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -74,6 +74,17 @@ Files extracted from upstream source:  - all .cpp, .h, and .txt files in ConvectionKernels/ +## doctest +- Upstream: https://github.com/onqtam/doctest +- Version: 1c8da00 (2.4.0) +- License: MIT + +Extracted from .zip provided. Extracted license and header only. + +Important: Some files have Godot-made changes. +They are marked with `// -- GODOT start --` and `// -- GODOT end --` +comments. +  ## enet  - Upstream: http://enet.bespin.org diff --git a/thirdparty/doctest/LICENSE.txt b/thirdparty/doctest/LICENSE.txt new file mode 100644 index 0000000000..a204721468 --- /dev/null +++ b/thirdparty/doctest/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016-2019 Viktor Kirilov + +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. diff --git a/thirdparty/doctest/doctest.h b/thirdparty/doctest/doctest.h new file mode 100644 index 0000000000..e4fed12767 --- /dev/null +++ b/thirdparty/doctest/doctest.h @@ -0,0 +1,6211 @@ +// ====================================================================== lgtm [cpp/missing-header-guard] +// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == +// ====================================================================== +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2019 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 4 +#define DOCTEST_VERSION_PATCH 0 +#define DOCTEST_VERSION_STR "2.4.0" + +#define DOCTEST_VERSION                                                                            \ +    (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC                                                                               \ +    DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) &&              \ +        !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w)                                                \ +    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w)                                                  \ +    DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w)                                                 \ +    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +// 4548 - expression before comma has no effect; expected expression with side - effect +// 4265 - class has virtual functions, but destructor is not virtual +// 4986 - exception specification does not match previous declaration +// 4350 - behavior change: 'member1' called instead of 'member2' +// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch +// 4774 - format string expected in argument 'x' is not a string literal +// 4820 - padding in structs + +// only 4 should be disabled globally: +// - 4514 # unreferenced inline function has been removed +// - 4571 # SEH related +// - 4710 # function not inlined +// - 4711 # function 'x' selected for automatic inline expansion + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN                                 \ +    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH                                                             \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4548)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4265)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4986)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4350)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4668)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4365)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4774)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4820)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4625)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4626)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(5027)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(5026)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(4623)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(5039)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(5045)                                                            \ +    DOCTEST_MSVC_SUPPRESS_WARNING(5105) + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0      _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0      _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0      _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0      _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0       _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0       _MSC_VER == 1400 (Visual Studio 2005) + +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) &&             \ +        !defined(__EMSCRIPTEN__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else  // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else  // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else // MSVC +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif // MSVC + +#ifndef DOCTEST_NORETURN +#define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_NORETURN + +#ifndef DOCTEST_NOEXCEPT +#define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_NOEXCEPT + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#define DOCTEST_TOSTR(x) #x + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +#define DOCTEST_GLOBAL_NO_WARNINGS(var)                                                            \ +    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors")                              \ +    DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable")                                            \ +    static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) +#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#ifndef DOCTEST_BREAK_INTO_DEBUGGER +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_MAC +// -- GODOT start -- +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) +#else +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); +#endif +// -- GODOT end -- +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) +#endif // linux +#endif // DOCTEST_BREAK_INTO_DEBUGGER + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // DOCTEST_CONFIG_USE_IOSFWD + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#include <iosfwd> +#include <cstddef> +#include <ostream> +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +#if DOCTEST_CLANG +// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) +#include <ciso646> +#endif // clang + +#ifdef _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD +#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD +#else // _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { +#define DOCTEST_STD_NAMESPACE_END } +#endif // _LIBCPP_VERSION + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; +template <class charT> +struct char_traits; +template <> +struct char_traits<char>; +template <class charT, class traits> +class basic_ostream; +typedef basic_ostream<char, char_traits<char>> ostream; +template <class... Types> +class tuple; +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +template <class _Ty> +class allocator; +template <class _Elem, class _Traits, class _Alloc> +class basic_string; +using string = basic_string<char, char_traits<char>, allocator<char>>; +#endif // VS 2019 +DOCTEST_STD_NAMESPACE_END + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include <type_traits> +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +DOCTEST_INTERFACE extern bool is_running_in_test; + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +//              and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - substr +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ +    static const unsigned len  = 24;      //!OCLINT avoid private static members +    static const unsigned last = len - 1; //!OCLINT avoid private static members + +    struct view // len should be more than sizeof(view) - because of the final byte for flags +    { +        char*    ptr; +        unsigned size; +        unsigned capacity; +    }; + +    union +    { +        char buf[len]; +        view data; +    }; + +    bool isOnStack() const { return (buf[last] & 128) == 0; } +    void setOnHeap(); +    void setLast(unsigned in = last); + +    void copy(const String& other); + +public: +    String(); +    ~String(); + +    // cppcheck-suppress noExplicitConstructor +    String(const char* in); +    String(const char* in, unsigned in_size); + +    String(const String& other); +    String& operator=(const String& other); + +    String& operator+=(const String& other); +    String  operator+(const String& other) const; + +    String(String&& other); +    String& operator=(String&& other); + +    char  operator[](unsigned i) const; +    char& operator[](unsigned i); + +    // the only functions I'm willing to leave in the interface - available for inlining +    const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT +    char*       c_str() { +        if(isOnStack()) +            return reinterpret_cast<char*>(buf); +        return data.ptr; +    } + +    unsigned size() const; +    unsigned capacity() const; + +    int compare(const char* other, bool no_case = false) const; +    int compare(const String& other, bool no_case = false) const; +}; + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); + +namespace Color { +    enum Enum +    { +        None = 0, +        White, +        Red, +        Green, +        Blue, +        Cyan, +        Yellow, +        Grey, + +        Bright = 0x10, + +        BrightRed   = Bright | Red, +        BrightGreen = Bright | Green, +        LightGrey   = Bright | Grey, +        BrightWhite = Bright | White +    }; + +    DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { +    enum Enum +    { +        // macro traits + +        is_warn    = 1, +        is_check   = 2 * is_warn, +        is_require = 2 * is_check, + +        is_normal      = 2 * is_require, +        is_throws      = 2 * is_normal, +        is_throws_as   = 2 * is_throws, +        is_throws_with = 2 * is_throws_as, +        is_nothrow     = 2 * is_throws_with, + +        is_false = 2 * is_nothrow, +        is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + +        is_eq = 2 * is_unary, +        is_ne = 2 * is_eq, + +        is_lt = 2 * is_ne, +        is_gt = 2 * is_lt, + +        is_ge = 2 * is_gt, +        is_le = 2 * is_ge, + +        // macro types + +        DT_WARN    = is_normal | is_warn, +        DT_CHECK   = is_normal | is_check, +        DT_REQUIRE = is_normal | is_require, + +        DT_WARN_FALSE    = is_normal | is_false | is_warn, +        DT_CHECK_FALSE   = is_normal | is_false | is_check, +        DT_REQUIRE_FALSE = is_normal | is_false | is_require, + +        DT_WARN_THROWS    = is_throws | is_warn, +        DT_CHECK_THROWS   = is_throws | is_check, +        DT_REQUIRE_THROWS = is_throws | is_require, + +        DT_WARN_THROWS_AS    = is_throws_as | is_warn, +        DT_CHECK_THROWS_AS   = is_throws_as | is_check, +        DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + +        DT_WARN_THROWS_WITH    = is_throws_with | is_warn, +        DT_CHECK_THROWS_WITH   = is_throws_with | is_check, +        DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, +         +        DT_WARN_THROWS_WITH_AS    = is_throws_with | is_throws_as | is_warn, +        DT_CHECK_THROWS_WITH_AS   = is_throws_with | is_throws_as | is_check, +        DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, + +        DT_WARN_NOTHROW    = is_nothrow | is_warn, +        DT_CHECK_NOTHROW   = is_nothrow | is_check, +        DT_REQUIRE_NOTHROW = is_nothrow | is_require, + +        DT_WARN_EQ    = is_normal | is_eq | is_warn, +        DT_CHECK_EQ   = is_normal | is_eq | is_check, +        DT_REQUIRE_EQ = is_normal | is_eq | is_require, + +        DT_WARN_NE    = is_normal | is_ne | is_warn, +        DT_CHECK_NE   = is_normal | is_ne | is_check, +        DT_REQUIRE_NE = is_normal | is_ne | is_require, + +        DT_WARN_GT    = is_normal | is_gt | is_warn, +        DT_CHECK_GT   = is_normal | is_gt | is_check, +        DT_REQUIRE_GT = is_normal | is_gt | is_require, + +        DT_WARN_LT    = is_normal | is_lt | is_warn, +        DT_CHECK_LT   = is_normal | is_lt | is_check, +        DT_REQUIRE_LT = is_normal | is_lt | is_require, + +        DT_WARN_GE    = is_normal | is_ge | is_warn, +        DT_CHECK_GE   = is_normal | is_ge | is_check, +        DT_REQUIRE_GE = is_normal | is_ge | is_require, + +        DT_WARN_LE    = is_normal | is_le | is_warn, +        DT_CHECK_LE   = is_normal | is_le | is_check, +        DT_REQUIRE_LE = is_normal | is_le | is_require, + +        DT_WARN_UNARY    = is_normal | is_unary | is_warn, +        DT_CHECK_UNARY   = is_normal | is_unary | is_check, +        DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + +        DT_WARN_UNARY_FALSE    = is_normal | is_false | is_unary | is_warn, +        DT_CHECK_UNARY_FALSE   = is_normal | is_false | is_unary | is_check, +        DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, +    }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ +    String      m_file;       // the file in which the test was registered +    unsigned    m_line;       // the line where the test was registered +    const char* m_name;       // name of the test case +    const char* m_test_suite; // the test suite in which the test was added +    const char* m_description; +    bool        m_skip; +    bool        m_may_fail; +    bool        m_should_fail; +    int         m_expected_failures; +    double      m_timeout; +}; + +struct DOCTEST_INTERFACE AssertData +{ +    // common - for all asserts +    const TestCaseData* m_test_case; +    assertType::Enum    m_at; +    const char*         m_file; +    int                 m_line; +    const char*         m_expr; +    bool                m_failed; + +    // exception-related - for all asserts +    bool   m_threw; +    String m_exception; + +    // for normal asserts +    String m_decomp; + +    // for specific exception-related asserts +    bool        m_threw_as; +    const char* m_exception_type; +    const char* m_exception_string; +}; + +struct DOCTEST_INTERFACE MessageData +{ +    String           m_string; +    const char*      m_file; +    int              m_line; +    assertType::Enum m_severity; +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ +    String      m_name; +    const char* m_file; +    int         m_line; + +    bool operator<(const SubcaseSignature& other) const; +}; + +struct DOCTEST_INTERFACE IContextScope +{ +    IContextScope(); +    virtual ~IContextScope(); +    virtual void stringify(std::ostream*) const = 0; +}; + +struct ContextOptions //!OCLINT too many fields +{ +    std::ostream* cout;        // stdout stream - std::cout by default +    std::ostream* cerr;        // stderr stream - std::cerr by default +    String        binary_name; // the test binary name + +    // == parameters from the command line +    String   out;       // output filename +    String   order_by;  // how tests should be ordered +    unsigned rand_seed; // the seed for rand ordering + +    unsigned first; // the first (matching) test to be executed +    unsigned last;  // the last (matching) test to be executed + +    int abort_after;           // stop tests after this many failed assertions +    int subcase_filter_levels; // apply the subcase filters for the first N levels + +    bool success;              // include successful assertions in output +    bool case_sensitive;       // if filtering should be case sensitive +    bool exit;                 // if the program should be exited after the tests are ran/whatever +    bool duration;             // print the time duration of each test case +    bool no_throw;             // to skip exceptions-related assertion macros +    bool no_exitcode;          // if the framework should return 0 as the exitcode +    bool no_run;               // to not run the tests at all (can be done with an "*" exclude) +    bool no_version;           // to not print the version of the framework +    bool no_colors;            // if output to the console should be colorized +    bool force_colors;         // forces the use of colors even when a tty cannot be detected +    bool no_breaks;            // to not break into the debugger +    bool no_skip;              // don't skip test cases which are marked to be skipped +    bool gnu_file_line;        // if line numbers should be surrounded with :x: and not (x): +    bool no_path_in_filenames; // if the path to files should be removed from the output +    bool no_line_numbers;      // if source code line numbers should be omitted from the output +    bool no_skipped_summary;   // don't print "skipped" in the summary !!! UNDOCUMENTED !!! +    bool no_time_in_output;    // omit any time/timestamps from output !!! UNDOCUMENTED !!! + +    bool help;             // to print the help +    bool version;          // to print the version +    bool count;            // if only the count of matching tests is to be retrieved +    bool list_test_cases;  // to list all tests matching the filters +    bool list_test_suites; // to list all suites matching the filters +    bool list_reporters;   // lists all registered reporters +}; + +namespace detail { +#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS) +    template <bool CONDITION, typename TYPE = void> +    struct enable_if +    {}; + +    template <typename TYPE> +    struct enable_if<true, TYPE> +    { typedef TYPE type; }; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +    // clang-format off +    template<class T> struct remove_reference      { typedef T type; }; +    template<class T> struct remove_reference<T&>  { typedef T type; }; +    template<class T> struct remove_reference<T&&> { typedef T type; }; + +    template<class T> struct remove_const          { typedef T type; }; +    template<class T> struct remove_const<const T> { typedef T type; }; +    // clang-format on + +    template <typename T> +    struct deferred_false +    // cppcheck-suppress unusedStructMember +    { static const bool value = false; }; + +    namespace has_insertion_operator_impl { +        std::ostream &os(); +        template<class T> +        DOCTEST_REF_WRAP(T) val(); + +        template<class, class = void> +        struct check { +            static constexpr auto value = false; +        }; + +        template<class T> +        struct check<T, decltype(os() << val<T>(), void())> { +            static constexpr auto value = true; +        }; +    } // namespace has_insertion_operator_impl + +    template<class T> +    using has_insertion_operator = has_insertion_operator_impl::check<T>; + +    DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + +    DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream +    DOCTEST_INTERFACE String getTlsOssResult(); + +    template <bool C> +    struct StringMakerBase +    { +        template <typename T> +        static String convert(const DOCTEST_REF_WRAP(T)) { +            return "{?}"; +        } +    }; + +    template <> +    struct StringMakerBase<true> +    { +        template <typename T> +        static String convert(const DOCTEST_REF_WRAP(T) in) { +            *getTlsOss() << in; +            return getTlsOssResult(); +        } +    }; + +    DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + +    template <typename T> +    String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { +        return rawMemoryToString(&object, sizeof(object)); +    } + +    template <typename T> +    const char* type_to_string() { +        return "<>"; +    } +} // namespace detail + +template <typename T> +struct StringMaker : public detail::StringMakerBase<detail::has_insertion_operator<T>::value> +{}; + +template <typename T> +struct StringMaker<T*> +{ +    template <typename U> +    static String convert(U* p) { +        if(p) +            return detail::rawMemoryToString(p); +        return "NULL"; +    } +}; + +template <typename R, typename C> +struct StringMaker<R C::*> +{ +    static String convert(R C::*p) { +        if(p) +            return detail::rawMemoryToString(p); +        return "NULL"; +    } +}; + +template <typename T> +String toString(const DOCTEST_REF_WRAP(T) value) { +    return StringMaker<T>::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(char* in); +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(bool in); +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(int short in); +DOCTEST_INTERFACE String toString(int short unsigned in); +DOCTEST_INTERFACE String toString(int in); +DOCTEST_INTERFACE String toString(int unsigned in); +DOCTEST_INTERFACE String toString(int long in); +DOCTEST_INTERFACE String toString(int long unsigned in); +DOCTEST_INTERFACE String toString(int long long in); +DOCTEST_INTERFACE String toString(int long long unsigned in); +DOCTEST_INTERFACE String toString(std::nullptr_t in); + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +class DOCTEST_INTERFACE Approx +{ +public: +    explicit Approx(double value); + +    Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +    template <typename T> +    explicit Approx(const T& value, +                    typename detail::enable_if<std::is_constructible<double, T>::value>::type* = +                            static_cast<T*>(nullptr)) { +        *this = Approx(static_cast<double>(value)); +    } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +    Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +    template <typename T> +    typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon( +            const T& newEpsilon) { +        m_epsilon = static_cast<double>(newEpsilon); +        return *this; +    } +#endif //  DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +    Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +    template <typename T> +    typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale( +            const T& newScale) { +        m_scale = static_cast<double>(newScale); +        return *this; +    } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +    // clang-format off +    DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); +    DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); +    DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); +    DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); +    DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); +    DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); +    DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); +    DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); +    DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); +    DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); +    DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); +    DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + +    DOCTEST_INTERFACE friend String toString(const Approx& in); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ +    template <typename T> friend typename detail::enable_if<std::is_constructible<double, T>::value, bool>::type + +    DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } +    DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } +    DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +    DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } +    DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } +    DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } +    DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } +    DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } +    DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } +    DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } +    DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } +    DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +    // clang-format on + +private: +    double m_epsilon; +    double m_scale; +    double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +#if !defined(DOCTEST_CONFIG_DISABLE) + +namespace detail { +    // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +    template<class T>               struct decay_array       { typedef T type; }; +    template<class T, unsigned N>   struct decay_array<T[N]> { typedef T* type; }; +    template<class T>               struct decay_array<T[]>  { typedef T* type; }; + +    template<class T>   struct not_char_pointer              { enum { value = 1 }; }; +    template<>          struct not_char_pointer<char*>       { enum { value = 0 }; }; +    template<>          struct not_char_pointer<const char*> { enum { value = 0 }; }; + +    template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +    // clang-format on + +    struct DOCTEST_INTERFACE TestFailureException +    { +    }; + +    DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +    DOCTEST_NORETURN +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +    DOCTEST_INTERFACE void throwException(); + +    struct DOCTEST_INTERFACE Subcase +    { +        SubcaseSignature m_signature; +        bool             m_entered = false; + +        Subcase(const String& name, const char* file, int line); +        ~Subcase(); + +        operator bool() const; +    }; + +    template <typename L, typename R> +    String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, +                               const DOCTEST_REF_WRAP(R) rhs) { +        return toString(lhs) + op + toString(rhs); +    } + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro)                              \ +    template <typename R>                                                                          \ +    DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) {                           \ +        bool res = op_macro(lhs, rhs);                                                             \ +        if(m_at & assertType::is_false)                                                            \ +            res = !res;                                                                            \ +        if(!res || doctest::getContextOptions()->success)                                          \ +            return Result(res, stringifyBinaryExpr(lhs, op_str, rhs));                             \ +        return Result(res);                                                                        \ +    } + +    // more checks could be added - like in Catch: +    // https://github.com/catchorg/Catch2/pull/1480/files +    // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op)                                                          \ +    template <typename R>                                                                          \ +    rt& operator op(const R&) {                                                                    \ +        static_assert(deferred_false<R>::value,                                                    \ +                      "Expression Too Complex Please Rewrite As Binary Comparison!");              \ +        return *this;                                                                              \ +    } + +    struct DOCTEST_INTERFACE Result +    { +        bool   m_passed; +        String m_decomp; + +        Result(bool passed, const String& decomposition = String()); + +        // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence +        DOCTEST_FORBIT_EXPRESSION(Result, &) +        DOCTEST_FORBIT_EXPRESSION(Result, ^) +        DOCTEST_FORBIT_EXPRESSION(Result, |) +        DOCTEST_FORBIT_EXPRESSION(Result, &&) +        DOCTEST_FORBIT_EXPRESSION(Result, ||) +        DOCTEST_FORBIT_EXPRESSION(Result, ==) +        DOCTEST_FORBIT_EXPRESSION(Result, !=) +        DOCTEST_FORBIT_EXPRESSION(Result, <) +        DOCTEST_FORBIT_EXPRESSION(Result, >) +        DOCTEST_FORBIT_EXPRESSION(Result, <=) +        DOCTEST_FORBIT_EXPRESSION(Result, >=) +        DOCTEST_FORBIT_EXPRESSION(Result, =) +        DOCTEST_FORBIT_EXPRESSION(Result, +=) +        DOCTEST_FORBIT_EXPRESSION(Result, -=) +        DOCTEST_FORBIT_EXPRESSION(Result, *=) +        DOCTEST_FORBIT_EXPRESSION(Result, /=) +        DOCTEST_FORBIT_EXPRESSION(Result, %=) +        DOCTEST_FORBIT_EXPRESSION(Result, <<=) +        DOCTEST_FORBIT_EXPRESSION(Result, >>=) +        DOCTEST_FORBIT_EXPRESSION(Result, &=) +        DOCTEST_FORBIT_EXPRESSION(Result, ^=) +        DOCTEST_FORBIT_EXPRESSION(Result, |=) +    }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +    DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +    DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") +    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") +    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") +    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + +    DOCTEST_GCC_SUPPRESS_WARNING_PUSH +    DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +    DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") +    //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") +    //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +    //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + +    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +    // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 +    DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch +    DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch +    DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch +    //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +    // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type +    inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } +    inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } +    inline bool lt(const char* lhs, const char* rhs) { return String(lhs) <  String(rhs); } +    inline bool gt(const char* lhs, const char* rhs) { return String(lhs) >  String(rhs); } +    inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } +    inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +    // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op)                                                            \ +    template <typename L, typename R>                                                              \ +    DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs,                             \ +                                        const DOCTEST_REF_WRAP(R) rhs) {                           \ +        return lhs op rhs;                                                                         \ +    } + +    DOCTEST_RELATIONAL_OP(eq, ==) +    DOCTEST_RELATIONAL_OP(ne, !=) +    DOCTEST_RELATIONAL_OP(lt, <) +    DOCTEST_RELATIONAL_OP(gt, >) +    DOCTEST_RELATIONAL_OP(le, <=) +    DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + +    template <typename L> +    // cppcheck-suppress copyCtorAndEqOperator +    struct Expression_lhs +    { +        L                lhs; +        assertType::Enum m_at; + +        explicit Expression_lhs(L in, assertType::Enum at) +                : lhs(in) +                , m_at(at) {} + +        DOCTEST_NOINLINE operator Result() { +            bool res = !!lhs; +            if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional +                res = !res; + +            if(!res || getContextOptions()->success) +                return Result(res, toString(lhs)); +            return Result(res); +        } + +        // clang-format off +        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional +        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional +        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>,  " >  ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional +        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<,  " <  ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional +        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional +        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional +        // clang-format on + +        // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) +        // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the +        // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) +        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) +    }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +    DOCTEST_CLANG_SUPPRESS_WARNING_POP +    DOCTEST_MSVC_SUPPRESS_WARNING_POP +    DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +    struct DOCTEST_INTERFACE ExpressionDecomposer +    { +        assertType::Enum m_at; + +        ExpressionDecomposer(assertType::Enum at); + +        // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) +        // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... +        // https://github.com/catchorg/Catch2/issues/870 +        // https://github.com/catchorg/Catch2/issues/565 +        template <typename L> +        Expression_lhs<const DOCTEST_REF_WRAP(L)> operator<<(const DOCTEST_REF_WRAP(L) operand) { +            return Expression_lhs<const DOCTEST_REF_WRAP(L)>(operand, m_at); +        } +    }; + +    struct DOCTEST_INTERFACE TestSuite +    { +        const char* m_test_suite; +        const char* m_description; +        bool        m_skip; +        bool        m_may_fail; +        bool        m_should_fail; +        int         m_expected_failures; +        double      m_timeout; + +        TestSuite& operator*(const char* in); + +        template <typename T> +        TestSuite& operator*(const T& in) { +            in.fill(*this); +            return *this; +        } +    }; + +    typedef void (*funcType)(); + +    struct DOCTEST_INTERFACE TestCase : public TestCaseData +    { +        funcType m_test; // a function pointer to the test case + +        const char* m_type; // for templated test cases - gets appended to the real name +        int m_template_id; // an ID used to distinguish between the different versions of a templated test case +        String m_full_name; // contains the name (only for templated test cases!) + the template type + +        TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, +                 const char* type = "", int template_id = -1); + +        TestCase(const TestCase& other); + +        DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function +        TestCase& operator=(const TestCase& other); +        DOCTEST_MSVC_SUPPRESS_WARNING_POP + +        TestCase& operator*(const char* in); + +        template <typename T> +        TestCase& operator*(const T& in) { +            in.fill(*this); +            return *this; +        } + +        bool operator<(const TestCase& other) const; +    }; + +    // forward declarations of functions used by the macros +    DOCTEST_INTERFACE int  regTest(const TestCase& tc); +    DOCTEST_INTERFACE int  setTestSuite(const TestSuite& ts); +    DOCTEST_INTERFACE bool isDebuggerActive(); + +    template<typename T> +    int instantiationHelper(const T&) { return 0; } + +    namespace binaryAssertComparison { +        enum Enum +        { +            eq = 0, +            ne, +            gt, +            lt, +            ge, +            le +        }; +    } // namespace binaryAssertComparison + +    // clang-format off +    template <int, class L, class R> struct RelationalComparator     { bool operator()(const DOCTEST_REF_WRAP(L),     const DOCTEST_REF_WRAP(R)    ) const { return false;        } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ +    template <class L, class R> struct RelationalComparator<n, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; +    // clang-format on + +    DOCTEST_BINARY_RELATIONAL_OP(0, eq) +    DOCTEST_BINARY_RELATIONAL_OP(1, ne) +    DOCTEST_BINARY_RELATIONAL_OP(2, gt) +    DOCTEST_BINARY_RELATIONAL_OP(3, lt) +    DOCTEST_BINARY_RELATIONAL_OP(4, ge) +    DOCTEST_BINARY_RELATIONAL_OP(5, le) + +    struct DOCTEST_INTERFACE ResultBuilder : public AssertData +    { +        ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, +                      const char* exception_type = "", const char* exception_string = ""); + +        void setResult(const Result& res); + +        template <int comparison, typename L, typename R> +        DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, +                                            const DOCTEST_REF_WRAP(R) rhs) { +            m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); +            if(m_failed || getContextOptions()->success) +                m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); +        } + +        template <typename L> +        DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { +            m_failed = !val; + +            if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional +                m_failed = !m_failed; + +            if(m_failed || getContextOptions()->success) +                m_decomp = toString(val); +        } + +        void translateException(); + +        bool log(); +        void react() const; +    }; + +    namespace assertAction { +        enum Enum +        { +            nothing     = 0, +            dbgbreak    = 1, +            shouldthrow = 2 +        }; +    } // namespace assertAction + +    DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + +    DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, +                                         const char* expr, Result result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp)                                                        \ +    do {                                                                                           \ +        if(!is_running_in_test) {                                                                  \ +            if(failed) {                                                                           \ +                ResultBuilder rb(at, file, line, expr);                                            \ +                rb.m_failed = failed;                                                              \ +                rb.m_decomp = decomp;                                                              \ +                failed_out_of_a_testing_context(rb);                                               \ +                if(isDebuggerActive() && !getContextOptions()->no_breaks)                          \ +                    DOCTEST_BREAK_INTO_DEBUGGER();                                                 \ +                if(checkIfShouldThrow(at))                                                         \ +                    throwException();                                                              \ +            }                                                                                      \ +            return;                                                                                \ +        }                                                                                          \ +    } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp)                                                            \ +    ResultBuilder rb(at, file, line, expr);                                                        \ +    rb.m_failed = failed;                                                                          \ +    if(rb.m_failed || getContextOptions()->success)                                                \ +        rb.m_decomp = decomp;                                                                      \ +    if(rb.log())                                                                                   \ +        DOCTEST_BREAK_INTO_DEBUGGER();                                                             \ +    if(rb.m_failed && checkIfShouldThrow(at))                                                      \ +    throwException() + +    template <int comparison, typename L, typename R> +    DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, +                                        const char* expr, const DOCTEST_REF_WRAP(L) lhs, +                                        const DOCTEST_REF_WRAP(R) rhs) { +        bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); + +        // ################################################################################### +        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT +        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED +        // ################################################################################### +        DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); +        DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); +    } + +    template <typename L> +    DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, +                                       const char* expr, const DOCTEST_REF_WRAP(L) val) { +        bool failed = !val; + +        if(at & assertType::is_false) //!OCLINT bitwise operator in conditional +            failed = !failed; + +        // ################################################################################### +        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT +        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED +        // ################################################################################### +        DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); +        DOCTEST_ASSERT_IN_TESTS(toString(val)); +    } + +    struct DOCTEST_INTERFACE IExceptionTranslator +    { +        IExceptionTranslator(); +        virtual ~IExceptionTranslator(); +        virtual bool translate(String&) const = 0; +    }; + +    template <typename T> +    class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class +    { +    public: +        explicit ExceptionTranslator(String (*translateFunction)(T)) +                : m_translateFunction(translateFunction) {} + +        bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +            try { +                throw; // lgtm [cpp/rethrow-no-exception] +                // cppcheck-suppress catchExceptionByValue +            } catch(T ex) {                    // NOLINT +                res = m_translateFunction(ex); //!OCLINT parameter reassignment +                return true; +            } catch(...) {} //!OCLINT -  empty catch statement +#endif                      // DOCTEST_CONFIG_NO_EXCEPTIONS +            ((void)res);    // to silence -Wunused-parameter +            return false; +        } + +    private: +        String (*m_translateFunction)(T); +    }; + +    DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + +    template <bool C> +    struct StringStreamBase +    { +        template <typename T> +        static void convert(std::ostream* s, const T& in) { +            *s << toString(in); +        } + +        // always treat char* as a string in this context - no matter +        // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined +        static void convert(std::ostream* s, const char* in) { *s << String(in); } +    }; + +    template <> +    struct StringStreamBase<true> +    { +        template <typename T> +        static void convert(std::ostream* s, const T& in) { +            *s << in; +        } +    }; + +    template <typename T> +    struct StringStream : public StringStreamBase<has_insertion_operator<T>::value> +    {}; + +    template <typename T> +    void toStream(std::ostream* s, const T& value) { +        StringStream<T>::convert(s, value); +    } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +    DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +    DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, float in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, double in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); + +    DOCTEST_INTERFACE void toStream(std::ostream* s, char in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); +    DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); + +    // ContextScope base class used to allow implementing methods of ContextScope  +    // that don't depend on the template parameter in doctest.cpp. +    class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { +    protected: +        ContextScopeBase(); + +        void destroy(); +    }; + +    template <typename L> class ContextScope : public ContextScopeBase +    { +        const L &lambda_; + +    public: +        explicit ContextScope(const L &lambda) : lambda_(lambda) {} + +        ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} + +        void stringify(std::ostream* s) const override { lambda_(s); } + +        ~ContextScope() override { destroy(); } +    }; + +    struct DOCTEST_INTERFACE MessageBuilder : public MessageData +    { +        std::ostream* m_stream; + +        MessageBuilder(const char* file, int line, assertType::Enum severity); +        MessageBuilder() = delete; +        ~MessageBuilder(); + +        template <typename T> +        MessageBuilder& operator<<(const T& in) { +            toStream(m_stream, in); +            return *this; +        } + +        bool log(); +        void react(); +    }; +     +    template <typename L> +    ContextScope<L> MakeContextScope(const L &lambda) { +        return ContextScope<L>(lambda); +    } +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def)                                                  \ +    struct name                                                                                    \ +    {                                                                                              \ +        type data;                                                                                 \ +        name(type in = def)                                                                        \ +                : data(in) {}                                                                      \ +        void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; }           \ +        void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; }          \ +    } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template <typename T> +int registerExceptionTranslator(String (*translateFunction)(T)) { +    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") +    static detail::ExceptionTranslator<T> exceptionTranslator(translateFunction); +    DOCTEST_CLANG_SUPPRESS_WARNING_POP +    detail::registerExceptionTranslatorImpl(&exceptionTranslator); +    return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else  // DOCTEST_CONFIG_DISABLE +template <typename T> +int registerExceptionTranslator(String (*)(T)) { +    return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { +    typedef void (*assert_handler)(const AssertData&); +    struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ +    detail::ContextState* p; + +    void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: +    explicit Context(int argc = 0, const char* const* argv = nullptr); + +    ~Context(); + +    void applyCommandLine(int argc, const char* const* argv); + +    void addFilter(const char* filter, const char* value); +    void clearFilters(); +    void setOption(const char* option, int value); +    void setOption(const char* option, const char* value); + +    bool shouldExit(); + +    void setAsDefaultForAssertsOutOfTestCases(); + +    void setAssertHandler(detail::assert_handler ah); + +    int run(); +}; + +namespace TestCaseFailureReason { +    enum Enum +    { +        None                     = 0, +        AssertFailure            = 1,   // an assertion has failed in the test case +        Exception                = 2,   // test case threw an exception +        Crash                    = 4,   // a crash... +        TooManyFailedAsserts     = 8,   // the abort-after option +        Timeout                  = 16,  // see the timeout decorator +        ShouldHaveFailedButDidnt = 32,  // see the should_fail decorator +        ShouldHaveFailedAndDid   = 64,  // see the should_fail decorator +        DidntFailExactlyNumTimes = 128, // see the expected_failures decorator +        FailedExactlyNumTimes    = 256, // see the expected_failures decorator +        CouldHaveFailedAndDid    = 512  // see the may_fail decorator +    }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ +    int    numAssertsCurrentTest; +    int    numAssertsFailedCurrentTest; +    double seconds; +    int    failure_flags; // use TestCaseFailureReason::Enum +}; + +struct DOCTEST_INTERFACE TestCaseException +{ +    String error_string; +    bool   is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ +    unsigned numTestCases; +    unsigned numTestCasesPassingFilters; +    unsigned numTestSuitesPassingFilters; +    unsigned numTestCasesFailed; +    int      numAsserts; +    int      numAssertsFailed; +}; + +struct QueryData +{ +    const TestRunStats*  run_stats = nullptr; +    const TestCaseData** data      = nullptr; +    unsigned             num_data  = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ +    // The constructor has to accept "const ContextOptions&" as a single argument +    // which has most of the options for the run + a pointer to the stdout stream +    // Reporter(const ContextOptions& in) + +    // called when a query should be reported (listing test cases, printing the version, etc.) +    virtual void report_query(const QueryData&) = 0; + +    // called when the whole test run starts +    virtual void test_run_start() = 0; +    // called when the whole test run ends (caching a pointer to the input doesn't make sense here) +    virtual void test_run_end(const TestRunStats&) = 0; + +    // called when a test case is started (safe to cache a pointer to the input) +    virtual void test_case_start(const TestCaseData&) = 0; +    // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) +    virtual void test_case_reenter(const TestCaseData&) = 0; +    // called when a test case has ended +    virtual void test_case_end(const CurrentTestCaseStats&) = 0; + +    // called when an exception is thrown from the test case (or it crashes) +    virtual void test_case_exception(const TestCaseException&) = 0; + +    // called whenever a subcase is entered (don't cache pointers to the input) +    virtual void subcase_start(const SubcaseSignature&) = 0; +    // called whenever a subcase is exited (don't cache pointers to the input) +    virtual void subcase_end() = 0; + +    // called for each assert (don't cache pointers to the input) +    virtual void log_assert(const AssertData&) = 0; +    // called for each message (don't cache pointers to the input) +    virtual void log_message(const MessageData&) = 0; + +    // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator +    // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) +    virtual void test_case_skipped(const TestCaseData&) = 0; + +    // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have +    virtual ~IReporter(); + +    // can obtain all currently active contexts and stringify them if one wishes to do so +    static int                         get_num_active_contexts(); +    static const IContextScope* const* get_active_contexts(); + +    // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown +    static int           get_num_stringified_contexts(); +    static const String* get_stringified_contexts(); +}; + +namespace detail { +    typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + +    DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); + +    template <typename Reporter> +    IReporter* reporterCreator(const ContextOptions& o) { +        return new Reporter(o); +    } +} // namespace detail + +template <typename Reporter> +int registerReporter(const char* name, int priority, bool isReporter) { +    detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter); +    return 0; +} +} // namespace doctest + +// if registering is not disabled +#if !defined(DOCTEST_CONFIG_DISABLE) + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_AND_REACT(b)                                                            \ +    if(b.log())                                                                                    \ +        DOCTEST_BREAK_INTO_DEBUGGER();                                                             \ +    b.react() + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x)                                                                     \ +    try {                                                                                          \ +        x;                                                                                         \ +    } catch(...) { _DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...)                                                                  \ +    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast")                                       \ +    static_cast<void>(__VA_ARGS__);                                                                \ +    DOCTEST_GCC_SUPPRESS_WARNING_POP +#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; +#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators)                                    \ +    global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) =              \ +            doctest::detail::regTest(                                                              \ +                    doctest::detail::TestCase(                                                     \ +                            f, __FILE__, __LINE__,                                                 \ +                            doctest_detail_test_suite_ns::getCurrentTestSuite()) *                 \ +                    decorators);                                                                   \ +    DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators)                                     \ +    namespace {                                                                                    \ +        struct der : public base                                                                   \ +        {                                                                                          \ +            void f();                                                                              \ +        };                                                                                         \ +        static void func() {                                                                       \ +            der v;                                                                                 \ +            v.f();                                                                                 \ +        }                                                                                          \ +        DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators)                                 \ +    }                                                                                              \ +    inline DOCTEST_NOINLINE void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators)                                        \ +    static void f();                                                                               \ +    DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators)                                        \ +    static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators)                        \ +    static doctest::detail::funcType proxy() { return f; }                                         \ +    DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators)                                   \ +    static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators)                                                              \ +    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#define DOCTEST_TEST_CASE_CLASS(decorators)                                                        \ +    DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_),          \ +                                                  DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_),         \ +                                                  decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...)                                                               \ +    TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators)                                                   \ +    DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c,                          \ +                              DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the <typeinfo> header and demangling +#define DOCTEST_TYPE_TO_STRING_IMPL(...)                                                           \ +    template <>                                                                                    \ +    inline const char* type_to_string<__VA_ARGS__>() {                                             \ +        return "<" #__VA_ARGS__ ">";                                                               \ +    } +#define DOCTEST_TYPE_TO_STRING(...)                                                                \ +    namespace doctest { namespace detail {                                                         \ +            DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__)                                               \ +        }                                                                                          \ +    }                                                                                              \ +    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func)                                 \ +    template <typename T>                                                                          \ +    static void func();                                                                            \ +    namespace {                                                                                    \ +        template <typename Tuple>                                                                  \ +        struct iter;                                                                               \ +        template <typename Type, typename... Rest>                                                 \ +        struct iter<std::tuple<Type, Rest...>>                                                     \ +        {                                                                                          \ +            iter(const char* file, unsigned line, int index) {                                     \ +                doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line,         \ +                                            doctest_detail_test_suite_ns::getCurrentTestSuite(),   \ +                                            doctest::detail::type_to_string<Type>(),               \ +                                            int(line) * 1000 + index)                              \ +                                         * dec);                                                   \ +                iter<std::tuple<Rest...>>(file, line, index + 1);                                  \ +            }                                                                                      \ +        };                                                                                         \ +        template <>                                                                                \ +        struct iter<std::tuple<>>                                                                  \ +        {                                                                                          \ +            iter(const char*, unsigned, int) {}                                                    \ +        };                                                                                         \ +    }                                                                                              \ +    template <typename T>                                                                          \ +    static void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id)                                              \ +    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR),                      \ +                                           DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...)                                 \ +    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) =                                         \ +        doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ +    DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...)                                                 \ +    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ +    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...)                                                  \ +    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ +    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...)                                         \ +    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon);             \ +    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>)               \ +    template <typename T>                                                                          \ +    static void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...)                                                    \ +    DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name)                                                                      \ +    if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ +               doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name)                                               \ +    namespace ns_name { namespace doctest_detail_test_suite_ns {                                   \ +            static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() {            \ +                DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640)                                      \ +                DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")                \ +                static doctest::detail::TestSuite data;                                            \ +                static bool                       inited = false;                                  \ +                DOCTEST_MSVC_SUPPRESS_WARNING_POP                                                  \ +                DOCTEST_CLANG_SUPPRESS_WARNING_POP                                                 \ +                if(!inited) {                                                                      \ +                    data* decorators;                                                              \ +                    inited = true;                                                                 \ +                }                                                                                  \ +                return data;                                                                       \ +            }                                                                                      \ +        }                                                                                          \ +    }                                                                                              \ +    namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators)                                                             \ +    DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators)                                                       \ +    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) =                            \ +            doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators);              \ +    DOCTEST_GLOBAL_NO_WARNINGS_END()                                                               \ +    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END                                                                     \ +    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) =                            \ +            doctest::detail::setTestSuite(doctest::detail::TestSuite() * "");                      \ +    DOCTEST_GLOBAL_NO_WARNINGS_END()                                                               \ +    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature)                      \ +    inline doctest::String translatorName(signature);                                              \ +    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) =                     \ +            doctest::registerExceptionTranslator(translatorName);                                  \ +    DOCTEST_GLOBAL_NO_WARNINGS_END()                                                               \ +    doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)                                           \ +    DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_),       \ +                                               signature) + +// for registering reporters +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)                                        \ +    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) =                       \ +            doctest::registerReporter<reporter>(name, priority, true);                             \ +    DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering listeners +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)                                        \ +    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) =                       \ +            doctest::registerReporter<reporter>(name, priority, false);                            \ +    DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for logging +#define DOCTEST_INFO(expression)                                                                   \ +    DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_),  \ +                      DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression) + +#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, expression)                                \ +    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626)                                                  \ +    auto lambda_name = [&](std::ostream* s_name) {                                                 \ +        doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ +        mb_name.m_stream = s_name;                                                                 \ +        mb_name << expression;                                                                     \ +    };                                                                                             \ +    DOCTEST_MSVC_SUPPRESS_WARNING_POP                                                              \ +    auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x)                                               \ +    do {                                                                                           \ +        doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type);                 \ +        mb << x;                                                                                   \ +        DOCTEST_ASSERT_LOG_AND_REACT(mb);                                                          \ +    } while(false) + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +// clang-format on + +#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) + +#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...)                                               \ +    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses")                  \ +    doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__,         \ +                                               __LINE__, #__VA_ARGS__);                            \ +    DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult(                                                     \ +            doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type)                \ +            << __VA_ARGS__))                                                                       \ +    DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB)                                                      \ +    DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...)                                               \ +    do {                                                                                           \ +        DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__);                                      \ +    } while(false) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +// necessary for <ASSERT>_MESSAGE +#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...)                                               \ +    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses")                  \ +    doctest::detail::decomp_assert(                                                                \ +            doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__,                    \ +            doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type)                \ +                    << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false) +#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false) +// clang-format on + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...)                                  \ +    do {                                                                                           \ +        if(!doctest::getContextOptions()->no_throw) {                                              \ +            doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ +                                                       __LINE__, #expr, #__VA_ARGS__, message);    \ +            try {                                                                                  \ +                DOCTEST_CAST_TO_VOID(expr)                                                         \ +            } catch(const doctest::detail::remove_const<                                           \ +                    doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) {                \ +                _DOCTEST_RB.translateException();                                                  \ +                _DOCTEST_RB.m_threw_as = true;                                                     \ +            } catch(...) { _DOCTEST_RB.translateException(); }                                     \ +            DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                             \ +        }                                                                                          \ +    } while(false) + +#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...)                               \ +    do {                                                                                           \ +        if(!doctest::getContextOptions()->no_throw) {                                              \ +            doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ +                                                       __LINE__, expr_str, "", __VA_ARGS__);       \ +            try {                                                                                  \ +                DOCTEST_CAST_TO_VOID(expr)                                                         \ +            } catch(...) { _DOCTEST_RB.translateException(); }                                     \ +            DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                             \ +        }                                                                                          \ +    } while(false) + +#define DOCTEST_ASSERT_NOTHROW(assert_type, ...)                                                   \ +    do {                                                                                           \ +        doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__,     \ +                                                   __LINE__, #__VA_ARGS__);                        \ +        try {                                                                                      \ +            DOCTEST_CAST_TO_VOID(__VA_ARGS__)                                                      \ +        } catch(...) { _DOCTEST_RB.translateException(); }                                         \ +        DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                                 \ +    } while(false) + +// clang-format off +#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while(false) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while(false) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while(false) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while(false) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while(false) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while(false) +// clang-format on + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...)                                              \ +    do {                                                                                           \ +        doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__,     \ +                                                   __LINE__, #__VA_ARGS__);                        \ +        DOCTEST_WRAP_IN_TRY(                                                                       \ +                _DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>(          \ +                        __VA_ARGS__))                                                              \ +        DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                                 \ +    } while(false) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...)                                                     \ +    do {                                                                                           \ +        doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__,     \ +                                                   __LINE__, #__VA_ARGS__);                        \ +        DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__))                                 \ +        DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                                 \ +    } while(false) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...)                                        \ +    doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>(           \ +            doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...)                                                     \ +    doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__,            \ +                                  #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#undef DOCTEST_WARN_THROWS +#undef DOCTEST_CHECK_THROWS +#undef DOCTEST_REQUIRE_THROWS +#undef DOCTEST_WARN_THROWS_AS +#undef DOCTEST_CHECK_THROWS_AS +#undef DOCTEST_REQUIRE_THROWS_AS +#undef DOCTEST_WARN_THROWS_WITH +#undef DOCTEST_CHECK_THROWS_WITH +#undef DOCTEST_REQUIRE_THROWS_WITH +#undef DOCTEST_WARN_THROWS_WITH_AS +#undef DOCTEST_CHECK_THROWS_WITH_AS +#undef DOCTEST_REQUIRE_THROWS_WITH_AS +#undef DOCTEST_WARN_NOTHROW +#undef DOCTEST_CHECK_NOTHROW +#undef DOCTEST_REQUIRE_NOTHROW + +#undef DOCTEST_WARN_THROWS_MESSAGE +#undef DOCTEST_CHECK_THROWS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_MESSAGE +#undef DOCTEST_WARN_THROWS_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_WARN_NOTHROW_MESSAGE +#undef DOCTEST_CHECK_NOTHROW_MESSAGE +#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(...) ((void)0) +#define DOCTEST_CHECK_THROWS(...) ((void)0) +#define DOCTEST_REQUIRE_THROWS(...) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(...) ((void)0) +#define DOCTEST_CHECK_NOTHROW(...) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING!                      == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY!                            == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name)                                           \ +    namespace {                                                                                    \ +        template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                           \ +        struct der : public base                                                                   \ +        { void f(); };                                                                             \ +    }                                                                                              \ +    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \ +    inline void der<DOCTEST_UNUSED_TEMPLATE_TYPE>::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name)                                              \ +    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \ +    static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name)                                                                    \ +    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name)                                                              \ +    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name)                                                         \ +    DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x,                          \ +                              DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the <typeinfo> header and demangling +#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TYPE_TO_STRING_IMPL(...) + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...)                                                \ +    template <typename type>                                                                       \ +    inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id)                                          \ +    template <typename type>                                                                       \ +    inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...)                                                 \ +    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...)                                                  \ +    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)                                           \ +    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \ +    static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) + +#define DOCTEST_INFO(x) ((void)0) +#define DOCTEST_CAPTURE(x) ((void)0) +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) +#define DOCTEST_MESSAGE(x) ((void)0) +#define DOCTEST_FAIL_CHECK(x) ((void)0) +#define DOCTEST_FAIL(x) ((void)0) + +#define DOCTEST_WARN(...) ((void)0) +#define DOCTEST_CHECK(...) ((void)0) +#define DOCTEST_REQUIRE(...) ((void)0) +#define DOCTEST_WARN_FALSE(...) ((void)0) +#define DOCTEST_CHECK_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_FALSE(...) ((void)0) + +#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) + +#define DOCTEST_WARN_THROWS(...) ((void)0) +#define DOCTEST_CHECK_THROWS(...) ((void)0) +#define DOCTEST_REQUIRE_THROWS(...) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(...) ((void)0) +#define DOCTEST_CHECK_NOTHROW(...) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(...) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#define DOCTEST_WARN_EQ(...) ((void)0) +#define DOCTEST_CHECK_EQ(...) ((void)0) +#define DOCTEST_REQUIRE_EQ(...) ((void)0) +#define DOCTEST_WARN_NE(...) ((void)0) +#define DOCTEST_CHECK_NE(...) ((void)0) +#define DOCTEST_REQUIRE_NE(...) ((void)0) +#define DOCTEST_WARN_GT(...) ((void)0) +#define DOCTEST_CHECK_GT(...) ((void)0) +#define DOCTEST_REQUIRE_GT(...) ((void)0) +#define DOCTEST_WARN_LT(...) ((void)0) +#define DOCTEST_CHECK_LT(...) ((void)0) +#define DOCTEST_REQUIRE_LT(...) ((void)0) +#define DOCTEST_WARN_GE(...) ((void)0) +#define DOCTEST_CHECK_GE(...) ((void)0) +#define DOCTEST_REQUIRE_GE(...) ((void)0) +#define DOCTEST_WARN_LE(...) ((void)0) +#define DOCTEST_CHECK_LE(...) ((void)0) +#define DOCTEST_REQUIRE_LE(...) ((void)0) + +#define DOCTEST_WARN_UNARY(...) ((void)0) +#define DOCTEST_CHECK_UNARY(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY(...) ((void)0) +#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) +#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) + +#endif // DOCTEST_CONFIG_DISABLE + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ             DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ            DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ          DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE             DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE            DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE          DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT             DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT            DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT          DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT             DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT            DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT          DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE             DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE            DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE          DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE             DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE            DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE          DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY          DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY         DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY       DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE    DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE   DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE("  Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS("  Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...)  DOCTEST_TEST_CASE_TEMPLATE("  Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE("  Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name)     DOCTEST_SUBCASE("   Given: " name) +#define DOCTEST_WHEN(name)      DOCTEST_SUBCASE("    When: " name) +#define DOCTEST_AND_WHEN(name)  DOCTEST_SUBCASE("And when: " name) +#define DOCTEST_THEN(name)      DOCTEST_SUBCASE("    Then: " name) +#define DOCTEST_AND_THEN(name)  DOCTEST_SUBCASE("     And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) + +#define TEST_CASE DOCTEST_TEST_CASE +#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS +#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE +#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING +#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE +#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE +#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY +#define SUBCASE DOCTEST_SUBCASE +#define TEST_SUITE DOCTEST_TEST_SUITE +#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR +#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER +#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER +#define INFO DOCTEST_INFO +#define CAPTURE DOCTEST_CAPTURE +#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT +#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT +#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT +#define MESSAGE DOCTEST_MESSAGE +#define FAIL_CHECK DOCTEST_FAIL_CHECK +#define FAIL DOCTEST_FAIL +#define TO_LVALUE DOCTEST_TO_LVALUE + +#define WARN DOCTEST_WARN +#define WARN_FALSE DOCTEST_WARN_FALSE +#define WARN_THROWS DOCTEST_WARN_THROWS +#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS +#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH +#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS +#define WARN_NOTHROW DOCTEST_WARN_NOTHROW +#define CHECK DOCTEST_CHECK +#define CHECK_FALSE DOCTEST_CHECK_FALSE +#define CHECK_THROWS DOCTEST_CHECK_THROWS +#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS +#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH +#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS +#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW +#define REQUIRE DOCTEST_REQUIRE +#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE +#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS +#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS +#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH +#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS +#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW + +#define WARN_MESSAGE DOCTEST_WARN_MESSAGE +#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE +#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE +#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE +#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE +#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE +#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE +#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE +#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE +#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE +#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE +#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE +#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE +#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE +#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE +#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#define SCENARIO DOCTEST_SCENARIO +#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS +#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE +#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE +#define GIVEN DOCTEST_GIVEN +#define WHEN DOCTEST_WHEN +#define AND_WHEN DOCTEST_AND_WHEN +#define THEN DOCTEST_THEN +#define AND_THEN DOCTEST_AND_THEN + +#define WARN_EQ DOCTEST_WARN_EQ +#define CHECK_EQ DOCTEST_CHECK_EQ +#define REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define WARN_NE DOCTEST_WARN_NE +#define CHECK_NE DOCTEST_CHECK_NE +#define REQUIRE_NE DOCTEST_REQUIRE_NE +#define WARN_GT DOCTEST_WARN_GT +#define CHECK_GT DOCTEST_CHECK_GT +#define REQUIRE_GT DOCTEST_REQUIRE_GT +#define WARN_LT DOCTEST_WARN_LT +#define CHECK_LT DOCTEST_CHECK_LT +#define REQUIRE_LT DOCTEST_REQUIRE_LT +#define WARN_GE DOCTEST_WARN_GE +#define CHECK_GE DOCTEST_CHECK_GE +#define REQUIRE_GE DOCTEST_REQUIRE_GE +#define WARN_LE DOCTEST_WARN_LE +#define CHECK_LE DOCTEST_CHECK_LE +#define REQUIRE_LE DOCTEST_REQUIRE_LE +#define WARN_UNARY DOCTEST_WARN_UNARY +#define CHECK_UNARY DOCTEST_CHECK_UNARY +#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ +#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ +#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ +#define FAST_WARN_NE DOCTEST_FAST_WARN_NE +#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE +#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE +#define FAST_WARN_GT DOCTEST_FAST_WARN_GT +#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT +#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT +#define FAST_WARN_LT DOCTEST_FAST_WARN_LT +#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT +#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT +#define FAST_WARN_GE DOCTEST_FAST_WARN_GE +#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE +#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE +#define FAST_WARN_LE DOCTEST_FAST_WARN_LE +#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE +#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE + +#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY +#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY +#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY +#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE +#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE +#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE + +#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#if !defined(DOCTEST_CONFIG_DISABLE) + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +// add stringification for primitive/fundamental types +namespace doctest { namespace detail { +    DOCTEST_TYPE_TO_STRING_IMPL(bool) +    DOCTEST_TYPE_TO_STRING_IMPL(float) +    DOCTEST_TYPE_TO_STRING_IMPL(double) +    DOCTEST_TYPE_TO_STRING_IMPL(long double) +    DOCTEST_TYPE_TO_STRING_IMPL(char) +    DOCTEST_TYPE_TO_STRING_IMPL(signed char) +    DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) +#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) +    DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) +#endif // not MSVC or wchar_t support enabled +    DOCTEST_TYPE_TO_STRING_IMPL(short int) +    DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) +    DOCTEST_TYPE_TO_STRING_IMPL(int) +    DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) +    DOCTEST_TYPE_TO_STRING_IMPL(long int) +    DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) +    DOCTEST_TYPE_TO_STRING_IMPL(long long int) +    DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) +}} // namespace doctest::detail + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_INCLUDED + +#ifndef DOCTEST_SINGLE_HEADER +#define DOCTEST_SINGLE_HEADER +#endif // DOCTEST_SINGLE_HEADER + +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") + +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include <ctime> +#include <cmath> +#include <climits> +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +#ifdef __BORLANDC__ +#include <math.h> +#endif // __BORLANDC__ +#include <new> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <limits> +#include <utility> +#include <fstream> +#include <sstream> +#include <iostream> +#include <algorithm> +#include <iomanip> +#include <vector> +#include <atomic> +#include <mutex> +#include <set> +#include <map> +#include <exception> +#include <stdexcept> +#ifdef DOCTEST_CONFIG_POSIX_SIGNALS +#include <csignal> +#endif // DOCTEST_CONFIG_POSIX_SIGNALS +#include <cfloat> +#include <cctype> +#include <cstdint> + +#ifdef DOCTEST_PLATFORM_MAC +#include <sys/types.h> +#include <unistd.h> +#include <sys/sysctl.h> +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include <AfxWin.h> +#else +#if defined(__MINGW32__) || defined(__MINGW64__) +#include <windows.h> +#else // MINGW +#include <Windows.h> +#endif // MINGW +#endif +#include <io.h> + +#else // DOCTEST_PLATFORM_WINDOWS + +#include <sys/time.h> +#include <unistd.h> + +#endif // DOCTEST_PLATFORM_WINDOWS + +// this is a fix for https://github.com/onqtam/doctest/issues/348 +// https://mail.gnome.org/archives/xml/2012-January/msg00000.html +#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) +#define STDOUT_FILENO fileno(stdout) +#endif // HAVE_UNISTD_H + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#define DOCTEST_THREAD_LOCAL thread_local +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { +    using namespace detail; +    // case insensitive strcmp +    int stricmp(const char* a, const char* b) { +        for(;; a++, b++) { +            const int d = tolower(*a) - tolower(*b); +            if(d != 0 || !*a) +                return d; +        } +    } + +    template <typename T> +    String fpToString(T value, int precision) { +        std::ostringstream oss; +        oss << std::setprecision(precision) << std::fixed << value; +        std::string d = oss.str(); +        size_t      i = d.find_last_not_of('0'); +        if(i != std::string::npos && i != d.size() - 1) { +            if(d[i] == '.') +                i++; +            d = d.substr(0, i + 1); +        } +        return d.c_str(); +    } + +    struct Endianness +    { +        enum Arch +        { +            Big, +            Little +        }; + +        static Arch which() { +            int x = 1; +            // casting any data pointer to char* is allowed +            auto ptr = reinterpret_cast<char*>(&x); +            if(*ptr) +                return Little; +            return Big; +        } +    }; +} // namespace + +namespace detail { +    void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } + +    String rawMemoryToString(const void* object, unsigned size) { +        // Reverse order for little endian architectures +        int i = 0, end = static_cast<int>(size), inc = 1; +        if(Endianness::which() == Endianness::Little) { +            i   = end - 1; +            end = inc = -1; +        } + +        unsigned const char* bytes = static_cast<unsigned const char*>(object); +        std::ostringstream   oss; +        oss << "0x" << std::setfill('0') << std::hex; +        for(; i != end; i += inc) +            oss << std::setw(2) << static_cast<unsigned>(bytes[i]); +        return oss.str().c_str(); +    } + +    DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) + +    std::ostream* getTlsOss() { +        g_oss.clear(); // there shouldn't be anything worth clearing in the flags +        g_oss.str(""); // the slow way of resetting a string stream +        //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 +        return &g_oss; +    } + +    String getTlsOssResult() { +        //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 +        return g_oss.str().c_str(); +    } + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace timer_large_integer +{ +     +#if defined(DOCTEST_PLATFORM_WINDOWS) +    typedef ULONGLONG type; +#else // DOCTEST_PLATFORM_WINDOWS +    using namespace std; +    typedef uint64_t type; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +typedef timer_large_integer::type ticks_t; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS +    ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) +    ticks_t getCurrentTicks() { +        static LARGE_INTEGER hz = {0}, hzo = {0}; +        if(!hz.QuadPart) { +            QueryPerformanceFrequency(&hz); +            QueryPerformanceCounter(&hzo); +        } +        LARGE_INTEGER t; +        QueryPerformanceCounter(&t); +        return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; +    } +#else  // DOCTEST_PLATFORM_WINDOWS +    ticks_t getCurrentTicks() { +        timeval t; +        gettimeofday(&t, nullptr); +        return static_cast<ticks_t>(t.tv_sec) * 1000000 + static_cast<ticks_t>(t.tv_usec); +    } +#endif // DOCTEST_PLATFORM_WINDOWS + +    struct Timer +    { +        void         start() { m_ticks = getCurrentTicks(); } +        unsigned int getElapsedMicroseconds() const { +            return static_cast<unsigned int>(getCurrentTicks() - m_ticks); +        } +        //unsigned int getElapsedMilliseconds() const { +        //    return static_cast<unsigned int>(getElapsedMicroseconds() / 1000); +        //} +        double getElapsedSeconds() const { return static_cast<double>(getCurrentTicks() - m_ticks) / 1000000.0; } + +    private: +        ticks_t m_ticks = 0; +    }; + +    // this holds both parameters from the command line and runtime data for tests +    struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats +    { +        std::atomic<int> numAssertsCurrentTest_atomic; +        std::atomic<int> numAssertsFailedCurrentTest_atomic; + +        std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters + +        std::vector<IReporter*> reporters_currently_used; + +        const TestCase* currentTest = nullptr; + +        assert_handler ah = nullptr; + +        Timer timer; + +        std::vector<String> stringifiedContexts; // logging from INFO() due to an exception + +        // stuff for subcases +        std::vector<SubcaseSignature>     subcasesStack; +        std::set<decltype(subcasesStack)> subcasesPassed; +        int                               subcasesCurrentMaxLevel; +        bool                              should_reenter; +        std::atomic<bool>                 shouldLogCurrentException; + +        void resetRunData() { +            numTestCases                = 0; +            numTestCasesPassingFilters  = 0; +            numTestSuitesPassingFilters = 0; +            numTestCasesFailed          = 0; +            numAsserts                  = 0; +            numAssertsFailed            = 0; +            numAssertsCurrentTest       = 0; +            numAssertsFailedCurrentTest = 0; +        } + +        void finalizeTestCaseData() { +            seconds = timer.getElapsedSeconds(); + +            // update the non-atomic counters +            numAsserts += numAssertsCurrentTest_atomic; +            numAssertsFailed += numAssertsFailedCurrentTest_atomic; +            numAssertsCurrentTest       = numAssertsCurrentTest_atomic; +            numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + +            if(numAssertsFailedCurrentTest) +                failure_flags |= TestCaseFailureReason::AssertFailure; + +            if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && +               Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) +                failure_flags |= TestCaseFailureReason::Timeout; + +            if(currentTest->m_should_fail) { +                if(failure_flags) { +                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; +                } else { +                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; +                } +            } else if(failure_flags && currentTest->m_may_fail) { +                failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; +            } else if(currentTest->m_expected_failures > 0) { +                if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { +                    failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; +                } else { +                    failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; +                } +            } + +            bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || +                              (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || +                              (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + +            // if any subcase has failed - the whole test case has failed +            if(failure_flags && !ok_to_fail) +                numTestCasesFailed++; +        } +    }; + +    ContextState* g_cs = nullptr; + +    // used to avoid locks for the debug output +    // TODO: figure out if this is indeed necessary/correct - seems like either there still +    // could be a race or that there wouldn't be a race even if using the context directly +    DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +void String::setOnHeap() { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; } +void String::setLast(unsigned in) { buf[last] = char(in); } + +void String::copy(const String& other) { +    using namespace std; +    if(other.isOnStack()) { +        memcpy(buf, other.buf, len); +    } else { +        setOnHeap(); +        data.size     = other.data.size; +        data.capacity = data.size + 1; +        data.ptr      = new char[data.capacity]; +        memcpy(data.ptr, other.data.ptr, data.size + 1); +    } +} + +String::String() { +    buf[0] = '\0'; +    setLast(); +} + +String::~String() { +    if(!isOnStack()) +        delete[] data.ptr; +} + +String::String(const char* in) +        : String(in, strlen(in)) {} + +String::String(const char* in, unsigned in_size) { +    using namespace std; +    if(in_size <= last) { +        memcpy(buf, in, in_size + 1); +        setLast(last - in_size); +    } else { +        setOnHeap(); +        data.size     = in_size; +        data.capacity = data.size + 1; +        data.ptr      = new char[data.capacity]; +        memcpy(data.ptr, in, in_size + 1); +    } +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { +    if(this != &other) { +        if(!isOnStack()) +            delete[] data.ptr; + +        copy(other); +    } + +    return *this; +} + +String& String::operator+=(const String& other) { +    const unsigned my_old_size = size(); +    const unsigned other_size  = other.size(); +    const unsigned total_size  = my_old_size + other_size; +    using namespace std; +    if(isOnStack()) { +        if(total_size < len) { +            // append to the current stack space +            memcpy(buf + my_old_size, other.c_str(), other_size + 1); +            setLast(last - total_size); +        } else { +            // alloc new chunk +            char* temp = new char[total_size + 1]; +            // copy current data to new location before writing in the union +            memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed +            // update data in union +            setOnHeap(); +            data.size     = total_size; +            data.capacity = data.size + 1; +            data.ptr      = temp; +            // transfer the rest of the data +            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); +        } +    } else { +        if(data.capacity > total_size) { +            // append to the current heap block +            data.size = total_size; +            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); +        } else { +            // resize +            data.capacity *= 2; +            if(data.capacity <= total_size) +                data.capacity = total_size + 1; +            // alloc new chunk +            char* temp = new char[data.capacity]; +            // copy current data to new location before releasing it +            memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed +            // release old chunk +            delete[] data.ptr; +            // update the rest of the union members +            data.size = total_size; +            data.ptr  = temp; +            // transfer the rest of the data +            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); +        } +    } + +    return *this; +} + +String String::operator+(const String& other) const { return String(*this) += other; } + +String::String(String&& other) { +    using namespace std; +    memcpy(buf, other.buf, len); +    other.buf[0] = '\0'; +    other.setLast(); +} + +String& String::operator=(String&& other) { +    using namespace std; +    if(this != &other) { +        if(!isOnStack()) +            delete[] data.ptr; +        memcpy(buf, other.buf, len); +        other.buf[0] = '\0'; +        other.setLast(); +    } +    return *this; +} + +char String::operator[](unsigned i) const { +    return const_cast<String*>(this)->operator[](i); // NOLINT +} + +char& String::operator[](unsigned i) { +    if(isOnStack()) +        return reinterpret_cast<char*>(buf)[i]; +    return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +unsigned String::size() const { +    if(isOnStack()) +        return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 +    return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +unsigned String::capacity() const { +    if(isOnStack()) +        return len; +    return data.capacity; +} + +int String::compare(const char* other, bool no_case) const { +    if(no_case) +        return doctest::stricmp(c_str(), other); +    return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { +    return compare(other.c_str(), no_case); +} + +// clang-format off +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } +// clang-format on + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +namespace { +    void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { +    std::ostream& operator<<(std::ostream& s, Color::Enum code) { +        color_to_stream(s, code); +        return s; +    } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { +    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled +    switch(at) {  //!OCLINT missing default in switch statements +        case assertType::DT_WARN                    : return "WARN"; +        case assertType::DT_CHECK                   : return "CHECK"; +        case assertType::DT_REQUIRE                 : return "REQUIRE"; + +        case assertType::DT_WARN_FALSE              : return "WARN_FALSE"; +        case assertType::DT_CHECK_FALSE             : return "CHECK_FALSE"; +        case assertType::DT_REQUIRE_FALSE           : return "REQUIRE_FALSE"; + +        case assertType::DT_WARN_THROWS             : return "WARN_THROWS"; +        case assertType::DT_CHECK_THROWS            : return "CHECK_THROWS"; +        case assertType::DT_REQUIRE_THROWS          : return "REQUIRE_THROWS"; + +        case assertType::DT_WARN_THROWS_AS          : return "WARN_THROWS_AS"; +        case assertType::DT_CHECK_THROWS_AS         : return "CHECK_THROWS_AS"; +        case assertType::DT_REQUIRE_THROWS_AS       : return "REQUIRE_THROWS_AS"; + +        case assertType::DT_WARN_THROWS_WITH        : return "WARN_THROWS_WITH"; +        case assertType::DT_CHECK_THROWS_WITH       : return "CHECK_THROWS_WITH"; +        case assertType::DT_REQUIRE_THROWS_WITH     : return "REQUIRE_THROWS_WITH"; + +        case assertType::DT_WARN_THROWS_WITH_AS     : return "WARN_THROWS_WITH_AS"; +        case assertType::DT_CHECK_THROWS_WITH_AS    : return "CHECK_THROWS_WITH_AS"; +        case assertType::DT_REQUIRE_THROWS_WITH_AS  : return "REQUIRE_THROWS_WITH_AS"; + +        case assertType::DT_WARN_NOTHROW            : return "WARN_NOTHROW"; +        case assertType::DT_CHECK_NOTHROW           : return "CHECK_NOTHROW"; +        case assertType::DT_REQUIRE_NOTHROW         : return "REQUIRE_NOTHROW"; + +        case assertType::DT_WARN_EQ                 : return "WARN_EQ"; +        case assertType::DT_CHECK_EQ                : return "CHECK_EQ"; +        case assertType::DT_REQUIRE_EQ              : return "REQUIRE_EQ"; +        case assertType::DT_WARN_NE                 : return "WARN_NE"; +        case assertType::DT_CHECK_NE                : return "CHECK_NE"; +        case assertType::DT_REQUIRE_NE              : return "REQUIRE_NE"; +        case assertType::DT_WARN_GT                 : return "WARN_GT"; +        case assertType::DT_CHECK_GT                : return "CHECK_GT"; +        case assertType::DT_REQUIRE_GT              : return "REQUIRE_GT"; +        case assertType::DT_WARN_LT                 : return "WARN_LT"; +        case assertType::DT_CHECK_LT                : return "CHECK_LT"; +        case assertType::DT_REQUIRE_LT              : return "REQUIRE_LT"; +        case assertType::DT_WARN_GE                 : return "WARN_GE"; +        case assertType::DT_CHECK_GE                : return "CHECK_GE"; +        case assertType::DT_REQUIRE_GE              : return "REQUIRE_GE"; +        case assertType::DT_WARN_LE                 : return "WARN_LE"; +        case assertType::DT_CHECK_LE                : return "CHECK_LE"; +        case assertType::DT_REQUIRE_LE              : return "REQUIRE_LE"; + +        case assertType::DT_WARN_UNARY              : return "WARN_UNARY"; +        case assertType::DT_CHECK_UNARY             : return "CHECK_UNARY"; +        case assertType::DT_REQUIRE_UNARY           : return "REQUIRE_UNARY"; +        case assertType::DT_WARN_UNARY_FALSE        : return "WARN_UNARY_FALSE"; +        case assertType::DT_CHECK_UNARY_FALSE       : return "CHECK_UNARY_FALSE"; +        case assertType::DT_REQUIRE_UNARY_FALSE     : return "REQUIRE_UNARY_FALSE"; +    } +    DOCTEST_MSVC_SUPPRESS_WARNING_POP +    return ""; +} +// clang-format on + +const char* failureString(assertType::Enum at) { +    if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional +        return "WARNING"; +    if(at & assertType::is_check) //!OCLINT bitwise operator in conditional +        return "ERROR"; +    if(at & assertType::is_require) //!OCLINT bitwise operator in conditional +        return "FATAL ERROR"; +    return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { +    if(getContextOptions()->no_path_in_filenames) { +        auto back    = std::strrchr(file, '\\'); +        auto forward = std::strrchr(file, '/'); +        if(back || forward) { +            if(back > forward) +                forward = back; +            return forward + 1; +        } +    } +    return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { +    if(m_line != other.m_line) +        return m_line < other.m_line; +    if(std::strcmp(m_file, other.m_file) != 0) +        return std::strcmp(m_file, other.m_file) < 0; +    return m_name.compare(other.m_name) < 0; +} + +IContextScope::IContextScope()  = default; +IContextScope::~IContextScope() = default; + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(char* in) { return toString(static_cast<const char*>(in)); } +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(bool in) { return in ? "true" : "false"; } +String toString(float in) { return fpToString(in, 5) + "f"; } +String toString(double in) { return fpToString(in, 10); } +String toString(double long in) { return fpToString(in, 15); } + +#define DOCTEST_TO_STRING_OVERLOAD(type, fmt)                                                      \ +    String toString(type in) {                                                                     \ +        char buf[64];                                                                              \ +        std::sprintf(buf, fmt, in);                                                                \ +        return buf;                                                                                \ +    } + +DOCTEST_TO_STRING_OVERLOAD(char, "%d") +DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") +DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int short, "%d") +DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int, "%d") +DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") +DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") +DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") +DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") + +String toString(std::nullptr_t) { return "NULL"; } + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +String toString(const std::string& in) { return in.c_str(); } +#endif // VS 2019 + +Approx::Approx(double value) +        : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100) +        , m_scale(1.0) +        , m_value(value) {} + +Approx Approx::operator()(double value) const { +    Approx approx(value); +    approx.epsilon(m_epsilon); +    approx.scale(m_scale); +    return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { +    m_epsilon = newEpsilon; +    return *this; +} +Approx& Approx::scale(double newScale) { +    m_scale = newScale; +    return *this; +} + +bool operator==(double lhs, const Approx& rhs) { +    // Thanks to Richard Harris for his help refining this formula +    return std::fabs(lhs - rhs.m_value) < +           rhs.m_epsilon * (rhs.m_scale + std::max<double>(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { +    return String("Approx( ") + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +int  Context::run() { return 0; } + +IReporter::~IReporter() = default; + +int                         IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int                         IReporter::get_num_stringified_contexts() { return 0; } +const String*               IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { +    static doctest::detail::TestSuite data; +    return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { +    // the int (priority) is part of the key for automatic sorting - sadly one can register a +    // reporter with a duplicate name and a different priority but hopefully that won't happen often :| +    typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap; + +    reporterMap& getReporters() { +        static reporterMap data; +        return data; +    } +    reporterMap& getListeners() { +        static reporterMap data; +        return data; +    } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...)                                           \ +    for(auto& curr_rep : g_cs->reporters_currently_used)                                           \ +    curr_rep->function(__VA_ARGS__) + +    bool checkIfShouldThrow(assertType::Enum at) { +        if(at & assertType::is_require) //!OCLINT bitwise operator in conditional +            return true; + +        if((at & assertType::is_check) //!OCLINT bitwise operator in conditional +           && getContextOptions()->abort_after > 0 && +           (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= +                   getContextOptions()->abort_after) +            return true; + +        return false; +    } + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +    DOCTEST_NORETURN void throwException() { +        g_cs->shouldLogCurrentException = false; +        throw TestFailureException(); +    } // NOLINT(cert-err60-cpp) +#else // DOCTEST_CONFIG_NO_EXCEPTIONS +    void throwException() {} +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +} // namespace detail + +namespace { +    using namespace detail; +    // matching of a string against a wildcard mask (case sensitivity configurable) taken from +    // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing +    int wildcmp(const char* str, const char* wild, bool caseSensitive) { +        const char* cp = str; +        const char* mp = wild; + +        while((*str) && (*wild != '*')) { +            if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && +               (*wild != '?')) { +                return 0; +            } +            wild++; +            str++; +        } + +        while(*str) { +            if(*wild == '*') { +                if(!*++wild) { +                    return 1; +                } +                mp = wild; +                cp = str + 1; +            } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || +                      (*wild == '?')) { +                wild++; +                str++; +            } else { +                wild = mp;   //!OCLINT parameter reassignment +                str  = cp++; //!OCLINT parameter reassignment +            } +        } + +        while(*wild == '*') { +            wild++; +        } +        return !*wild; +    } + +    //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html +    //unsigned hashStr(unsigned const char* str) { +    //    unsigned long hash = 5381; +    //    char          c; +    //    while((c = *str++)) +    //        hash = ((hash << 5) + hash) + c; // hash * 33 + c +    //    return hash; +    //} + +    // checks if the name matches any of the filters (and can be configured what to do when empty) +    bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty, +                    bool caseSensitive) { +        if(filters.empty() && matchEmpty) +            return true; +        for(auto& curr : filters) +            if(wildcmp(name, curr.c_str(), caseSensitive)) +                return true; +        return false; +    } +} // namespace +namespace detail { + +    Subcase::Subcase(const String& name, const char* file, int line) +            : m_signature({name, file, line}) { +        ContextState* s = g_cs; + +        // check subcase filters +        if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { +            if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) +                return; +            if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) +                return; +        } +         +        // if a Subcase on the same level has already been entered +        if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { +            s->should_reenter = true; +            return; +        } + +        // push the current signature to the stack so we can check if the +        // current stack + the current new subcase have been traversed +        s->subcasesStack.push_back(m_signature); +        if(s->subcasesPassed.count(s->subcasesStack) != 0) { +            // pop - revert to previous stack since we've already passed this +            s->subcasesStack.pop_back(); +            return; +        } + +        s->subcasesCurrentMaxLevel = s->subcasesStack.size(); +        m_entered = true; + +        DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); +    } + +    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17	 +    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")	 +    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + +    Subcase::~Subcase() { +        if(m_entered) { +            // only mark the subcase stack as passed if no subcases have been skipped +            if(g_cs->should_reenter == false) +                g_cs->subcasesPassed.insert(g_cs->subcasesStack); +            g_cs->subcasesStack.pop_back(); + +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L +            if(std::uncaught_exceptions() > 0 +#else +            if(std::uncaught_exception() +#endif +            && g_cs->shouldLogCurrentException) { +                DOCTEST_ITERATE_THROUGH_REPORTERS( +                        test_case_exception, {"exception thrown in subcase - will translate later " +                                              "when the whole test case has been exited (cannot " +                                              "translate while there is an active exception)", +                                              false}); +                g_cs->shouldLogCurrentException = false; +            } +            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); +        } +    } + +    DOCTEST_CLANG_SUPPRESS_WARNING_POP	 +    DOCTEST_GCC_SUPPRESS_WARNING_POP	 +    DOCTEST_MSVC_SUPPRESS_WARNING_POP + +    Subcase::operator bool() const { return m_entered; } + +    Result::Result(bool passed, const String& decomposition) +            : m_passed(passed) +            , m_decomp(decomposition) {} + +    ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) +            : m_at(at) {} + +    TestSuite& TestSuite::operator*(const char* in) { +        m_test_suite = in; +        // clear state +        m_description       = nullptr; +        m_skip              = false; +        m_may_fail          = false; +        m_should_fail       = false; +        m_expected_failures = 0; +        m_timeout           = 0; +        return *this; +    } + +    TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, +                       const char* type, int template_id) { +        m_file              = file; +        m_line              = line; +        m_name              = nullptr; // will be later overridden in operator* +        m_test_suite        = test_suite.m_test_suite; +        m_description       = test_suite.m_description; +        m_skip              = test_suite.m_skip; +        m_may_fail          = test_suite.m_may_fail; +        m_should_fail       = test_suite.m_should_fail; +        m_expected_failures = test_suite.m_expected_failures; +        m_timeout           = test_suite.m_timeout; + +        m_test        = test; +        m_type        = type; +        m_template_id = template_id; +    } + +    TestCase::TestCase(const TestCase& other) +            : TestCaseData() { +        *this = other; +    } + +    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function +    DOCTEST_MSVC_SUPPRESS_WARNING(26437)           // Do not slice +    TestCase& TestCase::operator=(const TestCase& other) { +        static_cast<TestCaseData&>(*this) = static_cast<const TestCaseData&>(other); + +        m_test        = other.m_test; +        m_type        = other.m_type; +        m_template_id = other.m_template_id; +        m_full_name   = other.m_full_name; + +        if(m_template_id != -1) +            m_name = m_full_name.c_str(); +        return *this; +    } +    DOCTEST_MSVC_SUPPRESS_WARNING_POP + +    TestCase& TestCase::operator*(const char* in) { +        m_name = in; +        // make a new name with an appended type for templated test case +        if(m_template_id != -1) { +            m_full_name = String(m_name) + m_type; +            // redirect the name to point to the newly constructed full name +            m_name = m_full_name.c_str(); +        } +        return *this; +    } + +    bool TestCase::operator<(const TestCase& other) const { +        if(m_line != other.m_line) +            return m_line < other.m_line; +        const int file_cmp = m_file.compare(other.m_file); +        if(file_cmp != 0) +            return file_cmp < 0; +        return m_template_id < other.m_template_id; +    } +} // namespace detail +namespace { +    using namespace detail; +    // for sorting tests by file/line +    bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { +        // this is needed because MSVC gives different case for drive letters +        // for __FILE__ when evaluated in a header and a source file +        const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); +        if(res != 0) +            return res < 0; +        if(lhs->m_line != rhs->m_line) +            return lhs->m_line < rhs->m_line; +        return lhs->m_template_id < rhs->m_template_id; +    } + +    // for sorting tests by suite/file/line +    bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { +        const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); +        if(res != 0) +            return res < 0; +        return fileOrderComparator(lhs, rhs); +    } + +    // for sorting tests by name/suite/file/line +    bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { +        const int res = std::strcmp(lhs->m_name, rhs->m_name); +        if(res != 0) +            return res < 0; +        return suiteOrderComparator(lhs, rhs); +    } + +    // all the registered tests +    std::set<TestCase>& getRegisteredTests() { +        static std::set<TestCase> data; +        return data; +    } + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS +    HANDLE g_stdoutHandle; +    WORD   g_origFgAttrs; +    WORD   g_origBgAttrs; +    bool   g_attrsInitted = false; + +    int colors_init() { +        if(!g_attrsInitted) { +            g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); +            g_attrsInitted = true; +            CONSOLE_SCREEN_BUFFER_INFO csbiInfo; +            GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); +            g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | +                                                     BACKGROUND_BLUE | BACKGROUND_INTENSITY); +            g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | +                                                     FOREGROUND_BLUE | FOREGROUND_INTENSITY); +        } +        return 0; +    } + +    int dumy_init_console_colors = colors_init(); +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + +    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") +    void color_to_stream(std::ostream& s, Color::Enum code) { +        ((void)s);    // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS +        ((void)code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI +        if(g_no_colors || +           (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) +            return; + +        auto col = ""; +        // clang-format off +            switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement +                case Color::Red:         col = "[0;31m"; break; +                case Color::Green:       col = "[0;32m"; break; +                case Color::Blue:        col = "[0;34m"; break; +                case Color::Cyan:        col = "[0;36m"; break; +                case Color::Yellow:      col = "[0;33m"; break; +                case Color::Grey:        col = "[1;30m"; break; +                case Color::LightGrey:   col = "[0;37m"; break; +                case Color::BrightRed:   col = "[1;31m"; break; +                case Color::BrightGreen: col = "[1;32m"; break; +                case Color::BrightWhite: col = "[1;37m"; break; +                case Color::Bright: // invalid +                case Color::None: +                case Color::White: +                default:                 col = "[0m"; +            } +        // clang-format on +        s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS +        if(g_no_colors || +           (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) +            return; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + +        // clang-format off +        switch (code) { +            case Color::White:       DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; +            case Color::Red:         DOCTEST_SET_ATTR(FOREGROUND_RED);                                      break; +            case Color::Green:       DOCTEST_SET_ATTR(FOREGROUND_GREEN);                                    break; +            case Color::Blue:        DOCTEST_SET_ATTR(FOREGROUND_BLUE);                                     break; +            case Color::Cyan:        DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN);                  break; +            case Color::Yellow:      DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN);                   break; +            case Color::Grey:        DOCTEST_SET_ATTR(0);                                                   break; +            case Color::LightGrey:   DOCTEST_SET_ATTR(FOREGROUND_INTENSITY);                                break; +            case Color::BrightRed:   DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED);               break; +            case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN);             break; +            case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; +            case Color::None: +            case Color::Bright: // invalid +            default:                 DOCTEST_SET_ATTR(g_origFgAttrs); +        } +            // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS +    } +    DOCTEST_CLANG_SUPPRESS_WARNING_POP + +    std::vector<const IExceptionTranslator*>& getExceptionTranslators() { +        static std::vector<const IExceptionTranslator*> data; +        return data; +    } + +    String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +        String res; +        auto&  translators = getExceptionTranslators(); +        for(auto& curr : translators) +            if(curr->translate(res)) +                return res; +        // clang-format off +        DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") +        try { +            throw; +        } catch(std::exception& ex) { +            return ex.what(); +        } catch(std::string& msg) { +            return msg.c_str(); +        } catch(const char* msg) { +            return msg; +        } catch(...) { +            return "unknown exception"; +        } +        DOCTEST_GCC_SUPPRESS_WARNING_POP +// clang-format on +#else  // DOCTEST_CONFIG_NO_EXCEPTIONS +        return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +    } +} // namespace + +namespace detail { +    // used by the macros for registering tests +    int regTest(const TestCase& tc) { +        getRegisteredTests().insert(tc); +        return 0; +    } + +    // sets the current test suite +    int setTestSuite(const TestSuite& ts) { +        doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; +        return 0; +    } + +#ifdef DOCTEST_IS_DEBUGGER_ACTIVE +    bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } +#else // DOCTEST_IS_DEBUGGER_ACTIVE +#ifdef DOCTEST_PLATFORM_MAC +    // The following function is taken directly from the following technical note: +    // https://developer.apple.com/library/archive/qa/qa1361/_index.html +    // Returns true if the current process is being debugged (either +    // running under the debugger or has a debugger attached post facto). +    bool isDebuggerActive() { +        int        mib[4]; +        kinfo_proc info; +        size_t     size; +        // Initialize the flags so that, if sysctl fails for some bizarre +        // reason, we get a predictable result. +        info.kp_proc.p_flag = 0; +        // Initialize mib, which tells sysctl the info we want, in this case +        // we're looking for information about a specific process ID. +        mib[0] = CTL_KERN; +        mib[1] = KERN_PROC; +        mib[2] = KERN_PROC_PID; +        mib[3] = getpid(); +        // Call sysctl. +        size = sizeof(info); +        if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { +            std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; +            return false; +        } +        // We're being debugged if the P_TRACED flag is set. +        return ((info.kp_proc.p_flag & P_TRACED) != 0); +    } +#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) +    bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else +    bool isDebuggerActive() { return false; } +#endif // Platform +#endif // DOCTEST_IS_DEBUGGER_ACTIVE + +    void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { +        if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == +           getExceptionTranslators().end()) +            getExceptionTranslators().push_back(et); +    } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +    void toStream(std::ostream* s, char* in) { *s << in; } +    void toStream(std::ostream* s, const char* in) { *s << in; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +    void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } +    void toStream(std::ostream* s, float in) { *s << in; } +    void toStream(std::ostream* s, double in) { *s << in; } +    void toStream(std::ostream* s, double long in) { *s << in; } + +    void toStream(std::ostream* s, char in) { *s << in; } +    void toStream(std::ostream* s, char signed in) { *s << in; } +    void toStream(std::ostream* s, char unsigned in) { *s << in; } +    void toStream(std::ostream* s, int short in) { *s << in; } +    void toStream(std::ostream* s, int short unsigned in) { *s << in; } +    void toStream(std::ostream* s, int in) { *s << in; } +    void toStream(std::ostream* s, int unsigned in) { *s << in; } +    void toStream(std::ostream* s, int long in) { *s << in; } +    void toStream(std::ostream* s, int long unsigned in) { *s << in; } +    void toStream(std::ostream* s, int long long in) { *s << in; } +    void toStream(std::ostream* s, int long long unsigned in) { *s << in; } + +    DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO() + +    ContextScopeBase::ContextScopeBase() { +        g_infoContexts.push_back(this); +    } + +    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17	 +    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")	 +    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + +    // destroy cannot be inlined into the destructor because that would mean calling stringify after +    // ContextScope has been destroyed (base class destructors run after derived class destructors). +    // Instead, ContextScope calls this method directly from its destructor. +    void ContextScopeBase::destroy() { +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L +        if(std::uncaught_exceptions() > 0) { +#else +        if(std::uncaught_exception()) { +#endif +            std::ostringstream s; +            this->stringify(&s); +            g_cs->stringifiedContexts.push_back(s.str().c_str()); +        } +        g_infoContexts.pop_back(); +    } + +    DOCTEST_CLANG_SUPPRESS_WARNING_POP	 +    DOCTEST_GCC_SUPPRESS_WARNING_POP	 +    DOCTEST_MSVC_SUPPRESS_WARNING_POP +} // namespace detail +namespace { +    using namespace detail; + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +    struct FatalConditionHandler +    { +        void reset() {} +    }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +    void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + +    struct SignalDefs +    { +        DWORD id; +        const char* name; +    }; +    // There is no 1-1 mapping between signals and windows exceptions. +    // Windows can easily distinguish between SO and SigSegV, +    // but SigInt, SigTerm, etc are handled differently. +    SignalDefs signalDefs[] = { +            {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, +            {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, +            {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, +            {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, +    }; + +    struct FatalConditionHandler +    { +        static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { +            for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { +                if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { +                    reportFatal(signalDefs[i].name); +                    break; +                } +            } +            // If its not an exception we care about, pass it along. +            // This stops us from eating debugger breaks etc. +            return EXCEPTION_CONTINUE_SEARCH; +        } + +        FatalConditionHandler() { +            isSet = true; +            // 32k seems enough for doctest to handle stack overflow, +            // but the value was found experimentally, so there is no strong guarantee +            guaranteeSize = 32 * 1024; +            // Register an unhandled exception filter +            previousTop = SetUnhandledExceptionFilter(handleException); +            // Pass in guarantee size to be filled +            SetThreadStackGuarantee(&guaranteeSize); +        } + +        static void reset() { +            if(isSet) { +                // Unregister handler and restore the old guarantee +                SetUnhandledExceptionFilter(previousTop); +                SetThreadStackGuarantee(&guaranteeSize); +                previousTop = nullptr; +                isSet = false; +            } +        } + +        ~FatalConditionHandler() { reset(); } + +    private: +        static bool isSet; +        static ULONG guaranteeSize; +        static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; +    }; + +    bool FatalConditionHandler::isSet = false; +    ULONG FatalConditionHandler::guaranteeSize = 0; +    LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + +    struct SignalDefs +    { +        int         id; +        const char* name; +    }; +    SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, +                               {SIGILL, "SIGILL - Illegal instruction signal"}, +                               {SIGFPE, "SIGFPE - Floating point error signal"}, +                               {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, +                               {SIGTERM, "SIGTERM - Termination request signal"}, +                               {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + +    struct FatalConditionHandler +    { +        static bool             isSet; +        static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; +        static stack_t          oldSigStack; +        static char             altStackMem[4 * SIGSTKSZ]; + +        static void handleSignal(int sig) { +            const char* name = "<unknown signal>"; +            for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { +                SignalDefs& def = signalDefs[i]; +                if(sig == def.id) { +                    name = def.name; +                    break; +                } +            } +            reset(); +            reportFatal(name); +            raise(sig); +        } + +        FatalConditionHandler() { +            isSet = true; +            stack_t sigStack; +            sigStack.ss_sp    = altStackMem; +            sigStack.ss_size  = sizeof(altStackMem); +            sigStack.ss_flags = 0; +            sigaltstack(&sigStack, &oldSigStack); +            struct sigaction sa = {}; +            sa.sa_handler       = handleSignal; // NOLINT +            sa.sa_flags         = SA_ONSTACK; +            for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { +                sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); +            } +        } + +        ~FatalConditionHandler() { reset(); } +        static void reset() { +            if(isSet) { +                // Set signals back to previous values -- hopefully nobody overwrote them in the meantime +                for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { +                    sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); +                } +                // Return the old stack +                sigaltstack(&oldSigStack, nullptr); +                isSet = false; +            } +        } +    }; + +    bool             FatalConditionHandler::isSet                                      = false; +    struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; +    stack_t          FatalConditionHandler::oldSigStack                                = {}; +    char             FatalConditionHandler::altStackMem[]                              = {}; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { +    using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else +    // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) +#endif // Platform + +    void addAssert(assertType::Enum at) { +        if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional +            g_cs->numAssertsCurrentTest_atomic++; +    } + +    void addFailedAssert(assertType::Enum at) { +        if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional +            g_cs->numAssertsFailedCurrentTest_atomic++; +    } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) +    void reportFatal(const std::string& message) { +        g_cs->failure_flags |= TestCaseFailureReason::Crash; + +        DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + +        while(g_cs->subcasesStack.size()) { +            g_cs->subcasesStack.pop_back(); +            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); +        } + +        g_cs->finalizeTestCaseData(); + +        DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + +        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); +    } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace +namespace detail { + +    ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, +                                 const char* exception_type, const char* exception_string) { +        m_test_case        = g_cs->currentTest; +        m_at               = at; +        m_file             = file; +        m_line             = line; +        m_expr             = expr; +        m_failed           = true; +        m_threw            = false; +        m_threw_as         = false; +        m_exception_type   = exception_type; +        m_exception_string = exception_string; +#if DOCTEST_MSVC +        if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC +            ++m_expr; +#endif // MSVC +    } + +    void ResultBuilder::setResult(const Result& res) { +        m_decomp = res.m_decomp; +        m_failed = !res.m_passed; +    } + +    void ResultBuilder::translateException() { +        m_threw     = true; +        m_exception = translateActiveException(); +    } + +    bool ResultBuilder::log() { +        if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional +            m_failed = !m_threw; +        } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT +            m_failed = !m_threw_as || (m_exception != m_exception_string); +        } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional +            m_failed = !m_threw_as; +        } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional +            m_failed = m_exception != m_exception_string; +        } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional +            m_failed = m_threw; +        } + +        if(m_exception.size()) +            m_exception = String("\"") + m_exception + "\""; + +        if(is_running_in_test) { +            addAssert(m_at); +            DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + +            if(m_failed) +                addFailedAssert(m_at); +        } else if(m_failed) { +            failed_out_of_a_testing_context(*this); +        } + +        return m_failed && isDebuggerActive() && +               !getContextOptions()->no_breaks; // break into debugger +    } + +    void ResultBuilder::react() const { +        if(m_failed && checkIfShouldThrow(m_at)) +            throwException(); +    } + +    void failed_out_of_a_testing_context(const AssertData& ad) { +        if(g_cs->ah) +            g_cs->ah(ad); +        else +            std::abort(); +    } + +    void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, +                       Result result) { +        bool failed = !result.m_passed; + +        // ################################################################################### +        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT +        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED +        // ################################################################################### +        DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); +        DOCTEST_ASSERT_IN_TESTS(result.m_decomp); +    } + +    MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { +        m_stream   = getTlsOss(); +        m_file     = file; +        m_line     = line; +        m_severity = severity; +    } + +    IExceptionTranslator::IExceptionTranslator()  = default; +    IExceptionTranslator::~IExceptionTranslator() = default; + +    bool MessageBuilder::log() { +        m_string = getTlsOssResult(); +        DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + +        const bool isWarn = m_severity & assertType::is_warn; + +        // warn is just a message in this context so we don't treat it as an assert +        if(!isWarn) { +            addAssert(m_severity); +            addFailedAssert(m_severity); +        } + +        return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break +    } + +    void MessageBuilder::react() { +        if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional +            throwException(); +    } + +    MessageBuilder::~MessageBuilder() = default; +} // namespace detail +namespace { +    using namespace detail; + +    template <typename Ex> +    DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +        throw e; +#else  // DOCTEST_CONFIG_NO_EXCEPTIONS +        std::cerr << "doctest will terminate because it needed to throw an exception.\n" +                  << "The message was: " << e.what() << '\n'; +        std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +    } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg)                                                                \ +    throw_exception(std::logic_error(                                                              \ +            __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + +    // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +    class XmlEncode { +    public: +        enum ForWhat { ForTextNodes, ForAttributes }; + +        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + +        void encodeTo( std::ostream& os ) const; + +        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + +    private: +        std::string m_str; +        ForWhat m_forWhat; +    }; + +    class XmlWriter { +    public: + +        class ScopedElement { +        public: +            ScopedElement( XmlWriter* writer ); + +            ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; +            ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; + +            ~ScopedElement(); + +            ScopedElement& writeText( std::string const& text, bool indent = true ); + +            template<typename T> +            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { +                m_writer->writeAttribute( name, attribute ); +                return *this; +            } + +        private: +            mutable XmlWriter* m_writer = nullptr; +        }; + +        XmlWriter( std::ostream& os = std::cout ); +        ~XmlWriter(); + +        XmlWriter( XmlWriter const& ) = delete; +        XmlWriter& operator=( XmlWriter const& ) = delete; + +        XmlWriter& startElement( std::string const& name ); + +        ScopedElement scopedElement( std::string const& name ); + +        XmlWriter& endElement(); + +        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + +        XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + +        XmlWriter& writeAttribute( std::string const& name, bool attribute ); + +        template<typename T> +        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { +        std::stringstream rss; +            rss << attribute; +            return writeAttribute( name, rss.str() ); +        } + +        XmlWriter& writeText( std::string const& text, bool indent = true ); + +        //XmlWriter& writeComment( std::string const& text ); + +        //void writeStylesheetRef( std::string const& url ); + +        //XmlWriter& writeBlankLine(); + +        void ensureTagClosed(); + +    private: + +        void writeDeclaration(); + +        void newlineIfNecessary(); + +        bool m_tagIsOpen = false; +        bool m_needsNewline = false; +        std::vector<std::string> m_tags; +        std::string m_indent; +        std::ostream& m_os; +    }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + +    size_t trailingBytes(unsigned char c) { +        if ((c & 0xE0) == 0xC0) { +            return 2; +        } +        if ((c & 0xF0) == 0xE0) { +            return 3; +        } +        if ((c & 0xF8) == 0xF0) { +            return 4; +        } +        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); +    } + +    uint32_t headerValue(unsigned char c) { +        if ((c & 0xE0) == 0xC0) { +            return c & 0x1F; +        } +        if ((c & 0xF0) == 0xE0) { +            return c & 0x0F; +        } +        if ((c & 0xF8) == 0xF0) { +            return c & 0x07; +        } +        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); +    } + +    void hexEscapeChar(std::ostream& os, unsigned char c) { +        std::ios_base::fmtflags f(os.flags()); +        os << "\\x" +            << std::uppercase << std::hex << std::setfill('0') << std::setw(2) +            << static_cast<int>(c); +        os.flags(f); +    } + +} // anonymous namespace + +    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) +    :   m_str( str ), +        m_forWhat( forWhat ) +    {} + +    void XmlEncode::encodeTo( std::ostream& os ) const { +        // Apostrophe escaping not necessary if we always use " to write attributes +        // (see: https://www.w3.org/TR/xml/#syntax) + +        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { +            uchar c = m_str[idx]; +            switch (c) { +            case '<':   os << "<"; break; +            case '&':   os << "&"; break; + +            case '>': +                // See: https://www.w3.org/TR/xml/#syntax +                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') +                    os << ">"; +                else +                    os << c; +                break; + +            case '\"': +                if (m_forWhat == ForAttributes) +                    os << """; +                else +                    os << c; +                break; + +            default: +                // Check for control characters and invalid utf-8 + +                // Escape control characters in standard ascii +                // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 +                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { +                    hexEscapeChar(os, c); +                    break; +                } + +                // Plain ASCII: Write it to stream +                if (c < 0x7F) { +                    os << c; +                    break; +                } + +                // UTF-8 territory +                // Check if the encoding is valid and if it is not, hex escape bytes. +                // Important: We do not check the exact decoded values for validity, only the encoding format +                // First check that this bytes is a valid lead byte: +                // This means that it is not encoded as 1111 1XXX +                // Or as 10XX XXXX +                if (c <  0xC0 || +                    c >= 0xF8) { +                    hexEscapeChar(os, c); +                    break; +                } + +                auto encBytes = trailingBytes(c); +                // Are there enough bytes left to avoid accessing out-of-bounds memory? +                if (idx + encBytes - 1 >= m_str.size()) { +                    hexEscapeChar(os, c); +                    break; +                } +                // The header is valid, check data +                // The next encBytes bytes must together be a valid utf-8 +                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) +                bool valid = true; +                uint32_t value = headerValue(c); +                for (std::size_t n = 1; n < encBytes; ++n) { +                    uchar nc = m_str[idx + n]; +                    valid &= ((nc & 0xC0) == 0x80); +                    value = (value << 6) | (nc & 0x3F); +                } + +                if ( +                    // Wrong bit pattern of following bytes +                    (!valid) || +                    // Overlong encodings +                    (value < 0x80) || +                    (                 value < 0x800   && encBytes > 2) || // removed "0x80 <= value &&" because redundant +                    (0x800 < value && value < 0x10000 && encBytes > 3) || +                    // Encoded value out of range +                    (value >= 0x110000) +                    ) { +                    hexEscapeChar(os, c); +                    break; +                } + +                // If we got here, this is in fact a valid(ish) utf-8 sequence +                for (std::size_t n = 0; n < encBytes; ++n) { +                    os << m_str[idx + n]; +                } +                idx += encBytes - 1; +                break; +            } +        } +    } + +    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { +        xmlEncode.encodeTo( os ); +        return os; +    } + +    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) +    :   m_writer( writer ) +    {} + +    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT +    :   m_writer( other.m_writer ){ +        other.m_writer = nullptr; +    } +    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { +        if ( m_writer ) { +            m_writer->endElement(); +        } +        m_writer = other.m_writer; +        other.m_writer = nullptr; +        return *this; +    } + + +    XmlWriter::ScopedElement::~ScopedElement() { +        if( m_writer ) +            m_writer->endElement(); +    } + +    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { +        m_writer->writeText( text, indent ); +        return *this; +    } + +    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) +    { +        writeDeclaration(); +    } + +    XmlWriter::~XmlWriter() { +        while( !m_tags.empty() ) +            endElement(); +    } + +    XmlWriter& XmlWriter::startElement( std::string const& name ) { +        ensureTagClosed(); +        newlineIfNecessary(); +        m_os << m_indent << '<' << name; +        m_tags.push_back( name ); +        m_indent += "  "; +        m_tagIsOpen = true; +        return *this; +    } + +    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { +        ScopedElement scoped( this ); +        startElement( name ); +        return scoped; +    } + +    XmlWriter& XmlWriter::endElement() { +        newlineIfNecessary(); +        m_indent = m_indent.substr( 0, m_indent.size()-2 ); +        if( m_tagIsOpen ) { +            m_os << "/>"; +            m_tagIsOpen = false; +        } +        else { +            m_os << m_indent << "</" << m_tags.back() << ">"; +        } +        m_os << std::endl; +        m_tags.pop_back(); +        return *this; +    } + +    XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { +        if( !name.empty() && !attribute.empty() ) +            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; +        return *this; +    } + +    XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { +        if( !name.empty() && attribute && attribute[0] != '\0' ) +            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; +        return *this; +    } + +    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { +        m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; +        return *this; +    } + +    XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { +        if( !text.empty() ){ +            bool tagWasOpen = m_tagIsOpen; +            ensureTagClosed(); +            if( tagWasOpen && indent ) +                m_os << m_indent; +            m_os << XmlEncode( text ); +            m_needsNewline = true; +        } +        return *this; +    } + +    //XmlWriter& XmlWriter::writeComment( std::string const& text ) { +    //    ensureTagClosed(); +    //    m_os << m_indent << "<!--" << text << "-->"; +    //    m_needsNewline = true; +    //    return *this; +    //} + +    //void XmlWriter::writeStylesheetRef( std::string const& url ) { +    //    m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; +    //} + +    //XmlWriter& XmlWriter::writeBlankLine() { +    //    ensureTagClosed(); +    //    m_os << '\n'; +    //    return *this; +    //} + +    void XmlWriter::ensureTagClosed() { +        if( m_tagIsOpen ) { +            m_os << ">" << std::endl; +            m_tagIsOpen = false; +        } +    } + +    void XmlWriter::writeDeclaration() { +        m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; +    } + +    void XmlWriter::newlineIfNecessary() { +        if( m_needsNewline ) { +            m_os << std::endl; +            m_needsNewline = false; +        } +    } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + +    // clang-format on + +    struct XmlReporter : public IReporter +    { +        XmlWriter  xml; +        std::mutex mutex; + +        // caching pointers/references to objects of these types - safe to do +        const ContextOptions& opt; +        const TestCaseData*   tc = nullptr; + +        XmlReporter(const ContextOptions& co) +                : xml(*co.cout) +                , opt(co) {} + +        void log_contexts() { +            int num_contexts = get_num_active_contexts(); +            if(num_contexts) { +                auto              contexts = get_active_contexts(); +                std::stringstream ss; +                for(int i = 0; i < num_contexts; ++i) { +                    contexts[i]->stringify(&ss); +                    xml.scopedElement("Info").writeText(ss.str()); +                    ss.str(""); +                } +            } +        } + +        unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + +        void test_case_start_impl(const TestCaseData& in) { +            bool open_ts_tag = false; +            if(tc != nullptr) { // we have already opened a test suite +                if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { +                    xml.endElement(); +                    open_ts_tag = true; +                } +            } +            else { +                open_ts_tag = true; // first test case ==> first test suite +            } + +            if(open_ts_tag) { +                xml.startElement("TestSuite"); +                xml.writeAttribute("name", in.m_test_suite); +            } + +            tc = ∈ +            xml.startElement("TestCase") +                    .writeAttribute("name", in.m_name) +                    .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) +                    .writeAttribute("line", line(in.m_line)) +                    .writeAttribute("description", in.m_description); + +            if(Approx(in.m_timeout) != 0) +                xml.writeAttribute("timeout", in.m_timeout); +            if(in.m_may_fail) +                xml.writeAttribute("may_fail", true); +            if(in.m_should_fail) +                xml.writeAttribute("should_fail", true); +        } + +        // ========================================================================================= +        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE +        // ========================================================================================= + +        void report_query(const QueryData& in) override { +            test_run_start(); +            if(opt.list_reporters) { +                for(auto& curr : getListeners()) +                    xml.scopedElement("Listener") +                            .writeAttribute("priority", curr.first.first) +                            .writeAttribute("name", curr.first.second); +                for(auto& curr : getReporters()) +                    xml.scopedElement("Reporter") +                            .writeAttribute("priority", curr.first.first) +                            .writeAttribute("name", curr.first.second); +            } else if(opt.count || opt.list_test_cases) { +                for(unsigned i = 0; i < in.num_data; ++i) { +                    xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) +                        .writeAttribute("testsuite", in.data[i]->m_test_suite) +                        .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) +                        .writeAttribute("line", line(in.data[i]->m_line)); +                } +                xml.scopedElement("OverallResultsTestCases") +                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); +            } else if(opt.list_test_suites) { +                for(unsigned i = 0; i < in.num_data; ++i) +                    xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); +                xml.scopedElement("OverallResultsTestCases") +                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); +                xml.scopedElement("OverallResultsTestSuites") +                        .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); +            } +            xml.endElement(); +        } + +        void test_run_start() override { +            // remove .exe extension - mainly to have the same output on UNIX and Windows +            std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS +            if(binary_name.rfind(".exe") != std::string::npos) +                binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + +            xml.startElement("doctest").writeAttribute("binary", binary_name); +            if(opt.no_version == false) +                xml.writeAttribute("version", DOCTEST_VERSION_STR); + +            // only the consequential ones (TODO: filters) +            xml.scopedElement("Options") +                    .writeAttribute("order_by", opt.order_by.c_str()) +                    .writeAttribute("rand_seed", opt.rand_seed) +                    .writeAttribute("first", opt.first) +                    .writeAttribute("last", opt.last) +                    .writeAttribute("abort_after", opt.abort_after) +                    .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) +                    .writeAttribute("case_sensitive", opt.case_sensitive) +                    .writeAttribute("no_throw", opt.no_throw) +                    .writeAttribute("no_skip", opt.no_skip); +        } + +        void test_run_end(const TestRunStats& p) override { +            if(tc) // the TestSuite tag - only if there has been at least 1 test case +                xml.endElement(); + +            xml.scopedElement("OverallResultsAsserts") +                    .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) +                    .writeAttribute("failures", p.numAssertsFailed); + +            xml.startElement("OverallResultsTestCases") +                    .writeAttribute("successes", +                                    p.numTestCasesPassingFilters - p.numTestCasesFailed) +                    .writeAttribute("failures", p.numTestCasesFailed); +            if(opt.no_skipped_summary == false) +                xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); +            xml.endElement(); + +            xml.endElement(); +        } + +        void test_case_start(const TestCaseData& in) override { +            test_case_start_impl(in); +            xml.ensureTagClosed(); +        } +         +        void test_case_reenter(const TestCaseData&) override {} + +        void test_case_end(const CurrentTestCaseStats& st) override { +            xml.startElement("OverallResultsAsserts") +                    .writeAttribute("successes", +                                    st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) +                    .writeAttribute("failures", st.numAssertsFailedCurrentTest); +            if(opt.duration) +                xml.writeAttribute("duration", st.seconds); +            if(tc->m_expected_failures) +                xml.writeAttribute("expected_failures", tc->m_expected_failures); +            xml.endElement(); + +            xml.endElement(); +        } + +        void test_case_exception(const TestCaseException& e) override { +            std::lock_guard<std::mutex> lock(mutex); + +            xml.scopedElement("Exception") +                    .writeAttribute("crash", e.is_crash) +                    .writeText(e.error_string.c_str()); +        } + +        void subcase_start(const SubcaseSignature& in) override { +            std::lock_guard<std::mutex> lock(mutex); + +            xml.startElement("SubCase") +                    .writeAttribute("name", in.m_name) +                    .writeAttribute("filename", skipPathFromFilename(in.m_file)) +                    .writeAttribute("line", line(in.m_line)); +            xml.ensureTagClosed(); +        } + +        void subcase_end() override { xml.endElement(); } + +        void log_assert(const AssertData& rb) override { +            if(!rb.m_failed && !opt.success) +                return; + +            std::lock_guard<std::mutex> lock(mutex); + +            xml.startElement("Expression") +                    .writeAttribute("success", !rb.m_failed) +                    .writeAttribute("type", assertString(rb.m_at)) +                    .writeAttribute("filename", skipPathFromFilename(rb.m_file)) +                    .writeAttribute("line", line(rb.m_line)); + +            xml.scopedElement("Original").writeText(rb.m_expr); + +            if(rb.m_threw) +                xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + +            if(rb.m_at & assertType::is_throws_as) +                xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); +            if(rb.m_at & assertType::is_throws_with) +                xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); +            if((rb.m_at & assertType::is_normal) && !rb.m_threw) +                xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + +            log_contexts(); + +            xml.endElement(); +        } + +        void log_message(const MessageData& mb) override { +            std::lock_guard<std::mutex> lock(mutex); + +            xml.startElement("Message") +                    .writeAttribute("type", failureString(mb.m_severity)) +                    .writeAttribute("filename", skipPathFromFilename(mb.m_file)) +                    .writeAttribute("line", line(mb.m_line)); + +            xml.scopedElement("Text").writeText(mb.m_string.c_str()); + +            log_contexts(); + +            xml.endElement(); +        } + +        void test_case_skipped(const TestCaseData& in) override { +            if(opt.no_skipped_summary == false) { +                test_case_start_impl(in); +                xml.writeAttribute("skipped", "true"); +                xml.endElement(); +            } +        } +    }; + +    DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + +    void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { +        if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == +            0) //!OCLINT bitwise operator in conditional +            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " +                << Color::None; + +        if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional +            s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; +        } else if((rb.m_at & assertType::is_throws_as) && +                    (rb.m_at & assertType::is_throws_with)) { //!OCLINT +            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" +                << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; +            if(rb.m_threw) { +                if(!rb.m_failed) { +                    s << "threw as expected!\n"; +                } else { +                    s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; +                } +            } else { +                s << "did NOT throw at all!\n"; +            } +        } else if(rb.m_at & +                    assertType::is_throws_as) { //!OCLINT bitwise operator in conditional +            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " +                << rb.m_exception_type << " ) " << Color::None +                << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : +                                                "threw a DIFFERENT exception: ") : +                                "did NOT throw at all!") +                << Color::Cyan << rb.m_exception << "\n"; +        } else if(rb.m_at & +                    assertType::is_throws_with) { //!OCLINT bitwise operator in conditional +            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" +                << rb.m_exception_string << "\" ) " << Color::None +                << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : +                                                "threw a DIFFERENT exception: ") : +                                "did NOT throw at all!") +                << Color::Cyan << rb.m_exception << "\n"; +        } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional +            s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan +                << rb.m_exception << "\n"; +        } else { +            s << (rb.m_threw ? "THREW exception: " : +                                (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); +            if(rb.m_threw) +                s << rb.m_exception << "\n"; +            else +                s << "  values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; +        } +    } + +    // TODO: +    // - log_contexts() +    // - log_message() +    // - respond to queries +    // - honor remaining options +    // - more attributes in tags +    struct JUnitReporter : public IReporter +    { +        XmlWriter  xml; +        std::mutex mutex; +        Timer timer; +        std::vector<String> deepestSubcaseStackNames; + +        struct JUnitTestCaseData +        { +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") // gmtime +            static std::string getCurrentTimestamp() { +                // Beware, this is not reentrant because of backward compatibility issues +                // Also, UTC only, again because of backward compatibility (%z is C++11) +                time_t rawtime; +                std::time(&rawtime); +                auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +                std::tm* timeInfo; +                timeInfo = std::gmtime(&rawtime); + +                char timeStamp[timeStampSize]; +                const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +                std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +                return std::string(timeStamp); +            } +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +            struct JUnitTestMessage +            { +                JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) +                    : message(_message), type(_type), details(_details) {} + +                JUnitTestMessage(const std::string& _message, const std::string& _details) +                    : message(_message), type(), details(_details) {} + +                std::string message, type, details; +            }; + +            struct JUnitTestCase +            { +                JUnitTestCase(const std::string& _classname, const std::string& _name) +                    : classname(_classname), name(_name), time(0), failures() {} + +                std::string classname, name; +                double time; +                std::vector<JUnitTestMessage> failures, errors; +            }; + +            void add(const std::string& classname, const std::string& name) { +                testcases.emplace_back(classname, name); +            } + +            void appendSubcaseNamesToLastTestcase(std::vector<String> nameStack) { +                for(auto& curr: nameStack) +                    if(curr.size()) +                        testcases.back().name += std::string("/") + curr.c_str(); +            } + +            void addTime(double time) { +                if(time < 1e-4) +                    time = 0; +                testcases.back().time = time; +                totalSeconds += time; +            } + +            void addFailure(const std::string& message, const std::string& type, const std::string& details) { +                testcases.back().failures.emplace_back(message, type, details); +                ++totalFailures; +            } + +            void addError(const std::string& message, const std::string& details) { +                testcases.back().errors.emplace_back(message, details); +                ++totalErrors; +            } + +            std::vector<JUnitTestCase> testcases; +            double totalSeconds = 0; +            int totalErrors = 0, totalFailures = 0; +        }; + +        JUnitTestCaseData testCaseData; + +        // caching pointers/references to objects of these types - safe to do +        const ContextOptions& opt; +        const TestCaseData*   tc = nullptr; + +        JUnitReporter(const ContextOptions& co) +                : xml(*co.cout) +                , opt(co) {} + +        unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + +        // ========================================================================================= +        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE +        // ========================================================================================= + +        void report_query(const QueryData&) override {} + +        void test_run_start() override {} + +        void test_run_end(const TestRunStats& p) override { +            // remove .exe extension - mainly to have the same output on UNIX and Windows +            std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS +            if(binary_name.rfind(".exe") != std::string::npos) +                binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS +            xml.startElement("testsuites"); +            xml.startElement("testsuite").writeAttribute("name", binary_name) +                    .writeAttribute("errors", testCaseData.totalErrors) +                    .writeAttribute("failures", testCaseData.totalFailures) +                    .writeAttribute("tests", p.numAsserts); +            if(opt.no_time_in_output == false) { +                xml.writeAttribute("time", testCaseData.totalSeconds); +                xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); +            } +            if(opt.no_version == false) +                xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); + +            for(const auto& testCase : testCaseData.testcases) { +                xml.startElement("testcase") +                    .writeAttribute("classname", testCase.classname) +                    .writeAttribute("name", testCase.name); +                if(opt.no_time_in_output == false) +                    xml.writeAttribute("time", testCase.time); +                // This is not ideal, but it should be enough to mimic gtest's junit output. +                xml.writeAttribute("status", "run"); + +                for(const auto& failure : testCase.failures) { +                    xml.scopedElement("failure") +                        .writeAttribute("message", failure.message) +                        .writeAttribute("type", failure.type) +                        .writeText(failure.details, false); +                } + +                for(const auto& error : testCase.errors) { +                    xml.scopedElement("error") +                        .writeAttribute("message", error.message) +                        .writeText(error.details); +                } + +                xml.endElement(); +            } +            xml.endElement(); +            xml.endElement(); +        } + +        void test_case_start(const TestCaseData& in) override { +            testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); +            timer.start(); +        } + +        void test_case_reenter(const TestCaseData& in) override { +            testCaseData.addTime(timer.getElapsedSeconds()); +            testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); +            deepestSubcaseStackNames.clear(); + +            timer.start(); +            testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); +        } + +        void test_case_end(const CurrentTestCaseStats&) override { +            testCaseData.addTime(timer.getElapsedSeconds()); +            testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); +            deepestSubcaseStackNames.clear(); +        } + +        void test_case_exception(const TestCaseException& e) override { +            std::lock_guard<std::mutex> lock(mutex); +            testCaseData.addError("exception", e.error_string.c_str()); +        } + +        void subcase_start(const SubcaseSignature& in) override { +            std::lock_guard<std::mutex> lock(mutex); +            deepestSubcaseStackNames.push_back(in.m_name); +        } + +        void subcase_end() override {} + +        void log_assert(const AssertData& rb) override { +            if(!rb.m_failed) // report only failures & ignore the `success` option +                return; + +            std::lock_guard<std::mutex> lock(mutex); + +            std::ostringstream os; +            os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") +              << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + +            fulltext_log_assert_to_stream(os, rb); +            testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); +        } + +        void log_message(const MessageData&) override {} + +        void test_case_skipped(const TestCaseData&) override {} +    }; + +    DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); + +    struct Whitespace +    { +        int nrSpaces; +        explicit Whitespace(int nr) +                : nrSpaces(nr) {} +    }; + +    std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { +        if(ws.nrSpaces != 0) +            out << std::setw(ws.nrSpaces) << ' '; +        return out; +    } + +    struct ConsoleReporter : public IReporter +    { +        std::ostream&                 s; +        bool                          hasLoggedCurrentTestStart; +        std::vector<SubcaseSignature> subcasesStack; +        size_t                        currentSubcaseLevel; +        std::mutex                    mutex; + +        // caching pointers/references to objects of these types - safe to do +        const ContextOptions& opt; +        const TestCaseData*   tc; + +        ConsoleReporter(const ContextOptions& co) +                : s(*co.cout) +                , opt(co) {} + +        ConsoleReporter(const ContextOptions& co, std::ostream& ostr) +                : s(ostr) +                , opt(co) {} + +        // ========================================================================================= +        // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE +        // ========================================================================================= + +        void separator_to_stream() { +            s << Color::Yellow +              << "===============================================================================" +                 "\n"; +        } + +        const char* getSuccessOrFailString(bool success, assertType::Enum at, +                                           const char* success_str) { +            if(success) +                return success_str; +            return failureString(at); +        } + +        Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { +            return success ? Color::BrightGreen : +                             (at & assertType::is_warn) ? Color::Yellow : Color::Red; +        } + +        void successOrFailColoredStringToStream(bool success, assertType::Enum at, +                                                const char* success_str = "SUCCESS") { +            s << getSuccessOrFailColor(success, at) +              << getSuccessOrFailString(success, at, success_str) << ": "; +        } + +        void log_contexts() { +            int num_contexts = get_num_active_contexts(); +            if(num_contexts) { +                auto contexts = get_active_contexts(); + +                s << Color::None << "  logged: "; +                for(int i = 0; i < num_contexts; ++i) { +                    s << (i == 0 ? "" : "          "); +                    contexts[i]->stringify(&s); +                    s << "\n"; +                } +            } + +            s << "\n"; +        } + +        // this was requested to be made virtual so users could override it +        virtual void file_line_to_stream(const char* file, int line, +                                        const char* tail = "") { +            s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") +            << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option +            << (opt.gnu_file_line ? ":" : "):") << tail; +        } + +        void logTestStart() { +            if(hasLoggedCurrentTestStart) +                return; + +            separator_to_stream(); +            file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); +            if(tc->m_description) +                s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; +            if(tc->m_test_suite && tc->m_test_suite[0] != '\0') +                s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; +            if(strncmp(tc->m_name, "  Scenario:", 11) != 0) +                s << Color::Yellow << "TEST CASE:  "; +            s << Color::None << tc->m_name << "\n"; + +            for(size_t i = 0; i < currentSubcaseLevel; ++i) { +                if(subcasesStack[i].m_name[0] != '\0') +                    s << "  " << subcasesStack[i].m_name << "\n"; +            } + +            if(currentSubcaseLevel != subcasesStack.size()) { +                s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; +                for(size_t i = 0; i < subcasesStack.size(); ++i) { +                    if(subcasesStack[i].m_name[0] != '\0') +                        s << "  " << subcasesStack[i].m_name << "\n"; +                } +            } + +            s << "\n"; + +            hasLoggedCurrentTestStart = true; +        } + +        void printVersion() { +            if(opt.no_version == false) +                s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" +                  << DOCTEST_VERSION_STR << "\"\n"; +        } + +        void printIntro() { +            printVersion(); +            s << Color::Cyan << "[doctest] " << Color::None +              << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; +        } + +        void printHelp() { +            int sizePrefixDisplay = static_cast<int>(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); +            printVersion(); +            // clang-format off +            s << Color::Cyan << "[doctest]\n" << Color::None; +            s << Color::Cyan << "[doctest] " << Color::None; +            s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; +            s << Color::Cyan << "[doctest] " << Color::None; +            s << "filter  values: \"str1,str2,str3\" (comma separated strings)\n"; +            s << Color::Cyan << "[doctest]\n" << Color::None; +            s << Color::Cyan << "[doctest] " << Color::None; +            s << "filters use wildcards for matching strings\n"; +            s << Color::Cyan << "[doctest] " << Color::None; +            s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +            s << Color::Cyan << "[doctest]\n" << Color::None; +            s << Color::Cyan << "[doctest] " << Color::None; +            s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif +            s << Color::Cyan << "[doctest]\n" << Color::None; +            s << Color::Cyan << "[doctest] " << Color::None; +            s << "Query flags - the program quits after them. Available:\n\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h                      " +              << Whitespace(sizePrefixDisplay*0) <<  "prints this message\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version                       " +              << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count                         " +              << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases               " +              << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites              " +              << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters                " +              << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; +            // ================================================================================== << 79 +            s << Color::Cyan << "[doctest] " << Color::None; +            s << "The available <int>/<string> options/filters are:\n\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case=<filters>           " +              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their name\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude=<filters>   " +              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file=<filters>         " +              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their file\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude=<filters> " +              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite=<filters>          " +              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their test suite\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude=<filters>  " +              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase=<filters>             " +              << Whitespace(sizePrefixDisplay*1) << "filters     subcases by their name\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude=<filters>     " +              << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters>           " +              << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string>                  " +              << Whitespace(sizePrefixDisplay*1) << "output filename\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string>             " +              << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; +            s << Whitespace(sizePrefixDisplay*3) << "                                       <string> - by [file/suite/name/rand]\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int>               " +              << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int>                   " +              << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; +            s << Whitespace(sizePrefixDisplay*3) << "                                       execute - for range-based execution\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last=<int>                    " +              << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; +            s << Whitespace(sizePrefixDisplay*3) << "                                       execute - for range-based execution\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after=<int>             " +              << Whitespace(sizePrefixDisplay*1) << "stop after <int> failed assertions\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels=<int>   " +              << Whitespace(sizePrefixDisplay*1) << "apply filters for the first <int> levels\n"; +            s << Color::Cyan << "\n[doctest] " << Color::None; +            s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success=<bool>                " +              << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive=<bool>         " +              << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit=<bool>                   " +              << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool>               " +              << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool>               " +              << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool>            " +              << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool>                 " +              << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool>             " +              << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool>              " +              << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors=<bool>           " +              << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks=<bool>              " +              << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip=<bool>                " +              << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line=<bool>          " +              << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames=<bool>      " +              << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; +            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers=<bool>        " +              << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; +            // ================================================================================== << 79 +            // clang-format on + +            s << Color::Cyan << "\n[doctest] " << Color::None; +            s << "for more information visit the project documentation\n\n"; +        } + +        void printRegisteredReporters() { +            printVersion(); +            auto printReporters = [this] (const reporterMap& reporters, const char* type) { +                if(reporters.size()) { +                    s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; +                    for(auto& curr : reporters) +                        s << "priority: " << std::setw(5) << curr.first.first +                          << " name: " << curr.first.second << "\n"; +                } +            }; +            printReporters(getListeners(), "listeners"); +            printReporters(getReporters(), "reporters"); +        } + +        void list_query_results() { +            separator_to_stream(); +            if(opt.count || opt.list_test_cases) { +                s << Color::Cyan << "[doctest] " << Color::None +                  << "unskipped test cases passing the current filters: " +                  << g_cs->numTestCasesPassingFilters << "\n"; +            } else if(opt.list_test_suites) { +                s << Color::Cyan << "[doctest] " << Color::None +                  << "unskipped test cases passing the current filters: " +                  << g_cs->numTestCasesPassingFilters << "\n"; +                s << Color::Cyan << "[doctest] " << Color::None +                  << "test suites with unskipped test cases passing the current filters: " +                  << g_cs->numTestSuitesPassingFilters << "\n"; +            } +        } + +        // ========================================================================================= +        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE +        // ========================================================================================= + +        void report_query(const QueryData& in) override { +            if(opt.version) { +                printVersion(); +            } else if(opt.help) { +                printHelp(); +            } else if(opt.list_reporters) { +                printRegisteredReporters(); +            } else if(opt.count || opt.list_test_cases) { +                if(opt.list_test_cases) { +                    s << Color::Cyan << "[doctest] " << Color::None +                      << "listing all test case names\n"; +                    separator_to_stream(); +                } + +                for(unsigned i = 0; i < in.num_data; ++i) +                    s << Color::None << in.data[i]->m_name << "\n"; + +                separator_to_stream(); + +                s << Color::Cyan << "[doctest] " << Color::None +                  << "unskipped test cases passing the current filters: " +                  << g_cs->numTestCasesPassingFilters << "\n"; + +            } else if(opt.list_test_suites) { +                s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; +                separator_to_stream(); + +                for(unsigned i = 0; i < in.num_data; ++i) +                    s << Color::None << in.data[i]->m_test_suite << "\n"; + +                separator_to_stream(); + +                s << Color::Cyan << "[doctest] " << Color::None +                  << "unskipped test cases passing the current filters: " +                  << g_cs->numTestCasesPassingFilters << "\n"; +                s << Color::Cyan << "[doctest] " << Color::None +                  << "test suites with unskipped test cases passing the current filters: " +                  << g_cs->numTestSuitesPassingFilters << "\n"; +            } +        } + +        void test_run_start() override { printIntro(); } + +        void test_run_end(const TestRunStats& p) override { +            separator_to_stream(); +            s << std::dec; + +            const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; +            s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6) +              << p.numTestCasesPassingFilters << " | " +              << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : +                                                                          Color::Green) +              << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" +              << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) +              << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | "; +            if(opt.no_skipped_summary == false) { +                const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; +                s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped +                  << " skipped" << Color::None; +            } +            s << "\n"; +            s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6) +              << p.numAsserts << " | " +              << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) +              << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None +              << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6) +              << p.numAssertsFailed << " failed" << Color::None << " |\n"; +            s << Color::Cyan << "[doctest] " << Color::None +              << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) +              << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; +        } + +        void test_case_start(const TestCaseData& in) override { +            hasLoggedCurrentTestStart = false; +            tc                        = ∈ +            subcasesStack.clear(); +            currentSubcaseLevel = 0; +        } +         +        void test_case_reenter(const TestCaseData&) override { +            subcasesStack.clear(); +        } + +        void test_case_end(const CurrentTestCaseStats& st) override { +            // log the preamble of the test case only if there is something +            // else to print - something other than that an assert has failed +            if(opt.duration || +               (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) +                logTestStart(); + +            if(opt.duration) +                s << Color::None << std::setprecision(6) << std::fixed << st.seconds +                  << " s: " << tc->m_name << "\n"; + +            if(st.failure_flags & TestCaseFailureReason::Timeout) +                s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) +                  << std::fixed << tc->m_timeout << "!\n"; + +            if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { +                s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; +            } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { +                s << Color::Yellow << "Failed as expected so marking it as not failed\n"; +            } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { +                s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; +            } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { +                s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures +                  << " times so marking it as failed!\n"; +            } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { +                s << Color::Yellow << "Failed exactly " << tc->m_expected_failures +                  << " times as expected so marking it as not failed!\n"; +            } +            if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { +                s << Color::Red << "Aborting - too many failed asserts!\n"; +            } +            s << Color::None; // lgtm [cpp/useless-expression] +        } + +        void test_case_exception(const TestCaseException& e) override { +            logTestStart(); + +            file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); +            successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : +                                                                   assertType::is_check); +            s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") +              << Color::Cyan << e.error_string << "\n"; + +            int num_stringified_contexts = get_num_stringified_contexts(); +            if(num_stringified_contexts) { +                auto stringified_contexts = get_stringified_contexts(); +                s << Color::None << "  logged: "; +                for(int i = num_stringified_contexts; i > 0; --i) { +                    s << (i == num_stringified_contexts ? "" : "          ") +                      << stringified_contexts[i - 1] << "\n"; +                } +            } +            s << "\n" << Color::None; +        } + +        void subcase_start(const SubcaseSignature& subc) override { +            std::lock_guard<std::mutex> lock(mutex); +            subcasesStack.push_back(subc); +            ++currentSubcaseLevel; +            hasLoggedCurrentTestStart = false; +        } + +        void subcase_end() override { +            std::lock_guard<std::mutex> lock(mutex); +            --currentSubcaseLevel; +            hasLoggedCurrentTestStart = false; +        } + +        void log_assert(const AssertData& rb) override { +            if(!rb.m_failed && !opt.success) +                return; + +            std::lock_guard<std::mutex> lock(mutex); + +            logTestStart(); + +            file_line_to_stream(rb.m_file, rb.m_line, " "); +            successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + +            fulltext_log_assert_to_stream(s, rb); + +            log_contexts(); +        } + +        void log_message(const MessageData& mb) override { +            std::lock_guard<std::mutex> lock(mutex); + +            logTestStart(); + +            file_line_to_stream(mb.m_file, mb.m_line, " "); +            s << getSuccessOrFailColor(false, mb.m_severity) +              << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, +                                        "MESSAGE") << ": "; +            s << Color::None << mb.m_string << "\n"; +            log_contexts(); +        } + +        void test_case_skipped(const TestCaseData&) override {} +    }; + +    DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS +    struct DebugOutputWindowReporter : public ConsoleReporter +    { +        DOCTEST_THREAD_LOCAL static std::ostringstream oss; + +        DebugOutputWindowReporter(const ContextOptions& co) +                : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg)                                    \ +    void func(type arg) override {                                                                 \ +        bool with_col = g_no_colors;                                                               \ +        g_no_colors   = false;                                                                     \ +        ConsoleReporter::func(arg);                                                                \ +        DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str());                                            \ +        oss.str("");                                                                               \ +        g_no_colors = with_col;                                                                    \ +    } + +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) +        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) +    }; + +    DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + +    // the implementation of parseOption() +    bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { +        // going from the end to the beginning and stopping on the first occurrence from the end +        for(int i = argc; i > 0; --i) { +            auto index = i - 1; +            auto temp = std::strstr(argv[index], pattern); +            if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue +                // eliminate matches in which the chars before the option are not '-' +                bool noBadCharsFound = true; +                auto curr            = argv[index]; +                while(curr != temp) { +                    if(*curr++ != '-') { +                        noBadCharsFound = false; +                        break; +                    } +                } +                if(noBadCharsFound && argv[index][0] == '-') { +                    if(value) { +                        // parsing the value of an option +                        temp += strlen(pattern); +                        const unsigned len = strlen(temp); +                        if(len) { +                            *value = temp; +                            return true; +                        } +                    } else { +                        // just a flag - no value +                        return true; +                    } +                } +            } +        } +        return false; +    } + +    // parses an option and returns the string after the '=' character +    bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, +                     const String& defaultVal = String()) { +        if(value) +            *value = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +        // offset (normally 3 for "dt-") to skip prefix +        if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) +            return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +        return parseOptionImpl(argc, argv, pattern, value); +    } + +    // locates a flag on the command line +    bool parseFlag(int argc, const char* const* argv, const char* pattern) { +        return parseOption(argc, argv, pattern); +    } + +    // parses a comma separated list of words after a pattern in one of the arguments in argv +    bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, +                           std::vector<String>& res) { +        String filtersString; +        if(parseOption(argc, argv, pattern, &filtersString)) { +            // tokenize with "," as a separator +            // cppcheck-suppress strtokCalled +            DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") +            auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string +            while(pch != nullptr) { +                if(strlen(pch)) +                    res.push_back(pch); +                // uses the strtok() internal state to go to the next token +                // cppcheck-suppress strtokCalled +                pch = std::strtok(nullptr, ","); +            } +            DOCTEST_CLANG_SUPPRESS_WARNING_POP +            return true; +        } +        return false; +    } + +    enum optionType +    { +        option_bool, +        option_int +    }; + +    // parses an int/bool option from the command line +    bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, +                        int& res) { +        String parsedValue; +        if(!parseOption(argc, argv, pattern, &parsedValue)) +            return false; + +        if(type == 0) { +            // boolean +            const char positive[][5] = {"1", "true", "on", "yes"};  // 5 - strlen("true") + 1 +            const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + +            // if the value matches any of the positive/negative possibilities +            for(unsigned i = 0; i < 4; i++) { +                if(parsedValue.compare(positive[i], true) == 0) { +                    res = 1; //!OCLINT parameter reassignment +                    return true; +                } +                if(parsedValue.compare(negative[i], true) == 0) { +                    res = 0; //!OCLINT parameter reassignment +                    return true; +                } +            } +        } else { +            // integer +            // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... +            int theInt = std::atoi(parsedValue.c_str()); // NOLINT +            if(theInt != 0) { +                res = theInt; //!OCLINT parameter reassignment +                return true; +            } +        } +        return false; +    } +} // namespace + +Context::Context(int argc, const char* const* argv) +        : p(new detail::ContextState) { +    parseArgs(argc, argv, true); +    if(argc) +        p->binary_name = argv[0]; +} + +Context::~Context() { +    if(g_cs == p) +        g_cs = nullptr; +    delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { +    parseArgs(argc, argv); +    if(argc) +        p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { +    using namespace detail; + +    // clang-format off +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=",        p->filters[0]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=",                 p->filters[0]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=",                p->filters[1]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=",         p->filters[2]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=",                 p->filters[2]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=",                p->filters[3]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=",          p->filters[4]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=",                 p->filters[4]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=",  p->filters[5]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=",                p->filters[5]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=",            p->filters[6]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=",                 p->filters[6]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=",    p->filters[7]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=",                p->filters[7]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=",          p->filters[8]); +    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=",                  p->filters[8]); +    // clang-format on + +    int    intRes = 0; +    String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default)                                   \ +    if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) ||  \ +       parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes))   \ +        p->var = !!intRes;                                                                         \ +    else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) ||                           \ +            parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname))                            \ +        p->var = true;                                                                             \ +    else if(withDefaults)                                                                          \ +    p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default)                                        \ +    if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) ||   \ +       parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes))    \ +        p->var = intRes;                                                                           \ +    else if(withDefaults)                                                                          \ +    p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default)                                        \ +    if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) ||        \ +       parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) ||       \ +       withDefaults)                                                                               \ +    p->var = strRes + +    // clang-format off +    DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); +    DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); +    DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + +    DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); +    DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + +    DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); +    DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); +    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); +    // clang-format on + +    if(withDefaults) { +        p->help             = false; +        p->version          = false; +        p->count            = false; +        p->list_test_cases  = false; +        p->list_test_suites = false; +        p->list_reporters   = false; +    } +    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || +       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || +       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { +        p->help = true; +        p->exit = true; +    } +    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || +       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { +        p->version = true; +        p->exit    = true; +    } +    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || +       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { +        p->count = true; +        p->exit  = true; +    } +    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || +       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { +        p->list_test_cases = true; +        p->exit            = true; +    } +    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || +       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { +        p->list_test_suites = true; +        p->exit             = true; +    } +    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || +       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { +        p->list_reporters = true; +        p->exit           = true; +    } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { +    for(auto& curr : p->filters) +        curr.clear(); +} + +// allows the user to override procedurally the int/bool options from the command line +void Context::setOption(const char* option, int value) { +    setOption(option, toString(value).c_str()); +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { +    auto argv   = String("-") + option + "=" + value; +    auto lvalue = argv.c_str(); +    parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +// the main function that does all the filtering and test running +int Context::run() { +    using namespace detail; + +    // save the old context state in case such was setup - for using asserts out of a testing context +    auto old_cs = g_cs; +    // this is the current contest +    g_cs               = p; +    is_running_in_test = true; + +    g_no_colors = p->no_colors; +    p->resetRunData(); + +    // stdout by default +    p->cout = &std::cout; +    p->cerr = &std::cerr; + +    // or to a file if specified +    std::fstream fstr; +    if(p->out.size()) { +        fstr.open(p->out.c_str(), std::fstream::out); +        p->cout = &fstr; +    } + +    auto cleanup_and_return = [&]() { +        if(fstr.is_open()) +            fstr.close(); + +        // restore context +        g_cs               = old_cs; +        is_running_in_test = false; + +        // we have to free the reporters which were allocated when the run started +        for(auto& curr : p->reporters_currently_used) +            delete curr; +        p->reporters_currently_used.clear(); + +        if(p->numTestCasesFailed && !p->no_exitcode) +            return EXIT_FAILURE; +        return EXIT_SUCCESS; +    }; + +    // setup default reporter if none is given through the command line +    if(p->filters[8].empty()) +        p->filters[8].push_back("console"); + +    // check to see if any of the registered reporters has been selected +    for(auto& curr : getReporters()) { +        if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) +            p->reporters_currently_used.push_back(curr.second(*g_cs)); +    } + +    // TODO: check if there is nothing in reporters_currently_used + +    // prepend all listeners +    for(auto& curr : getListeners()) +        p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); + +#ifdef DOCTEST_PLATFORM_WINDOWS +    if(isDebuggerActive()) +        p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + +    // handle version, help and no_run +    if(p->no_run || p->version || p->help || p->list_reporters) { +        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + +        return cleanup_and_return(); +    } + +    std::vector<const TestCase*> testArray; +    for(auto& curr : getRegisteredTests()) +        testArray.push_back(&curr); +    p->numTestCases = testArray.size(); + +    // sort the collected records +    if(!testArray.empty()) { +        if(p->order_by.compare("file", true) == 0) { +            std::sort(testArray.begin(), testArray.end(), fileOrderComparator); +        } else if(p->order_by.compare("suite", true) == 0) { +            std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); +        } else if(p->order_by.compare("name", true) == 0) { +            std::sort(testArray.begin(), testArray.end(), nameOrderComparator); +        } else if(p->order_by.compare("rand", true) == 0) { +            std::srand(p->rand_seed); + +            // random_shuffle implementation +            const auto first = &testArray[0]; +            for(size_t i = testArray.size() - 1; i > 0; --i) { +                int idxToSwap = std::rand() % (i + 1); // NOLINT + +                const auto temp = first[i]; + +                first[i]         = first[idxToSwap]; +                first[idxToSwap] = temp; +            } +        } +    } + +    std::set<String> testSuitesPassingFilt; + +    bool                             query_mode = p->count || p->list_test_cases || p->list_test_suites; +    std::vector<const TestCaseData*> queryResults; + +    if(!query_mode) +        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + +    // invoke the registered functions if they match the filter criteria (or just count them) +    for(auto& curr : testArray) { +        const auto& tc = *curr; + +        bool skip_me = false; +        if(tc.m_skip && !p->no_skip) +            skip_me = true; + +        if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) +            skip_me = true; +        if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) +            skip_me = true; +        if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) +            skip_me = true; +        if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) +            skip_me = true; +        if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) +            skip_me = true; +        if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) +            skip_me = true; + +        if(!skip_me) +            p->numTestCasesPassingFilters++; + +        // skip the test if it is not in the execution range +        if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || +           (p->first > p->numTestCasesPassingFilters)) +            skip_me = true; + +        if(skip_me) { +            if(!query_mode) +                DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); +            continue; +        } + +        // do not execute the test if we are to only count the number of filter passing tests +        if(p->count) +            continue; + +        // print the name of the test and don't execute it +        if(p->list_test_cases) { +            queryResults.push_back(&tc); +            continue; +        } + +        // print the name of the test suite if not done already and don't execute it +        if(p->list_test_suites) { +            if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { +                queryResults.push_back(&tc); +                testSuitesPassingFilt.insert(tc.m_test_suite); +                p->numTestSuitesPassingFilters++; +            } +            continue; +        } + +        // execute the test if it passes all the filtering +        { +            p->currentTest = &tc; + +            p->failure_flags = TestCaseFailureReason::None; +            p->seconds       = 0; + +            // reset atomic counters +            p->numAssertsFailedCurrentTest_atomic = 0; +            p->numAssertsCurrentTest_atomic       = 0; + +            p->subcasesPassed.clear(); + +            DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + +            p->timer.start(); +             +            bool run_test = true; + +            do { +                // reset some of the fields for subcases (except for the set of fully passed ones) +                p->should_reenter          = false; +                p->subcasesCurrentMaxLevel = 0; +                p->subcasesStack.clear(); + +                p->shouldLogCurrentException = true; + +                // reset stuff for logging with INFO() +                p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +                try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +                    FatalConditionHandler fatalConditionHandler; // Handle signals +                    // execute the test +                    tc.m_test(); +                    fatalConditionHandler.reset(); +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +                } catch(const TestFailureException&) { +                    p->failure_flags |= TestCaseFailureReason::AssertFailure; +                } catch(...) { +                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, +                                                      {translateActiveException(), false}); +                    p->failure_flags |= TestCaseFailureReason::Exception; +                } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +                // exit this loop if enough assertions have failed - even if there are more subcases +                if(p->abort_after > 0 && +                   p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { +                    run_test = false; +                    p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; +                } +                 +                if(p->should_reenter && run_test) +                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); +                if(!p->should_reenter) +                    run_test = false; +            } while(run_test); + +            p->finalizeTestCaseData(); + +            DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + +            p->currentTest = nullptr; + +            // stop executing tests if enough assertions have failed +            if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) +                break; +        } +    } + +    if(!query_mode) { +        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); +    } else { +        QueryData qdata; +        qdata.run_stats = g_cs; +        qdata.data      = queryResults.data(); +        qdata.num_data  = unsigned(queryResults.size()); +        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); +    } + +    // see these issues on the reasoning for this: +    // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 +    // - https://github.com/onqtam/doctest/issues/126 +    auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE +        { std::cout << std::string(); }; +    DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); + +    return cleanup_and_return(); +} + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { +    return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { +    return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { +    void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { +        if(isReporter) +            getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); +        else +            getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); +    } +} // namespace detail + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/thirdparty/doctest/patches/fix-arm64-mac.patch b/thirdparty/doctest/patches/fix-arm64-mac.patch new file mode 100644 index 0000000000..f78014534f --- /dev/null +++ b/thirdparty/doctest/patches/fix-arm64-mac.patch @@ -0,0 +1,18 @@ +diff --git a/thirdparty/doctest/doctest.h b/thirdparty/doctest/doctest.h +index 9444698286..e4fed12767 100644 +--- a/thirdparty/doctest/doctest.h ++++ b/thirdparty/doctest/doctest.h +@@ -356,7 +356,13 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + #ifndef DOCTEST_BREAK_INTO_DEBUGGER + // should probably take a look at https://github.com/scottt/debugbreak + #ifdef DOCTEST_PLATFORM_MAC ++// -- GODOT start -- ++#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) + #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) ++#else ++#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); ++#endif ++// -- GODOT end -- + #elif DOCTEST_MSVC + #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() + #elif defined(__MINGW32__) diff --git a/thirdparty/vulkan/patches/VMA-assert-remove.patch b/thirdparty/vulkan/patches/VMA-assert-remove.patch new file mode 100644 index 0000000000..3d57ab7d42 --- /dev/null +++ b/thirdparty/vulkan/patches/VMA-assert-remove.patch @@ -0,0 +1,29 @@ +diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h +index 0dfb66efc6..8a42699e7f 100644 +--- a/thirdparty/vulkan/vk_mem_alloc.h ++++ b/thirdparty/vulkan/vk_mem_alloc.h +@@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( +         allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq, +             requiresDedicatedAllocation, prefersDedicatedAllocation); +  +-        // Make sure alignment requirements for specific buffer usages reported +-        // in Physical Device Properties are included in alignment reported by memory requirements. +-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0) +-        { +-           VMA_ASSERT(vkMemReq.alignment % +-              allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0); +-        } +-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0) +-        { +-           VMA_ASSERT(vkMemReq.alignment % +-              allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0); +-        } +-        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0) +-        { +-           VMA_ASSERT(vkMemReq.alignment % +-              allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0); +-        } +- +         // 3. Allocate memory using allocator. +         res = allocator->AllocateMemory( +             vkMemReq, diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h index 0dfb66efc6..8a42699e7f 100644 --- a/thirdparty/vulkan/vk_mem_alloc.h +++ b/thirdparty/vulkan/vk_mem_alloc.h @@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(          allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,              requiresDedicatedAllocation, prefersDedicatedAllocation); -        // Make sure alignment requirements for specific buffer usages reported -        // in Physical Device Properties are included in alignment reported by memory requirements. -        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0) -        { -           VMA_ASSERT(vkMemReq.alignment % -              allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0); -        } -        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0) -        { -           VMA_ASSERT(vkMemReq.alignment % -              allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0); -        } -        if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0) -        { -           VMA_ASSERT(vkMemReq.alignment % -              allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0); -        } -          // 3. Allocate memory using allocator.          res = allocator->AllocateMemory(              vkMemReq,  |