carb/launcher/LauncherUtils.h
File members: carb/launcher/LauncherUtils.h
// Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../Defines.h"
#include "../extras/StringSafe.h"
#include "../../omni/extras/PathMap.h"
#include "../settings/SettingsUtils.h"
#include <string>
#include <unordered_map>
#include <vector>
#include <sstream>
#if CARB_PLATFORM_LINUX
# include <unistd.h>
#elif CARB_PLATFORM_MACOS
# include <crt_externs.h>
#else
# include "../CarbWindows.h"
# include "../extras/Unicode.h"
#endif
namespace carb
{
namespace launcher
{
using SettingsEnumFlags = uint32_t;
constexpr SettingsEnumFlags fSettingsEnumFlagRecursive = 0x01;
using AddSettingPredicateFn = bool (*)(const char* path, void* context);
class ArgCollector
{
public:
ArgCollector() = default;
ArgCollector(const ArgCollector& rhs)
{
*this = rhs;
}
ArgCollector(ArgCollector&& rhs)
{
*this = std::move(rhs);
}
~ArgCollector()
{
clear();
}
void clear()
{
m_argList.reset();
m_args.clear();
m_allocCount = 0;
}
const char* const* getArgs(size_t* argCountOut = nullptr)
{
if (argCountOut)
{
*argCountOut = m_args.size();
}
if (m_args.empty())
{
return emptyArgList();
}
if (m_allocCount < m_args.size())
{
m_allocCount = m_args.size();
m_argList.reset(new (std::nothrow) const char*[m_args.size() + 1]);
if (CARB_UNLIKELY(m_argList == nullptr))
{
if (argCountOut)
{
*argCountOut = 0;
}
m_allocCount = 0;
return nullptr;
}
}
for (size_t i = 0; i < m_args.size(); i++)
{
m_argList[i] = m_args[i].c_str();
}
// null terminate the list since some platforms and apps expect that behavior.
m_argList[m_args.size()] = nullptr;
return m_argList.get();
}
size_t getCount() const
{
return m_args.size();
}
ArgCollector& operator=(const ArgCollector& rhs)
{
if (this == &rhs)
return *this;
clear();
if (rhs.m_args.size() == 0)
return *this;
m_args = rhs.m_args;
return *this;
}
ArgCollector& operator=(ArgCollector&& rhs)
{
if (this == &rhs)
return *this;
clear();
m_args = std::move(rhs.m_args);
return *this;
}
bool operator==(const ArgCollector& rhs)
{
size_t count = m_args.size();
if (&rhs == this)
return true;
if (count != rhs.m_args.size())
return false;
for (size_t i = 0; i < count; i++)
{
if (m_args[i] != rhs.m_args[i])
return false;
}
return true;
}
bool operator!=(const ArgCollector& rhs)
{
return !(*this == rhs);
}
bool operator!() const
{
return m_args.size() == 0;
}
explicit operator bool() const
{
return !m_args.empty();
}
ArgCollector& format(const char* fmt, ...) CARB_PRINTF_FUNCTION(2, 3)
{
CARB_FORMATTED(fmt, [&](const char* p) { add(p); });
return *this;
}
ArgCollector& add(const char* value)
{
m_args.push_back(value);
return *this;
}
ArgCollector& add(const std::string& value)
{
m_args.push_back(value);
return *this;
}
ArgCollector& add(const ArgCollector& value)
{
for (auto& arg : value.m_args)
m_args.push_back(arg);
return *this;
}
ArgCollector& add(const char* const* value)
{
for (const char* const* arg = value; arg[0] != nullptr; arg++)
m_args.push_back(arg[0]);
return *this;
}
ArgCollector& add(const std::vector<const char*>& value)
{
for (auto& v : value)
add(v);
return *this;
}
ArgCollector& add(const std::vector<std::string>& value)
{
for (auto& v : value)
add(v);
return *this;
}
ArgCollector& operator+=(const char* value)
{
return add(value);
}
ArgCollector& operator+=(const std::string& value)
{
return add(value);
}
ArgCollector& operator+=(const ArgCollector& value)
{
return add(value);
}
ArgCollector& operator+=(const char* const* value)
{
return add(value);
}
ArgCollector& operator+=(const std::vector<const char*>& value)
{
return add(value);
}
ArgCollector& operator+=(const std::vector<std::string>& value)
{
return add(value);
}
#define ADD_PRIMITIVE_HANDLER(type, fmt) \
\
ArgCollector& add(type value) \
{ \
char buffer[128]; \
carb::extras::formatString(buffer, CARB_COUNTOF(buffer), fmt, value); \
m_args.push_back(buffer); \
return *this; \
} \
\
ArgCollector& operator+=(type value) \
{ \
return add(value); \
}
// unsigned integer handlers.
ADD_PRIMITIVE_HANDLER(unsigned char, "%u")
ADD_PRIMITIVE_HANDLER(unsigned short, "%u")
ADD_PRIMITIVE_HANDLER(unsigned int, "%u")
ADD_PRIMITIVE_HANDLER(unsigned long, "%lu")
ADD_PRIMITIVE_HANDLER(unsigned long long, "%llu")
// signed integer handlers.
ADD_PRIMITIVE_HANDLER(char, "%d")
ADD_PRIMITIVE_HANDLER(short, "%d")
ADD_PRIMITIVE_HANDLER(int, "%d")
ADD_PRIMITIVE_HANDLER(long, "%ld")
ADD_PRIMITIVE_HANDLER(long long, "%lld")
// other numerical handlers. Note that some of these can be trivially implicitly cast to
// other primitive types so we can't define them again. Specifically the size_t,
// intmax_t, and uintmax_t types often match other types with handlers defined above.
// Which handler each of these matches to will differ by platform however.
ADD_PRIMITIVE_HANDLER(float, "%.10f")
ADD_PRIMITIVE_HANDLER(double, "%.20f")
#undef ADD_PRIMITIVE_HANDLER
ArgCollector& add(const char* root,
const char* prefix,
SettingsEnumFlags flags = 0,
AddSettingPredicateFn predicate = nullptr,
void* context = nullptr)
{
dictionary::IDictionary* dictionary = getCachedInterface<dictionary::IDictionary>();
settings::ISettings* settings = getCachedInterface<settings::ISettings>();
std::string rootPath;
auto addSetting = [&](const char* path, int32_t elementData, void* context) -> int32_t {
dictionary::ItemType type;
CARB_UNUSED(context);
type = settings->getItemType(path);
// skip dictionaries since we're only interested in leaves here.
if (type == dictionary::ItemType::eDictionary)
return elementData + 1;
if ((flags & fSettingsEnumFlagRecursive) == 0 && elementData > 1)
return elementData;
// verify that the caller wants this setting added.
if (predicate != nullptr && !predicate(path, context))
return elementData + 1;
switch (type)
{
case dictionary::ItemType::eBool:
format("%s%s=%s", prefix, &path[1], settings->getAsBool(path) ? "true" : "false");
break;
case dictionary::ItemType::eInt:
format("%s%s=%" PRId64, prefix, &path[1], settings->getAsInt64(path));
break;
case dictionary::ItemType::eFloat:
format("%s%s=%g", prefix, &path[1], settings->getAsFloat64(path));
break;
case dictionary::ItemType::eString:
format("%s%s=\"%s\"", prefix, &path[1], settings->getStringBuffer(path));
break;
default:
break;
}
return elementData;
};
// unexpected root prefix (not an absolute settings path) => fail.
if (root == nullptr || root[0] == 0)
root = "/";
// avoid a `nullptr` check later.
if (prefix == nullptr)
prefix = "";
// make sure to strip off any trailing separators since that would break the lookups.
rootPath = root;
if (rootPath.size() > 1 && rootPath[rootPath.size() - 1] == '/')
rootPath = rootPath.substr(0, rootPath.size() - 1);
// walk the settings tree to collect all the requested settings.
settings::walkSettings(
dictionary, settings, dictionary::WalkerMode::eIncludeRoot, rootPath.c_str(), 0, addSetting, context);
return *this;
}
const std::string& at(size_t index) const
{
if (index >= m_args.size())
return m_empty;
return m_args[index];
}
const std::string& operator[](size_t index) const
{
return at(index);
}
void pop()
{
if (m_args.empty())
return;
m_args.pop_back();
}
bool erase(size_t index)
{
if (index >= m_args.size())
return false;
m_args.erase(m_args.begin() + index);
return true;
}
std::string toString() const
{
std::ostringstream stream;
for (auto& arg : m_args)
{
size_t index = size_t(-1);
for (;;)
{
size_t start = index + 1;
index = arg.find_first_of("\\\" '", start); // Characters that must be escaped
stream << arg.substr(start, index - start);
if (index == std::string::npos)
break;
stream << '\\' << arg[index];
}
// Always add a trailing space. It will be popped before return.
stream << ' ';
}
std::string out = stream.str();
if (!out.empty())
out.pop_back(); // Remove the extra space
return out;
}
private:
static const char* const* emptyArgList()
{
static const char* const empty{ nullptr };
return ∅
}
std::string m_empty;
std::vector<std::string> m_args;
std::unique_ptr<const char*[]> m_argList;
size_t m_allocCount = 0;
};
class EnvCollector
{
public:
EnvCollector() = default;
EnvCollector(const EnvCollector& rhs)
{
*this = rhs;
}
EnvCollector(EnvCollector&& rhs)
{
*this = std::move(rhs);
}
~EnvCollector()
{
clear();
}
void clear()
{
m_env.clear();
m_args.clear();
}
const char* const* getEnv()
{
m_args.clear();
for (auto& var : m_env)
m_args.format("%s=%s", var.first.c_str(), var.second.c_str());
return m_args.getArgs();
}
size_t getCount() const
{
return m_env.size();
}
EnvCollector& operator=(const EnvCollector& rhs)
{
if (this == &rhs)
return *this;
clear();
if (rhs.m_env.size() == 0)
return *this;
m_env = rhs.m_env;
return *this;
}
EnvCollector& operator=(EnvCollector&& rhs)
{
if (this == &rhs)
return *this;
clear();
m_env = std::move(rhs.m_env);
return *this;
}
bool operator==(const EnvCollector& rhs)
{
size_t count = m_env.size();
if (&rhs == this)
return true;
if (count != rhs.m_env.size())
return false;
for (auto var : m_env)
{
auto other = rhs.m_env.find(var.first);
if (other == rhs.m_env.end())
return false;
if (other->second != var.second)
return false;
}
return true;
}
bool operator!=(const EnvCollector& rhs)
{
return !(*this == rhs);
}
bool operator!()
{
return m_env.size() == 0;
}
operator bool()
{
return !m_env.empty();
}
EnvCollector& add(const char* name, const char* value)
{
m_env[name] = value == nullptr ? "" : value;
return *this;
}
EnvCollector& add(const std::string& name, const std::string& value)
{
m_env[name] = value;
return *this;
}
EnvCollector& add(const std::string& name, const char* value)
{
m_env[name] = value == nullptr ? "" : value;
return *this;
}
EnvCollector& add(const char* name, const std::string& value)
{
m_env[name] = value;
return *this;
}
#define ADD_PRIMITIVE_HANDLER(type, fmt) \
\
EnvCollector& add(const char* name, type value) \
{ \
char buffer[128]; \
carb::extras::formatString(buffer, CARB_COUNTOF(buffer), fmt, value); \
return add(name, buffer); \
} \
\
EnvCollector& add(const std::string& name, type value) \
{ \
return add(name.c_str(), value); \
}
// unsigned integer handlers.
ADD_PRIMITIVE_HANDLER(uint8_t, "%" PRIu8)
ADD_PRIMITIVE_HANDLER(uint16_t, "%" PRIu16)
ADD_PRIMITIVE_HANDLER(uint32_t, "%" PRIu32)
ADD_PRIMITIVE_HANDLER(uint64_t, "%" PRIu64)
// signed integer handlers.
ADD_PRIMITIVE_HANDLER(int8_t, "%" PRId8)
ADD_PRIMITIVE_HANDLER(int16_t, "%" PRId16)
ADD_PRIMITIVE_HANDLER(int32_t, "%" PRId32)
ADD_PRIMITIVE_HANDLER(int64_t, "%" PRId64)
// other numerical handlers. Note that some of these can be trivially implicitly cast to
// other primitive types so we can't define them again. Specifically the size_t,
// intmax_t, and uintmax_t types often match other types with handlers defined above.
// Which handler each of these matches to will differ by platform however.
ADD_PRIMITIVE_HANDLER(float, "%.10f")
ADD_PRIMITIVE_HANDLER(double, "%.20f")
#undef ADD_PRIMITIVE_HANDLER
EnvCollector& add(const char* var)
{
const char* sep;
#if CARB_PLATFORM_WINDOWS
// Windows' environment sets variables such as "=C:=C:\". We need to handle this case.
// Here the variable's name is "=C:" and its value is "C:\". This similar behavior is
// not allowed on Linux however.
if (var[0] == '=')
sep = strchr(var + 1, '=');
else
#endif
sep = strchr(var, '=');
// no assignment in the string => clear out that variable.
if (sep == nullptr)
{
m_env[var] = "";
return *this;
}
m_env[std::string(var, sep - var)] = sep + 1;
return *this;
}
EnvCollector& add(const std::string& var)
{
return add(var.c_str());
}
EnvCollector& operator+=(const char* var)
{
return add(var);
}
EnvCollector& operator+=(const std::string& var)
{
return add(var.c_str());
}
EnvCollector& add(const char* const* vars)
{
for (const char* const* var = vars; var[0] != nullptr; var++)
add(var[0]);
return *this;
}
EnvCollector& add(const std::vector<const char*>& vars)
{
for (auto& v : vars)
add(v);
return *this;
}
EnvCollector& add(const std::vector<std::string>& vars)
{
for (auto& v : vars)
add(v);
return *this;
}
EnvCollector& add(const EnvCollector& vars)
{
for (auto& var : vars.m_env)
m_env[var.first] = var.second;
return *this;
}
EnvCollector& operator+=(const char* const* vars)
{
return add(vars);
}
EnvCollector& operator+=(const std::vector<const char*>& vars)
{
return add(vars);
}
EnvCollector& operator+=(const std::vector<std::string>& vars)
{
return add(vars);
}
EnvCollector& operator+=(const EnvCollector& vars)
{
return add(vars);
}
#if CARB_POSIX
EnvCollector& add()
{
# if CARB_PLATFORM_MACOS
char*** tmp = _NSGetEnviron(); // see man 7 environ
if (tmp == nullptr)
{
CARB_LOG_ERROR("_NSGetEnviron() returned nullptr");
return *this;
}
char** environ = *tmp;
# endif
for (char** env = environ; env[0] != nullptr; env++)
add(env[0]);
return *this;
}
#else
EnvCollector& add()
{
LPWCH origEnv = GetEnvironmentStringsW();
LPWCH env = origEnv;
std::string var;
size_t len;
// walk the environment strings table and add each variable to this object.
for (len = wcslen(env); env[0] != 0; env += len + 1, len = wcslen(env))
{
var = extras::convertWideToUtf8(env);
add(var);
}
FreeEnvironmentStringsW(origEnv);
return *this;
}
#endif
EnvCollector& remove(const char* name)
{
m_env.erase(name);
return *this;
}
EnvCollector& remove(const std::string& name)
{
return remove(name.c_str());
}
EnvCollector& operator-=(const char* name)
{
return remove(name);
}
EnvCollector& operator-=(const std::string& name)
{
return remove(name.c_str());
}
const std::string& at(const char* name)
{
auto var = m_env.find(name);
if (var == m_env.end())
return m_empty;
return var->second;
}
const std::string& at(const std::string& name)
{
return at(name.c_str());
}
const std::string& operator[](const char* name)
{
return at(name);
}
const std::string& operator[](const std::string& name)
{
return at(name.c_str());
}
private:
omni::extras::UnorderedPathMap<std::string> m_env;
ArgCollector m_args;
std::string m_empty;
};
} // namespace launcher
} // namespace carb