SettingsUtils.h#
Fully qualified name: carb/settings/SettingsUtils.h
File members: carb/settings/SettingsUtils.h
// SPDX-FileCopyrightText: Copyright (c) 2019-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.
#pragma once
#include "../../omni/Vector.h"
#include "../dictionary/DictionaryUtils.h"
#include "../dictionary/ISerializer.h"
#include "../cpp/Optional.h"
#include "../extras/StringUtils.h"
#include "../cpp/Span.h"
#include "../cpp/StringViewLike.h"
#include "ISettings.h"
#include <atomic>
#include <mutex>
#include <string>
namespace carb::settings
{
namespace detail
{
#if CARB_VERSION_ATLEAST(carb_settings_ISettings, 2, 0)
inline dictionary::ItemType getItemType(const ISettings* settings, cpp::string_view path)
{
return settings->getItemType(path);
}
inline std::string getString(const ISettings* settings, cpp::string_view path, cpp::string_view defaultValue = {})
{
ScopedRead lock(settings);
auto val = settings->getStringView(path);
return val ? std::string(*val) : std::string(defaultValue);
}
inline cpp::optional<std::string> getStringOpt(const ISettings* settings, cpp::string_view path)
{
ScopedRead lock(settings);
if (auto val = settings->getStringView(path))
return std::string(*val);
return cpp::nullopt;
}
inline cpp::optional<omni::string> createStringFromItemValue(const ISettings* settings, cpp::string_view path)
{
return settings->createStringFromItemValue(path);
}
inline cpp::optional<omni::string> createStringFromItemValueAt(const ISettings* settings,
cpp::string_view path,
size_t index)
{
return settings->createStringFromItemValueAt(path, index);
}
inline cpp::optional<cpp::zstring_view> getStringView(const ISettings* settings, cpp::string_view path)
{
return settings->getStringView(path);
}
inline cpp::optional<cpp::zstring_view> getStringViewAt(const ISettings* settings, cpp::string_view path, size_t index)
{
return settings->getStringViewAt(path, index);
}
inline bool isAccessibleAs(const ISettings* settings, dictionary::ItemType type, cpp::string_view path)
{
return settings->isAccessibleAs(type, path);
}
inline size_t getArrayLength(const ISettings* settings, cpp::string_view path)
{
return settings->getArrayLength(path);
}
inline void setStringArray(const ISettings* settings, cpp::string_view path, const cpp::span<const cpp::zstring_view> array)
{
settings->setArray(path, array);
}
template <class Container, std::enable_if_t<!std::is_convertible_v<Container, cpp::span<const cpp::zstring_view>>, bool> = false>
void setStringArray(const ISettings* settings, cpp::string_view path, const Container& c)
{
auto views = CARB_STACK_ALLOC(cpp::zstring_view, c.size());
for (size_t i = 0; i != c.size(); ++i)
views[i] = cpp::zstring_view(c[i]);
setStringArray(settings, path, cpp::span<cpp::zstring_view>(views, c.size()));
}
inline const dictionary::Item* getSettingsDictionary(const ISettings* settings, cpp::string_view path)
{
return settings->getSettingsDictionary(path);
}
template <class T>
cpp::optional<T> get(const ISettings* settings, cpp::string_view path)
{
return settings->getOpt<T>(path);
}
template <class T, std::enable_if_t<!cpp::is_std_string_view_like_v<T>, bool> = false>
void set(const ISettings* settings, cpp::string_view path, T value)
{
settings->set(path, value);
}
inline void set(const ISettings* settings, cpp::string_view path, cpp::string_view value)
{
settings->set(path, value);
}
template <class T, std::enable_if_t<!cpp::is_std_string_view_like_v<T>, bool> = false>
void setDefault(const ISettings* settings, cpp::string_view path, T value)
{
settings->setDefault(path, value);
}
inline void setDefault(const ISettings* settings, cpp::string_view path, cpp::string_view value)
{
settings->setDefault(path, value);
}
template <class T, std::enable_if_t<!cpp::is_carb_zstring_view_like_v<T>, bool> = false>
void setDefaultArray(const ISettings* settings, cpp::string_view path, const cpp::span<const T> array)
{
settings->setDefaultArray(path, array);
}
inline void setDefaultArray(const ISettings* settings, cpp::string_view path, const cpp::span<const cpp::zstring_view> array)
{
settings->setDefaultArray(path, array);
}
inline void update(const ISettings* settings,
cpp::string_view path,
const dictionary::Item* dictionary,
cpp::string_view dictionaryPath,
dictionary::OnUpdateItemFn onUpdateItemFn,
void* userData)
{
settings->update(path, dictionary, dictionaryPath, onUpdateItemFn, userData);
}
inline dictionary::SubscriptionId* subscribeToNodeChangeEvents(const ISettings* settings,
cpp::string_view path,
dictionary::OnNodeChangeEventFn onChangeEventFn,
void* userData)
{
return settings->subscribeToNodeChangeEvents(path, onChangeEventFn, userData);
}
inline void unsubscribeFromChangeEvents(const ISettings* settings, dictionary::SubscriptionId* sub)
{
settings->unsubscribeFromChangeEvents(sub);
}
inline void destroyItem(const ISettings* settings, cpp::string_view path)
{
settings->destroyItem(path);
}
#else
inline dictionary::ItemType getItemType(const ISettings* settings, cpp::string_view path)
{
return settings->getItemTypeS(path);
}
inline std::string getString(const ISettings* settings, cpp::string_view path, cpp::string_view defaultValue)
{
ScopedRead lock(settings);
auto val = settings->getStringBufferS(path);
return val ? std::string(*val) : std::string(defaultValue);
}
inline cpp::optional<std::string> getStringOpt(const ISettings* settings, cpp::string_view path)
{
ScopedRead lock(settings);
if (auto val = settings->getStringBufferS(path))
return std::string(*val);
return cpp::nullopt;
}
inline cpp::optional<omni::string> createStringFromItemValue(const ISettings* settings, cpp::string_view path)
{
cpp::optional<omni::string> ret;
if (auto buf = settings->createStringBufferFromItemValueS(path))
{
ret.emplace(buf);
settings->destroyStringBuffer(buf);
}
return ret;
}
inline cpp::optional<omni::string> createStringFromItemValueAt(const ISettings* settings,
cpp::string_view path,
size_t index)
{
cpp::optional<omni::string> ret;
if (auto buf = settings->createStringBufferFromItemValueAtS(path, index))
{
ret.emplace(buf);
settings->destroyStringBuffer(buf);
}
return ret;
}
inline cpp::optional<cpp::zstring_view> getStringView(const ISettings* settings, cpp::string_view path)
{
return settings->getStringBufferS(path);
}
inline cpp::optional<cpp::zstring_view> getStringViewAt(const ISettings* settings, cpp::string_view path, size_t index)
{
return settings->getStringBufferAtS(path, index);
}
inline bool isAccessibleAs(const ISettings* settings, dictionary::ItemType type, cpp::string_view path)
{
return settings->isAccessibleAsS(type, path);
}
inline size_t getArrayLength(const ISettings* settings, cpp::string_view path)
{
return settings->getArrayLengthS(path);
}
inline void setStringArray(const ISettings* settings, cpp::string_view path, const cpp::span<const cpp::zstring_view> array)
{
auto mem = CARB_STACK_ALLOC(const char*, array.size());
for (size_t i = 0; i != array.size(); ++i)
mem[i] = array[i].c_str();
settings->setStringArrayS(path, mem, array.size());
}
template <class Container, std::enable_if_t<!std::is_convertible_v<Container, cpp::span<const cpp::zstring_view>>, bool> = false>
void setStringArray(const ISettings* settings, cpp::string_view path, const Container& c)
{
auto views = CARB_STACK_ALLOC(const char*, c.size());
for (size_t i = 0; i != c.size(); ++i)
views[i] = cpp::zstring_view(c[i]).c_str();
settings->setStringArrayS(path, views, c.size());
}
inline const dictionary::Item* getSettingsDictionary(const ISettings* settings, cpp::string_view path)
{
return settings->getSettingsDictionaryS(path);
}
template <class T>
cpp::optional<T> get(const ISettings* settings, cpp::string_view path)
{
if constexpr (std::is_same_v<T, cpp::zstring_view>)
return settings->getS<cpp::optional<T>>(path);
else if constexpr (cpp::is_unbounded_character_sequence_v<T, char>)
{
auto opt = settings->getS<cpp::optional<cpp::zstring_view>>(path);
return opt ? opt->c_str() : nullptr;
}
else
{
ScopedRead lock(settings);
if (settings->getItemTypeS(path) == dictionary::ItemType::eCount)
return cpp::nullopt;
return settings->getS<T>(path);
}
}
template <class T, std::enable_if_t<!cpp::is_std_string_view_like_v<T>, bool> = false>
void set(const ISettings* settings, cpp::string_view path, T value)
{
settings->setS(path, value);
}
inline void set(const ISettings* settings, cpp::string_view path, cpp::string_view value)
{
settings->setS(path, value);
}
template <class T, std::enable_if_t<!cpp::is_std_string_view_like_v<T>, bool> = false>
void setDefault(const ISettings* settings, cpp::string_view path, T value)
{
settings->setDefaultS(path, value);
}
inline void setDefault(const ISettings* settings, cpp::string_view path, cpp::string_view value)
{
settings->setDefaultS(path, value);
}
template <class T, std::enable_if_t<!cpp::is_carb_zstring_view_like_v<T>, bool> = false>
void setDefaultArray(const ISettings* settings, cpp::string_view path, const cpp::span<const T> array)
{
settings->setDefaultArrayS(path, array.data(), array.size());
}
inline void setDefaultArray(const ISettings* settings, cpp::string_view path, const cpp::span<const cpp::zstring_view> array)
{
auto vals = CARB_STACK_ALLOC(const char*, array.size());
for (size_t i = 0; i != array.size(); ++i)
vals[i] = array[i].c_str();
settings->setDefaultArrayS(path, vals, array.size());
}
inline void update(const ISettings* settings,
cpp::string_view path,
const dictionary::Item* dictionary,
cpp::string_view dictionaryPath,
dictionary::OnUpdateItemFn onUpdateItemFn,
void* userData)
{
settings->updateS(path, dictionary, dictionaryPath, onUpdateItemFn, userData);
}
inline dictionary::SubscriptionId* subscribeToNodeChangeEvents(const ISettings* settings,
cpp::string_view path,
dictionary::OnNodeChangeEventFn onChangeEventFn,
void* userData)
{
return settings->subscribeToNodeChangeEventsS(path, onChangeEventFn, userData);
}
inline void unsubscribeFromChangeEvents(const ISettings* settings, dictionary::SubscriptionId* sub)
{
settings->unsubscribeToChangeEvents(sub);
}
inline void destroyItem(const ISettings* settings, cpp::string_view path)
{
settings->destroyItemS(path);
}
#endif
template <class Container>
Container getStringArray(const ISettings* settings, cpp::string_view path, cpp::string_view defaultValue = {})
{
ScopedRead readLock(settings);
Container array(getArrayLength(settings, path));
using value_type = typename Container::value_type;
const size_t arraySize = array.size();
for (size_t i = 0; i != arraySize; ++i)
{
auto val = getStringViewAt(settings, path, i);
array[i] = val ? value_type(*val) : value_type(defaultValue);
}
return array;
}
template <typename ElementData, typename OnItemFnTypeS>
inline void walkSettings(dictionary::IDictionary* idict,
settings::ISettings* settings,
dictionary::WalkerMode walkerMode,
cpp::string_view rootPath,
ElementData rootElementData,
OnItemFnTypeS onItemFnS)
{
using namespace carb;
if (rootPath.empty())
rootPath = "/";
struct ValueToParse
{
std::string srcPath;
ElementData elementData;
};
std::vector<ValueToParse> valuesToParse;
valuesToParse.reserve(10);
auto enqueueChildren = [&idict, &settings, &valuesToParse](
cpp::string_view parentPath, ElementData parentElementData) {
const dictionary::Item* parentItem = getSettingsDictionary(settings, parentPath);
size_t numChildren = idict->getItemChildCount(parentItem);
for (size_t chIdx = 0; chIdx < numChildren; ++chIdx)
{
const dictionary::Item* childItem = idict->getItemChildByIndex(parentItem, numChildren - chIdx - 1);
auto childItemName = dictionary::detail::getItemName(idict, childItem).value_or("");
std::string childPath;
bool isRootParent = (idict->getItemParent(parentItem) == nullptr);
if (isRootParent)
{
childPath = extras::join(parentPath, childItemName);
}
else
{
childPath = extras::join(parentPath, "/", childItemName);
}
valuesToParse.push_back({ childPath, parentElementData });
}
};
// lock settings to ensure consistency and allow us to safely access the settings dictionary
ScopedRead readLock(settings, idict);
if (walkerMode == dictionary::WalkerMode::eSkipRoot)
{
enqueueChildren(rootPath, rootElementData);
}
else
{
valuesToParse.push_back({ std::string{ rootPath }, rootElementData });
}
while (valuesToParse.size())
{
ValueToParse& valueToParse = valuesToParse.back(); // this is not a const reference because we move the value
// out of the vector
std::string curItemPath = std::move(valueToParse.srcPath);
ElementData elementData = std::move(valueToParse.elementData);
valuesToParse.pop_back();
dictionary::ItemType curItemType = getItemType(settings, curItemPath);
[[maybe_unused]] ElementData parentElementData = onItemFnS(curItemPath, elementData);
if (curItemType == dictionary::ItemType::eDictionary)
enqueueChildren(curItemPath, parentElementData);
}
}
} // namespace detail
#if CARB_VERSION_ATLEAST(carb_settings_ISettings, 2, 0)
inline omni::string getStringFromItemValue(const ISettings* settings,
cpp::string_view path,
cpp::string_view defaultValue = {})
{
auto val = settings->createStringFromItemValue(path);
return val ? *val : omni::string(defaultValue);
}
inline omni::string getStringFromItemValueAt(const ISettings* settings,
cpp::string_view path,
size_t index,
cpp::string_view defaultValue = {})
{
auto val = settings->createStringFromItemValueAt(path, index);
return val ? *val : omni::string(defaultValue);
}
inline omni::string getString(const ISettings* settings, cpp::string_view path, cpp::string_view defaultValue = {})
{
ScopedRead lock(settings);
auto val = settings->getStringView(path);
return val ? omni::string(*val) : omni::string(defaultValue);
}
inline cpp::optional<omni::string> getStringOpt(const ISettings* settings, cpp::string_view path)
{
ScopedRead lock(settings);
if (auto val = settings->getStringView(path))
return omni::string(*val);
return cpp::nullopt;
}
inline omni::string getStringAt(const ISettings* settings,
cpp::string_view path,
size_t index,
cpp::string_view defaultValue = {})
{
ScopedRead lock(settings);
auto value = settings->getStringViewAt(path, index);
return value ? omni::string(*value) : omni::string(defaultValue);
}
inline cpp::optional<omni::string> getStringAtOpt(const ISettings* settings, cpp::string_view path, size_t index)
{
ScopedRead lock(settings);
if (auto value = settings->getStringViewAt(path, index))
return omni::string(*value);
return cpp::nullopt;
}
inline std::vector<std::string> getStringArray(ISettings* settings,
cpp::string_view path,
cpp::string_view defaultValue = {})
{
return detail::getStringArray<std::vector<std::string>>(settings, path, defaultValue);
}
inline std::vector<std::string> getStringArrayFromItemValues(const ISettings* settings,
cpp::string_view path,
cpp::string_view defaultValue = {})
{
ScopedRead readLock(settings);
std::vector<std::string> array(settings->getArrayLength(path));
const size_t arraySize = array.size();
for (size_t i = 0; i < arraySize; ++i)
{
auto val = settings->createStringFromItemValueAt(path, i);
array[i] = val ? std::string(*val) : std::string(defaultValue);
}
return array;
}
inline void setStringArray(ISettings* settings, cpp::string_view path, const std::vector<std::string>& array)
{
const size_t arraySize = array.size();
auto mem = CARB_STACK_ALLOC(cpp::string_view, arraySize);
for (size_t i = 0; i != arraySize; ++i)
mem[i] = array[i];
settings->setStringArray(path, { mem, arraySize });
}
inline void loadSettingsFromFile(ISettings* settings,
cpp::string_view path,
dictionary::IDictionary* dictionary,
dictionary::ISerializer* serializer,
cpp::zstring_view filename)
{
dictionary::Item* settingsFromFile = dictionary::createDictionaryFromFileS(serializer, filename);
if (settingsFromFile)
{
CARB_SCOPE_EXIT
{
dictionary->destroyItem(settingsFromFile);
};
settings->update(path, settingsFromFile, {}, dictionary::overwriteOriginalWithArrayHandling, dictionary);
}
}
inline void saveFileFromSettings(const ISettings* settings,
dictionary::ISerializer* serializer,
cpp::string_view path,
cpp::zstring_view filename,
dictionary::SerializerOptions serializerOptions)
{
ScopedRead lock;
auto settingsDictionaryAtPath = detail::getSettingsDictionary(settings, path);
dictionary::saveFileFromDictionaryS(serializer, settingsDictionaryAtPath, filename, serializerOptions);
}
template <typename ElementData, typename OnItemFnTypeS>
inline void walkSettings(dictionary::IDictionary* idict,
settings::ISettings* settings,
dictionary::WalkerMode walkerMode,
cpp::string_view rootPath,
ElementData rootElementData,
OnItemFnTypeS onItemFnS)
{
detail::walkSettings(idict, settings, walkerMode, rootPath, std::move(rootElementData), std::move(onItemFnS));
}
#else
inline std::string getStringFromItemValueS(const ISettings* settings,
cpp::string_view path,
cpp::string_view defaultValue = "")
{
if (auto val = detail::createStringFromItemValue(settings, path))
return std::string((cpp::string_view)*val);
return std::string(defaultValue);
}
inline std::string getStringFromItemValue(const ISettings* settings, const char* path, const std::string& defaultValue = "")
{
if (auto val = detail::createStringFromItemValue(settings, cpp::string_view(cpp::unsafe_length, path)))
return std::string((cpp::string_view)*val);
return std::string(defaultValue);
}
inline std::string getStringFromItemValueAtS(const ISettings* settings,
cpp::string_view path,
size_t index,
cpp::string_view defaultValue = "")
{
if (auto val = detail::createStringFromItemValueAt(settings, path, index))
return std::string((cpp::string_view)*val);
return std::string(defaultValue);
}
inline std::string getStringFromItemValueAt(const ISettings* settings,
const char* path,
size_t index,
const std::string& defaultValue = "")
{
if (auto val = detail::createStringFromItemValueAt(settings, cpp::string_view(cpp::unsafe_length, path), index))
return std::string((cpp::string_view)*val);
return std::string(defaultValue);
}
inline std::string getStringS(const ISettings* settings, cpp::string_view path, cpp::string_view defaultValue = "")
{
return detail::getString(settings, path, defaultValue);
}
inline std::string getString(const ISettings* settings, const char* path, const std::string& defaultValue = "")
{
return detail::getString(settings, cpp::string_view(cpp::unsafe_length, path), defaultValue);
}
inline cpp::optional<std::string> getStringOptS(const ISettings* settings, cpp::string_view path)
{
return detail::getStringOpt(settings, path);
}
inline cpp::optional<std::string> getStringOpt(const ISettings* settings, const char* path)
{
return detail::getStringOpt(settings, cpp::string_view(cpp::unsafe_length, path));
}
inline std::string getStringAtS(const ISettings* settings,
cpp::string_view path,
size_t index,
cpp::string_view defaultValue = "")
{
ScopedRead lock(settings);
if (auto value = detail::getStringViewAt(settings, path, index))
{
return std::string{ *value };
}
return std::string{ defaultValue };
}
inline std::string getStringAt(const ISettings* settings,
const char* path,
size_t index,
const std::string& defaultValue = "")
{
ScopedRead lock(settings);
if (auto value = detail::getStringViewAt(settings, cpp::string_view(cpp::unsafe_length, path), index))
{
return std::string{ *value };
}
return std::string{ defaultValue };
}
inline cpp::optional<std::string> getStringAtOptS(const ISettings* settings, cpp::string_view path, size_t index)
{
ScopedRead lock(settings);
if (auto value = detail::getStringViewAt(settings, path, index))
{
return std::string{ *value };
}
return cpp::nullopt;
}
inline cpp::optional<std::string> getStringAtOpt(const ISettings* settings, const char* path, size_t index)
{
ScopedRead lock(settings);
if (auto value = detail::getStringViewAt(settings, cpp::string_view(cpp::unsafe_length, path), index))
{
return std::string{ *value };
}
return cpp::nullopt;
}
inline void setIntArrayS(ISettings* settings, cpp::string_view path, const std::vector<int>& array)
{
settings->setIntArrayS(path, array.data(), array.size());
}
inline void setIntArray(ISettings* settings, const char* path, const std::vector<int>& array)
{
settings->setIntArray(path, array.data(), array.size());
}
inline void setIntArrayS(ISettings* settings, cpp::string_view path, const std::vector<int64_t>& array)
{
settings->setInt64ArrayS(path, array.data(), array.size());
}
inline void setIntArray(ISettings* settings, const char* path, const std::vector<int64_t>& array)
{
settings->setInt64Array(path, array.data(), array.size());
}
inline void setFloatArrayS(ISettings* settings, cpp::string_view path, const std::vector<float>& array)
{
settings->setFloatArrayS(path, array.data(), array.size());
}
inline void setFloatArray(ISettings* settings, const char* path, const std::vector<float>& array)
{
settings->setFloatArray(path, array.data(), array.size());
}
inline void setFloatArrayS(ISettings* settings, cpp::string_view path, const std::vector<double>& array)
{
settings->setFloat64ArrayS(path, array.data(), array.size());
}
inline void setFloatArray(ISettings* settings, const char* path, const std::vector<double>& array)
{
settings->setFloat64Array(path, array.data(), array.size());
}
inline void setBoolArrayS(ISettings* settings, cpp::string_view path, const std::vector<bool>& array)
{
const size_t arraySize = array.size();
// Since std::vector<bool> is typically specialized and doesn't function like normal vector (i.e. no data()), first
// convert to an array of bools on the stack.
bool* pbools = CARB_STACK_ALLOC(bool, arraySize);
for (size_t i = 0; i != arraySize; ++i)
pbools[i] = array[i];
settings->setBoolArrayS(path, pbools, arraySize);
}
inline void setBoolArray(ISettings* settings, const char* path, const std::vector<bool>& array)
{
const size_t arraySize = array.size();
// Since std::vector<bool> is typically specialized and doesn't function like normal vector (i.e. no data()), first
// convert to an array of bools on the stack.
bool* pbools = CARB_STACK_ALLOC(bool, arraySize);
for (size_t i = 0; i != arraySize; ++i)
pbools[i] = array[i];
settings->setBoolArray(path, pbools, arraySize);
}
inline std::vector<std::string> getStringArrayS(ISettings* settings,
cpp::string_view path,
cpp::string_view defaultValue = {})
{
return detail::getStringArray<std::vector<std::string>>(settings, path, defaultValue);
}
inline std::vector<std::string> getStringArray(ISettings* settings,
const char* path,
const cpp::string_view defaultValue = {})
{
return detail::getStringArray<std::vector<std::string>>(
settings, cpp::string_view(cpp::unsafe_length, path), defaultValue);
}
inline std::vector<std::string> getStringArrayFromItemValuesS(ISettings* settings,
cpp::string_view path,
cpp::string_view defaultValue = "")
{
ScopedRead readLock(settings);
std::vector<std::string> array(detail::getArrayLength(settings, path));
for (size_t i = 0, arraySize = array.size(); i < arraySize; ++i)
{
array[i] = getStringFromItemValueAtS(settings, path, i, defaultValue);
}
return array;
}
inline std::vector<std::string> getStringArrayFromItemValues(ISettings* settings,
const char* path,
const std::string& defaultValue = "")
{
dictionary::ScopedRead readLock(*getCachedInterface<dictionary::IDictionary>(), settings->getSettingsDictionary(""));
std::vector<std::string> array(detail::getArrayLength(settings, cpp::string_view(cpp::unsafe_length, path)));
for (size_t i = 0, arraySize = array.size(); i < arraySize; ++i)
{
array[i] = getStringFromItemValueAt(settings, path, i, defaultValue);
}
return array;
}
inline void setStringArrayS(ISettings* settings, cpp::string_view path, const std::vector<std::string>& array)
{
const size_t arraySize = array.size();
auto mem = CARB_STACK_ALLOC(cpp::zstring_view, arraySize);
for (size_t i = 0; i != arraySize; ++i)
mem[i] = array[i];
detail::setStringArray(settings, path, cpp::span<cpp::zstring_view>(mem, arraySize));
}
inline void setStringArray(ISettings* settings, const char* path, const std::vector<std::string>& array)
{
const size_t arraySize = array.size();
auto mem = CARB_STACK_ALLOC(cpp::zstring_view, arraySize);
for (size_t i = 0; i != arraySize; ++i)
mem[i] = array[i];
detail::setStringArray(
settings, cpp::string_view(cpp::unsafe_length, path), cpp::span<cpp::zstring_view>(mem, arraySize));
}
inline void loadSettingsFromFileS(ISettings* settings,
cpp::string_view path,
dictionary::IDictionary* dictionary,
dictionary::ISerializer* serializer,
cpp::zstring_view filename)
{
dictionary::Item* settingsFromFile = dictionary::createDictionaryFromFileS(serializer, filename);
if (settingsFromFile)
{
settings->updateS(path, settingsFromFile, {}, dictionary::overwriteOriginalWithArrayHandling, dictionary);
dictionary->destroyItem(settingsFromFile);
}
}
inline void loadSettingsFromFile(ISettings* settings,
const char* path,
dictionary::IDictionary* dictionary,
dictionary::ISerializer* serializer,
const char* filename)
{
dictionary::Item* settingsFromFile = dictionary::createDictionaryFromFile(serializer, filename);
if (settingsFromFile)
{
settings->update(path, settingsFromFile, {}, dictionary::overwriteOriginalWithArrayHandling, dictionary);
dictionary->destroyItem(settingsFromFile);
}
}
inline void saveFileFromSettingsS(const ISettings* settings,
dictionary::ISerializer* serializer,
cpp::string_view path,
cpp::zstring_view filename,
dictionary::SerializerOptions serializerOptions)
{
ScopedRead lock;
auto settingsDictionaryAtPath = detail::getSettingsDictionary(settings, path);
dictionary::saveFileFromDictionaryS(serializer, settingsDictionaryAtPath, filename, serializerOptions);
}
inline void saveFileFromSettings(const ISettings* settings,
dictionary::ISerializer* serializer,
const char* path,
const char* filename,
dictionary::SerializerOptions serializerOptions)
{
ScopedRead lock;
auto settingsDictionaryAtPath = detail::getSettingsDictionary(settings, cpp::string_view(cpp::unsafe_length, path));
dictionary::saveFileFromDictionary(serializer, settingsDictionaryAtPath, filename, serializerOptions);
}
template <typename ElementData, typename OnItemFnTypeS>
inline void walkSettingsS(dictionary::IDictionary* idict,
settings::ISettings* settings,
dictionary::WalkerMode walkerMode,
cpp::string_view rootPath,
ElementData rootElementData,
OnItemFnTypeS onItemFnS,
void* userData)
{
detail::walkSettings(idict, settings, walkerMode, rootPath, std::move(rootElementData),
[&](const std::string& str, const ElementData& ed) { return onItemFnS(str, ed, userData); });
}
template <typename ElementData, typename OnItemFnType>
inline void walkSettings(dictionary::IDictionary* idict,
settings::ISettings* settings,
dictionary::WalkerMode walkerMode,
const char* rootPath,
ElementData rootElementData,
OnItemFnType onItemFn,
void* userData)
{
if (!rootPath)
return;
detail::walkSettings(
idict, settings, walkerMode, cpp::string_view(cpp::unsafe_length, rootPath), std::move(rootElementData),
[&](cpp::zstring_view str, const ElementData& ed) { return onItemFn(str.c_str(), ed, userData); });
}
#endif
template <typename SettingType>
class ThreadSafeLocalCache
{
public:
ThreadSafeLocalCache(SettingType initState = SettingType{}) : m_value(initState), m_valueDirty(false)
{
}
~ThreadSafeLocalCache()
{
stopTracking();
}
void startTracking(cpp::string_view settingPath)
{
CARB_ASSERT(m_subscription == nullptr,
"Already tracking this value, do not track again without calling stopTracking first.");
Framework* f = getFramework();
m_settings = f->tryAcquireInterface<settings::ISettings>();
m_dictionary = f->tryAcquireInterface<dictionary::IDictionary>();
if (!m_settings || !m_dictionary)
return;
m_valueSettingsPath = settingPath;
m_value.store(
detail::get<SettingType>(m_settings, settingPath).value_or(SettingType{}), std::memory_order_relaxed);
m_valueDirty.store(false, std::memory_order_release);
m_subscription = detail::subscribeToNodeChangeEvents(
m_settings, settingPath,
[](const dictionary::Item* changedItem, dictionary::ChangeEventType changeEventType, void* userData) {
if (changeEventType == dictionary::ChangeEventType::eChanged)
{
ThreadSafeLocalCache* thisClassInstance = reinterpret_cast<ThreadSafeLocalCache*>(userData);
thisClassInstance->m_value.store(
thisClassInstance->getDictionaryInterface()->template get<SettingType>(changedItem),
std::memory_order_relaxed);
thisClassInstance->m_valueDirty.store(true, std::memory_order_release);
}
},
this);
if (m_subscription != nullptr)
{
f->addReleaseHook(m_settings, sOnRelease, this);
}
}
#if !CARB_VERSION_ATLEAST(carb_settings_ISettings, 2, 0)
void startTracking(const char* settingPath)
{
CARB_ASSERT(settingPath, "Must specify a valid setting name.");
startTracking(cpp::string_view(cpp::unsafe_length, settingPath));
}
#endif
void stopTracking()
{
if (m_subscription)
{
getFramework()->removeReleaseHook(m_settings, sOnRelease, this);
detail::unsubscribeFromChangeEvents(m_settings, std::exchange(m_subscription, nullptr));
}
}
SettingType get() const
{
CARB_ASSERT(m_subscription, "Call startTracking before reading this variable.");
return m_value.load(std::memory_order_relaxed);
}
operator SettingType() const
{
return get();
}
void set(SettingType value)
{
CARB_ASSERT(m_subscription);
if (!m_valueSettingsPath.empty())
m_settings->set<SettingType>(m_valueSettingsPath.c_str(), value);
}
bool isValueDirty() const
{
return m_valueDirty.load(std::memory_order_relaxed);
}
void clearValueDirty()
{
m_valueDirty.store(false, std::memory_order_release);
}
const char* getSettingsPath() const
{
return m_valueSettingsPath.c_str();
}
cpp::zstring_view getSettingsPathS() const
{
return m_valueSettingsPath;
}
inline dictionary::IDictionary* getDictionaryInterface() const
{
return m_dictionary;
}
private:
static void sOnRelease(void* iface, void* user)
{
// Settings has gone away, so our subscription is defunct
static_cast<ThreadSafeLocalCache*>(user)->m_subscription = nullptr;
getFramework()->removeReleaseHook(iface, sOnRelease, user);
}
// NOTE: The callback may come in on another thread so wrap it in an atomic to prevent a race.
std::atomic<SettingType> m_value;
std::atomic<bool> m_valueDirty;
std::string m_valueSettingsPath;
dictionary::SubscriptionId* m_subscription = nullptr;
dictionary::IDictionary* m_dictionary = nullptr;
settings::ISettings* m_settings = nullptr;
};
#ifndef DOXYGEN_BUILD
# if CARB_VERSION_ATLEAST(carb_settings_ISettings, 2, 0)
template <>
class ThreadSafeLocalCache<std::string>
{
public:
ThreadSafeLocalCache(cpp::string_view initState = {}) : m_valueDirty(false)
{
m_value = initState;
}
~ThreadSafeLocalCache()
{
stopTracking();
}
void startTracking(cpp::string_view settingPath)
{
CARB_ASSERT(m_subscription == nullptr,
"Already tracking this value, do not track again without calling stopTracking first.");
m_settings = getCachedInterface<settings::ISettings>();
m_dictionary = getCachedInterface<dictionary::IDictionary>();
m_valueSettingsPath = settingPath;
m_value = detail::get<cpp::zstring_view>(m_settings, settingPath).value_or("");
m_valueDirty.store(false, std::memory_order_release);
m_subscription = m_settings->subscribeToNodeChangeEvents(
settingPath,
[](const dictionary::Item* changedItem, dictionary::ChangeEventType changeEventType, void* userData) {
if (changeEventType == dictionary::ChangeEventType::eChanged)
{
ThreadSafeLocalCache* thisClassInstance = reinterpret_cast<ThreadSafeLocalCache*>(userData);
{
auto valueStringBuffer =
thisClassInstance->getDictionaryInterface()->template get<std::string>(changedItem);
std::lock_guard<std::mutex> guard(thisClassInstance->m_valueMutex);
thisClassInstance->m_value = std::move(valueStringBuffer);
}
thisClassInstance->m_valueDirty.store(true, std::memory_order_release);
}
},
this);
if (m_subscription)
{
getFramework()->addReleaseHook(m_settings, sOnRelease, this);
}
}
void stopTracking()
{
if (m_subscription)
{
detail::unsubscribeFromChangeEvents(m_settings, std::exchange(m_subscription, nullptr));
getFramework()->removeReleaseHook(m_settings, sOnRelease, this);
}
}
std::string get() const
{
std::lock_guard guard(m_valueMutex);
return m_value;
}
operator std::string() const
{
return get();
}
std::string getStringSafe() const
{
// Not a safe operation
CARB_ASSERT(m_subscription, "Call startTracking before reading this variable.");
std::lock_guard guard(m_valueMutex);
return m_value;
}
void set(cpp::string_view value)
{
m_settings->set(m_valueSettingsPath, value);
}
bool isValueDirty() const
{
return m_valueDirty.load(std::memory_order_relaxed);
}
void clearValueDirty()
{
m_valueDirty.store(false, std::memory_order_release);
}
cpp::zstring_view getSettingsPath() const
{
return m_valueSettingsPath;
}
inline dictionary::IDictionary* getDictionaryInterface() const
{
return m_dictionary;
}
private:
static void sOnRelease(void* iface, void* user)
{
// Settings has gone away, so our subscription is defunct
static_cast<ThreadSafeLocalCache*>(user)->m_subscription = nullptr;
getFramework()->removeReleaseHook(iface, sOnRelease, user);
}
// NOTE: The callback may come in on another thread so wrap it in a mutex to prevent a race.
std::string m_value;
mutable std::mutex m_valueMutex;
std::atomic<bool> m_valueDirty;
std::string m_valueSettingsPath;
dictionary::SubscriptionId* m_subscription = nullptr;
dictionary::IDictionary* m_dictionary = nullptr;
settings::ISettings* m_settings = nullptr;
};
# else
template <>
class ThreadSafeLocalCache<const char*>
{
public:
ThreadSafeLocalCache(const char* initState = "") : m_valueDirty(false)
{
std::lock_guard guard(m_valueMutex);
m_value = initState;
}
~ThreadSafeLocalCache()
{
stopTracking();
}
void startTracking(cpp::string_view settingPath)
{
CARB_ASSERT(m_subscription == nullptr,
"Already tracking this value, do not track again without calling stopTracking first.");
m_settings = getCachedInterface<settings::ISettings>();
m_dictionary = getCachedInterface<dictionary::IDictionary>();
m_valueSettingsPath = settingPath;
m_value = detail::get<cpp::zstring_view>(m_settings, settingPath).value_or("");
m_valueDirty.store(false, std::memory_order_release);
m_subscription = detail::subscribeToNodeChangeEvents(
m_settings, settingPath,
[](const dictionary::Item* changedItem, dictionary::ChangeEventType changeEventType, void* userData) {
if (changeEventType == dictionary::ChangeEventType::eChanged)
{
ThreadSafeLocalCache* thisClassInstance = reinterpret_cast<ThreadSafeLocalCache*>(userData);
{
auto valueStringBuffer =
thisClassInstance->getDictionaryInterface()->template get<std::string>(changedItem);
std::lock_guard guard(thisClassInstance->m_valueMutex);
thisClassInstance->m_value = std::move(valueStringBuffer);
}
thisClassInstance->m_valueDirty.store(true, std::memory_order_release);
}
},
this);
if (m_subscription)
{
getFramework()->addReleaseHook(m_settings, sOnRelease, this);
}
}
void startTracking(const char* settingPath)
{
CARB_ASSERT(settingPath, "Must specify a valid setting name.");
startTracking(cpp::string_view(cpp::unsafe_length, settingPath));
}
void stopTracking()
{
if (m_subscription)
{
detail::unsubscribeFromChangeEvents(m_settings, std::exchange(m_subscription, nullptr));
getFramework()->removeReleaseHook(m_settings, sOnRelease, this);
}
}
const char* get() const
{
// Not a safe operation
CARB_ASSERT(false);
CARB_LOG_ERROR("Shouldn't use unsafe get on a ThreadSafeLocalCache<const char*>");
return "";
}
operator const char*() const
{
// Not a safe operation
return get();
}
std::string getStringSafe() const
{
// Not a safe operation
CARB_ASSERT(m_subscription, "Call startTracking before reading this variable.");
std::lock_guard guard(m_valueMutex);
return m_value;
}
void set(const char* value)
{
# if CARB_VERSION_ATLEAST(carb_settings_ISettings, 2, 0)
m_settings->set(m_valueSettingsPath, cpp::string_view(cpp::unsafe_length, value));
# else
m_settings->setStringS(m_valueSettingsPath, cpp::string_view(cpp::unsafe_length, value));
# endif
}
bool isValueDirty() const
{
return m_valueDirty.load(std::memory_order_relaxed);
}
void clearValueDirty()
{
m_valueDirty.store(false, std::memory_order_release);
}
const char* getSettingsPath() const
{
return m_valueSettingsPath.c_str();
}
cpp::zstring_view getSettingsPathS() const
{
return m_valueSettingsPath;
}
inline dictionary::IDictionary* getDictionaryInterface() const
{
return m_dictionary;
}
private:
static void sOnRelease(void* iface, void* user)
{
// Settings has gone away, so our subscription is defunct
static_cast<ThreadSafeLocalCache*>(user)->m_subscription = nullptr;
getFramework()->removeReleaseHook(iface, sOnRelease, user);
}
// NOTE: The callback may come in on another thread so wrap it in a mutex to prevent a race.
std::string m_value;
mutable std::mutex m_valueMutex;
std::atomic<bool> m_valueDirty;
std::string m_valueSettingsPath;
dictionary::SubscriptionId* m_subscription = nullptr;
dictionary::IDictionary* m_dictionary = nullptr;
settings::ISettings* m_settings = nullptr;
};
# endif
#endif
class ScopedSubscription
{
public:
ScopedSubscription() = default;
ScopedSubscription(cpp::string_view path, dictionary::OnNodeChangeEventFn fn, void* userData)
{
_getSettingsInterface();
if (m_settings != nullptr)
{
m_sub = detail::subscribeToNodeChangeEvents(m_settings, path, fn, userData);
}
}
ScopedSubscription(cpp::string_view path, dictionary::OnTreeChangeEventFn fn, void* userData)
{
_getSettingsInterface();
if (m_settings != nullptr)
{
#if CARB_VERSION_ATLEAST(carb_settings_ISettings, 2, 0)
m_sub = m_settings->subscribeToTreeChangeEvents(path, fn, userData);
#else
m_sub = m_settings->subscribeToTreeChangeEventsS(path, fn, userData);
#endif
}
}
#if !CARB_VERSION_ATLEAST(carb_settings_ISettings, 2, 0)
ScopedSubscription(const char* path, dictionary::OnNodeChangeEventFn fn, void* userData)
: ScopedSubscription(cpp::string_view(cpp::unsafe_length, path), fn, userData)
{
}
ScopedSubscription(const char* path, dictionary::OnTreeChangeEventFn fn, void* userData)
: ScopedSubscription(cpp::string_view(cpp::unsafe_length, path), fn, userData)
{
}
#endif
ScopedSubscription(ScopedSubscription&& other)
{
*this = std::move(other);
}
~ScopedSubscription()
{
// remove the release hook since we no longer need it. Note that if the release hook
// had fired, it will already have been removed and this will be a no-op.
getFramework()->removeReleaseHook(m_settings, sOnReleaseSettings, this);
_clearSubscription();
}
CARB_PREVENT_COPY(ScopedSubscription);
ScopedSubscription& operator=(ScopedSubscription&& other)
{
if (&other == this)
return *this;
_clearSubscription();
_getSettingsInterface();
m_sub = std::exchange(other.m_sub, nullptr);
return *this;
}
explicit operator bool() const noexcept
{
return m_sub != nullptr;
}
private:
static void sOnReleaseSettings([[maybe_unused]] void* iface, void* userData)
{
ScopedSubscription* self = reinterpret_cast<ScopedSubscription*>(userData);
if (self == nullptr)
return;
getFramework()->removeReleaseHook(self->m_settings, sOnReleaseSettings, self);
self->m_settings = nullptr;
self->m_sub = nullptr;
}
void _getSettingsInterface()
{
if (m_settings != nullptr)
return;
m_settings = getCachedInterface<settings::ISettings>();
getFramework()->addReleaseHook(m_settings, sOnReleaseSettings, this);
}
void _clearSubscription()
{
if (m_sub != nullptr && m_settings != nullptr)
{
detail::unsubscribeFromChangeEvents(m_settings, std::exchange(m_sub, nullptr));
}
}
settings::ISettings* m_settings = nullptr;
dictionary::SubscriptionId* m_sub = nullptr;
};
} // namespace carb::settings