135 lines
3.5 KiB
C++
135 lines
3.5 KiB
C++
#pragma once
|
|
|
|
#include <functional>
|
|
#include <vector>
|
|
|
|
#define DECLARE_MULTICAST_DELEGATE(multicastDelegateName, ...) typedef MulticastDelegate<__VA_ARGS__> multicastDelegateName;
|
|
|
|
struct DelegateHandle
|
|
{
|
|
size_t id = InvalidHandle;
|
|
|
|
static inline size_t InvalidHandle = static_cast<size_t>(-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<typename... Args>
|
|
class MulticastDelegate
|
|
{
|
|
public:
|
|
// Add a callable with bound variables (supports no arguments as well)
|
|
template<typename Callable, typename... BoundArgs>
|
|
DelegateHandle AddStatic(Callable&& func, BoundArgs&&... boundArgs)
|
|
{
|
|
size_t id = GetNextID();
|
|
auto boundFunction = [=](Args... args) {
|
|
if constexpr (sizeof...(Args) > 0)
|
|
{
|
|
func(boundArgs..., std::forward<Args>(args)...);
|
|
}
|
|
else
|
|
{
|
|
func(boundArgs...);
|
|
}
|
|
};
|
|
SetDelegate(id, boundFunction);
|
|
return DelegateHandle{ id };
|
|
}
|
|
|
|
template<typename Obj, typename Callable, typename... BoundArgs>
|
|
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>(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>(args)...);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
struct Delegate
|
|
{
|
|
bool active = false;
|
|
std::function<void(Args...)> function;
|
|
};
|
|
|
|
// A vector of delegates with active state
|
|
std::vector<Delegate> delegates;
|
|
// List of reusable slots
|
|
std::vector<size_t> 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<void(Args...)>& func)
|
|
{
|
|
if (id >= delegates.size())
|
|
delegates.resize(id + 1);
|
|
|
|
delegates[id] = { true, func };
|
|
}
|
|
};
|