Files
AssetUsageAudit/Source/AssetUsageAuditCore/Public/AssetExportManifest.h
T
Admin 72bf94d142 feat: asset usage audit plugin
Editor tool for the LA and 3D departments: which assets each level uses,
which are used nowhere, and export of a chosen set.

Three modules. AssetUsageAuditCore holds the whole analysis and links no
UI and no editor-only asset pipeline, so it stays runnable from a
commandlet; AssetUsageAuditEditor holds the Slate panel and everything
that needs UnrealEd or AssetTools; AssetUsageAuditTests holds 152 specs.

Load-bearing decisions, each of which produces a wrong answer if undone:

- Dependency queries are always Package + NoRequirements, never Hard. The
  map-to-external-actor edges the OFPA gatherer emits carry Game|Build
  without Hard, so a Hard query drops all 16117 external actor packages
  in this project. There is deliberately no Hard constant in the code.
- Crossing into another map is allowed only from a level or its external
  actor package. Without that rule WP_Main reported 18136 assets, of
  which 9994 belonged to L_MainLevel, reached through the GameMode.
- The verdict has five states, never a bool. The registry cannot see
  FMOD events, DataTable rows or string-built paths; those are Unknown,
  and the tool never proposes a deletion.
- Copying .uasset files does not preserve references - they are stored as
  full package paths. Only the Migrate layout produces something Unreal
  can open; the others write a manifest so the graph can be rebuilt.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-03 17:07:59 +07:00

84 lines
3.1 KiB
C++

// NextGenium 2026. Asset Usage Audit.
#pragma once
#include "CoreMinimal.h"
#include "AssetUsageAuditTypes.h"
#include "AssetUsageExporter.h"
class IAssetRegistry;
/**
* The JSON written beside a reference-breaking export.
*
* Why it exists: references inside a .uasset are stored as full package names, so a copied file
* only resolves when it sits at exactly that package path in the destination. The Flat and
* FolderPerAsset layouts deliberately do not put it there - they arrange files for a person to read
* and hand around - so the reference graph is lost the moment the files leave the project.
*
* The manifest records that graph next to the files, so a later import can rebuild it rather than
* guess. It is written from what the exporter actually wrote, never from what it intended to write:
* the two differ exactly when the collision policy renamed something, which is the case a manifest
* has to get right.
*
* Migrate needs none of this - the engine preserves the paths itself - and asking for a manifest
* there is treated as a caller mistake rather than silently producing a misleading file.
*/
namespace AssetExportManifest
{
/** Bumped when the schema changes in a way a reader must notice. */
inline constexpr int32 SchemaVersion = 1;
/** Manifest filename, written into the export root. */
inline const TCHAR* FileName = TEXT("AssetUsageAudit.manifest.json");
struct FOptions
{
/** Export root. The manifest is written here and paths are relative to it. */
FString TargetDirectory;
/** Recorded so a reader knows why the paths look the way they do. */
EExportLayout Layout = EExportLayout::Flat;
EExportCollisionPolicy CollisionPolicy = EExportCollisionPolicy::Index;
/**
* Packages the user ticked, as opposed to those pulled in as dependencies.
*
* Kept apart because an importer needs to know which assets were the point and which came
* along to make them work - "restore what I exported" and "restore everything in this
* folder" are different requests.
*/
TSet<FName> SeedPackages;
};
struct ASSETUSAGEAUDITCORE_API FResult
{
bool bSuccess = false;
/** Absolute path of the manifest, empty on failure. */
FString FilePath;
int32 EntriesWritten = 0;
/** Dependency edges recorded across all entries. */
int32 EdgesRecorded = 0;
FString ErrorMessage;
};
/**
* Write the manifest.
*
* @param WrittenFiles From FAssetUsageExporter::FResult::WrittenFiles, which requires the
* exporter to have been run with bRecordWrittenFiles. An empty array is a
* failure rather than an empty manifest: it almost always means the flag
* was forgotten, and an empty manifest beside a full folder is worse than
* no manifest at all.
*/
ASSETUSAGEAUDITCORE_API FResult Write(
IAssetRegistry& Registry,
const TArray<FAssetUsageExporter::FWrittenFile>& WrittenFiles,
const FOptions& Options);
}