Use HashSet as backing storage for bound Delegate functions

This commit is contained in:
2023-09-02 22:43:40 +03:00
parent 017b45d674
commit 4ffec5e09a
2 changed files with 118 additions and 158 deletions

View File

@@ -4,6 +4,7 @@
#include "Engine/Core/Memory/Allocation.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Core/Collections/HashSet.h"
/// <summary>
/// The function object that supports binding static, member and lambda functions.
@@ -12,6 +13,9 @@ template<typename ReturnType, typename... Params>
class Function<ReturnType(Params ...)>
{
friend Delegate<Params...>;
template<typename... TParams>
friend uint32 GetHash(const Function<void(TParams ...)>& key);
public:
/// <summary>
/// Signature of the function to call.
@@ -192,7 +196,7 @@ public:
public:
/// <summary>
/// Returns true if any function has been binded.
/// Returns true if any function has been bound.
/// </summary>
FORCE_INLINE bool IsBinded() const
{
@@ -200,7 +204,7 @@ public:
}
/// <summary>
/// Calls the binded function (it must be assigned).
/// Calls the bound function (it must be assigned).
/// </summary>
/// <param name="params">A list of parameters for the function invocation.</param>
/// <returns>Function result</returns>
@@ -247,7 +251,7 @@ public:
};
/// <summary>
/// Delegate object that can be used to bind and call multiply functions. Thread-safe to register/unregister during the call.
/// 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.
/// </summary>
template<typename... Params>
class Delegate
@@ -264,52 +268,65 @@ public:
using FunctionType = Function<void(Params ...)>;
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 _ptr = 0;
intptr _size = 0;
CriticalSection _locker;
HashSet<FunctionType>* _functions = nullptr;
CriticalSection* _locker;
int32* _lockerRefCount;
typedef void (*StubSignature)(void*, Params ...);
public:
Delegate()
{
_locker = New<CriticalSection>();
_lockerRefCount = New<int32>();
*_lockerRefCount = 1;
}
Delegate(const Delegate& other)
{
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++)
if (other._functions == nullptr)
return;
_functions = New<HashSet<FunctionType>>(*other._functions);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
FunctionType& f = newBindings[i];
if (f._function && f._lambda)
f.LambdaCtor();
if (i->Item._function && i->Item._lambda)
i->Item.LambdaCtor();
}
_ptr = (intptr)newBindings;
_size = newSize;
_locker = other._locker;
_lockerRefCount = other._lockerRefCount;
*_lockerRefCount = *_lockerRefCount + 1;
}
Delegate(Delegate&& other) noexcept
{
_ptr = other._ptr;
_size = other._size;
other._ptr = 0;
other._size = 0;
_functions = other._functions;
_locker = other._locker;
_lockerRefCount = other._lockerRefCount;
other._functions = nullptr;
other._locker = nullptr;
other._lockerRefCount = nullptr;
}
~Delegate()
{
auto ptr = (FunctionType*)_ptr;
if (ptr)
int32& lockerRefCount = *_lockerRefCount;
lockerRefCount--;
if (lockerRefCount == 0)
{
while (_size--)
Allocator::Free(_locker);
Allocator::Free(_lockerRefCount);
_locker = nullptr;
_lockerRefCount = nullptr;
}
if (_functions != nullptr)
{
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (ptr->_lambda)
ptr->LambdaDtor();
++ptr;
if (i->Item._lambda)
i->Item.LambdaCtor();
}
Allocator::Free((void*)_ptr);
Allocator::Free(_functions);
}
}
@@ -318,10 +335,8 @@ public:
if (this != &other)
{
UnbindAll();
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]);
for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
}
return *this;
}
@@ -330,10 +345,12 @@ public:
{
if (this != &other)
{
_ptr = other._ptr;
_size = other._size;
other._ptr = 0;
other._size = 0;
_functions = other._functions;
_locker = other._locker;
_lockerRefCount = other._lockerRefCount;
other._functions = nullptr;
other._locker = nullptr;
other._lockerRefCount = nullptr;
}
return *this;
}
@@ -390,44 +407,14 @@ public:
/// <param name="f">The function to bind.</param>
void Bind(const FunctionType& f)
{
ScopeLock lock(_locker);
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
if (bindings)
{
// Find a first free slot
for (intptr i = 0; i < size; i++)
{
if (bindings[i]._function == 0)
{
bindings[i]._function = f._function;
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
_ptr = (intptr)newBindings;
_size = newSize;
Allocator::Free(bindings);
ScopeLock lock(*_locker);
if (_functions == nullptr)
_functions = New<HashSet<FunctionType>>(32);
_functions->Add(f);
}
/// <summary>
/// Binds a static function (if not binded yet).
/// Binds a static function (if not bound yet).
/// </summary>
template<void(*Method)(Params ...)>
void BindUnique()
@@ -438,7 +425,7 @@ public:
}
/// <summary>
/// Binds a member function (if not binded yet).
/// Binds a member function (if not bound yet).
/// </summary>
/// <param name="callee">The object instance.</param>
template<class T, void(T::*Method)(Params ...)>
@@ -450,7 +437,7 @@ public:
}
/// <summary>
/// Binds a function (if not binded yet).
/// Binds a function (if not bound yet).
/// </summary>
/// <param name="method">The method.</param>
void BindUnique(Signature method)
@@ -460,22 +447,17 @@ public:
}
/// <summary>
/// Binds a function (if not binded yet).
/// Binds a function (if not bound yet).
/// </summary>
/// <param name="f">The function to bind.</param>
void BindUnique(const FunctionType& f)
{
ScopeLock lock(_locker);
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
if (bindings)
ScopeLock lock(*_locker);
if (_functions != nullptr)
{
// Skip if already binded
for (intptr i = 0; i < size; i++)
{
if (bindings[i]._callee == f._callee && bindings[i]._function == f._function)
return;
}
// Skip if already bound
if (_functions->Contains(f))
return;
}
Bind(f);
}
@@ -519,24 +501,10 @@ public:
/// <param name="f">The function to unbind.</param>
void Unbind(const FunctionType& f)
{
ScopeLock lock(_locker);
// Find slot with that function
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
for (intptr i = 0; i < size; i++)
{
if (bindings[i]._callee == f._callee && bindings[i]._function == f._function)
{
if (bindings[i]._lambda)
{
bindings[i].LambdaDtor();
bindings[i]._lambda = nullptr;
}
bindings[i]._callee = 0;
bindings[i]._function = 0;
break;
}
}
if (_functions == nullptr)
return;
ScopeLock lock(*_locker);
_functions->Remove(f);
}
/// <summary>
@@ -544,37 +512,27 @@ public:
/// </summary>
void UnbindAll()
{
ScopeLock lock(_locker);
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
for (intptr i = 0; i < size; i++)
if (_functions == nullptr)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (bindings[i]._lambda)
{
bindings[i].LambdaDtor();
bindings[i]._lambda = nullptr;
}
bindings[i]._callee = 0;
bindings[i]._function = 0;
if (i->Item._lambda)
i->Item.LambdaDtor();
}
_functions->Clear();
}
/// <summary>
/// Counts the amount of binded functions.
/// Counts the amount of bound functions.
/// </summary>
/// <returns>The binded functions count.</returns>
/// <returns>The bound functions count.</returns>
int32 Count() const
{
ScopeLock lock(_locker);
int32 count = 0;
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
for (intptr i = 0; i < size; i++)
{
if (bindings[i]._function != 0)
count++;
}
return count;
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Count();
}
/// <summary>
@@ -582,45 +540,40 @@ public:
/// </summary>
int32 Capacity() const
{
return (int32)Platform::AtomicRead((intptr volatile*)&_size);
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Capacity();
}
/// <summary>
/// Determines whether any function is binded.
/// Determines whether any function is bound.
/// </summary>
/// <returns><c>true</c> if any function is binded; otherwise, <c>false</c>.</returns>
/// <returns><c>true</c> if any function is bound; otherwise, <c>false</c>.</returns>
bool IsBinded() const
{
ScopeLock lock(_locker);
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
for (intptr i = 0; i < size; i++)
{
if (bindings[i]._function != 0)
return true;
}
return false;
ScopeLock lock(*_locker);
return _functions != nullptr && _functions->Count() > 0;
}
/// <summary>
/// Gets all the binded functions.
/// Gets all the bound functions.
/// </summary>
/// <param name="buffer">The result bindings functions memory.</param>
/// <param name="bufferSize">The result bindings functions memory size.</param>
/// <returns>The amount of written items into the output bindings buffer. Can be equal or less than input bindingsCount capacity.</returns>
int32 GetBindings(FunctionType* buffer, int32 bufferSize) const
{
ScopeLock lock(_locker);
ScopeLock lock(*_locker);
int32 count = 0;
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
for (intptr i = 0; i < size && i < bufferSize; i++)
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
buffer[count]._function = (StubSignature)bindings[i]._function;
if (buffer[count]._function != nullptr)
if (i->Item._function != nullptr)
{
buffer[count]._callee = (void*)bindings[i]._callee;
buffer[count]._lambda = (typename FunctionType::Lambda*)bindings[i]._lambda;
buffer[count]._function = (StubSignature)i->Item._function;
buffer[count]._callee = (void*)i->Item._callee;
buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda;
if (buffer[count]._lambda)
buffer[count].LambdaCtor();
count++;
@@ -630,25 +583,32 @@ public:
}
/// <summary>
/// Calls all the binded functions. Supports unbinding of the called functions during invocation.
/// Calls all the bound functions. Supports unbinding of the called functions during invocation.
/// </summary>
/// <param name="params">A list of parameters for the function invocation.</param>
void operator()(Params ... params) const
{
ScopeLock lock(_locker); // TODO: We probably don't need to hold the lock during the function calls, only when iterating through the bindings?
const intptr size = _size;
FunctionType* bindings = (FunctionType*)_ptr;
for (intptr i = 0; i < size; i++)
if (_functions == nullptr)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
auto function = (StubSignature)bindings->_function;
auto callee = (void*)bindings->_callee;
auto function = (StubSignature)(i->Item._function);
auto callee = (void*)(i->Item._callee);
if (function != nullptr)
function(callee, Forward<Params>(params)...);
++bindings;
}
}
};
template<typename... Params>
inline uint32 GetHash(const Function<void(Params ...)>& key)
{
uint32 hash = GetHash((void*)key._callee);
CombineHash(hash, GetHash((void*)key._function));
return hash;
}
/// <summary>
/// Action delegate.
/// </summary>

View File

@@ -128,11 +128,6 @@ bool ScriptingService::Init()
{
const auto startTime = DateTime::NowUTC();
// Link for assemblies events
auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly;
engineAssembly->Loaded.Bind(onEngineLoaded);
engineAssembly->Unloading.Bind(onEngineUnloading);
// Initialize managed runtime
if (MCore::LoadEngine())
{
@@ -483,6 +478,8 @@ bool Scripting::Load()
return true;
}
onEngineLoaded(flaxEngineModule->Assembly);
// Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
// TODO: add support for automatic typedef aliases setup for scripting module to properly lookup type from the alias typename
#if USE_LARGE_WORLDS
@@ -585,6 +582,9 @@ void Scripting::Release()
asset->DeleteObjectNow();
}
auto* flaxEngineModule = (NativeBinaryModule*)GetBinaryModuleFlaxEngine();
onEngineUnloading(flaxEngineModule->Assembly);
// Unload assemblies (from back to front)
{
LOG(Info, "Unloading binary modules");