/* DOCUMENTATION // CONFIG Override default asset #define DELEGATE_ASSERT(expression, ...) Override default static_assert #define DELEGATE_STATIC_ASSERT(expression, msg) Set inline allocator size (default: 32) #define DELEGATE_INLINE_ALLOCATION_SIZE Reassign allocation functions: Delegates::SetAllocationCallbacks(allocFunction, freeFunc); // USAGE ## Classes ## - ```Delegate``` - ```MulticastDelegate``` ## Features ## - Support for: - Static/Global methods - Member functions - Lambda's - std::shared_ptr - Delegate object is allocated inline if it is under 32 bytes - Add payload to delegate during bind-time - Move operations enable optimization ## Example Usage ## ### Delegate ### Delegate del; del.BindLambda([](float a, int payload) { std::cout << "Lambda delegate parameter: " << a << std::endl; std::cout << "Lambda delegate payload: " << payload << std::endl; return 10; }, 50); std::cout << "Lambda delegate return value: " << del.Execute(20) << std::endl; Output: Lambda delegate parameter: 20 Lambda delegate payload: 50 Lambda delegate return value: 10 ### MulticastDelegate ### struct Foo { void Bar(float a, int payload) { std::cout << "Raw delegate parameter: " << a << std::endl; std::cout << "Raw delegate payload: " << payload << std::endl; } }; MulticastDelegate del; del.AddLambda([](float a, int payload) { std::cout << "Lambda delegate parameter: " << a << std::endl; std::cout << "Lambda delegate payload: " << payload << std::endl; }, 90); Foo foo; del.AddRaw(&foo, &Foo::Bar, 10); del.Broadcast(20); Output: Lambda delegate parameter: 20 Lambda delegate payload: 90 Raw delegate parameter: 20 Raw delegate payload: 10 */ #ifndef CPP_DELEGATES #define CPP_DELEGATES #include #include #include /////////////////////////////////////////////////////////////// //////////////////// DEFINES SECTION ////////////////////////// /////////////////////////////////////////////////////////////// #ifndef DELEGATE_ASSERT #include #define DELEGATE_ASSERT(expression, ...) assert(expression) #endif #ifndef DELEGATE_STATIC_ASSERT #if __cplusplus <= 199711L #define DELEGATE_STATIC_ASSERT(expression, msg) static_assert(expression, msg) #else #define DELEGATE_STATIC_ASSERT(expression, msg) #endif #endif //The allocation size of delegate data. //Delegates larger than this will be heap allocated. #ifndef DELEGATE_INLINE_ALLOCATION_SIZE #define DELEGATE_INLINE_ALLOCATION_SIZE 32 #endif #define DECLARE_DELEGATE(name, ...) \ using name = Delegate #define DECLARE_DELEGATE_RET(name, retValue, ...) \ using name = Delegate #define DECLARE_MULTICAST_DELEGATE(name, ...) \ using name = MulticastDelegate<__VA_ARGS__>; \ using name ## Delegate = MulticastDelegate<__VA_ARGS__>::DelegateT #define DECLARE_EVENT(name, ownerType, ...) \ class name : public MulticastDelegate<__VA_ARGS__> \ { \ private: \ friend class ownerType; \ using MulticastDelegate::Broadcast; \ using MulticastDelegate::RemoveAll; \ using MulticastDelegate::Remove; \ }; /////////////////////////////////////////////////////////////// /////////////////// INTERNAL SECTION ////////////////////////// /////////////////////////////////////////////////////////////// #if __cplusplus >= 201703L #define NO_DISCARD [[nodiscard]] #else #define NO_DISCARD #endif namespace _DelegatesInteral { template struct MemberFunction; template struct MemberFunction { using Type = RetVal(Object::*)(Args...) const; }; template struct MemberFunction { using Type = RetVal(Object::*)(Args...); }; static void* (*Alloc)(size_t size) = [](size_t size) { return malloc(size); }; static void(*Free)(void* pPtr) = [](void* pPtr) { free(pPtr); }; template void DelegateDeleteFunc(T* pPtr) { pPtr->~T(); DelegateFreeFunc(pPtr); } } namespace Delegates { using AllocateCallback = void* (*)(size_t size); using FreeCallback = void(*)(void* pPtr); inline void SetAllocationCallbacks(AllocateCallback allocateCallback, FreeCallback freeCallback) { _DelegatesInteral::Alloc = allocateCallback; _DelegatesInteral::Free = freeCallback; } } class i_delegate_base { public: i_delegate_base() = default; virtual ~i_delegate_base() noexcept = default; virtual const void* get_owner() const { return nullptr; } virtual void clone(void* pDestination) = 0; }; //Base type for delegates template class i_delegate : public i_delegate_base { public: virtual RetVal execute(Args&&... args) = 0; }; template class static_delegate; template class static_delegate : public i_delegate { public: using DelegateFunction = RetVal(*)(Args..., Args2...); static_delegate(DelegateFunction function, Args2&&... payload) : function_(function), payload_(std::forward(payload)...) {} static_delegate(DelegateFunction function, const std::tuple& payload) : function_(function), payload_(payload) {} virtual RetVal execute(Args&&... args) override { return execute_internal(std::forward(args)..., std::index_sequence_for()); } virtual void clone(void* pDestination) override { new (pDestination) static_delegate(function_, payload_); } private: template RetVal execute_internal(Args&&... args, std::index_sequence) { return function_(std::forward(args)..., std::get(payload_)...); } DelegateFunction function_; std::tuple payload_; }; template class raw_delegate; template class raw_delegate : public i_delegate { public: using DelegateFunction = typename _DelegatesInteral::MemberFunction::Type; raw_delegate(T* pObject, DelegateFunction function, Args2&&... payload) : object_(pObject), function_(function), payload_(std::forward(payload)...) {} raw_delegate(T* pObject, DelegateFunction function, const std::tuple& payload) : object_(pObject), function_(function), payload_(payload) {} virtual RetVal execute(Args&&... args) override { return execute_internal(std::forward(args)..., std::index_sequence_for()); } virtual const void* get_owner() const override { return object_; } virtual void clone(void* pDestination) override { new (pDestination) raw_delegate(object_, function_, payload_); } private: template RetVal execute_internal(Args&&... args, std::index_sequence) { return (object_->*function_)(std::forward(args)..., std::get(payload_)...); } T* object_; DelegateFunction function_; std::tuple payload_; }; template class lambda_delegate; template class lambda_delegate : public i_delegate { public: explicit lambda_delegate(TLambda&& lambda, Args2&&... payload) : lambda_(std::forward(lambda)), payload_(std::forward(payload)...) {} explicit lambda_delegate(const TLambda& lambda, const std::tuple& payload) : lambda_(lambda), payload_(payload) {} RetVal execute(Args&&... args) override { return execute_internal(std::forward(args)..., std::index_sequence_for()); } virtual void clone(void* pDestination) override { new (pDestination) lambda_delegate(lambda_, payload_); } private: template RetVal execute_internal(Args&&... args, std::index_sequence) { return (RetVal)((lambda_)(std::forward(args)..., std::get(payload_)...)); } TLambda lambda_; std::tuple payload_; }; template class sp_delegate; template class sp_delegate : public i_delegate { public: using DelegateFunction = typename _DelegatesInteral::MemberFunction::Type; sp_delegate(std::shared_ptr pObject, DelegateFunction pFunction, Args2&&... payload) : object_(pObject), function_(pFunction), payload_(std::forward(payload)...) {} sp_delegate(std::weak_ptr pObject, DelegateFunction pFunction, const std::tuple& payload) : object_(pObject), function_(pFunction), payload_(payload) {} virtual RetVal execute(Args&&... args) override { return execute_internal(std::forward(args)..., std::index_sequence_for()); } virtual const void* get_owner() const override { return object_.expired() ? nullptr : object_.lock().get(); } virtual void clone(void* pDestination) override { new (pDestination) sp_delegate(object_, function_, payload_); } private: template RetVal execute_internal(Args&&... args, std::index_sequence) { if (object_.expired()) { return RetVal(); } else { std::shared_ptr pPinned = object_.lock(); return (pPinned.get()->*function_)(std::forward(args)..., std::get(payload_)...); } } std::weak_ptr object_; DelegateFunction function_; std::tuple payload_; }; //A handle to a delegate used for a multicast delegate //Static ID so that every handle is unique class delegate_handle { public: constexpr delegate_handle() noexcept : id_(INVALID_ID) { } explicit delegate_handle(bool /*generateId*/) noexcept : id_(get_new_id()) { } ~delegate_handle() noexcept = default; delegate_handle(const delegate_handle& other) = default; delegate_handle& operator=(const delegate_handle& other) = default; delegate_handle(delegate_handle&& other) noexcept : id_(other.id_) { other.reset(); } delegate_handle& operator=(delegate_handle&& other) noexcept { id_ = other.id_; other.reset(); return *this; } operator bool() const noexcept { return is_valid(); } bool operator==(const delegate_handle& other) const noexcept { return id_ == other.id_; } bool operator<(const delegate_handle& other) const noexcept { return id_ < other.id_; } bool is_valid() const noexcept { return id_ != INVALID_ID; } void reset() noexcept { id_ = INVALID_ID; } constexpr static const unsigned int INVALID_ID = (unsigned int)~0; private: unsigned int id_; static unsigned int CURRENT_ID; static int get_new_id() { unsigned int output = delegate_handle::CURRENT_ID++; if (delegate_handle::CURRENT_ID == INVALID_ID) { delegate_handle::CURRENT_ID = 0; } return output; } }; template class inline_allocator { public: //Constructor constexpr inline_allocator() noexcept : size_(0) { DELEGATE_STATIC_ASSERT(MaxStackSize > sizeof(void*), "MaxStackSize is smaller or equal to the size of a pointer. This will make the use of an InlineAllocator pointless. Please increase the MaxStackSize."); } //Destructor ~inline_allocator() noexcept { free(); } //Copy constructor inline_allocator(const inline_allocator& other) : size_(0) { if (other.has_allocation()) { memcpy(allocate(other.size_), other.get_allocation(), other.size_); } size_ = other.size_; } //Copy assignment operator inline_allocator& operator=(const inline_allocator& other) { if (other.has_allocation()) { memcpy(allocate(other.size_), other.get_allocation(), other.size_); } size_ = other.size_; return *this; } //Move constructor inline_allocator(inline_allocator&& other) noexcept : size_(other.size_) { other.size_ = 0; if (size_ > MaxStackSize) { std::swap(pPtr, other.pPtr); } else { memcpy(Buffer, other.Buffer, size_); } } //Move assignment operator inline_allocator& operator=(inline_allocator&& other) noexcept { free(); size_ = other.size_; other.size_ = 0; if (size_ > MaxStackSize) { std::swap(pPtr, other.pPtr); } else { memcpy(Buffer, other.Buffer, size_); } return *this; } //Allocate memory of given size //If the size is over the predefined threshold, it will be allocated on the heap void* allocate(const size_t size) { if (size_ != size) { free(); size_ = size; if (size > MaxStackSize) { pPtr = _DelegatesInteral::Alloc(size); return pPtr; } } return (void*)Buffer; } //Free the allocated memory void free() { if (size_ > MaxStackSize) { _DelegatesInteral::Free(pPtr); } size_ = 0; } //Return the allocated memory either on the stack or on the heap void* get_allocation() const { if (has_allocation()) { return has_heap_allocation() ? pPtr : (void*)Buffer; } else { return nullptr; } } size_t get_size() const { return size_; } bool has_allocation() const { return size_ > 0; } bool has_heap_allocation() const { return size_ > MaxStackSize; } private: //If the allocation is smaller than the threshold, Buffer is used //Otherwise pPtr is used together with a separate dynamic allocation union { char Buffer[MaxStackSize]; void* pPtr; }; size_t size_; }; class delegate_base { public: //Default constructor constexpr delegate_base() noexcept : allocator_() {} //Default destructor virtual ~delegate_base() noexcept { release(); } //Copy contructor delegate_base(const delegate_base& other) { if (other.allocator_.has_allocation()) { allocator_.allocate(other.allocator_.get_size()); other.get_delegate()->clone(allocator_.get_allocation()); } } //Copy assignment operator delegate_base& operator=(const delegate_base& other) { release(); if (other.allocator_.has_allocation()) { allocator_.allocate(other.allocator_.get_size()); other.get_delegate()->clone(allocator_.get_allocation()); } return *this; } //Move constructor delegate_base(delegate_base&& other) noexcept : allocator_(std::move(other.allocator_)) {} //Move assignment operator delegate_base& operator=(delegate_base&& other) noexcept { release(); allocator_ = std::move(other.allocator_); return *this; } //Gets the owner of the deletage //Only valid for SPDelegate and RawDelegate. //Otherwise returns nullptr by default const void* get_owner() const { if (allocator_.has_allocation()) { return get_delegate()->get_owner(); } return nullptr; } size_t get_size() const { return allocator_.get_size(); } //Clear the bound delegate if it is bound to the given object. //Ignored when pObject is a nullptr void clear_if_bound_to(void* pObject) { if (pObject != nullptr && is_bound_to(pObject)) { release(); } } //Clear the bound delegate if it exists void clear() { release(); } //If the allocator has a size, it means it's bound to something bool is_bound() const { return allocator_.has_allocation(); } bool is_bound_to(void* pObject) const { if (pObject == nullptr || allocator_.has_allocation() == false) { return false; } return get_delegate()->get_owner() == pObject; } protected: void release() { if (allocator_.has_allocation()) { get_delegate()->~i_delegate_base(); allocator_.free(); } } i_delegate_base* get_delegate() const { return static_cast(allocator_.get_allocation()); } //Allocator for the delegate itself. //Delegate gets allocated when its is smaller or equal than 64 bytes in size. //Can be changed by preference inline_allocator allocator_; }; //Delegate that can be bound to by just ONE object template class delegate : public delegate_base { private: template using ConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; template using NonConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; public: using IDelegateT = i_delegate; //Create delegate using member function template NO_DISCARD static delegate create_raw(T* pObj, NonConstMemberFunction pFunction, Args2... args) { delegate handler; handler.bind>(pObj, pFunction, std::forward(args)...); return handler; } template NO_DISCARD static delegate create_raw(T* pObj, ConstMemberFunction pFunction, Args2... args) { delegate handler; handler.bind>(pObj, pFunction, std::forward(args)...); return handler; } //Create delegate using global/static function template NO_DISCARD static delegate create_static(RetVal(*pFunction)(Args..., Args2...), Args2... args) { delegate handler; handler.bind>(pFunction, std::forward(args)...); return handler; } //Create delegate using std::shared_ptr template NO_DISCARD static delegate create_sp(const std::shared_ptr& pObject, NonConstMemberFunction pFunction, Args2... args) { delegate handler; handler.bind>(pObject, pFunction, std::forward(args)...); return handler; } template NO_DISCARD static delegate create_sp(const std::shared_ptr& pObject, ConstMemberFunction pFunction, Args2... args) { delegate handler; handler.bind>(pObject, pFunction, std::forward(args)...); return handler; } //Create delegate using a lambda template NO_DISCARD static delegate create_lambda(TLambda&& lambda, Args2... args) { delegate handler; handler.bind>(std::forward(lambda), std::forward(args)...); return handler; } //Bind a member function template void bind_raw(T* pObject, NonConstMemberFunction pFunction, Args2&&... args) { DELEGATE_STATIC_ASSERT(!std::is_const::value, "Cannot bind a non-const function on a const object"); *this = create_raw(pObject, pFunction, std::forward(args)...); } template void bind_raw(T* pObject, ConstMemberFunction pFunction, Args2&&... args) { *this = create_raw(pObject, pFunction, std::forward(args)...); } //Bind a static/global function template void bind_static(RetVal(*pFunction)(Args..., Args2...), Args2&&... args) { *this = create_static(pFunction, std::forward(args)...); } //Bind a lambda template void bind_lambda(LambdaType&& lambda, Args2&&... args) { *this = create_lambda(std::forward(lambda), std::forward(args)...); } //Bind a member function with a shared_ptr object template void bind_sp(std::shared_ptr pObject, NonConstMemberFunction pFunction, Args2&&... args) { DELEGATE_STATIC_ASSERT(!std::is_const::value, "Cannot bind a non-const function on a const object"); *this = create_sp(pObject, pFunction, std::forward(args)...); } template void bind_sp(std::shared_ptr pObject, ConstMemberFunction pFunction, Args2&&... args) { *this = create_sp(pObject, pFunction, std::forward(args)...); } //Execute the delegate with the given parameters RetVal execute(Args... args) const { DELEGATE_ASSERT(allocator_.has_allocation(), "Delegate is not bound"); return ((IDelegateT*)get_delegate())->execute(std::forward(args)...); } RetVal execute_if_bound(Args... args) const { if (is_bound()) { return ((IDelegateT*)get_delegate())->execute(std::forward(args)...); } return RetVal(); } private: template void bind(Args3&&... args) { release(); void* pAlloc = allocator_.allocate(sizeof(T)); new (pAlloc) T(std::forward(args)...); } }; //Delegate that can be bound to by MULTIPLE objects template class multicast_delegate : public delegate_base { public: using DelegateT = delegate; private: struct delegate_handler_pair { delegate_handle handle; DelegateT callback; delegate_handler_pair() : handle(false) {} delegate_handler_pair(const delegate_handle& handle, const DelegateT& callback) : handle(handle), callback(callback) {} delegate_handler_pair(const delegate_handle& handle, DelegateT&& callback) : handle(handle), callback(std::move(callback)) {} }; template using ConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; template using NonConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; public: //Default constructor constexpr multicast_delegate() : locks_(0) { } //Default destructor ~multicast_delegate() noexcept = default; //Default copy constructor multicast_delegate(const multicast_delegate& other) = default; //Defaul copy assignment operator multicast_delegate& operator=(const multicast_delegate& other) = default; //Move constructor multicast_delegate(multicast_delegate&& other) noexcept : events_(std::move(other.events_)), locks_(std::move(other.locks_)) { } //Move assignment operator multicast_delegate& operator=(multicast_delegate&& other) noexcept { events_ = std::move(other.events_); locks_ = std::move(other.locks_); return *this; } template delegate_handle operator+=(T&& l) { return add(DelegateT::create_lambda(std::move(l))); } //Add delegate with the += operator delegate_handle operator+=(DelegateT&& handler) noexcept { return add(std::forward(handler)); } //Remove a delegate using its DelegateHandle bool operator-=(delegate_handle& handle) { return remove(handle); } delegate_handle add(DelegateT&& handler) noexcept { //Favour an empty space over a possible array reallocation for (size_t i = 0; i < events_.size(); ++i) { if (events_[i].handle.is_valid() == false) { events_[i] = delegate_handler_pair(delegate_handle(true), std::move(handler)); return events_[i].handle; } } events_.emplace_back(delegate_handle(true), std::move(handler)); return events_.back().handle; } //Bind a member function template delegate_handle add_raw(T* pObject, NonConstMemberFunction pFunction, Args2&&... args) { return add(DelegateT::create_raw(pObject, pFunction, std::forward(args)...)); } template delegate_handle add_raw(T* pObject, ConstMemberFunction pFunction, Args2&&... args) { return add(DelegateT::create_raw(pObject, pFunction, std::forward(args)...)); } //Bind a static/global function template delegate_handle add_static(void(*pFunction)(Args..., Args2...), Args2&&... args) { return add(DelegateT::create_static(pFunction, std::forward(args)...)); } //Bind a lambda template delegate_handle add_lambda(LambdaType&& lambda, Args2&&... args) { return add(DelegateT::create_lambda(std::forward(lambda), std::forward(args)...)); } //Bind a member function with a shared_ptr object template delegate_handle add_sp(std::shared_ptr pObject, NonConstMemberFunction pFunction, Args2&&... args) { return add(DelegateT::create_sp(pObject, pFunction, std::forward(args)...)); } template delegate_handle add_sp(std::shared_ptr pObject, ConstMemberFunction pFunction, Args2&&... args) { return add(DelegateT::create_sp(pObject, pFunction, std::forward(args)...)); } //Removes all handles that are bound from a specific object //Ignored when pObject is null //Note: Only works on Raw and SP bindings void remove_object(void* pObject) { if (pObject != nullptr) { for (size_t i = 0; i < events_.size(); ++i) { if (events_[i].callback.get_owner() == pObject) { if (is_locked()) { events_[i].callback.clear(); } else { std::swap(events_[i], events_[events_.size() - 1]); events_.pop_back(); } } } } } //Remove a function from the event list by the handle bool remove(delegate_handle& handle) { if (handle.is_valid()) { for (size_t i = 0; i < events_.size(); ++i) { if (events_[i].handle == handle) { if (is_locked()) { events_[i].callback.clear(); } else { std::swap(events_[i], events_[events_.size() - 1]); events_.pop_back(); } handle.reset(); return true; } } } return false; } bool is_bound_to(const delegate_handle& handle) const { if (handle.is_valid()) { for (size_t i = 0; i < events_.size(); ++i) { if (events_[i].handle == handle) { return true; } } } return false; } //Remove all the functions bound to the delegate void remove_all() { if (is_locked()) { for (delegate_handler_pair& handler : events_) { handler.callback.clear(); } } else { events_.clear(); } } void compress(const size_t maxSpace = 0) { if (is_locked() == false) { size_t toDelete = 0; for (size_t i = 0; i < events_.size() - toDelete; ++i) { if (events_[i].handle.is_valid() == false) { std::swap(events_[i], events_[toDelete]); ++toDelete; } } if (toDelete > maxSpace) { events_.resize(events_.size() - toDelete); } } } //Execute all functions that are bound void broadcast(Args ...args) { lock(); for (size_t i = 0; i < events_.size(); ++i) { if (events_[i].handle.is_valid()) { events_[i].callback.execute(std::forward(args)...); } } unlock(); } size_t get_size() const { return events_.size(); } private: void lock() { ++locks_; } void unlock() { //Unlock() should never be called more than Lock()! DELEGATE_ASSERT(locks_ > 0); --locks_; } //Returns true is the delegate is currently broadcasting //If this is true, the order of the array should not be changed otherwise this causes undefined behaviour bool is_locked() const { return locks_ > 0; } std::vector events_; unsigned int locks_; }; #endif