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