// Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumLifetime.h" #include "CesiumRuntime.h" #if WITH_EDITOR #include "Editor.h" #include "Editor/EditorEngine.h" #include "Engine/Selection.h" #endif #include "Engine/StaticMesh.h" #include "Engine/Texture2D.h" #include "PhysicsEngine/BodySetup.h" #include "Runtime/Launch/Resources/Version.h" #include "StaticMeshResources.h" #include "UObject/Object.h" #include /*static*/ AmortizedDestructor CesiumLifetime::amortizedDestructor = AmortizedDestructor(); /*static*/ void CesiumLifetime::destroy(UObject* pObject) { amortizedDestructor.destroy(pObject); } /*static*/ void CesiumLifetime::destroyComponentRecursively(USceneComponent* pComponent) { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::DestroyComponent) UE_LOG( LogCesium, VeryVerbose, TEXT("Destroying scene component recursively")); if (!pComponent) { return; } if (pComponent->IsRegistered()) { pComponent->UnregisterComponent(); } TArray children = pComponent->GetAttachChildren(); for (USceneComponent* pChild : children) { destroyComponentRecursively(pChild); } #if WITH_EDITOR // If the editor is currently selecting this, remove the reference if (GEditor) { USelection* editorSelection = GEditor->GetSelectedComponents(); if (editorSelection && editorSelection->IsSelected(pComponent)) editorSelection->Deselect(pComponent); } #endif pComponent->DestroyPhysicsState(); pComponent->DestroyComponent(); pComponent->ConditionalBeginDestroy(); UE_LOG(LogCesium, VeryVerbose, TEXT("Destroying scene component done")); } void AmortizedDestructor::Tick(float DeltaTime) { processPending(); } ETickableTickType AmortizedDestructor::GetTickableTickType() const { return ETickableTickType::Always; } bool AmortizedDestructor::IsTickableWhenPaused() const { return true; } bool AmortizedDestructor::IsTickableInEditor() const { return true; } TStatId AmortizedDestructor::GetStatId() const { return TStatId(); } void AmortizedDestructor::destroy(UObject* pObject) { if (!runDestruction(pObject)) { addToPending(pObject); } } bool AmortizedDestructor::runDestruction(UObject* pObject) const { TRACE_CPUPROFILER_EVENT_SCOPE(Cesium::RunDestruction) if (!pObject) { return true; } pObject->MarkAsGarbage(); if (pObject->HasAnyFlags(RF_FinishDestroyed)) { // Already done being destroyed. return true; } if (!pObject->HasAnyFlags(RF_BeginDestroyed)) { pObject->ConditionalBeginDestroy(); } if (!pObject->HasAnyFlags(RF_FinishDestroyed) && pObject->IsReadyForFinishDestroy()) { // Don't actually call ConditionalFinishDestroy here, because if we do the // UE garbage collector will freak out that it's already been called. The // IsReadyForFinishDestroy call is important, though. In some objects, // calling that actually continues the async destruction! finalizeDestroy(pObject); return true; } return false; } void AmortizedDestructor::addToPending(UObject* pObject) { _pending.Add(pObject); } void AmortizedDestructor::processPending() { std::swap(_nextPending, _pending); _pending.Empty(); for (TWeakObjectPtr pObject : _nextPending) { destroy(pObject.Get(true)); } } void AmortizedDestructor::finalizeDestroy(UObject* pObject) const { // The freeing/clearing/destroying done here is normally done in these // objects' FinishDestroy method, but unfortunately we can't call that // directly without confusing the garbage collector if and when it _does_ // run. So instead we manually release some critical resources here. UTexture2D* pTexture2D = Cast(pObject); if (pTexture2D) { pTexture2D->ReleaseResource(); FTexturePlatformData* pPlatformData = pTexture2D->GetPlatformData(); pTexture2D->SetPlatformData(nullptr); delete pPlatformData; } UStaticMesh* pMesh = Cast(pObject); if (pMesh) { pMesh->SetRenderData(nullptr); } UBodySetup* pBodySetup = Cast(pObject); if (pBodySetup) { pBodySetup->UVInfo.IndexBuffer.Empty(); pBodySetup->UVInfo.VertPositions.Empty(); pBodySetup->UVInfo.VertUVs.Empty(); pBodySetup->FaceRemap.Empty(); pBodySetup->ClearPhysicsMeshes(); } }