Merge branch 'fix/nullable' of https://github.com/mtszkarbowiak/FlaxEngine into mtszkarbowiak-fix/nullable

This commit is contained in:
Wojtek Figat
2024-10-25 15:38:44 +02:00
16 changed files with 610 additions and 140 deletions

View File

@@ -403,7 +403,7 @@ int32 Editor::LoadProduct()
}
// Create new project option
if (CommandLine::Options.NewProject)
if (CommandLine::Options.NewProject.IsTrue())
{
Array<String> 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);

View File

@@ -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");

View File

@@ -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);

View File

@@ -2,50 +2,178 @@
#pragma once
#include "Engine/Core/Templates.h"
#include "Engine/Platform/Platform.h"
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">
/// The type of the wrapped value. It must be move-constructible but does not have to be copy-constructible. Value is never reassigned.
/// </typeparam>
template<typename T>
struct NullableBase
struct Nullable
{
protected:
private:
struct Dummy { Dummy() {} };
union
{
T _value;
Dummy _dummy;
};
bool _hasValue;
T _value;
public:
/// <summary>
/// Initializes a new instance of the <see cref="NullableBase{T}"/> struct.
/// Initializes <see cref="Nullable{T}"/> by setting the wrapped value to null.
/// </summary>
NullableBase()
Nullable()
: _dummy()
, _hasValue(false)
{
_hasValue = false;
// Value is not initialized.
}
~Nullable()
{
if (_hasValue)
{
_value.~T();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="NullableBase{T}"/> struct.
/// Initializes <see cref="Nullable{T}"/> by copying the wrapped value.
/// </summary>
/// <NullableBase name="value">The initial value.</param>
NullableBase<T>(const T& value)
/// <NullableBase name="value">The initial wrapped value to be copied.</param>
template<typename U = T, typename = typename TEnableIf<TIsCopyConstructible<U>::Value>::Type>
Nullable(const T& value)
: _value(value)
, _hasValue(true)
{
_value = value;
_hasValue = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="NullableBase{T}"/> struct.
/// Initializes <see cref="Nullable{T}"/> by moving the wrapped value.
/// </summary>
/// <param name="other">The other.</param>
NullableBase(const NullableBase& other)
/// <NullableBase name="value">The initial wrapped value to be moved.</param>
Nullable(T&& value) noexcept
: _value(MoveTemp(value))
, _hasValue(true)
{
_value = other._value;
}
/// <summary>
/// Initializes <see cref="Nullable{T}"/> by copying another <see cref="Nullable{T}"/>.
/// </summary>
/// <param name="other">The wrapped value to be copied.</param>
template<typename U = T, typename = typename TEnableIf<TIsCopyConstructible<U>::Value>::Type>
Nullable(const Nullable& other)
: _value(other._value)
, _hasValue(other._hasValue)
{
}
/// <summary>
/// Initializes <see cref="Nullable{T}"/> by moving another <see cref="Nullable{T}"/>.
/// </summary>
/// <param name="other">The wrapped value to be moved.</param>
Nullable(Nullable&& other) noexcept
{
if (other._hasValue)
{
new (&_value) T(MoveTemp(other._value)); // Placement new (move constructor)
}
_hasValue = other._hasValue;
other.Reset();
}
public:
/// <summary>
/// Gets a value indicating whether the current NullableBase{T} object has a valid value of its underlying type.
/// Reassigns the wrapped value by copying.
/// </summary>
template<typename U = T, typename = typename TEnableIf<TIsCopyConstructible<U>::Value>::Type>
auto operator=(const T& value) -> Nullable&
{
if (_hasValue)
{
_value.~T();
}
new (&_value) T(value); // Placement new (copy constructor)
_hasValue = true;
return *this;
}
/// <summary>
/// Reassigns the wrapped value by moving.
/// </summary>
auto operator=(T&& value) noexcept -> Nullable&
{
if (_hasValue)
{
_value.~T();
}
new (&_value) T(MoveTemp(value)); // Placement new (move constructor)
_hasValue = true;
return *this;
}
/// <summary>
/// Reassigns the wrapped value by copying another <see cref="Nullable{T}"/>.
/// </summary>
template<typename U = T, typename = typename TEnableIf<TIsCopyConstructible<U>::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;
}
/// <summary>
/// Reassigns the wrapped value by moving another <see cref="Nullable{T}"/>.
/// </summary>
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;
}
/// <summary>
/// Checks if wrapped object has a valid value.
/// </summary>
/// <returns><c>true</c> if this object has a valid value; otherwise, <c>false</c>.</returns>
FORCE_INLINE bool HasValue() const
@@ -54,9 +182,9 @@ public:
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The value.</returns>
/// <returns>Reference to the wrapped value.</returns>
FORCE_INLINE const T& GetValue() const
{
ASSERT(_hasValue);
@@ -64,152 +192,331 @@ public:
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The value.</returns>
FORCE_INLINE T GetValue()
/// <returns>Reference to the wrapped value.</returns>
FORCE_INLINE T& GetValue()
{
ASSERT(_hasValue);
return _value;
}
/// <summary>
/// Sets the value.
/// Gets a const reference to the wrapped value or a default value if the value is not valid.
/// </summary>
/// <param name="value">The value.</param>
void SetValue(const T& value)
/// <returns>Reference to the wrapped value or the default value.</returns>
FORCE_INLINE const T& GetValueOr(const T& defaultValue) const
{
_value = value;
_hasValue = true;
return _hasValue ? _value : defaultValue;
}
/// <summary>
/// Resets the value.
/// Gets a mutable reference to the wrapped value or a default value if the value is not valid.
/// </summary>
void Reset()
/// <returns>Reference to the wrapped value or the default value.</returns>
FORCE_INLINE T& GetValueOr(T& defaultValue) const
{
_hasValue = false;
return _hasValue ? _value : defaultValue;
}
public:
/// <summary>
/// Indicates whether the current NullableBase{T} object is equal to a specified object.
/// Sets the wrapped value by copying.
/// </summary>
/// <param name="other">The other object.</param>
/// <returns>True if both values are equal.</returns>
bool operator==(const NullableBase& other) const
/// <param name="value">The value to be copied.</param>
template<typename U = T, typename = typename TEnableIf<TIsCopyConstructible<U>::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.
}
/// <summary>
/// Indicates whether the current NullableBase{T} object is not equal to a specified object.
/// Sets the wrapped value by moving.
/// </summary>
/// <param name="value">The value to be moved.</param>
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.
}
/// <summary>
/// If the wrapped value is not valid, sets it by copying. Otherwise, does nothing.
/// </summary>
/// <returns>True if the wrapped value was changed, otherwise false.</returns>
template<typename U = T, typename = typename TEnableIf<TIsCopyConstructible<U>::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;
}
/// <summary>
/// If the wrapped value is not valid, sets it by moving. Otherwise, does nothing.
/// </summary>
/// <returns>True if the wrapped value was changed, otherwise false.</returns>
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;
}
/// <summary>
/// Disposes the wrapped value and sets the wrapped value to null. If the wrapped value is not valid, does nothing.
/// </summary>
FORCE_INLINE void Reset()
{
if (!_hasValue)
{
return;
}
_hasValue = false; // Reset the flag BEFORE the value is (potentially) destructed.
_value.~T();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="value">The output parameter that will receive the wrapped value.</param>
FORCE_INLINE void GetAndReset(T& value)
{
ASSERT(_hasValue);
value = MoveTemp(_value);
Reset();
}
/// <summary>
/// Indicates whether this instance is equal to other one.
/// </summary>
/// <param name="other">The other object.</param>
/// <returns>True if both values are not equal.</returns>
FORCE_INLINE bool operator!=(const NullableBase& other) const
/// <returns><c>true</c> if both values are equal.</returns>
FORCE_INLINE bool operator==(const Nullable& other) const
{
if (other._hasValue != _hasValue)
{
return false;
}
return _value == other._value;
}
/// <summary>
/// Indicates whether this instance is NOT equal to other one.
/// </summary>
/// <param name="other">The other object.</param>
/// <returns><c>true</c> if both values are not equal.</returns>
FORCE_INLINE bool operator!=(const Nullable& other) const
{
return !operator==(other);
}
};
/// <summary>
/// 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.
/// </summary>
template<typename T>
struct Nullable : NullableBase<T>
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="Nullable{T}"/> struct.
/// Explicit conversion to boolean value. Allows to check if the wrapped value is valid in if-statements without casting.
/// </summary>
Nullable()
: NullableBase<T>()
/// <returns><c>true</c> if this object has a valid value, otherwise false</returns>
FORCE_INLINE explicit operator bool() const
{
return _hasValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="Nullable{T}"/> struct.
/// </summary>
/// <NullableBase name="value">The initial value.</param>
Nullable<T>(const T& value)
: NullableBase<T>(value)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Nullable{T}"/> struct.
/// Matches the wrapped value with a handler for the value or a handler for the null value.
/// </summary>
/// <param name="other">The other.</param>
Nullable(const Nullable& other)
: NullableBase<T>(other)
/// <param name="valueHandler">Value visitor handling valid nullable value.</param>
/// <param name="nullHandler">Null visitor handling invalid nullable value.</param>
/// <returns>Result of the call of one of handlers. Handlers must share the same result type.</returns>
template<typename ValueVisitor, typename NullVisitor>
FORCE_INLINE auto Match(ValueVisitor valueHandler, NullVisitor nullHandler) const
{
if (_hasValue)
{
return valueHandler(_value);
}
else
{
return nullHandler();
}
}
};
/// <summary>
/// Nullable value container that contains a boolean value or null.
/// Specialization of <see cref="Nullable{T}"/> for <see cref="bool"/> type.
/// </summary>
template<>
struct Nullable<bool> : NullableBase<bool>
struct Nullable<bool>
{
public:
private:
/// <summary>
/// Initializes a new instance of the <see cref="Nullable{T}"/> struct.
/// Underlying value of the nullable boolean. Uses only one byte to optimize memory usage.
/// </summary>
Nullable()
: NullableBase<bool>()
enum class Value : uint8
{
}
Null,
False,
True,
};
/// <summary>
/// Initializes a new instance of the <see cref="Nullable{T}"/> struct.
/// </summary>
/// <NullableBase name="value">The initial value.</param>
Nullable(bool value)
: NullableBase<bool>(value)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Nullable{T}"/> struct.
/// </summary>
/// <param name="other">The other.</param>
Nullable(const Nullable& other)
: NullableBase<bool>(other)
{
}
Value _value = Value::Null;
public:
/// <summary>
/// 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.
/// </summary>
Nullable() = default;
~Nullable() = default;
/// <summary>
/// Initializes nullable boolean by moving another nullable boolean.
/// </summary>
Nullable(Nullable&& value) = default;
/// <summary>
/// Initializes nullable boolean by copying another nullable boolean.
/// </summary>
Nullable(const Nullable& value) = default;
/// <summary>
/// Initializes nullable boolean by implicitly casting a boolean value.
/// </summary>
Nullable(const bool value) noexcept
{
_value = value ? Value::True : Value::False;
}
/// <summary>
/// Reassigns the wrapped value by implicitly casting a boolean value.
/// </summary>
auto operator=(const bool value) noexcept -> Nullable&
{
_value = value ? Value::True : Value::False;
return *this;
}
/// <summary>
/// Reassigns the wrapped value by copying another nullable boolean.
/// </summary>
auto operator=(const Nullable& value) -> Nullable& = default;
/// <summary>
/// Reassigns the wrapped value by moving another nullable boolean.
/// </summary>
auto operator=(Nullable&& value) -> Nullable& = default;
/// <summary>
/// Checks if wrapped bool has a valid value.
/// </summary>
FORCE_INLINE bool HasValue() const noexcept
{
return _value != Value::Null;
}
/// <summary>
/// Gets the wrapped boolean value. If the value is not valid, the behavior is undefined.
/// </summary>
FORCE_INLINE bool GetValue() const
{
ASSERT(_value != Value::Null);
return _value == Value::True;
}
/// <summary>
/// Gets the wrapped boolean value. If the value is not valid, returns the default value.
/// </summary>
FORCE_INLINE bool GetValueOr(const bool defaultValue) const noexcept
{
return _value == Value::Null ? defaultValue : _value == Value::True;
}
/// <summary>
/// Sets the wrapped value to a valid boolean.
/// </summary>
FORCE_INLINE void SetValue(const bool value) noexcept
{
_value = value ? Value::True : Value::False;
}
/// <summary>
/// If the wrapped value is not valid, sets it to a valid boolean.
/// </summary>
FORCE_INLINE bool TrySet(const bool value) noexcept
{
if (_value != Value::Null)
{
return false;
}
_value = value ? Value::True : Value::False;
return true;
}
/// <summary>
/// Sets the wrapped bool to null.
/// </summary>
FORCE_INLINE void Reset() noexcept
{
_value = Value::Null;
}
/// <summary>
/// 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.
/// </summary>
FORCE_INLINE void GetAndReset(bool& value) noexcept
{
ASSERT(_value != Value::Null);
value = _value == Value::True;
_value = Value::Null;
}
/// <summary>
/// Checks if the current object has a valid value and it's set to <c>true</c>. If the value is <c>false</c> or not valid, the method returns <c>false</c>.
/// </summary>
/// <returns><c>true</c> if this object has a valid value set to true; otherwise, <c>false</c>.</returns>
FORCE_INLINE bool IsTrue() const
{
return _hasValue && _value;
return _value == Value::True;
}
/// <summary>
/// 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 <c>false</c>. If the value is <c>true</c> or not valid, the method returns <c>false</c>.
/// </summary>
/// <returns><c>true</c> if this object has a valid value set to false; otherwise, <c>false</c>.</returns>
FORCE_INLINE bool IsFalse() const
{
return _hasValue && !_value;
return _value == Value::False;
}
/// <summary>
/// Implicit conversion to boolean value.
/// Deletes implicit conversion to bool to prevent ambiguous code.
/// </summary>
/// <returns>True if this object has a valid value set to true, otherwise false</returns>
FORCE_INLINE operator bool() const
{
return _hasValue && _value;
}
/// <remarks>
/// Implicit cast from nullable bool to a bool produces unacceptably ambiguous code. For template meta-programming use explicit <c>HasValue</c> instead.
/// </remarks>
explicit operator bool() const = delete;
};

