feat: asset deletion scan and delete dialog (WIP)
Adds AssetDeletionScan to Core (no UI/editor-pipeline deps, invariant holds) and SAssetDeleteDialog to the Editor module, plus 10 spec cases. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
// NextGenium 2026. Asset Usage Audit.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/TopLevelAssetPath.h"
|
||||
|
||||
class IAssetRegistry;
|
||||
|
||||
/**
|
||||
* What would happen if this set of packages were deleted - worked out before anything is touched.
|
||||
*
|
||||
* Deletion is the one operation in this tool that destroys work, and the audit's own verdicts are
|
||||
* not a safe basis for it on their own: Unknown means "the registry cannot see it", not "unused",
|
||||
* and the whole point of the five-state verdict is that the tool never presents a guess as a fact.
|
||||
* So the scan answers a narrower, checkable question - who still points at this, right now, in the
|
||||
* registry - and leaves the judgement to the person reading it.
|
||||
*
|
||||
* Pure registry and filesystem work. It loads nothing, deletes nothing, and is deliberately
|
||||
* separate from the code that performs the delete: the analysis has to be testable without an
|
||||
* editor, and the actual removal is ObjectTools' job in the Editor module, not ours.
|
||||
*/
|
||||
namespace AssetDeletionScan
|
||||
{
|
||||
/**
|
||||
* Why a ticked package will not be deleted.
|
||||
*
|
||||
* Refusals are policy, not failures, and each one is shown with its reason. Silently dropping a
|
||||
* row from the delete set would be worse than refusing loudly - the user ticked it and is owed
|
||||
* an explanation.
|
||||
*/
|
||||
enum class ERefusal : uint8
|
||||
{
|
||||
/** No objection. */
|
||||
None,
|
||||
|
||||
/**
|
||||
* The package is a map.
|
||||
*
|
||||
* Refused by decision, not by capability. A level is the unit the whole tool measures usage
|
||||
* against, and deleting one silently invalidates every other row in the result. The engine
|
||||
* also refuses to delete a level that is currently open, which would make the outcome depend
|
||||
* on which map the user happens to have loaded.
|
||||
*/
|
||||
IsLevel,
|
||||
|
||||
/**
|
||||
* A One File Per Actor package - an actor or object belonging to some level.
|
||||
*
|
||||
* Deleting one is not deleting an asset, it is deleting a placed actor out of a map behind
|
||||
* the level editor's back. That belongs in the level editor, with its undo.
|
||||
*/
|
||||
IsExternalPackage,
|
||||
|
||||
/** /Engine, /Script or /Temp. Not this project's to delete. */
|
||||
NotProjectContent,
|
||||
|
||||
/** The registry has no asset under this package name; nothing to delete. */
|
||||
Missing
|
||||
};
|
||||
|
||||
ASSETUSAGEAUDITCORE_API const TCHAR* LexToString(ERefusal Refusal);
|
||||
|
||||
/** One line of explanation, suitable for showing beside the row. */
|
||||
ASSETUSAGEAUDITCORE_API FText DescribeRefusal(ERefusal Refusal);
|
||||
|
||||
/** One ticked package and what stands in the way of deleting it. */
|
||||
struct FCandidate
|
||||
{
|
||||
FName PackageName;
|
||||
FName AssetName;
|
||||
FTopLevelAssetPath ClassPath;
|
||||
|
||||
/** Content/... form, as the report shows it. */
|
||||
FString PathFromProjectRoot;
|
||||
|
||||
ERefusal Refusal = ERefusal::None;
|
||||
|
||||
/**
|
||||
* Packages that reference this one and are NOT themselves being deleted.
|
||||
*
|
||||
* The exclusion matters: deleting a Blueprint together with the mesh only it uses is a
|
||||
* clean operation, and listing the Blueprint as a blocker would make every sensible
|
||||
* multi-asset delete look dangerous. Truncated to FOptions::MaxReferencersListed - the list
|
||||
* is there to be read, and forty names is not read.
|
||||
*/
|
||||
TArray<FName> OutsideReferencers;
|
||||
|
||||
/** Full count before truncation. */
|
||||
int32 OutsideReferencerCount = 0;
|
||||
|
||||
/** How many of those referencers are maps or OFPA packages - i.e. it is placed in a level. */
|
||||
int32 LevelReferencerCount = 0;
|
||||
|
||||
/**
|
||||
* The file exists on disk and is marked read-only.
|
||||
*
|
||||
* Worth surfacing here rather than discovering it as a failure afterwards: this project is
|
||||
* Perforce-primary and unopened files are read-only by default, so a delete attempted
|
||||
* without checking out first fails per-file, halfway through.
|
||||
*/
|
||||
bool bReadOnlyOnDisk = false;
|
||||
|
||||
bool CanDelete() const
|
||||
{
|
||||
return Refusal == ERefusal::None;
|
||||
}
|
||||
};
|
||||
|
||||
struct FOptions
|
||||
{
|
||||
/** How many referencer names to keep per candidate. The count is always exact. */
|
||||
int32 MaxReferencersListed = 8;
|
||||
};
|
||||
|
||||
// Exported: Summarise is defined out of line, so the Editor module cannot link without this.
|
||||
struct ASSETUSAGEAUDITCORE_API FStats
|
||||
{
|
||||
int32 Requested = 0;
|
||||
int32 Deletable = 0;
|
||||
|
||||
int32 RefusedLevels = 0;
|
||||
int32 RefusedExternal = 0;
|
||||
int32 RefusedNotProjectContent = 0;
|
||||
int32 RefusedMissing = 0;
|
||||
|
||||
/** Deletable candidates that something outside the set still points at. */
|
||||
int32 WithOutsideReferencers = 0;
|
||||
|
||||
/** Deletable candidates whose file is read-only on disk. */
|
||||
int32 ReadOnly = 0;
|
||||
|
||||
FString Summarise() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Classify the packages and collect their outside referencers.
|
||||
*
|
||||
* Order is preserved, refusals included, so the caller can show the whole ticked set with a
|
||||
* reason against each row instead of a shorter list that quietly lost entries.
|
||||
*
|
||||
* Referencers are queried with AssetUsageAudit::MakeTraversalQuery() for the same reason the
|
||||
* audit uses it: a Hard-only query misses every soft reference, and a soft reference still
|
||||
* breaks when its target disappears - it just breaks at runtime instead of at load.
|
||||
*/
|
||||
ASSETUSAGEAUDITCORE_API TArray<FCandidate> Scan(
|
||||
IAssetRegistry& Registry,
|
||||
const TArray<FName>& Packages,
|
||||
const FOptions& Options,
|
||||
FStats& OutStats);
|
||||
}
|
||||
Reference in New Issue
Block a user