carb/settings/SettingsUtils.h
File members: carb/settings/SettingsUtils.h
// Copyright (c) 2019-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 "../dictionary/DictionaryUtils.h"
#include "../dictionary/ISerializer.h"
#include "ISettings.h"
#include <atomic>
#include <string>
namespace carb
{
namespace settings
{
inline std::string getStringFromItemValue(const ISettings* settings, const char* path, const std::string& defaultValue = "")
{
const char* stringBuf = settings->createStringBufferFromItemValue(path);
if (!stringBuf)
return defaultValue;
std::string returnString = stringBuf;
settings->destroyStringBuffer(stringBuf);
return returnString;
}
inline std::string getStringFromItemValueAt(const ISettings* settings,
const char* path,
size_t index,
const std::string& defaultValue = "")
{
const char* stringBuf = settings->createStringBufferFromItemValueAt(path, index);
if (!stringBuf)
return defaultValue;
std::string returnString = stringBuf;
settings->destroyStringBuffer(stringBuf);
return returnString;
}
inline std::string getString(const ISettings* settings, const char* path, const std::string& defaultValue = "")
{
const char* value = settings->getStringBuffer(path);
if (!value)
return defaultValue;
return value;
}
inline std::string getStringAt(const ISettings* settings,
const char* path,
size_t index,
const std::string& defaultValue = "")
{
const char* value = settings->getStringBufferAt(path, index);
if (!value)
return defaultValue;
return value;
}
inline void setIntArray(ISettings* settings, const char* path, const std::vector<int>& array)
{
settings->setIntArray(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 setFloatArray(ISettings* settings, const char* path, const std::vector<float>& array)
{
settings->setFloatArray(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 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> getStringArray(ISettings* settings, const char* path, const std::string& defaultValue = "")
{
dictionary::ScopedRead readLock(
*carb::getCachedInterface<dictionary::IDictionary>(), settings->getSettingsDictionary(""));
std::vector<std::string> array(settings->getArrayLength(path));
for (size_t i = 0, arraySize = array.size(); i < arraySize; ++i)
{
array[i] = getStringAt(settings, path, i, defaultValue);
}
return array;
}
inline std::vector<std::string> getStringArrayFromItemValues(ISettings* settings,
const char* path,
const std::string& defaultValue = "")
{
dictionary::ScopedRead readLock(
*carb::getCachedInterface<dictionary::IDictionary>(), settings->getSettingsDictionary(""));
std::vector<std::string> array(settings->getArrayLength(path));
for (size_t i = 0, arraySize = array.size(); i < arraySize; ++i)
{
array[i] = getStringFromItemValueAt(settings, path, i, defaultValue);
}
return array;
}
inline void setStringArray(ISettings* settings, const char* path, const std::vector<std::string>& array)
{
const size_t arraySize = array.size();
const char** pp = CARB_STACK_ALLOC(const char*, arraySize);
for (size_t i = 0; i != arraySize; ++i)
pp[i] = array[i].c_str();
settings->setStringArray(path, pp, arraySize);
}
inline void loadSettingsFromFile(ISettings* settings,
const char* path,
dictionary::IDictionary* dictionary,
dictionary::ISerializer* serializer,
const char* filename)
{
carb::dictionary::Item* settingsFromFile = carb::dictionary::createDictionaryFromFile(serializer, filename);
if (settingsFromFile)
{
settings->update(path, settingsFromFile, nullptr, dictionary::overwriteOriginalWithArrayHandling, dictionary);
dictionary->destroyItem(settingsFromFile);
}
}
inline void saveFileFromSettings(const ISettings* settings,
dictionary::ISerializer* serializer,
const char* path,
const char* filename,
dictionary::SerializerOptions serializerOptions)
{
const dictionary::Item* settingsDictionaryAtPath = settings->getSettingsDictionary(path);
dictionary::saveFileFromDictionary(serializer, settingsDictionaryAtPath, filename, serializerOptions);
}
template <typename ElementData, typename OnItemFnType>
inline void walkSettings(carb::dictionary::IDictionary* idict,
carb::settings::ISettings* settings,
dictionary::WalkerMode walkerMode,
const char* rootPath,
ElementData rootElementData,
OnItemFnType onItemFn,
void* userData)
{
using namespace carb;
if (!rootPath)
{
return;
}
if (rootPath[0] == 0)
rootPath = "/";
struct ValueToParse
{
std::string srcPath;
ElementData elementData;
};
std::vector<ValueToParse> valuesToParse;
valuesToParse.reserve(100);
auto enqueueChildren = [&idict, &settings, &valuesToParse](const char* parentPath, ElementData parentElementData) {
if (!parentPath)
{
return;
}
const dictionary::Item* parentItem = settings->getSettingsDictionary(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);
const char* childItemName = idict->getItemName(childItem);
std::string childPath;
bool isRootParent = (idict->getItemParent(parentItem) == nullptr);
if (isRootParent)
{
childPath = std::string(parentPath) + childItemName;
}
else
{
childPath = std::string(parentPath) + "/" + childItemName;
}
valuesToParse.push_back({ childPath, parentElementData });
}
};
if (walkerMode == dictionary::WalkerMode::eSkipRoot)
{
const char* parentPath = rootPath;
ElementData parentElementData = rootElementData;
enqueueChildren(parentPath, parentElementData);
}
else
{
valuesToParse.push_back({ rootPath, rootElementData });
}
while (valuesToParse.size())
{
const ValueToParse& valueToParse = valuesToParse.back();
std::string curItemPathStorage = std::move(valueToParse.srcPath);
const char* curItemPath = curItemPathStorage.c_str();
ElementData elementData = std::move(valueToParse.elementData);
valuesToParse.pop_back();
dictionary::ItemType curItemType = settings->getItemType(curItemPath);
if (curItemType == dictionary::ItemType::eDictionary)
{
ElementData parentElementData = onItemFn(curItemPath, elementData, userData);
enqueueChildren(curItemPath, parentElementData);
}
else
{
onItemFn(curItemPath, elementData, userData);
}
}
}
template <typename SettingType>
class ThreadSafeLocalCache
{
public:
ThreadSafeLocalCache(SettingType initState = SettingType{}) : m_value(initState), m_valueDirty(false)
{
}
~ThreadSafeLocalCache()
{
stopTracking();
}
void startTracking(const char* settingPath)
{
CARB_ASSERT(settingPath, "Must specify a valid setting name.");
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(m_settings->get<SettingType>(settingPath), std::memory_order_relaxed);
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);
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);
}
}
void stopTracking()
{
if (m_subscription)
{
carb::getFramework()->removeReleaseHook(m_settings, sOnRelease, this);
m_settings->unsubscribeToChangeEvents(m_subscription);
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();
}
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;
carb::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_SHOULD_SKIP_THIS
template <>
class ThreadSafeLocalCache<const char*>
{
public:
ThreadSafeLocalCache(const char* initState = "") : m_valueDirty(false)
{
std::lock_guard<std::mutex> guard(m_valueMutex);
m_value = initState;
}
~ThreadSafeLocalCache()
{
stopTracking();
}
void startTracking(const char* settingPath)
{
CARB_ASSERT(settingPath, "Must specify a valid setting name.");
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>();
m_valueSettingsPath = settingPath;
const char* valueRaw = m_settings->get<const char*>(settingPath);
m_value = valueRaw ? valueRaw : "";
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);
{
const char* valueStringBuffer =
thisClassInstance->getDictionaryInterface()->template get<const char*>(changedItem);
std::lock_guard<std::mutex> guard(thisClassInstance->m_valueMutex);
thisClassInstance->m_value = valueStringBuffer ? valueStringBuffer : "";
}
thisClassInstance->m_valueDirty.store(true, std::memory_order_release);
}
},
this);
if (m_subscription)
{
f->addReleaseHook(m_settings, sOnRelease, this);
}
}
void stopTracking()
{
if (m_subscription)
{
m_settings->unsubscribeToChangeEvents(m_subscription);
m_subscription = nullptr;
carb::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<std::mutex> guard(m_valueMutex);
return m_value;
}
void set(const char* value)
{
m_settings->set<const char*>(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();
}
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;
carb::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
class ScopedSubscription
{
public:
ScopedSubscription(const char* path, carb::dictionary::OnNodeChangeEventFn fn, void* userData)
{
_getSettingsInterface();
if (m_settings != nullptr)
{
m_sub = m_settings->subscribeToNodeChangeEvents(path, fn, userData);
}
}
ScopedSubscription(const char* path, carb::dictionary::OnTreeChangeEventFn fn, void* userData)
{
_getSettingsInterface();
if (m_settings != nullptr)
{
m_sub = m_settings->subscribeToTreeChangeEvents(path, fn, userData);
}
}
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.
carb::getFramework()->removeReleaseHook(m_settings, sOnReleaseSettings, this);
if (m_sub == nullptr)
return;
if (m_settings != nullptr)
{
m_settings->unsubscribeToChangeEvents(std::exchange(m_sub, nullptr));
}
}
CARB_PREVENT_COPY(ScopedSubscription);
ScopedSubscription& operator=(ScopedSubscription&& other)
{
if (&other == this)
return *this;
m_sub = std::exchange(other.m_sub, nullptr);
return *this;
}
explicit operator bool() const noexcept
{
return m_sub != nullptr;
}
private:
static void sOnReleaseSettings(void* iface, void* userData)
{
ScopedSubscription* self = reinterpret_cast<ScopedSubscription*>(userData);
CARB_UNUSED(iface);
if (self == nullptr)
return;
carb::getFramework()->removeReleaseHook(self->m_settings, sOnReleaseSettings, self);
self->m_settings = nullptr;
self->m_sub = nullptr;
}
void _getSettingsInterface()
{
m_settings = carb::getCachedInterface<carb::settings::ISettings>();
carb::getFramework()->addReleaseHook(m_settings, sOnReleaseSettings, this);
}
carb::settings::ISettings* m_settings = nullptr;
carb::dictionary::SubscriptionId* m_sub = nullptr;
};
} // namespace settings
} // namespace carb