// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #pragma once // Toggles Delegate implementation type (mutex+hashset or atomic+table) // [Deprecated on 12.09.2023, expires on 12.09.2024] #define DELEGATE_USE_ATOMIC 0 #include "Engine/Core/Memory/Allocation.h" #include "Engine/Core/Collections/HashFunctions.h" #if !DELEGATE_USE_ATOMIC #include "Engine/Threading/Threading.h" #include "Engine/Core/Collections/HashSet.h" #endif /// /// The function object that supports binding static, member and lambda functions. /// template class Function { public: friend Delegate; template friend uint32 GetHash(const Function& key); /// /// Signature of the function to call. /// typedef ReturnType (*Signature)(Params...); private: typedef ReturnType (*StubSignature)(void*, Params...); template static ReturnType StaticMethodStub(void*, Params... params) { return (Method)(Forward(params)...); } static ReturnType StaticPointerMethodStub(void* callee, Params... params) { return reinterpret_cast(callee)(Forward(params)...); } template static ReturnType ClassMethodStub(void* callee, Params... params) { return (reinterpret_cast(callee)->*Method)(Forward(params)...); } template static ReturnType ClassMethodStub(void* callee, Params... params) { return (reinterpret_cast(callee)->*Method)(Forward(params)...); } struct Lambda { int64 Refs; void (*Dtor)(void*); }; void* _callee; StubSignature _function; Lambda* _lambda; FORCE_INLINE void LambdaCtor() const { Platform::InterlockedIncrement((int64 volatile*)&_lambda->Refs); } FORCE_INLINE void LambdaDtor() { if (Platform::InterlockedDecrement(&_lambda->Refs) == 0) { ((Lambda*)_lambda)->Dtor(_callee); Allocator::Free(_lambda); } } public: /// /// Initializes a new instance of the class. /// Function() { _callee = nullptr; _function = nullptr; _lambda = nullptr; } /// /// Initializes a new instance of the class. /// Function(Signature method) { _callee = (void*)method; _function = &StaticPointerMethodStub; _lambda = nullptr; } /// /// Initializes a new instance of the class. /// template Function(const T& lambda) { _lambda = nullptr; Bind(lambda); } Function(const Function& other) : _callee(other._callee) , _function(other._function) , _lambda(other._lambda) { if (_lambda) LambdaCtor(); } Function(Function&& other) noexcept : _callee(other._callee) , _function(other._function) , _lambda(other._lambda) { other._lambda = nullptr; } ~Function() { if (_lambda) LambdaDtor(); } public: /// /// Binds a static function. /// template void Bind() { if (_lambda) LambdaDtor(); _callee = nullptr; _function = &StaticMethodStub; _lambda = nullptr; } /// /// Binds a member function. /// /// The object instance. template void Bind(T* callee) { if (_lambda) LambdaDtor(); _callee = callee; _function = &ClassMethodStub; _lambda = nullptr; } /// /// Binds a function. /// /// The method. void Bind(Signature method) { if (_lambda) LambdaDtor(); _callee = (void*)method; _function = &StaticPointerMethodStub; _lambda = nullptr; } /// /// Binds a lambda. /// /// The lambda. template void Bind(const T& lambda) { if (_lambda) LambdaDtor(); _lambda = (Lambda*)Allocator::Allocate(sizeof(Lambda) + sizeof(T)); _lambda->Refs = 1; _lambda->Dtor = [](void* callee) -> void { static_cast(callee)->~T(); }; _function = [](void* callee, Params... params) -> ReturnType { return (*static_cast(callee))(Forward(params)...); }; _callee = (byte*)_lambda + sizeof(Lambda); new(_callee) T(lambda); } /// /// Unbinds the function. /// void Unbind() { if (_lambda) LambdaDtor(); _callee = nullptr; _function = nullptr; _lambda = nullptr; } public: /// /// Returns true if any function has been bound. /// FORCE_INLINE bool IsBinded() const { return _function != nullptr; } /// /// Calls the bound function (it must be assigned). /// /// A list of parameters for the function invocation. /// Function result FORCE_INLINE ReturnType operator()(Params... params) const { ASSERT_LOW_LAYER(_function); return _function(_callee, Forward(params)...); } Function& operator=(const Function& other) { if (this == &other) return *this; _callee = other._callee; _function = other._function; _lambda = other._lambda; if (_lambda) LambdaCtor(); return *this; } Function& operator=(Function&& other) noexcept { if (this == &other) return *this; _callee = other._callee; _function = other._function; _lambda = other._lambda; other._callee = nullptr; other._function = nullptr; other._lambda = nullptr; return *this; } FORCE_INLINE bool operator==(const Function& other) const { return _function == other._function && _callee == other._callee; } FORCE_INLINE bool operator!=(const Function& other) const { return _function != other._function || _callee != other._callee; } }; /// /// Delegate object that can be used to bind and call multiply functions. Thread-safe to register/unregister during the call. Execution order of bound functions is not stable. /// template class Delegate { public: /// /// Signature of the function to call. /// typedef void (*Signature)(Params...); /// /// Template for the function. /// using FunctionType = Function; protected: #if DELEGATE_USE_ATOMIC // Single allocation for list of binded functions. Thread-safe access via atomic operations. Removing binded function simply clears the entry to handle function unregister during invocation. intptr volatile _ptr = 0; intptr volatile _size = 0; typedef void (*StubSignature)(void*, Params...); #else struct Data { HashSet Functions; CriticalSection Locker; }; // Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations. intptr volatile _data = 0; #endif public: Delegate() { } Delegate(const Delegate& other) { #if DELEGATE_USE_ATOMIC const intptr newSize = other._size; auto newBindings = (FunctionType*)Allocator::Allocate(newSize * sizeof(FunctionType)); Platform::MemoryCopy((void*)newBindings, (const void*)other._ptr, newSize * sizeof(FunctionType)); for (intptr i = 0; i < newSize; i++) { FunctionType& f = newBindings[i]; if (f._function && f._lambda) f.LambdaCtor(); } _ptr = (intptr)newBindings; _size = newSize; #else Data* otherData = (Data*)Platform::AtomicRead(&_data); if (otherData == nullptr) return; ScopeLock lock(otherData->Locker); for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i) Bind(i->Item); #endif } Delegate(Delegate&& other) noexcept { #if DELEGATE_USE_ATOMIC _ptr = other._ptr; _size = other._size; other._ptr = 0; other._size = 0; #else _data = other._data; other._data = 0; #endif } ~Delegate() { #if DELEGATE_USE_ATOMIC auto ptr = (FunctionType*)_ptr; if (ptr) { while (_size--) { if (ptr->_lambda) ptr->LambdaDtor(); ++ptr; } Allocator::Free((void*)_ptr); } #else Data* data = (Data*)_data; if (data) { _data = 0; Delete(data); } #endif } Delegate& operator=(const Delegate& other) { if (this != &other) { UnbindAll(); #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead((intptr volatile*)&other._size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&other._ptr); for (intptr i = 0; i < size; i++) Bind(bindings[i]); #else Data* otherData = (Data*)Platform::AtomicRead(&_data); if (otherData != nullptr) { ScopeLock lock(otherData->Locker); for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i) Bind(i->Item); } #endif } return *this; } Delegate& operator=(Delegate&& other) noexcept { if (this != &other) { #if DELEGATE_USE_ATOMIC _ptr = other._ptr; _size = other._size; other._ptr = 0; other._size = 0; #else _data = other._data; other._data = 0; #endif } return *this; } public: /// /// Binds a static function. /// template void Bind() { FunctionType f; f.template Bind(); Bind(f); } /// /// Binds a member function. /// /// The object instance. template void Bind(T* callee) { FunctionType f; f.template Bind(callee); Bind(f); } /// /// Binds a function. /// /// The method. void Bind(Signature method) { FunctionType f(method); Bind(f); } /// /// Binds a lambda. /// /// The lambda. template void Bind(const T& lambda) { FunctionType f; f.template Bind(lambda); Bind(f); } /// /// Binds a function. /// /// The function to bind. void Bind(const FunctionType& f) { #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead(&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); if (bindings) { // Find a first free slot for (intptr i = 0; i < size; i++) { if (Platform::InterlockedCompareExchange((intptr volatile*)&bindings[i]._function, (intptr)f._function, 0) == 0) { bindings[i]._callee = f._callee; bindings[i]._lambda = f._lambda; if (f._lambda) f.LambdaCtor(); return; } } } // Failed to find an empty slot in the list (empty or too small) so perform reallocation const intptr newSize = size ? size * 2 : 32; auto newBindings = (FunctionType*)Allocator::Allocate(newSize * sizeof(FunctionType)); Platform::MemoryCopy(newBindings, bindings, size * sizeof(FunctionType)); Platform::MemoryClear(newBindings + size, (newSize - size) * sizeof(FunctionType)); // Insert function into a first slot after the old list newBindings[size] = f; // Set the new list auto oldBindings = (FunctionType*)Platform::InterlockedCompareExchange(&_ptr, (intptr)newBindings, (intptr)bindings); if (oldBindings != bindings) { // Other thread already set the new list so free this list and try again Allocator::Free(newBindings); Bind(f); } else { // Free previous bindings and update list size Platform::AtomicStore(&_size, newSize); // TODO: what is someone read this value before and is using the old table? Allocator::Free(bindings); } #else Data* data = (Data*)Platform::AtomicRead(&_data); while (!data) { Data* newData = New(); Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data); if (oldData != data) { // Other thread already set the new data so free it and try again Delete(newData); } data = (Data*)Platform::AtomicRead(&_data); } ScopeLock lock(data->Locker); data->Functions.Add(f); #endif } /// /// Binds a static function (if not bound yet). /// template void BindUnique() { FunctionType f; f.template Bind(); BindUnique(f); } /// /// Binds a member function (if not bound yet). /// /// The object instance. template void BindUnique(T* callee) { FunctionType f; f.template Bind(callee); BindUnique(f); } /// /// Binds a function (if not bound yet). /// /// The method. void BindUnique(Signature method) { FunctionType f(method); BindUnique(f); } /// /// Binds a function (if not bound yet). /// /// The function to bind. void BindUnique(const FunctionType& f) { // Skip if already bound #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead(&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); if (bindings) { for (intptr i = 0; i < size; i++) { if (Platform::AtomicRead((intptr volatile*)&bindings[i]._callee) == (intptr)f._callee && Platform::AtomicRead((intptr volatile*)&bindings[i]._function) == (intptr)f._function) return; } } #else Data* data = (Data*)Platform::AtomicRead(&_data); if (data) { data->Locker.Lock(); if (data->Functions.Contains(f)) { data->Locker.Unlock(); return; } } #endif Bind(f); #if !DELEGATE_USE_ATOMIC if (data) data->Locker.Unlock(); #endif } /// /// Unbinds a static function. /// template void Unbind() { FunctionType f; f.template Bind(); Unbind(f); } /// /// Unbinds a member function. /// /// The object instance. template void Unbind(T* callee) { FunctionType f; f.template Bind(callee); Unbind(f); } /// /// Unbinds the specified function. /// /// The method. void Unbind(Signature method) { FunctionType f(method); Unbind(f); } /// /// Unbinds the specified function. /// /// The function to unbind. void Unbind(const FunctionType& f) { #if DELEGATE_USE_ATOMIC // Find slot with that function const intptr size = Platform::AtomicRead(&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); for (intptr i = 0; i < size; i++) { if (Platform::AtomicRead((intptr volatile*)&bindings[i]._callee) == (intptr)f._callee && Platform::AtomicRead((intptr volatile*)&bindings[i]._function) == (intptr)f._function) { if (bindings[i]._lambda) { bindings[i].LambdaDtor(); bindings[i]._lambda = nullptr; } Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0); Platform::AtomicStore((intptr volatile*)&bindings[i]._function, 0); break; } } if ((FunctionType*)Platform::AtomicRead(&_ptr) != bindings) { // Someone changed the bindings list so retry unbind from the new one Unbind(f); } #else Data* data = (Data*)Platform::AtomicRead(&_data); if (!data) return; ScopeLock lock(data->Locker); data->Functions.Remove(f); #endif } /// /// Unbinds all the functions. /// void UnbindAll() { #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead(&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); for (intptr i = 0; i < size; i++) { if (bindings[i]._lambda) { bindings[i].LambdaDtor(); bindings[i]._lambda = nullptr; } Platform::AtomicStore((intptr volatile*)&bindings[i]._function, 0); Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0); } #else Data* data = (Data*)Platform::AtomicRead(&_data); if (!data) return; ScopeLock lock(data->Locker); data->Functions.Clear(); #endif } /// /// Counts the amount of bound functions. /// /// The bound functions count. int32 Count() const { int32 result = 0; #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead((intptr volatile*)&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); for (intptr i = 0; i < size; i++) { if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0) result++; } #else Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); if (data) { ScopeLock lock(data->Locker); result = data->Functions.Count(); } #endif return result; } /// /// Gets the current capacity of delegate table (amount of function to store before resizing). /// int32 Capacity() const { #if DELEGATE_USE_ATOMIC return (int32)Platform::AtomicRead((intptr volatile*)&_size); #else int32 result = 0; Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); if (data) { ScopeLock lock(data->Locker); result = data->Functions.Capacity(); } return result; #endif } /// /// Determines whether any function is bound. /// /// true if any function is bound; otherwise, false. bool IsBinded() const { #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead((intptr volatile*)&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); for (intptr i = 0; i < size; i++) { if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0) return true; } return false; #else bool result = false; Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); if (data) { ScopeLock lock(data->Locker); result = data->Functions.Count() != 0; } return result; #endif } /// /// Gets all the bound functions. /// /// The result bindings functions memory. /// The result bindings functions memory size. /// The amount of written items into the output bindings buffer. Can be equal or less than input bindingsCount capacity. int32 GetBindings(FunctionType* buffer, int32 bufferSize) const { int32 count = 0; #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead((intptr volatile*)&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); for (intptr i = 0; i < size && i < bufferSize; i++) { buffer[count]._function = (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings[i]._function); if (buffer[count]._function != nullptr) { buffer[count]._callee = (void*)Platform::AtomicRead((intptr volatile*)&bindings[i]._callee); buffer[count]._lambda = (typename FunctionType::Lambda*)Platform::AtomicRead((intptr volatile*)&bindings[i]._lambda); if (buffer[count]._lambda) buffer[count].LambdaCtor(); count++; } } #else Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); if (data) { ScopeLock lock(data->Locker); for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i) { new(buffer + count) FunctionType((const FunctionType&)i->Item); count++; } } #endif return count; } /// /// Calls all the bound functions. Supports unbinding of the called functions during invocation. /// /// A list of parameters for the function invocation. void operator()(Params... params) const { #if DELEGATE_USE_ATOMIC const intptr size = Platform::AtomicRead((intptr volatile*)&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr); for (intptr i = 0; i < size; i++) { auto function = (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings->_function); auto callee = (void*)Platform::AtomicRead((intptr volatile*)&bindings->_callee); if (function != nullptr && function == (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings->_function)) function(callee, params...); ++bindings; } #else Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data); if (!data) return; ScopeLock lock(data->Locker); for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i) { const FunctionType& item = i->Item; ASSERT_LOW_LAYER(item._function); item._function(item._callee, params...); } #endif } }; template inline uint32 GetHash(const Function& key) { uint32 hash = GetHash((void*)key._callee); CombineHash(hash, GetHash((void*)key._function)); return hash; } /// /// Action delegate. /// typedef Delegate<> Action;