carb/InterfaceUtils.h
File members: carb/InterfaceUtils.h
// Copyright (c) 2019-2024, 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 "Framework.h"
#include "PluginInitializers.h"
#include "cpp/Atomic.h"
namespace carb
{
#ifndef DOXYGEN_BUILD
namespace detail
{
template <typename InterfaceT, const char* PluginName>
class CachedInterface : public ::carb::detail::Deinit
{
public:
constexpr CachedInterface() = default;
~CachedInterface()
{
reset();
}
InterfaceT* get(const InterfaceDesc& desc)
{
auto iface = m_cachedInterface.load(std::memory_order_acquire);
CARB_LIKELY_IF(iface)
{
CARB_FATAL_UNLESS(
desc.version == m_cachedVersion,
"Modules using multiple versions of an interface are not allowed! Ensure that the module's premake "
"defines <version_macro>=<version_macro_latest> so that the entire module is built with the same "
"interface version (%s: existing: %d.%d, requested: %d.%d)",
desc.name, m_cachedVersion.major, m_cachedVersion.minor, desc.version.major, desc.version.minor);
return iface;
}
return getInternal(desc);
}
void reset()
{
::carb::Framework* framework = ::carb::getFramework();
if (!framework)
{
// Framework no longer valid or already unloaded.
return;
}
auto iface = m_cachedInterface.exchange(nullptr, std::memory_order_relaxed);
if (iface)
{
framework->removeReleaseHook(iface, sReleaseHook, this);
}
framework->removeReleaseHook(nullptr, sFrameworkReleased, this);
m_reqState.store(NotRequested, std::memory_order_release);
m_reqState.notify_all();
}
private:
enum RequestState
{
NotRequested,
Requesting,
Finished,
};
std::atomic<InterfaceT*> m_cachedInterface{ nullptr };
carb::Version m_cachedVersion{};
carb::cpp::atomic<RequestState> m_reqState{ NotRequested };
static void sReleaseHook(void* iface, void* this_)
{
static_cast<CachedInterface*>(this_)->releaseHook(iface);
}
static void sFrameworkReleased(void*, void* this_)
{
// The Framework is fully released. Reset our request state.
static_cast<CachedInterface*>(this_)->reset();
}
void releaseHook(void* iface)
{
// Clear the cached interface pointer, but don't fully reset. Further attempts to get() will proceed to
// getInternal(), but will not attempt to acquire the interface again.
CARB_ASSERT(iface == m_cachedInterface);
CARB_UNUSED(iface);
m_cachedInterface.store(nullptr, std::memory_order_relaxed);
}
void deinit() override
{
reset();
}
CARB_NOINLINE InterfaceT* getInternal(const InterfaceDesc& desc)
{
::carb::Framework* framework = ::carb::getFramework();
if (!framework)
{
return nullptr;
}
RequestState state = m_reqState.load(std::memory_order_acquire);
while (state != Finished)
{
if (state == NotRequested && m_reqState.compare_exchange_weak(
state, Requesting, std::memory_order_relaxed, std::memory_order_relaxed))
{
InterfaceT* iface = framework->tryAcquireInterface<InterfaceT>(PluginName);
if (!iface)
{
// Failed to acquire. Reset to initial state
m_reqState.store(NotRequested, std::memory_order_release);
m_reqState.notify_all();
return nullptr;
}
CARB_UNLIKELY_IF(!framework->addReleaseHook(iface, sReleaseHook, this))
{
// This could only happen if something released the interface between us acquiring it and adding
// the release hook. Repeat the process again.
state = NotRequested;
m_reqState.store(state, std::memory_order_release);
m_reqState.notify_all();
continue;
}
bool b = framework->addReleaseHook(nullptr, sFrameworkReleased, this);
CARB_UNUSED(b);
CARB_ASSERT(b);
m_cachedVersion = desc.version;
m_cachedInterface.store(iface, std::memory_order_release);
m_reqState.store(Finished, std::memory_order_release);
m_reqState.notify_all();
return iface;
}
else if (state == Requesting)
{
m_reqState.wait(state, std::memory_order_relaxed);
state = m_reqState.load(std::memory_order_acquire);
}
}
return m_cachedInterface.load(std::memory_order_relaxed);
}
};
template <class T, const char* PluginName>
CachedInterface<T, PluginName>& cachedInterface()
{
static CachedInterface<T, PluginName> cached;
return cached;
}
} // namespace detail
#endif
template <typename InterfaceT, const char* PluginName = nullptr>
CARB_NODISCARD inline InterfaceT* getCachedInterface()
{
return ::carb::detail::cachedInterface<InterfaceT, PluginName>().get(InterfaceT::getInterfaceDesc());
}
template <typename InterfaceT, const char* PluginName = nullptr>
inline void resetCachedInterface()
{
::carb::detail::cachedInterface<InterfaceT, PluginName>().reset();
}
} // namespace carb