// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ObjectsRemovalService.h" #include "Collections/Dictionary.h" #include "Engine/Engine/EngineService.h" #include "Engine/Threading/Threading.h" #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ScriptingObject.h" #include "Log.h" namespace ObjectsRemovalServiceImpl { bool IsReady = false; CriticalSection PoolLocker; CriticalSection NewItemsLocker; DateTime LastUpdate; float LastUpdateGameTime; Dictionary Pool(8192); Dictionary NewItemsPool(2048); } using namespace ObjectsRemovalServiceImpl; class ObjectsRemovalServiceService : public EngineService { public: ObjectsRemovalServiceService() : EngineService(TEXT("Objects Removal Service"), -1000) { LastUpdate = DateTime::NowUTC(); LastUpdateGameTime = 0; } void LateUpdate() override; void Dispose() override; }; ObjectsRemovalServiceService ObjectsRemovalServiceServiceInstance; bool ObjectsRemovalService::IsInPool(RemovableObject* obj) { if (!IsReady) return false; { 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; } void ObjectsRemovalService::Dereference(RemovableObject* obj) { if (!IsReady) return; NewItemsLocker.Lock(); NewItemsPool.Remove(obj); NewItemsLocker.Unlock(); PoolLocker.Lock(); Pool.Remove(obj); PoolLocker.Unlock(); } void ObjectsRemovalService::Add(RemovableObject* obj, float timeToLive, bool useGameTime) { ScopeLock lock(NewItemsLocker); obj->Flags |= ObjectFlags::WasMarkedToDelete; if (useGameTime) obj->Flags |= ObjectFlags::UseGameTimeForDelete; else obj->Flags &= ~ObjectFlags::UseGameTimeForDelete; NewItemsPool[obj] = timeToLive; } void ObjectsRemovalService::Flush(float dt, float gameDelta) { // Add new items { ScopeLock lock(NewItemsLocker); 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) { auto obj = i->Key; const float ttl = i->Value - (obj->Flags & ObjectFlags::UseGameTimeForDelete ? gameDelta : dt); if (ttl <= ZeroTolerance) { Pool.Remove(i); #if BUILD_DEBUG || BUILD_DEVELOPMENT if (NewItemsPool.ContainsKey(obj)) { const auto asScriptingObj = dynamic_cast(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(); } else { i->Value = ttl; } } } // Perform removing in loop // 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(); } } } } } void ObjectsRemovalServiceService::LateUpdate() { PROFILE_CPU(); // Delete all objects const auto now = DateTime::NowUTC(); const float dt = (now - LastUpdate).GetTotalSeconds(); float gameDelta = Time::Update.DeltaTime.GetTotalSeconds(); if (Time::GetGamePaused()) gameDelta = 0; ObjectsRemovalService::Flush(dt, gameDelta); LastUpdate = now; } void ObjectsRemovalServiceService::Dispose() { // Collect new objects ObjectsRemovalService::Flush(); // Delete all remaining objects { ScopeLock lock(PoolLocker); for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) { auto obj = i->Key; Pool.Remove(i); obj->OnDeleteObject(); } Pool.Clear(); } IsReady = false; }