Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ jobs:
cmake -S external/Catch2 -B external/Catch2/build \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/.deps" \
-DCMAKE_CXX_STANDARD=20 \
-DCMAKE_CXX_STANDARD_REQUIRED=ON \
-DBUILD_TESTING=OFF
cmake --build external/Catch2/build --config ${{ matrix.build_type }} --target install

Expand Down
112 changes: 96 additions & 16 deletions FlingEngine/Core/inc/Misc/CommandLine.h
Original file line number Diff line number Diff line change
@@ -1,39 +1,119 @@
#pragma once

#include <string> // string, stoi, to_string
#include <string_view> // std::string_view
#include <unordered_map>
#include "FlingTypes.h"
#include "Misc/StringUtils.h"

// TODO: I think that we may gain a lot if we just use
// Boost. That certainly will have a better implementation then
// I can whip up, and I think has config file options as well.
// https://www.boost.org/doc/libs/1_85_0/doc/html/program_options.html

namespace Fling
{
/**
* Holds onto the command line arguments passed to this application
* Can be used to parse arguments into different types
*
* The syntax for specifying a command line argument is a key-value pair with a "-".
* For example, to specify the "foo" option with a value of 7, you could use:
*
* -foo=7
*
* on the command line.
*/
class CommandLine
{
public:

/** Sets the static command line */
static void Set(const std::string& CmdLine);
/** Maximum length, in characters, that a single command line argument may be. Any argument longer than this is ignored. */
static constexpr std::size_t MaxArgLength = 256;

/**
* @return Instance of the current command line that the application was started with.
*/
static CommandLine& Get();

/**
* Builds a string with a space in between each argument passed in via the command
* except for the first argument (the application name)
/**
* Initalize the command line instance with the given application args.
* This will initalize the command line's internal data structure for keepting
* track of the data passed into the command line
*
* @paran ArgC The number of command line arguements provided
* @param ArgV The char values of those command line arguments
* @return True if successfully initalized
*/
static std::string BuildFromArgs(int32 Argc, const char* ArgV[]);
bool Init(const int32 Argc, const char* ArgV[]);

/**
* Returns true if the given param had a value passed in via command line
* @param Param
* @return
*/
[[nodiscard]] bool HasParam(const std::string_view Param) const;

/**
* Gets the value of the given param as the given type.
*
* If the value as not specified on the command line,
* then the given "Default" value will be returned.
*/
template<typename T>
T GetValueAs(const std::string_view Param, const T& Default) const;

// TODO: make this a std::string_view
const char* GetValueAsString(const std::string_view Param) const;

[[nodiscard]] std::string_view GetCommandLineData() const;

static bool Parse(const std::string& InKey);

static const std::string& Get() { return CurrentCommandLine; }
/**
* Loads console variables from the "[ConsoleVariables]" section of an ini-style
* config file, following the same "Key=Value" syntax used on the command line
* (e.g. UE's ini config variables). Values loaded this way act as a fallback:
* if the same key was also passed directly on the command line, the command
* line value always takes precedence.
*
* @param FilePath Path to the ini file to load
* @return True if the file was opened and parsed successfully
*/
bool LoadConfigFile(const std::string& FilePath);

/** Returns true if the given flag is set on the command line */
static bool HasFlag(const std::string& Flag);

static bool HasParam(const std::string& Param);
/**
* Same as LoadConfigFile, but parses the ini data directly out of the given
* string rather than from a file on disk. Exposed publicly so that this parsing
* logic can be unit tested without touching the file system.
*
* @param IniContent Contents of an ini file to parse
* @return True if the content was parsed successfully
*/
bool LoadConfigVarsFromString(const std::string_view IniContent);

private:

static std::string CurrentCommandLine;

/** Looks up a param, checking command line values before config file (ConsoleVariables) values. */
const std::string* FindValue(const std::string_view Param) const;

std::string CurrentCommandLineData;

/** Key/value pairs parsed out of the command line, e.g. "-foo=7" becomes ParsedArgs["foo"] = "7" */
std::unordered_map<std::string, std::string> ParsedArgs;

/** Key/value pairs parsed out of a config file's "[ConsoleVariables]" section. Lower priority than ParsedArgs. */
std::unordered_map<std::string, std::string> ConfigArgs;
};

template<typename T>
T CommandLine::GetValueAs(const std::string_view Param, const T& Default) const
{
const std::string* Value = FindValue(Param);
if (Value == nullptr)
{
// Nothing was set on the command line or in a config file for this param,
// so use the default value
return Default;
}

return StringUtils::ParseAs<T>(*Value, Default);
}
} // namespace Fling
81 changes: 81 additions & 0 deletions FlingEngine/Core/inc/Misc/StringUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#pragma once

