Files
integration/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Asset_Delete.h
2026-03-18 10:17:58 -04:00

128 lines
3.7 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "Misc/PackageName.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "HAL/FileManager.h"
#include "UObject/LinkerLoad.h"
#include "UObject/Package.h"
#include "Asset_Delete.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Delete : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset to delete"))
FString Asset;
UPROPERTY(meta=(Optional, Description="If true, skip reference check and force delete"))
bool Force = false;
virtual FString GetDescription() const override
{
return TEXT("Delete a .uasset after verifying no references. "
"Use force=true to skip the reference check.");
}
virtual void Handle() override
{
// Verify the asset file exists on disk
FString PackageFilename = FPackageName::LongPackageNameToFilename(
Asset, FPackageName::GetAssetPackageExtension());
PackageFilename = FPaths::ConvertRelativePathToFull(PackageFilename);
if (!IFileManager::Get().FileExists(*PackageFilename))
{
UWingServer::Printf(TEXT("ERROR: Asset file not found on disk: %s\n"), *PackageFilename);
return;
}
// Check references
IAssetRegistry& Registry = *IAssetRegistry::Get();
TArray<FName> Referencers;
Registry.GetReferencers(FName(*Asset), Referencers);
// Filter out self-references
Referencers.RemoveAll([this](const FName& Ref) {
return Ref.ToString() == Asset;
});
if (Referencers.Num() > 0 && !Force)
{
UWingServer::Printf(TEXT("ERROR: Asset is still referenced by %d package(s):\n"), Referencers.Num());
for (const FName& Ref : Referencers)
{
FString RefStr = Ref.ToString();
UPackage* RefPackage = FindPackage(nullptr, *RefStr);
UWingServer::Printf(TEXT(" %s%s\n"), *RefStr,
RefPackage ? TEXT(" (loaded)") : TEXT(" (on-disk only)"));
}
UWingServer::Print(TEXT("Use force=true to skip the reference check.\n"));
return;
}
// Force delete: unload the package from memory first
if (Force && Referencers.Num() > 0)
{
UWingServer::Printf(TEXT("WARNING: Force-deleting despite %d referencer(s).\n"), Referencers.Num());
}
// Mark the package, and all the objects in it, as NOT
// GC Roots. Also, make them undiscoverable.
UPackage* Package = FindPackage(nullptr, *Asset);
if (Package)
{
// Collect all objects in this package
TArray<UObject*> ObjectsInPackage;
GetObjectsWithPackage(Package, ObjectsInPackage);
// Clear flags and remove from root to allow GC
for (UObject* Obj : ObjectsInPackage)
{
if (Obj)
{
Obj->ClearFlags(RF_Standalone | RF_Public);
Obj->RemoveFromRoot();
}
}
Package->ClearFlags(RF_Standalone | RF_Public);
Package->RemoveFromRoot();
}
// The loader that loaded the package might still
// have a file lock on it. Unlock the file.
ResetLoaders(Package);
// Delete the file on disk
bool bDeleted = IFileManager::Get().Delete(*PackageFilename, false, true);
if (!bDeleted)
{
UWingServer::Printf(TEXT("ERROR: Failed to delete file from disk: %s\n"), *PackageFilename);
return;
}
// Trigger an asset registry rescan so it notices the deletion
FString PackageDir;
int32 LastSlash;
if (Asset.FindLastChar(TEXT('/'), LastSlash))
{
PackageDir = Asset.Left(LastSlash);
Registry.ScanPathsSynchronous({PackageDir}, true);
}
UWingServer::Printf(TEXT("Deleted %s\n"), *Asset);
}
};