// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Core/Memory/Allocation.h" /// /// The function object that supports binding static, member and lambda functions. /// template class Function { friend Delegate; public: /// /// 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 binded. /// FORCE_INLINE bool IsBinded() const { return _function != nullptr; } /// /// Calls the binded function (it must be assigned). /// /// A list of parameters for the function invocation. /// Function result FORCE_INLINE ReturnType operator()(Params ... params) const { ASSERT(_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. /// template class Delegate { public: /// /// Signature of the function to call. /// typedef void (*Signature)(Params ...); /// /// Template for the function. /// using FunctionType = Function; protected: // 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 ...); public: NON_COPYABLE(Delegate); Delegate() { } ~Delegate() { auto ptr = (FunctionType*)_ptr; if (ptr) { while (_size--) { if (ptr->_lambda) ptr->LambdaDtor(); ++ptr; } Allocator::Free((void*)_ptr); } } 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) { 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); } } /// /// Binds a static function (if not binded yet). /// template void BindUnique() { FunctionType f; f.template Bind(); BindUnique(f); } /// /// Binds a member function (if not binded yet). /// /// The object instance. template void BindUnique(T* callee) { FunctionType f; f.template Bind(callee); BindUnique(f); } /// /// Binds a function (if not binded yet). /// /// The method. void BindUnique(Signature method) { FunctionType f(method); BindUnique(f); } /// /// Binds a function (if not binded yet). /// /// The function to bind. void BindUnique(const FunctionType& f) { const intptr size = Platform::AtomicRead(&_size); FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr); if (bindings) { // Skip if already binded 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; } } Bind(f); } /// /// 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(FunctionType& f) { // 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); } } /// /// Unbinds all the functions. /// void UnbindAll() { 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); } } /// /// Counts the amount of binded functions. /// /// The binded functions count. int32 Count() const { int32 count = 0; 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) count++; } return count; } /// /// Gets the current capacity of delegate table (amount of function to store before resizing). /// int32 Capacity() const { return (int32)Platform::AtomicRead((intptr volatile*)&_size); } /// /// Determines whether any function is binded. /// /// true if any function is binded; otherwise, false. bool IsBinded() const { 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; } /// /// Gets all the binded 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; 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++; } } return count; } /// /// Calls all the binded functions. Supports unbinding of the called functions during invocation. /// /// A list of parameters for the function invocation. void operator()(Params ... params) const { 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, Forward(params)...); ++bindings; } } }; /// /// Action delegate. /// typedef Delegate<> Action;