carb/delegate/Delegate.h

File members: carb/delegate/Delegate.h

// Copyright (c) 2021-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 "detail/DelegateBase.h"

#include "../thread/Mutex.h"

namespace carb
{

namespace delegate
{

template <class T>
class Delegate;

template <class T>
class DelegateRef;

template <class... Args>
class Delegate<void(Args...)> : public detail::DelegateBase<::carb::thread::mutex, detail::DefaultExec, void(Args...)>
{
    using Base = detail::DelegateBase<::carb::thread::mutex, detail::DefaultExec, void(Args...)>;

public:
    Delegate() = default;

    Delegate(Delegate&& other) : Base(std::move(other))
    {
    }

    Delegate& operator=(Delegate&& other)
    {
        Base::swap(other);
        return *this;
    }

private:
    template <class U>
    friend class DelegateRef;

    // Null constructor, only for DelegateRef use.
    constexpr Delegate(std::nullptr_t) : Base(nullptr)
    {
    }

    // Copy constructor, only for DelegateRef use.
    Delegate(const Delegate& other) : Base(other)
    {
    }

    // Copy-assign operator, only for DelegateRef use.
    Delegate& operator=(const Delegate& other)
    {
        Base::copy(other);
        return *this;
    }
};

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

    constexpr DelegateRef() noexcept : m_delegate{ nullptr }
    {
    }

    explicit DelegateRef(DelegateType& delegate) : m_delegate(delegate)
    {
    }

    DelegateRef(const DelegateRef& other) : m_delegate(other.m_delegate)
    {
    }

    DelegateRef(DelegateRef&& other) = default;

    ~DelegateRef()
    {
        // The Delegate destructor calls UnbindAll(), which we definitely don't want. So just reset our reference.
        m_delegate.reset();
    }

    DelegateRef& operator=(const DelegateRef& other)
    {
        m_delegate = other.m_delegate;
        return *this;
    }

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

    explicit operator bool() const noexcept
    {
        return m_delegate.isValid();
    }

    void reset()
    {
        m_delegate.reset();
    }

    void reset(DelegateType& delegate)
    {
        m_delegate = delegate;
    }

    void swap(DelegateRef& other)
    {
        m_delegate.swap(other);
    }

    DelegateType* get() const noexcept
    {
        return m_delegate.isValid() ? const_cast<DelegateType*>(&m_delegate) : nullptr;
    }

    DelegateType& operator*() const noexcept
    {
        CARB_ASSERT(*this);
        return const_cast<DelegateType&>(m_delegate);
    }

    DelegateType* operator->() const noexcept
    {
        CARB_ASSERT(*this);
        return const_cast<DelegateType*>(&m_delegate);
    }

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

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();
});