#include <string>
#include <string_view>
#include <sstream>
#include <type_traits>
#include <cstdlib>
#include "FlingTypes.h"

namespace Fling
{
namespace StringUtils
{
/**
* Attempts to parse the given string_view as an instance of T.
*
* This is the single place that knows how to turn a raw string into an engine
* type (bool, int, float, ...), so that command line args, ini config values,
* and any other string-serialized data all agree on what "true", "7", or
* "3.14" mean.
*
* @param Str The string to parse
* @param Default Value returned if Str is empty or cannot be converted to T
* @return The parsed value, or Default if Str could not be parsed as a T
*/
template<typename T>
T ParseAs(const std::string_view Str, const T& Default = T{})
{
if (Str.empty())
{
return Default;
}

if constexpr (std::is_same_v<T, std::string>)
{
return std::string(Str);
}
else if constexpr (std::is_same_v<T, bool>)
{
if (Str == "true" || Str == "True" || Str == "TRUE" || Str == "1")
{
return true;
}
if (Str == "false" || Str == "False" || Str == "FALSE" || Str == "0")
{
return false;
}
return Default;
}
else if constexpr (std::is_integral_v<T>)
{
const std::string Temp(Str);
char* End = nullptr;
const long long Result = std::strtoll(Temp.c_str(), &End, 10);
return (End != Temp.c_str()) ? static_cast<T>(Result) : Default;
}
else if constexpr (std::is_same_v<T, float>)
{
const std::string Temp(Str);
char* End = nullptr;
const float Result = std::strtof(Temp.c_str(), &End);
return (End != Temp.c_str()) ? Result : Default;
}
else if constexpr (std::is_same_v<T, double>)
{
const std::string Temp(Str);
char* End = nullptr;
const double Result = std::strtod(Temp.c_str(), &End);
return (End != Temp.c_str()) ? Result : Default;
}
else
{
// Fallback for any other stream-extractable type
std::istringstream ValueStream{ std::string(Str) };
T Value{};
ValueStream >> Value;
return ValueStream.fail() ? Default : Value;
}
}
} // namespace StringUtils
} // namespace Fling
21 changes: 15 additions & 6 deletions FlingEngine/Core/src/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ namespace Fling
{
Random::Init();
Logger::Get().Init();

CommandLine::Set(CommandLine::BuildFromArgs(argc, argv));
F_LOG_TRACE("Command line args: {}\t", CommandLine::Get());

// Initalize the command line
const bool bSuccessfulCommandLineInit = CommandLine::Get().Init(argc, argv);
F_LOG_TRACE("Command line initaliziation: {}\t", bSuccessfulCommandLineInit ? "successful" : "failed");
F_LOG_TRACE("Command line args: {}\t", CommandLine::Get().GetCommandLineData());

FlingConfig::Get().Init();

ResourceManager::Get().Init();
Timing::Get().Init();
FlingConfig::Get().Init();
Timing::Get().Init();
Input::Init();

F_LOG_TRACE("Fling Engine Sourcedir: \t{}", Fling::FlingPaths::EngineSourceDir());
Expand All @@ -31,13 +34,19 @@ namespace Fling
#endif

// Load command line args and any ini files
bool ConfigLoaded = FlingConfig::Get().LoadConfigFile(FlingPaths::EngineConfigDir() + "/EngineConf.ini");
const std::string EngineConfigPath = FlingPaths::EngineConfigDir() + "/EngineConf.ini";
bool ConfigLoaded = FlingConfig::Get().LoadConfigFile(EngineConfigPath);

if (!ConfigLoaded)
{
F_LOG_WARN("NO EngineConf.ini has been provided! This may result in unexpected behavior from Fling!");
}

// Let the command line consider the same ini's [ConsoleVariables] section as a
// fallback, so systems can query CommandLine::GetValueAs for values that were
// only set in the config file. Command line args passed directly still win.
CommandLine::Get().LoadConfigFile(EngineConfigPath);

VulkanApp::Get().Init(
static_cast<PipelineFlags>(PipelineFlags::DEFERRED | PipelineFlags::IMGUI),
g_Registry,
Expand Down
Loading
Loading