// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Core/Memory/Allocation.h" template class Function; template class Delegate; /// /// The function object. /// 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)...); } void* _callee; StubSignature _function; public: /// /// Initializes a new instance of the class. /// Function() { _callee = nullptr; _function = nullptr; } /// /// Initializes a new instance of the class. /// Function(Signature method) { ASSERT(method); _callee = (void*)method; _function = &StaticPointerMethodStub; } public: /// /// Binds a static function. /// template void Bind() { _callee = nullptr; _function = &StaticMethodStub; } /// /// Binds a member function. /// /// The object instance. template void Bind(T* callee) { _callee = callee; _function = &ClassMethodStub; } /// /// Binds a function. /// /// The method. void Bind(Signature method) { _callee = (void*)method; _function = &StaticPointerMethodStub; } /// /// Unbinds the function. /// void Unbind() { _callee = nullptr; _function = nullptr; } public: /// /// Returns true if any function has been binded. /// /// True if any function has been binded, otherwise false. FORCE_INLINE bool IsBinded() const { return _function != nullptr; } /// /// Calls the binded function if any has been assigned. /// /// A list of parameters for the function invocation. /// Function result void TryCall(Params ... params) const { if (_function) _function(_callee, Forward(params)...); } /// /// Calls the binded function (it must be assigned). /// /// A list of parameters for the function invocation. /// Function result ReturnType operator()(Params ... params) const { ASSERT(_function); return _function(_callee, Forward(params)...); } 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() { 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) { ASSERT(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 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) { Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, (intptr)f._callee); 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); } } /// /// 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) { ASSERT(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) { 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++) { 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; } /// /// 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); 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++) { FunctionType f; f._function = (StubSignature)Platform::AtomicRead((intptr volatile*)&bindings[i]._function); if (f._function != nullptr) { f._callee = (void*)Platform::AtomicRead((intptr volatile*)&bindings[i]._callee); f._function(f._callee, Forward(params)...); } } } }; /// /// Action delegate. /// typedef Delegate<> Action;