View File

@@ -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);

View File

@@ -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
)
{

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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;
}

View File

@@ -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<String>& 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();

View File

@@ -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);

View File

@@ -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]

View File

@@ -0,0 +1,163 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Engine/Core/Types/Nullable.h"
#include <ThirdParty/catch2/catch.hpp>
TEST_CASE("Nullable")
{
SECTION("Trivial Type")
{
Nullable<int> 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<MoveOnly> a;
REQUIRE(a.HasValue() == false);
a = MoveOnly();
REQUIRE(a.HasValue() == true);
}
SECTION("Bool Type")
{
Nullable<bool> 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<DoNotConstruct> 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<Lifetime> a = Lifetime(&constructed, &destructed);
REQUIRE(a.HasValue());
REQUIRE(constructed == destructed + 1);
a.Reset();
REQUIRE(!a.HasValue());
REQUIRE(constructed == destructed);
}
REQUIRE(constructed == destructed);
{
Nullable<Lifetime> b = Lifetime(&constructed, &destructed);
REQUIRE(constructed == destructed + 1);
}
REQUIRE(constructed == destructed);
{
Nullable<Lifetime> c = Lifetime(&constructed, &destructed);
Nullable<Lifetime> d = MoveTemp(c);
REQUIRE(constructed == destructed + 1);
}
REQUIRE(constructed == destructed);
}
SECTION("Matching")
{
Nullable<int> a;
Nullable<int> 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."); }
);
}
};