carb/delegate/Delegate.h

File members: carb/delegate/Delegate.h

// Copyright (c) 2021-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 "../Strong.h"
#include "../container/IntrusiveList.h"
#include "../cpp/Tuple.h"
#include "../thread/Mutex.h"
#include "../thread/Util.h"

#include <type_traits>
#include <vector>
#include <memory>

namespace carb
{

namespace delegate
{

template <class T>
class Delegate;

template <class T>
class DelegateRef;

template <class... Args>
class Delegate<void(Args...)>
{
public:
    using FunctionType = void(Args...);

    CARB_STRONGTYPE(Handle, size_t);
    CARB_DOC_CONSTEXPR static Handle kInvalidHandle{ 0 };

    Delegate() = default;

    Delegate(Delegate&& other);

    Delegate& operator=(Delegate&& other);

    ~Delegate();

    template <class Callable, class... BindArgs>
    Handle Bind(Handle* hOut, Callable&& func, BindArgs&&... args);

    template <class KeyType, class Callable, class... BindArgs>
    void BindWithKey(KeyType&& key, Callable&& func, BindArgs&&... args);

    template <class KeyType>
    bool Unbind(KeyType&& key);

    template <class KeyType>
    bool HasKey(KeyType&& key) const noexcept;

    bool UnbindCurrent();

    void UnbindAll();

    size_t Count() const noexcept;

    bool HasPending() const noexcept;

    bool IsEmpty() const noexcept;

    template <class KeyType>
    std::vector<std::decay_t<KeyType>> GetKeysByType() const;

    void Call(Args... args);

    void operator()(Args... args);

    void swap(Delegate& other);

    CARB_PREVENT_COPY(Delegate);

private:
    template <class U>
    friend class DelegateRef;

    struct BaseBinding;
    template <class Key>
    struct KeyedBinding;
    using Container = carb::container::IntrusiveList<BaseBinding, &BaseBinding::link>;

    struct ActiveCall;
    using ActiveCallList = carb::container::IntrusiveList<ActiveCall, &ActiveCall::link>;

    struct Impl : public std::enable_shared_from_this<Impl>
    {
        mutable carb::thread::mutex m_mutex;
        Container m_entries;
        ActiveCallList m_activeCalls;

        ~Impl();
    };

    constexpr Delegate(std::nullptr_t);
    Delegate(std::shared_ptr<Impl> pImpl);

    ActiveCall* lastCurrentThreadCall();
    const ActiveCall* lastCurrentThreadCall() const;
    void UnbindInternal(std::unique_lock<carb::thread::mutex>& g, typename Container::iterator iter);

    static size_t nextHandle();

    std::shared_ptr<Impl> m_impl{ std::make_shared<Impl>() };
};

template <class... Args>
class DelegateRef<void(Args...)>
{
public:
    using DelegateType = Delegate<void(Args...)>;

    constexpr DelegateRef() noexcept;

    explicit DelegateRef(DelegateType& delegate);

    DelegateRef(const DelegateRef& other);

    DelegateRef(DelegateRef&& other) = default;

    ~DelegateRef();

    DelegateRef& operator=(const DelegateRef& other);

    DelegateRef& operator=(DelegateRef&& other) = default;

    explicit operator bool() const noexcept;

    void reset();

    void reset(DelegateType& delegate);

    void swap(DelegateRef& other);

    DelegateType* get() const noexcept;

    DelegateType& operator*() const noexcept;

    DelegateType* operator->() const noexcept;

private:
    DelegateType m_delegate;
};

template <class Del>
struct RefFromDelegate
{
    using type = DelegateRef<typename Del::FunctionType>;
};

template <class Del>
using RefFromDelegate_t = typename RefFromDelegate<Del>::type;

} // namespace delegate

} // namespace carb

#include "DelegateImpl.inl"

CARB_INCLUDE_PURIFY_TEST({
    using namespace carb::delegate;
    Delegate<void()> d, d2{ Delegate<void()>{} }, d3 = Delegate<void()>();
    auto b = d.Bind(nullptr, [] {});
    d.Bind(nullptr, [](bool) {}, true);
    d.BindWithKey(0, [] {});
    d.BindWithKey(1, [](bool) {}, false);
    d.Unbind(1);
    d.Unbind(b);
    d.HasKey(0);
    d.UnbindCurrent();
    d2.UnbindAll();
    d.Count();
    d.HasPending();
    d.IsEmpty();
    d.GetKeysByType<int>();
    d.Call();
    d();
    d.swap(d3);

    DelegateRef<void()> dr(d), dr2{};
    DelegateRef<void()> dr3(dr);
    DelegateRef<void()> dr4(std::move(dr2));
    DelegateRef<void()> dr5 = std::move(dr4);
    DelegateRef<void()> dr6 = dr5;
    CARB_UNUSED(bool(dr6));
    dr6.reset();
    dr5.reset(d);
    dr5.get();
    (*dr).Call();
    dr->Call();
});