#pragma once #include #include #define DECLARE_MULTICAST_DELEGATE(multicastDelegateName, ...) typedef MulticastDelegate<__VA_ARGS__> multicastDelegateName; struct DelegateHandle { size_t id = InvalidHandle; static inline size_t InvalidHandle = static_cast(-1); bool IsValid() const { return id != InvalidHandle; } void Reset() { id = InvalidHandle; } // Comparison operators for convenience bool operator==(const DelegateHandle& other) const { return id == other.id; } bool operator!=(const DelegateHandle& other) const { return id != other.id; } }; template class MulticastDelegate { public: // Add a callable with bound variables (supports no arguments as well) template DelegateHandle AddStatic(Callable&& func, BoundArgs&&... boundArgs) { size_t id = GetNextID(); auto boundFunction = [=](Args... args) { if constexpr (sizeof...(Args) > 0) { func(boundArgs..., std::forward(args)...); } else { func(boundArgs...); } }; SetDelegate(id, boundFunction); return DelegateHandle{ id }; } template DelegateHandle AddRaw(Obj* object, Callable&& func, BoundArgs&&... boundArgs) { size_t id = GetNextID(); auto boundFunction = [=](Args... args) { if constexpr (sizeof...(Args) > 0) { (object->*func)(boundArgs..., std::forward(args)...); } else { (object->*func)(boundArgs...); } }; SetDelegate(id, boundFunction); return DelegateHandle{ id }; } // Remove a callable using the token returned by Add() void Remove(DelegateHandle& handle) { ASSERT(handle.IsValid()); if (handle.IsValid() && handle.id < delegates.size()) { delegates[handle.id].active = false; // Mark this slot as reusable freeIDs.push_back(handle.id); } // Invalidate the handle handle.Reset(); } // Clear all delegates void Clear() { delegates.clear(); freeIDs.clear(); nextID = 0; } // Invoke all callables void Broadcast(Args... args) { for (auto& delegate : delegates) { if (delegate.active) { delegate.function(std::forward(args)...); } } } private: struct Delegate { bool active = false; std::function function; }; // A vector of delegates with active state std::vector delegates; // List of reusable slots std::vector freeIDs; size_t nextID = 0; // Get the next available ID, either by reusing a free slot or by creating a new one size_t GetNextID() { if (!freeIDs.empty()) { size_t id = freeIDs.back(); freeIDs.pop_back(); return id; } return nextID++; } // Set the delegate in the vector, makes the array larger if necessary void SetDelegate(size_t id, const std::function& func) { if (id >= delegates.size()) delegates.resize(id + 1); delegates[id] = { true, func }; } };