diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index d0bfe2765..8fd646da9 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -403,7 +403,7 @@ int32 Editor::LoadProduct() } // Create new project option - if (CommandLine::Options.NewProject) + if (CommandLine::Options.NewProject.IsTrue()) { Array projectFiles; FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly); @@ -428,7 +428,7 @@ int32 Editor::LoadProduct() } } } - if (CommandLine::Options.NewProject) + if (CommandLine::Options.NewProject.IsTrue()) { if (projectPath.IsEmpty()) projectPath = Platform::GetWorkingDirectory(); @@ -529,7 +529,7 @@ int32 Editor::LoadProduct() if (projectPath.IsEmpty()) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { Platform::Fatal(TEXT("Missing project path.")); return -1; @@ -657,7 +657,7 @@ Window* Editor::CreateMainWindow() bool Editor::Init() { // Scripts project files generation from command line - if (CommandLine::Options.GenProjectFiles) + if (CommandLine::Options.GenProjectFiles.IsTrue()) { const String customArgs = TEXT("-verbose -log -logfile=\"Cache/Intermediate/ProjectFileLog.txt\""); const bool failed = ScriptsBuilder::GenerateProject(customArgs); diff --git a/Source/Editor/Windows/SplashScreen.cpp b/Source/Editor/Windows/SplashScreen.cpp index 49257d281..39f7691e7 100644 --- a/Source/Editor/Windows/SplashScreen.cpp +++ b/Source/Editor/Windows/SplashScreen.cpp @@ -147,7 +147,7 @@ SplashScreen::~SplashScreen() void SplashScreen::Show() { // Skip if already shown or in headless mode - if (IsVisible() || CommandLine::Options.Headless) + if (IsVisible() || CommandLine::Options.Headless.IsTrue()) return; LOG(Info, "Showing splash screen"); diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index c8cf4419e..38d591c51 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -119,7 +119,7 @@ void Log::Logger::Write(const StringView& msg) IsDuringLog = true; // Send message to standard process output - if (CommandLine::Options.Std) + if (CommandLine::Options.Std.IsTrue()) { #if PLATFORM_TEXT_IS_CHAR16 StringAnsi ansi(msg); diff --git a/Source/Engine/Core/Types/Nullable.h b/Source/Engine/Core/Types/Nullable.h index 9511f0df2..43fe5342a 100644 --- a/Source/Engine/Core/Types/Nullable.h +++ b/Source/Engine/Core/Types/Nullable.h @@ -2,50 +2,178 @@ #pragma once +#include "Engine/Core/Templates.h" #include "Engine/Platform/Platform.h" /// -/// Represents a value type that can be assigned null. A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. +/// Wrapper for a value type that can be assigned null, controlling the lifetime of the wrapped value. /// +/// +/// The type of the wrapped value. It must be move-constructible but does not have to be copy-constructible. Value is never reassigned. +/// template -struct NullableBase +struct Nullable { -protected: +private: + struct Dummy { Dummy() {} }; + + union + { + T _value; + Dummy _dummy; + }; bool _hasValue; - T _value; public: /// - /// Initializes a new instance of the struct. + /// Initializes by setting the wrapped value to null. /// - NullableBase() + Nullable() + : _dummy() + , _hasValue(false) { - _hasValue = false; + // Value is not initialized. + } + + ~Nullable() + { + if (_hasValue) + { + _value.~T(); + } } /// - /// Initializes a new instance of the struct. + /// Initializes by copying the wrapped value. /// - /// The initial value. - NullableBase(const T& value) + /// The initial wrapped value to be copied. + template::Value>::Type> + Nullable(const T& value) + : _value(value) + , _hasValue(true) { - _value = value; - _hasValue = true; } /// - /// Initializes a new instance of the struct. + /// Initializes by moving the wrapped value. /// - /// The other. - NullableBase(const NullableBase& other) + /// The initial wrapped value to be moved. + Nullable(T&& value) noexcept + : _value(MoveTemp(value)) + , _hasValue(true) { - _value = other._value; + } + + /// + /// Initializes by copying another . + /// + /// The wrapped value to be copied. + template::Value>::Type> + Nullable(const Nullable& other) + : _value(other._value) + , _hasValue(other._hasValue) + { + } + + /// + /// Initializes by moving another . + /// + /// The wrapped value to be moved. + Nullable(Nullable&& other) noexcept + { + if (other._hasValue) + { + new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) + } _hasValue = other._hasValue; + + other.Reset(); } -public: /// - /// Gets a value indicating whether the current NullableBase{T} object has a valid value of its underlying type. + /// Reassigns the wrapped value by copying. + /// + template::Value>::Type> + auto operator=(const T& value) -> Nullable& + { + if (_hasValue) + { + _value.~T(); + } + + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; + + return *this; + } + + /// + /// Reassigns the wrapped value by moving. + /// + auto operator=(T&& value) noexcept -> Nullable& + { + if (_hasValue) + { + _value.~T(); + } + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; + + return *this; + } + + /// + /// Reassigns the wrapped value by copying another . + /// + template::Value>::Type> + auto operator=(const Nullable& other) -> Nullable& + { + if (_hasValue) + { + _value.~T(); + } + + if (other._hasValue) + { + new (&_value) T(other._value); // Placement new (copy constructor) + } + + _hasValue = other._hasValue; // Set the flag AFTER the value is copied. + + return *this; + } + + /// + /// Reassigns the wrapped value by moving another . + /// + auto operator=(Nullable&& other) noexcept -> Nullable& + { + if (this == &other) + { + return *this; + } + + if (_hasValue) + { + _value.~T(); + } + + if (other._hasValue) + { + new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor) + + other._value.~T(); // Kill the old value in the source object. + other._hasValue = false; + } + + _hasValue = other._hasValue; // Set the flag AFTER the value is moved. + + return *this; + } + + /// + /// Checks if wrapped object has a valid value. /// /// true if this object has a valid value; otherwise, false. FORCE_INLINE bool HasValue() const @@ -54,9 +182,9 @@ public: } /// - /// Gets the value of the current NullableBase{T} object if it has been assigned a valid underlying value. + /// Gets a const reference to the wrapped value. If the value is not valid, the behavior is undefined. /// - /// The value. + /// Reference to the wrapped value. FORCE_INLINE const T& GetValue() const { ASSERT(_hasValue); @@ -64,152 +192,331 @@ public: } /// - /// Gets the value of the current NullableBase{T} object if it has been assigned a valid underlying value. + /// Gets a reference to the wrapped value. If the value is not valid, the behavior is undefined. + /// This method can be used to reassign the wrapped value. /// - /// The value. - FORCE_INLINE T GetValue() + /// Reference to the wrapped value. + FORCE_INLINE T& GetValue() { ASSERT(_hasValue); return _value; } /// - /// Sets the value. + /// Gets a const reference to the wrapped value or a default value if the value is not valid. /// - /// The value. - void SetValue(const T& value) + /// Reference to the wrapped value or the default value. + FORCE_INLINE const T& GetValueOr(const T& defaultValue) const { - _value = value; - _hasValue = true; + return _hasValue ? _value : defaultValue; } /// - /// Resets the value. + /// Gets a mutable reference to the wrapped value or a default value if the value is not valid. /// - void Reset() + /// Reference to the wrapped value or the default value. + FORCE_INLINE T& GetValueOr(T& defaultValue) const { - _hasValue = false; + return _hasValue ? _value : defaultValue; } -public: /// - /// Indicates whether the current NullableBase{T} object is equal to a specified object. + /// Sets the wrapped value by copying. /// - /// The other object. - /// True if both values are equal. - bool operator==(const NullableBase& other) const + /// The value to be copied. + template::Value>::Type> + FORCE_INLINE void SetValue(const T& value) { if (_hasValue) { - return other._hasValue && _value == other._value; + _value.~T(); } - return !other._hasValue; + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; // Set the flag AFTER the value is copied. } /// - /// Indicates whether the current NullableBase{T} object is not equal to a specified object. + /// Sets the wrapped value by moving. + /// + /// The value to be moved. + FORCE_INLINE void SetValue(T&& value) noexcept + { + if (_hasValue) + { + _value.~T(); + } + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; // Set the flag AFTER the value is moved. + } + + /// + /// If the wrapped value is not valid, sets it by copying. Otherwise, does nothing. + /// + /// True if the wrapped value was changed, otherwise false. + template::Value>::Type> + FORCE_INLINE bool TrySet(const T& value) + { + if (_hasValue) + { + return false; + } + + new (&_value) T(value); // Placement new (copy constructor) + _hasValue = true; // Set the flag AFTER the value is copied. + return true; + } + + /// + /// If the wrapped value is not valid, sets it by moving. Otherwise, does nothing. + /// + /// True if the wrapped value was changed, otherwise false. + FORCE_INLINE bool TrySet(T&& value) noexcept + { + if (_hasValue) + { + return false; + } + + new (&_value) T(MoveTemp(value)); // Placement new (move constructor) + _hasValue = true; // Set the flag AFTER the value is moved. + return true; + } + + /// + /// Disposes the wrapped value and sets the wrapped value to null. If the wrapped value is not valid, does nothing. + /// + FORCE_INLINE void Reset() + { + if (!_hasValue) + { + return; + } + + _hasValue = false; // Reset the flag BEFORE the value is (potentially) destructed. + _value.~T(); + } + + /// + /// Moves the wrapped value to the output parameter and sets the wrapped value to null. If the wrapped value is not valid, the behavior is undefined. + /// + /// The output parameter that will receive the wrapped value. + FORCE_INLINE void GetAndReset(T& value) + { + ASSERT(_hasValue); + value = MoveTemp(_value); + Reset(); + } + + /// + /// Indicates whether this instance is equal to other one. /// /// The other object. - /// True if both values are not equal. - FORCE_INLINE bool operator!=(const NullableBase& other) const + /// true if both values are equal. + FORCE_INLINE bool operator==(const Nullable& other) const + { + if (other._hasValue != _hasValue) + { + return false; + } + + return _value == other._value; + } + + /// + /// Indicates whether this instance is NOT equal to other one. + /// + /// The other object. + /// true if both values are not equal. + FORCE_INLINE bool operator!=(const Nullable& other) const { return !operator==(other); } -}; -/// -/// Represents a value type that can be assigned null. A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. -/// -template -struct Nullable : NullableBase -{ -public: /// - /// Initializes a new instance of the struct. + /// Explicit conversion to boolean value. Allows to check if the wrapped value is valid in if-statements without casting. /// - Nullable() - : NullableBase() + /// true if this object has a valid value, otherwise false + FORCE_INLINE explicit operator bool() const { + return _hasValue; } - /// - /// Initializes a new instance of the struct. - /// - /// The initial value. - Nullable(const T& value) - : NullableBase(value) - { - } /// - /// Initializes a new instance of the struct. + /// Matches the wrapped value with a handler for the value or a handler for the null value. /// - /// The other. - Nullable(const Nullable& other) - : NullableBase(other) + /// Value visitor handling valid nullable value. + /// Null visitor handling invalid nullable value. + /// Result of the call of one of handlers. Handlers must share the same result type. + template + FORCE_INLINE auto Match(ValueVisitor valueHandler, NullVisitor nullHandler) const { + if (_hasValue) + { + return valueHandler(_value); + } + else + { + return nullHandler(); + } } }; /// -/// Nullable value container that contains a boolean value or null. +/// Specialization of for type. /// template<> -struct Nullable : NullableBase +struct Nullable { -public: +private: /// - /// Initializes a new instance of the struct. + /// Underlying value of the nullable boolean. Uses only one byte to optimize memory usage. /// - Nullable() - : NullableBase() + enum class Value : uint8 { - } + Null, + False, + True, + }; - /// - /// Initializes a new instance of the struct. - /// - /// The initial value. - Nullable(bool value) - : NullableBase(value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The other. - Nullable(const Nullable& other) - : NullableBase(other) - { - } + Value _value = Value::Null; public: /// - /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to true. + /// Initializes nullable boolean by setting the wrapped value to null. + /// + Nullable() = default; + + ~Nullable() = default; + + /// + /// Initializes nullable boolean by moving another nullable boolean. + /// + Nullable(Nullable&& value) = default; + + /// + /// Initializes nullable boolean by copying another nullable boolean. + /// + Nullable(const Nullable& value) = default; + + /// + /// Initializes nullable boolean by implicitly casting a boolean value. + /// + Nullable(const bool value) noexcept + { + _value = value ? Value::True : Value::False; + } + + + /// + /// Reassigns the wrapped value by implicitly casting a boolean value. + /// + auto operator=(const bool value) noexcept -> Nullable& + { + _value = value ? Value::True : Value::False; + return *this; + } + + /// + /// Reassigns the wrapped value by copying another nullable boolean. + /// + auto operator=(const Nullable& value) -> Nullable& = default; + + /// + /// Reassigns the wrapped value by moving another nullable boolean. + /// + auto operator=(Nullable&& value) -> Nullable& = default; + + + /// + /// Checks if wrapped bool has a valid value. + /// + FORCE_INLINE bool HasValue() const noexcept + { + return _value != Value::Null; + } + + /// + /// Gets the wrapped boolean value. If the value is not valid, the behavior is undefined. + /// + FORCE_INLINE bool GetValue() const + { + ASSERT(_value != Value::Null); + return _value == Value::True; + } + + /// + /// Gets the wrapped boolean value. If the value is not valid, returns the default value. + /// + FORCE_INLINE bool GetValueOr(const bool defaultValue) const noexcept + { + return _value == Value::Null ? defaultValue : _value == Value::True; + } + + /// + /// Sets the wrapped value to a valid boolean. + /// + FORCE_INLINE void SetValue(const bool value) noexcept + { + _value = value ? Value::True : Value::False; + } + + /// + /// If the wrapped value is not valid, sets it to a valid boolean. + /// + FORCE_INLINE bool TrySet(const bool value) noexcept + { + if (_value != Value::Null) + { + return false; + } + + _value = value ? Value::True : Value::False; + return true; + } + + /// + /// Sets the wrapped bool to null. + /// + FORCE_INLINE void Reset() noexcept + { + _value = Value::Null; + } + + /// + /// Moves the wrapped value to the output parameter and sets the wrapped value to null. If the wrapped value is not valid, the behavior is undefined. + /// + FORCE_INLINE void GetAndReset(bool& value) noexcept + { + ASSERT(_value != Value::Null); + value = _value == Value::True; + _value = Value::Null; + } + + + /// + /// Checks if the current object has a valid value and it's set to true. If the value is false or not valid, the method returns false. /// - /// true if this object has a valid value set to true; otherwise, false. FORCE_INLINE bool IsTrue() const { - return _hasValue && _value; + return _value == Value::True; } /// - /// Gets a value indicating whether the current Nullable{T} object has a valid value and it's set to false. + /// Checks if the current object has a valid value and it's set to false. If the value is true or not valid, the method returns false. /// - /// true if this object has a valid value set to false; otherwise, false. FORCE_INLINE bool IsFalse() const { - return _hasValue && !_value; + return _value == Value::False; } /// - /// Implicit conversion to boolean value. + /// Deletes implicit conversion to bool to prevent ambiguous code. /// - /// True if this object has a valid value set to true, otherwise false - FORCE_INLINE operator bool() const - { - return _hasValue && _value; - } + /// + /// Implicit cast from nullable bool to a bool produces unacceptably ambiguous code. For template meta-programming use explicit HasValue instead. + /// + explicit operator bool() const = delete; }; diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index b26cbd25a..7fea9cd6d 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -631,9 +631,9 @@ void EngineImpl::InitPaths() FileSystem::CreateDirectory(Globals::ProjectContentFolder); if (!FileSystem::DirectoryExists(Globals::ProjectSourceFolder)) FileSystem::CreateDirectory(Globals::ProjectSourceFolder); - if (CommandLine::Options.ClearCache) + if (CommandLine::Options.ClearCache.IsTrue()) FileSystem::DeleteDirectory(Globals::ProjectCacheFolder, true); - else if (CommandLine::Options.ClearCookerCache) + else if (CommandLine::Options.ClearCookerCache.IsTrue()) FileSystem::DeleteDirectory(Globals::ProjectCacheFolder / TEXT("Cooker"), true); if (!FileSystem::DirectoryExists(Globals::ProjectCacheFolder)) FileSystem::CreateDirectory(Globals::ProjectCacheFolder); diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 3cd600955..b4fbefb7a 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -104,7 +104,7 @@ bool GraphicsService::Init() GPUDevice* device = nullptr; // Null - if (!device && CommandLine::Options.Null) + if (!device && CommandLine::Options.Null.IsTrue()) { #if GRAPHICS_API_NULL device = CreateGPUDeviceNull(); @@ -114,7 +114,7 @@ bool GraphicsService::Init() } // Vulkan - if (!device && CommandLine::Options.Vulkan) + if (!device && CommandLine::Options.Vulkan.IsTrue()) { #if GRAPHICS_API_VULKAN device = CreateGPUDeviceVulkan(); @@ -124,7 +124,7 @@ bool GraphicsService::Init() } // DirectX 12 - if (!device && CommandLine::Options.D3D12) + if (!device && CommandLine::Options.D3D12.IsTrue()) { #if GRAPHICS_API_DIRECTX12 if (Platform::IsWindows10()) @@ -137,7 +137,7 @@ bool GraphicsService::Init() } // DirectX 11 and DirectX 10 - if (!device && (CommandLine::Options.D3D11 || CommandLine::Options.D3D10)) + if (!device && (CommandLine::Options.D3D11.IsTrue() || CommandLine::Options.D3D10.IsTrue())) { #if GRAPHICS_API_DIRECTX11 device = CreateGPUDeviceDX11(); @@ -193,10 +193,10 @@ bool GraphicsService::Init() // Initialize if (device->IsDebugToolAttached #if USE_EDITOR || !BUILD_RELEASE - || CommandLine::Options.ShaderProfile + || CommandLine::Options.ShaderProfile.IsTrue() #endif #if USE_EDITOR - || CommandLine::Options.ShaderDebug + || CommandLine::Options.ShaderDebug.IsTrue() #endif ) { diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp index 6f91ff4be..acc1dc7db 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderAssetBase.cpp @@ -249,12 +249,12 @@ bool ShaderAssetBase::LoadShaderCache(ShaderCacheResult& result) options.SourceLength = sourceLength; options.Profile = shaderProfile; options.Output = &cacheStream; - if (CommandLine::Options.ShaderDebug) + if (CommandLine::Options.ShaderDebug.IsTrue()) { options.GenerateDebugData = true; options.NoOptimize = true; } - else if (CommandLine::Options.ShaderProfile) + else if (CommandLine::Options.ShaderProfile.IsTrue()) { options.GenerateDebugData = true; } diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp index b8adce351..112324b2c 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp @@ -193,8 +193,8 @@ bool ShaderCacheManagerService::Init() CacheVersion cacheVersion; const String cacheVerFile = rootDir / TEXT("CacheVersion"); #if USE_EDITOR - const bool shaderDebug = CommandLine::Options.ShaderDebug; - const bool shaderProfile = CommandLine::Options.ShaderProfile; + const bool shaderDebug = CommandLine::Options.ShaderDebug.IsTrue(); + const bool shaderProfile = CommandLine::Options.ShaderProfile.IsTrue(); #else const bool shaderDebug = false; #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp index 432a44ee8..724a5ea71 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUDeviceDX11.cpp @@ -106,9 +106,9 @@ GPUDevice* GPUDeviceDX11::Create() #else D3D_FEATURE_LEVEL maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_11_0; #endif - if (CommandLine::Options.D3D10) + if (CommandLine::Options.D3D10.IsTrue()) maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_10_0; - else if (CommandLine::Options.D3D11) + else if (CommandLine::Options.D3D11.IsTrue()) maxAllowedFeatureLevel = D3D_FEATURE_LEVEL_11_0; #if !USE_EDITOR && PLATFORM_WINDOWS auto winSettings = WindowsPlatformSettings::Get(); @@ -209,11 +209,11 @@ GPUDevice* GPUDeviceDX11::Create() } GPUAdapterDX selectedAdapter = adapters[selectedAdapterIndex]; uint32 vendorId = 0; - if (CommandLine::Options.NVIDIA) + if (CommandLine::Options.NVIDIA.IsTrue()) vendorId = GPU_VENDOR_ID_NVIDIA; - else if (CommandLine::Options.AMD) + else if (CommandLine::Options.AMD.IsTrue()) vendorId = GPU_VENDOR_ID_AMD; - else if (CommandLine::Options.Intel) + else if (CommandLine::Options.Intel.IsTrue()) vendorId = GPU_VENDOR_ID_INTEL; if (vendorId != 0) { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index a33cd8194..78c047cfc 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -161,11 +161,11 @@ GPUDevice* GPUDeviceDX12::Create() } GPUAdapterDX selectedAdapter = adapters[selectedAdapterIndex]; uint32 vendorId = 0; - if (CommandLine::Options.NVIDIA) + if (CommandLine::Options.NVIDIA.IsTrue()) vendorId = GPU_VENDOR_ID_NVIDIA; - else if (CommandLine::Options.AMD) + else if (CommandLine::Options.AMD.IsTrue()) vendorId = GPU_VENDOR_ID_AMD; - else if (CommandLine::Options.Intel) + else if (CommandLine::Options.Intel.IsTrue()) vendorId = GPU_VENDOR_ID_INTEL; if (vendorId != 0) { @@ -425,7 +425,7 @@ bool GPUDeviceDX12::Init() #if !BUILD_RELEASE // Prevent the GPU from overclocking or under-clocking to get consistent timings - if (CommandLine::Options.ShaderProfile) + if (CommandLine::Options.ShaderProfile.IsTrue()) { _device->SetStablePowerState(TRUE); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index 9b6d4ba2c..5e11e1a86 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1222,11 +1222,11 @@ GPUDevice* GPUDeviceVulkan::Create() return nullptr; } uint32 vendorId = 0; - if (CommandLine::Options.NVIDIA) + if (CommandLine::Options.NVIDIA.IsTrue()) vendorId = GPU_VENDOR_ID_NVIDIA; - else if (CommandLine::Options.AMD) + else if (CommandLine::Options.AMD.IsTrue()) vendorId = GPU_VENDOR_ID_AMD; - else if (CommandLine::Options.Intel) + else if (CommandLine::Options.Intel.IsTrue()) vendorId = GPU_VENDOR_ID_INTEL; if (vendorId != 0) { diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 6efa3c8b4..93b06b821 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -365,7 +365,7 @@ void PlatformBase::Fatal(const Char* msg, void* context) void PlatformBase::Error(const Char* msg) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { #if PLATFORM_TEXT_IS_CHAR16 StringAnsi ansi(msg); @@ -385,7 +385,7 @@ void PlatformBase::Error(const Char* msg) void PlatformBase::Warning(const Char* msg) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { std::cout << "Warning: " << msg << std::endl; } @@ -399,7 +399,7 @@ void PlatformBase::Warning(const Char* msg) void PlatformBase::Info(const Char* msg) { #if PLATFORM_HAS_HEADLESS_MODE - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) { std::cout << "Info: " << msg << std::endl; } diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 2c951effa..681b60c37 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -653,7 +653,7 @@ static int X11_MessageBoxLoop(MessageBoxData* data) DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return DialogResult::None; // Setup for simple popup @@ -1369,7 +1369,7 @@ public: DragDropEffect LinuxWindow::DoDragDrop(const StringView& data) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return DragDropEffect::None; auto cursorWrong = X11::XCreateFontCursor(xDisplay, 54); auto cursorTransient = X11::XCreateFontCursor(xDisplay, 24); @@ -1673,7 +1673,7 @@ void LinuxClipboard::Clear() void LinuxClipboard::SetText(const StringView& text) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return; auto mainWindow = (LinuxWindow*)Engine::MainWindow; if (!mainWindow) @@ -1695,7 +1695,7 @@ void LinuxClipboard::SetFiles(const Array& files) String LinuxClipboard::GetText() { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return String::Empty; String result; auto mainWindow = (LinuxWindow*)Engine::MainWindow; @@ -2118,7 +2118,7 @@ bool LinuxPlatform::Init() Platform::MemoryClear(CursorsImg, sizeof(CursorsImg)); // Skip setup if running in headless mode (X11 might not be available on servers) - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return false; X11::XInitThreads(); diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 2f054f7a6..84279e194 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -54,7 +54,7 @@ String ComputerName; DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) { - if (CommandLine::Options.Headless) + if (CommandLine::Options.Headless.IsTrue()) return DialogResult::None; NSAlert* alert = [[NSAlert alloc] init]; ASSERT(alert); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 0f1159fc5..57d46b1f8 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -616,7 +616,7 @@ bool WindowsPlatform::Init() return true; // Init console output (engine is linked with /SUBSYSTEM:WINDOWS so it lacks of proper console output on Windows) - if (CommandLine::Options.Std) + if (CommandLine::Options.Std.IsTrue()) { // Attaches output of application to parent console, returns true if running in console-mode // [Reference: https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application] diff --git a/Source/Engine/Tests/TestNullable.cpp b/Source/Engine/Tests/TestNullable.cpp new file mode 100644 index 000000000..d02d98c0d --- /dev/null +++ b/Source/Engine/Tests/TestNullable.cpp @@ -0,0 +1,163 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "Engine/Core/Types/Nullable.h" +#include + +TEST_CASE("Nullable") +{ + SECTION("Trivial Type") + { + Nullable a; + + REQUIRE(a.HasValue() == false); + REQUIRE(a.GetValueOr(2) == 2); + + a = 1; + + REQUIRE(a.HasValue() == true); + REQUIRE(a.GetValue() == 1); + REQUIRE(a.GetValueOr(2) == 1); + + a.Reset(); + + REQUIRE(a.HasValue() == false); + } + + SECTION("Move-Only Type") + { + struct MoveOnly + { + MoveOnly() = default; + ~MoveOnly() = default; + + MoveOnly(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) = default; + + MoveOnly& operator=(const MoveOnly&) = delete; + MoveOnly& operator=(MoveOnly&&) = default; + }; + + Nullable a; + + REQUIRE(a.HasValue() == false); + + a = MoveOnly(); + + REQUIRE(a.HasValue() == true); + } + + SECTION("Bool Type") + { + Nullable a; + + REQUIRE(a.HasValue() == false); + REQUIRE(a.GetValueOr(true) == true); + REQUIRE(a.IsTrue() == false); + REQUIRE(a.IsFalse() == false); + + a = false; + + REQUIRE(a.HasValue() == true); + REQUIRE(a.GetValue() == false); + REQUIRE(a.GetValueOr(true) == false); + + REQUIRE(a.IsTrue() == false); + REQUIRE(a.IsFalse() == true); + + a = true; + + REQUIRE(a.IsTrue() == true); + REQUIRE(a.IsFalse() == false); + + a.Reset(); + + REQUIRE(a.HasValue() == false); + } + + SECTION("Lifetime (No Construction)") + { + struct DoNotConstruct + { + DoNotConstruct() { FAIL("DoNotConstruct must not be constructed."); } + }; + + Nullable a; + a.Reset(); + } + + SECTION("Lifetime") + { + struct Lifetime + { + int* _constructed; + int* _destructed; + + Lifetime(int* constructed, int* destructed) + : _constructed(constructed) + , _destructed(destructed) + { + ++(*_constructed); + } + + Lifetime(Lifetime&& other) noexcept + : _constructed(other._constructed) + , _destructed(other._destructed) + { + ++(*_constructed); + } + + Lifetime() = delete; + Lifetime& operator=(const Lifetime&) = delete; + Lifetime& operator=(Lifetime&&) = delete; + + ~Lifetime() + { + ++(*_destructed); + } + }; + + int constructed = 0, destructed = 0; + REQUIRE(constructed == destructed); + + { + + Nullable a = Lifetime(&constructed, &destructed); + REQUIRE(a.HasValue()); + REQUIRE(constructed == destructed + 1); + + a.Reset(); + REQUIRE(!a.HasValue()); + REQUIRE(constructed == destructed); + } + REQUIRE(constructed == destructed); + + { + Nullable b = Lifetime(&constructed, &destructed); + REQUIRE(constructed == destructed + 1); + } + REQUIRE(constructed == destructed); + + { + Nullable c = Lifetime(&constructed, &destructed); + Nullable d = MoveTemp(c); + REQUIRE(constructed == destructed + 1); + } + REQUIRE(constructed == destructed); + } + + SECTION("Matching") + { + Nullable a; + Nullable b = 2; + + a.Match( + [](int) { FAIL("Null nullable must not match value handler."); }, + []() {} + ); + + b.Match( + [](int) {}, + []() { FAIL("Nullable with valid value must not match null handler."); } + ); + } +};