Refactor ObjectsRemovalService to skip double-buffering due to issues and complexity

This commit is contained in:
Wojtek Figat
2023-02-10 10:50:18 +01:00
parent b4ea70acbb
commit 07892ccf18
2 changed files with 20 additions and 108 deletions

View File

@@ -2,22 +2,18 @@
#include "ObjectsRemovalService.h" #include "ObjectsRemovalService.h"
#include "Collections/Dictionary.h" #include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h" #include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h" #include "Engine/Threading/Threading.h"
#include "Engine/Engine/Time.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
#include "Log.h"
namespace ObjectsRemovalServiceImpl namespace ObjectsRemovalServiceImpl
{ {
bool IsReady = false;
CriticalSection PoolLocker; CriticalSection PoolLocker;
CriticalSection NewItemsLocker;
DateTime LastUpdate; DateTime LastUpdate;
float LastUpdateGameTime; float LastUpdateGameTime;
Dictionary<Object*, float> Pool(8192); Dictionary<Object*, float> Pool(8192);
Dictionary<Object*, float> NewItemsPool(2048);
} }
using namespace ObjectsRemovalServiceImpl; using namespace ObjectsRemovalServiceImpl;
@@ -39,41 +35,14 @@ ObjectsRemoval ObjectsRemovalInstance;
bool ObjectsRemovalService::IsInPool(Object* obj) bool ObjectsRemovalService::IsInPool(Object* obj)
{ {
if (!IsReady) PoolLocker.Lock();
return false; const bool result = Pool.ContainsKey(obj);
PoolLocker.Unlock();
{
ScopeLock lock(NewItemsLocker);
if (NewItemsPool.ContainsKey(obj))
return true;
}
{
ScopeLock lock(PoolLocker);
if (Pool.ContainsKey(obj))
return true;
}
return false;
}
bool ObjectsRemovalService::HasNewItemsForFlush()
{
NewItemsLocker.Lock();
const bool result = NewItemsPool.HasItems();
NewItemsLocker.Unlock();
return result; return result;
} }
void ObjectsRemovalService::Dereference(Object* obj) void ObjectsRemovalService::Dereference(Object* obj)
{ {
if (!IsReady)
return;
NewItemsLocker.Lock();
NewItemsPool.Remove(obj);
NewItemsLocker.Unlock();
PoolLocker.Lock(); PoolLocker.Lock();
Pool.Remove(obj); Pool.Remove(obj);
PoolLocker.Unlock(); PoolLocker.Unlock();
@@ -81,57 +50,37 @@ void ObjectsRemovalService::Dereference(Object* obj)
void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime) void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime)
{ {
ScopeLock lock(NewItemsLocker);
obj->Flags |= ObjectFlags::WasMarkedToDelete; obj->Flags |= ObjectFlags::WasMarkedToDelete;
if (useGameTime) if (useGameTime)
obj->Flags |= ObjectFlags::UseGameTimeForDelete; obj->Flags |= ObjectFlags::UseGameTimeForDelete;
else else
obj->Flags &= ~ObjectFlags::UseGameTimeForDelete; obj->Flags &= ~ObjectFlags::UseGameTimeForDelete;
NewItemsPool[obj] = timeToLive;
PoolLocker.Lock();
Pool[obj] = timeToLive;
PoolLocker.Unlock();
} }
void ObjectsRemovalService::Flush(float dt, float gameDelta) void ObjectsRemovalService::Flush(float dt, float gameDelta)
{ {
PROFILE_CPU(); PROFILE_CPU();
// Add new items PoolLocker.Lock();
int32 itemsLeft;
do
{ {
ScopeLock lock(NewItemsLocker); // Update timeouts and delete objects that timed out
itemsLeft = Pool.Count();
for (auto i = NewItemsPool.Begin(); i.IsNotEnd(); ++i)
{
Pool[i->Key] = i->Value;
}
NewItemsPool.Clear();
}
// Update timeouts and delete objects that timed out
{
ScopeLock lock(PoolLocker);
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{ {
auto obj = i->Key; Object* obj = i->Key;
const float ttl = i->Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt); const float ttl = i->Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt);
if (ttl <= ZeroTolerance) if (ttl <= 0.0f)
{ {
Pool.Remove(i); Pool.Remove(i);
#if BUILD_DEBUG || BUILD_DEVELOPMENT
if (NewItemsPool.ContainsKey(obj))
{
const auto asScriptingObj = dynamic_cast<ScriptingObject*>(obj);
if (asScriptingObj)
{
LOG(Warning, "Object {0} was marked to delete after delete timeout", asScriptingObj->GetID());
}
}
#endif
NewItemsPool.Remove(obj);
//ASSERT(!NewItemsPool.ContainsKey(obj));
obj->OnDeleteObject(); obj->OnDeleteObject();
itemsLeft--;
} }
else else
{ {
@@ -139,38 +88,9 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
} }
} }
} }
while (itemsLeft != Pool.Count()); // Continue removing if any new item was added during removing (eg. sub-object delete with 0 timeout)
// Perform removing in loop PoolLocker.Unlock();
// Note: objects during OnDeleteObject call can register new objects to remove with timeout=0, for example Actors do that to remove children and scripts
while (HasNewItemsForFlush())
{
// Add new items
{
ScopeLock lock(NewItemsLocker);
for (auto i = NewItemsPool.Begin(); i.IsNotEnd(); ++i)
{
Pool[i->Key] = i->Value;
}
NewItemsPool.Clear();
}
// Delete objects that timed out
{
ScopeLock lock(PoolLocker);
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value <= ZeroTolerance)
{
auto obj = i->Key;
Pool.Remove(i);
ASSERT(!NewItemsPool.ContainsKey(obj));
obj->OnDeleteObject();
}
}
}
}
} }
bool ObjectsRemoval::Init() bool ObjectsRemoval::Init()
@@ -204,14 +124,12 @@ void ObjectsRemoval::Dispose()
ScopeLock lock(PoolLocker); ScopeLock lock(PoolLocker);
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{ {
auto obj = i->Key; Object* obj = i->Key;
Pool.Remove(i); Pool.Remove(i);
obj->OnDeleteObject(); obj->OnDeleteObject();
} }
Pool.Clear(); Pool.Clear();
} }
IsReady = false;
} }
Object::~Object() Object::~Object()

View File

@@ -17,12 +17,6 @@ public:
/// <returns>True if object has been registered in the pool for the removing, otherwise false.</returns> /// <returns>True if object has been registered in the pool for the removing, otherwise false.</returns>
static bool IsInPool(Object* obj); static bool IsInPool(Object* obj);
/// <summary>
/// Determines whether any object has been registered to be removed from pool (requests are flushed on Flush call).
/// </summary>
/// <returns>True if any object has been registered to be removed, otherwise false.</returns>
static bool HasNewItemsForFlush();
/// <summary> /// <summary>
/// Removes the specified object from the dead pool (clears the reference to it). /// Removes the specified object from the dead pool (clears the reference to it).
/// </summary> /// </summary>