Files
UnrealEngineUWP/Engine/Source/Editor/SizeMap/SSizeMap.cpp
Mike Fricker 1b6238ef4f Size Mapper fixes
- Fixed variable shadowing compile error

[CL 2530390 by Mike Fricker in Main branch]
2015-04-29 12:00:09 -04:00

603 lines
25 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "SizeMapModule.h"
#include "STreeMap.h"
#include "SSizeMap.h"
#include "AssetRegistryModule.h"
#include "AssetThumbnail.h"
#include "ClassIconFinder.h"
#include "UnitConversion.h"
#define LOCTEXT_NAMESPACE "SizeMap"
SSizeMap::SSizeMap()
: TreeMapWidget( nullptr ),
RootAssetPackageNames(),
RootTreeMapNode( new FTreeMapNodeData() ),
// @todo sizemap: Hard-coded thumbnail pool size. Not a big deal, but ideally move the constants elsewhere
AssetThumbnailPool( new FAssetThumbnailPool(1024) )
{
}
SSizeMap::~SSizeMap()
{
if( AssetThumbnailPool.IsValid() )
{
AssetThumbnailPool->ReleaseResources();
AssetThumbnailPool.Reset();
}
}
void SSizeMap::Construct( const FArguments& InArgs )
{
ChildSlot
[
SAssignNew( TreeMapWidget, STreeMap, RootTreeMapNode.ToSharedRef(), nullptr )
.OnTreeMapNodeDoubleClicked( this, &SSizeMap::OnTreeMapNodeDoubleClicked )
];
}
void SSizeMap::SetRootAssetPackageNames( const TArray<FName>& NewRootAssetPackageNames )
{
RootAssetPackageNames = NewRootAssetPackageNames;
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
if ( AssetRegistryModule.Get().IsLoadingAssets() )
{
// We are still discovering assets, listen for the completion delegate before building the graph
if (!AssetRegistryModule.Get().OnFilesLoaded().IsBoundToObject(this))
{
AssetRegistryModule.Get().OnFilesLoaded().AddSP( this, &SSizeMap::OnInitialAssetRegistrySearchComplete );
}
}
else
{
// All assets are already discovered, build the graph now.
RefreshMap();
}
}
namespace SizeMapInternals
{
/** Serialization archive that discovers assets referenced by a specific Unreal object */
class FAssetReferenceFinder : public FArchiveUObject
{
public:
FAssetReferenceFinder( UObject* Object )
{
ArIsObjectReferenceCollector = true;
ArIgnoreOuterRef = true;
check( Object != nullptr );
AllVisitedObjects.Add( Object );
Object->Serialize( *this );
}
FArchive& operator<<( UObject*& Object )
{
// Only look at objects which are valid
const bool bIsValidObject =
Object != nullptr && // Object should not be null
!Object->HasAnyFlags( RF_Transient | RF_PendingKill ); // Should not be transient or pending kill
if( bIsValidObject )
{
// Skip objects that we've already processed
if( !AllVisitedObjects.Contains( Object ) )
{
AllVisitedObjects.Add( Object );
const bool bIsAsset =
Object->GetOuter() != nullptr && // Not a package itself (such as a script package like '/Script/Engine')
Object->GetOuter()->IsA( UPackage::StaticClass() ) && // Only want outer assets (these should be the only public assets, anyway)
Object->HasAllFlags( RF_Public ); // Assets should be public
if( bIsAsset )
{
ReferencedAssets.Add( Object );
}
else
{
// It's probably an inner object. Recursively serialize.
Object->Serialize( *this );
// Make sure the object's class is serialized too, so that we catch any assets referenced from the class defaults
AllVisitedObjects.Add( Object->GetClass() );
Object->GetClass()->Serialize( *this ); // @todo sizemap urgent: Doesn't really seem to be needed
}
}
}
return *this;
}
TSet< UObject* >& GetReferencedAssets()
{
return ReferencedAssets;
}
protected:
/** The set of referenced assets */
TSet< UObject* > ReferencedAssets;
/** Set of all objects we've visited, so we don't follow cycles */
TSet< UObject* > AllVisitedObjects;
};
/** Given a size in bytes and a boolean that indicates whether the size is actually known to be correct, returns a pretty
string to represent that size, such as "256.0 MB", or "unknown size" */
static FString MakeBestSizeString( const SIZE_T SizeInBytes, const bool bHasKnownSize )
{
FString BestSizeString;
const FNumericUnit<double> BestUnit = FUnitConversion::QuantizeUnitsToBestFit( (double)SizeInBytes, EUnit::Bytes );
if( BestUnit.Units == EUnit::Bytes )
{
// We ended up with bytes, so show a decimal number
BestSizeString = FString::Printf( TEXT( "%s %s" ),
*FText::AsNumber( static_cast<uint64>(SizeInBytes) ).ToString(),
*LOCTEXT( "Bytes", "bytes" ).ToString() );
}
else
{
// Show a fractional number with the best possible units
FNumberFormattingOptions NumberFormattingOptions;
NumberFormattingOptions.MaximumFractionalDigits = 1; // @todo sizemap: We could make the number of digits customizable in the UI
NumberFormattingOptions.MinimumFractionalDigits = 0;
NumberFormattingOptions.MinimumIntegralDigits = 1;
BestSizeString = FString::Printf( TEXT( "%s %s" ),
*FText::AsNumber( BestUnit.Value, &NumberFormattingOptions ).ToString(),
FUnitConversion::GetUnitDisplayString( BestUnit.Units ) );
}
if( !bHasKnownSize )
{
if( SizeInBytes == 0 )
{
BestSizeString = LOCTEXT( "UnknownSize", "unknown size" ).ToString();
}
else
{
BestSizeString = FString::Printf( TEXT( "%s %s" ),
*LOCTEXT( "UnknownSizeButAtLeastThisBig", "at least" ).ToString(),
*BestSizeString );
}
}
return BestSizeString;
}
}
void SSizeMap::GatherDependenciesRecursively( FAssetRegistryModule& AssetRegistryModule, TSharedPtr<FAssetThumbnailPool>& InAssetThumbnailPool, TMap<FName, TSharedPtr<FTreeMapNodeData>>& VisitedAssetPackageNames, const TArray<FName>& AssetPackageNames, const TSharedPtr<FTreeMapNodeData>& Node, TSharedPtr<FTreeMapNodeData>& SharedRootNode, int32& NumAssetsWhichFailedToLoad )
{
for( const FName AssetPackageName : AssetPackageNames )
{
// Have we already added this asset to the tree? If so, we'll either move it to a "shared" group or (if it's referenced again by the same
// root-level asset) ignore it
if( VisitedAssetPackageNames.Contains( AssetPackageName ) )
{
// OK, we've determined that this asset has already been referenced by something else in our tree. We'll move it to a "shared" group
// so all of the assets that are referenced in multiple places can be seen together.
TSharedPtr<FTreeMapNodeData> ExistingNode = VisitedAssetPackageNames[ AssetPackageName ];
// Is the existing node not already under the "shared" group? Note that it might still be (indirectly) under
// the "shared" group, in which case we'll still want to move it up to the root since we've figured out that it is
// actually shared between multiple assets which themselves may be shared
if( ExistingNode->Parent != SharedRootNode.Get() )
{
// Don't bother moving any of the assets at the root level into a "shared" bucket. We're only trying to best
// represent the memory used when all of the root-level assets have become loaded. It's OK if root-level assets
// are referenced by other assets in the set -- we don't need to indicate they are shared explicitly
FTreeMapNodeData* ExistingNodeParent = ExistingNode->Parent;
check( ExistingNodeParent != nullptr );
const bool bExistingNodeIsAtRootLevel = ExistingNodeParent->Parent == nullptr || RootAssetPackageNames.Contains( AssetPackageName );
if( !bExistingNodeIsAtRootLevel )
{
// OK, the current asset (AssetPackageName) is definitely not a root level asset, but its already in the tree
// somewhere as a non-shared, non-root level asset. We need to make sure that this Node's reference is not from the
// same root-level asset as the ExistingNodeInTree. Otherwise, there's no need to move it to a 'shared' group.
FTreeMapNodeData* MyParentNode = Node.Get();
check( MyParentNode != nullptr );
FTreeMapNodeData* MyRootLevelAssetNode = MyParentNode;
while( MyRootLevelAssetNode->Parent != nullptr && MyRootLevelAssetNode->Parent->Parent != nullptr )
{
MyRootLevelAssetNode = MyRootLevelAssetNode->Parent;
}
if( MyRootLevelAssetNode->Parent == nullptr )
{
// No root asset (Node must be a root level asset itself!)
MyRootLevelAssetNode = nullptr;
}
// Find the existing node's root level asset node
FTreeMapNodeData* ExistingNodeRootLevelAssetNode = ExistingNodeParent;
while( ExistingNodeRootLevelAssetNode->Parent->Parent != nullptr )
{
ExistingNodeRootLevelAssetNode = ExistingNodeRootLevelAssetNode->Parent;
}
// If we're being referenced by another node within the same asset, no need to move it to a 'shared' group.
if( MyRootLevelAssetNode != ExistingNodeRootLevelAssetNode )
{
// This asset was already referenced by something else (or was in our top level list of assets to display sizes for)
if( !SharedRootNode.IsValid() )
{
// Find the root-most tree node
FTreeMapNodeData* RootNode = MyParentNode;
while( RootNode->Parent != nullptr )
{
RootNode = RootNode->Parent;
}
SharedRootNode = MakeShareable( new FTreeMapNodeData() );
RootNode->Children.Add( SharedRootNode );
SharedRootNode->Parent = RootNode; // Keep back-pointer to parent node
}
// Reparent the node that we've now determined to be shared
ExistingNode->Parent->Children.Remove( ExistingNode );
SharedRootNode->Children.Add( ExistingNode );
ExistingNode->Parent = SharedRootNode.Get();
}
}
}
}
else
{
// This asset is new to us so far! Let's add it to the tree. Later as we descend through references, we might find that the
// asset is referenced by something else as well, in which case we'll pull it out and move it to a "shared" top-level box
// Don't bother showing code references
const FString AssetPackageNameString = AssetPackageName.ToString();
if( !AssetPackageNameString.StartsWith( TEXT( "/Script/" ) ) )
{
FTreeMapNodeDataRef ChildTreeMapNode = MakeShareable( new FTreeMapNodeData() );
Node->Children.Add( ChildTreeMapNode );
ChildTreeMapNode->Parent = Node.Get(); // Keep back-pointer to parent node
VisitedAssetPackageNames.Add( AssetPackageName, ChildTreeMapNode );
FNodeSizeMapData& NodeSizeMapData = NodeSizeMapDataMap.Add( ChildTreeMapNode );
// Set some defaults for this node. These will be used if we can't actually locate the asset.
// @todo sizemap urgent: We need a better indication in the UI when there are one or more missing assets. Because missing assets have a size
// of zero, they are nearly impossible to zoom into. At the least, we should have some Output Log spew when assets cannot be loaded
NodeSizeMapData.AssetData.AssetName = AssetPackageName;
NodeSizeMapData.AssetData.AssetClass = FName( *LOCTEXT( "MissingAsset", "MISSING!" ).ToString() );
NodeSizeMapData.AssetSize = 0;
NodeSizeMapData.bHasKnownSize = false;
// Find the asset using the asset registry
// @todo sizemap: Asset registry-based reference gathering is faster but possibly not as exhaustive (no PostLoad created references, etc.) Maybe should be optional?
// @todo sizemap: With AR-based reference gathering, sometimes the size map is missing root level dependencies until you reopen it a few times (Buggy BP)
// @todo sizemap: With AR-based reference gathering, reference changes at editor-time do not appear in the Size Map until you restart
// @todo sizemap: With AR-based reference gathering, opening the size map for all engine content caused the window to not respond until a restart
// @todo sizemap: We don't really need the asset registry given we need to load the objects to figure out their size, unless we make that AR-searchable.
// ---> This would allow us to not have to wait for AR initialization. But if we made size AR-searchable, we could run very quickly for large data sets!
const bool bUseAssetRegistryForDependencies = false;
const FString AssetPathString = AssetPackageNameString + TEXT(".") + FPackageName::GetLongPackageAssetName( AssetPackageNameString );
const FAssetData FoundAssetData = AssetRegistryModule.Get().GetAssetByObjectPath( FName( *AssetPathString ) );
if( FoundAssetData.IsValid() )
{
NodeSizeMapData.AssetData = FoundAssetData;
// Now actually load up the asset. We need it in memory in order to accurately determine its size.
// @todo sizemap: We could async load these packages to make the editor experience a bit nicer (smoother progress)
UObject* Asset = StaticLoadObject( UObject::StaticClass(), nullptr, *AssetPathString );
if( Asset != nullptr )
{
TArray<FName> ReferencedAssetPackageNames;
if( bUseAssetRegistryForDependencies )
{
AssetRegistryModule.Get().GetDependencies( AssetPackageName, ReferencedAssetPackageNames );
}
else
{
SizeMapInternals::FAssetReferenceFinder References( Asset );
for( UObject* Object : References.GetReferencedAssets() )
{
ReferencedAssetPackageNames.Add( FName( *Object->GetOutermost()->GetPathName() ) );
}
}
// For textures, make sure we're getting the worst case size, not the size of the currently loaded set of mips
// @todo sizemap: We should instead have a special EResourceSizeMode that asks for the worst case size. Some assets (like UTextureCube) currently always report resident mip size, even when asked for inclusive size
if( Asset->IsA( UTexture2D::StaticClass() ) )
{
NodeSizeMapData.AssetSize = Asset->GetResourceSize( EResourceSizeMode::Inclusive );
}
else
{
NodeSizeMapData.AssetSize = Asset->GetResourceSize( EResourceSizeMode::Exclusive );
}
NodeSizeMapData.bHasKnownSize = NodeSizeMapData.AssetSize != UObject::RESOURCE_SIZE_NONE && NodeSizeMapData.AssetSize != 0;
if( !NodeSizeMapData.bHasKnownSize )
{
// Asset has no meaningful size
NodeSizeMapData.AssetSize = 0;
// @todo sizemap urgent: Try to serialize to figure out how big it is (not into sub-assets though!)
// FObjectMemoryAnalyzer ObjectMemoryAnalyzer( Asset );
}
// Now visit all of the assets that we are referencing
GatherDependenciesRecursively( AssetRegistryModule, InAssetThumbnailPool, VisitedAssetPackageNames, ReferencedAssetPackageNames, ChildTreeMapNode, SharedRootNode, NumAssetsWhichFailedToLoad );
}
else
{
++NumAssetsWhichFailedToLoad;
}
}
else
{
++NumAssetsWhichFailedToLoad;
}
}
}
}
}
void SSizeMap::FinalizeNodesRecursively( TSharedPtr<FTreeMapNodeData>& Node, const TSharedPtr<FTreeMapNodeData>& SharedRootNode, int32& TotalAssetCount, SIZE_T& TotalSize, bool& bAnyUnknownSizes )
{
// Process children first, so we can get the totals for the root node and shared nodes
int32 SubtreeAssetCount = 0;
SIZE_T SubtreeSize = 0;
bool bAnyUnknownSizesInSubtree = false;
{
for( TSharedPtr<FTreeMapNodeData> ChildNode : Node->Children )
{
FinalizeNodesRecursively( ChildNode, SharedRootNode, SubtreeAssetCount, SubtreeSize, bAnyUnknownSizesInSubtree );
}
TotalAssetCount += SubtreeAssetCount;
TotalSize += SubtreeSize;
if( bAnyUnknownSizesInSubtree )
{
bAnyUnknownSizes = true;
}
}
if( Node == SharedRootNode )
{
// @todo sizemap: Should we indicate in a non-shared parent node how many if its dependents ended up being in the "shared" bucket? Probably
// not that important, because the user can choose to view that asset in isolation to see the full tree.
Node->Name = FString::Printf( TEXT( "%s (%s)" ),
*LOCTEXT( "SharedGroupName", "*SHARED*" ).ToString(),
*SizeMapInternals::MakeBestSizeString( SubtreeSize, !bAnyUnknownSizes ) );
// Container nodes are always auto-sized
Node->Size = 0.0f;
}
else if( Node->Parent == nullptr )
{
// Tree root is always auto-sized
Node->Size = 0.0f;
}
else
{
const FNodeSizeMapData& NodeSizeMapData = NodeSizeMapDataMap.FindChecked( Node.ToSharedRef() );
++TotalAssetCount;
TotalSize += NodeSizeMapData.AssetSize;
if( !NodeSizeMapData.bHasKnownSize )
{
bAnyUnknownSizes = true;
}
// Setup a thumbnail
const FSlateBrush* DefaultThumbnailSlateBrush;
{
// For non-class types, use the default based upon the actual asset class
// This has the side effect of not showing a class icon for assets that don't have a proper thumbnail image available
bool bIsClassType = false;
const UClass* ThumbnailClass = FClassIconFinder::GetIconClassForAssetData( NodeSizeMapData.AssetData, &bIsClassType );
const FName DefaultThumbnail = (bIsClassType) ? NAME_None : FName(*FString::Printf(TEXT("ClassThumbnail.%s"), *NodeSizeMapData.AssetData.AssetClass.ToString()));
DefaultThumbnailSlateBrush = FClassIconFinder::FindThumbnailForClass(ThumbnailClass, DefaultThumbnail);
// @todo sizemap urgent: Actually implement rendered thumbnail support, not just class-based background images
// const int32 ThumbnailSize = 128; // @todo sizemap: Hard-coded thumbnail size. Move this elsewhere
// TSharedRef<FAssetThumbnail> AssetThumbnail( new FAssetThumbnail( NodeSizeMapData.AssetData, ThumbnailSize, ThumbnailSize, AssetThumbnailPool ) );
// ChildTreeMapNode->AssetThumbnail = AssetThumbnail->MakeThumbnailImage();
}
if( Node->IsLeafNode() )
{
Node->CenterText = SizeMapInternals::MakeBestSizeString( NodeSizeMapData.AssetSize, NodeSizeMapData.bHasKnownSize );
Node->Size = NodeSizeMapData.AssetSize;
// The STreeMap widget is not expecting zero-sized leaf nodes. So we make them very small instead.
if( Node->Size == 0 )
{
Node->Size = 1;
}
// Leaf nodes get a background picture
Node->BackgroundBrush = DefaultThumbnailSlateBrush;
// "Asset name"
// "Asset type"
Node->Name = NodeSizeMapData.AssetData.AssetName.ToString();
Node->Name2 = NodeSizeMapData.AssetData.AssetClass.ToString();
}
else
{
// Container nodes are always auto-sized
Node->Size = 0.0f;
// "Asset name (asset type, size)"
Node->Name = FString::Printf( TEXT( "%s (%s, %s)" ),
*NodeSizeMapData.AssetData.AssetName.ToString(),
*NodeSizeMapData.AssetData.AssetClass.ToString(),
*SizeMapInternals::MakeBestSizeString( SubtreeSize + NodeSizeMapData.AssetSize, !bAnyUnknownSizesInSubtree && NodeSizeMapData.bHasKnownSize ) );
const bool bNeedsSelfNode = NodeSizeMapData.AssetSize > 0;
if( bNeedsSelfNode )
{
// We have children, so make some space for our own asset's size within our box
FTreeMapNodeDataRef ChildSelfTreeMapNode = MakeShareable( new FTreeMapNodeData() );
Node->Children.Add( ChildSelfTreeMapNode );
ChildSelfTreeMapNode->Parent = Node.Get(); // Keep back-pointer to parent node
// Map the "self" node to the same node data as its parent
NodeSizeMapDataMap.Add( ChildSelfTreeMapNode, NodeSizeMapData );
// "*SELF*"
// "Asset type"
ChildSelfTreeMapNode->Name = LOCTEXT( "SelfNodeLabel", "*SELF*" ).ToString();
ChildSelfTreeMapNode->Name2 = NodeSizeMapData.AssetData.AssetClass.ToString();
ChildSelfTreeMapNode->CenterText = SizeMapInternals::MakeBestSizeString( NodeSizeMapData.AssetSize, NodeSizeMapData.bHasKnownSize );
ChildSelfTreeMapNode->Size = NodeSizeMapData.AssetSize;
// Leaf nodes get a background picture
ChildSelfTreeMapNode->BackgroundBrush = DefaultThumbnailSlateBrush;
}
}
}
// Sort all of my child nodes alphabetically. This is just so that we get deterministic results when viewing the
// same sets of assets.
Node->Children.StableSort(
[]( const FTreeMapNodeDataPtr& A, const FTreeMapNodeDataPtr& B )
{
return A->Name < B->Name;
}
);
}
void SSizeMap::RefreshMap()
{
// Wipe the current tree out
RootTreeMapNode->Children.Empty();
NodeSizeMapDataMap.Empty();
// First, do a pass to gather asset dependencies and build up a tree
TMap<FName, TSharedPtr<FTreeMapNodeData>> VisitedAssetPackageNames;
TSharedPtr<FTreeMapNodeData> SharedRootNode;
int32 NumAssetsWhichFailedToLoad = 0;
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
GatherDependenciesRecursively( AssetRegistryModule, AssetThumbnailPool, VisitedAssetPackageNames, RootAssetPackageNames, RootTreeMapNode, SharedRootNode, NumAssetsWhichFailedToLoad );
// Next, do another pass over our tree to and count how big the assets are and to set the node labels. Also in this pass, we may
// create some additional "self" nodes for assets that have children but also take up size themselves.
int32 TotalAssetCount = 0;
SIZE_T TotalSize = 0;
bool bAnyUnknownSizes = false;
FinalizeNodesRecursively( RootTreeMapNode, SharedRootNode, TotalAssetCount, TotalSize, bAnyUnknownSizes );
// Create a nice name for the tree!
if( NumAssetsWhichFailedToLoad > 0 )
{
RootTreeMapNode->Name = FString::Printf( TEXT( "%s %i %s" ),
*LOCTEXT( "RootNode_WarningPrefix", "WARNING:" ).ToString(),
NumAssetsWhichFailedToLoad,
*LOCTEXT( "RootNode_NAssetsFailedToLoad", "assets were missing! Only partial results shown." ).ToString() );
}
else if( RootAssetPackageNames.Num() == 1 && !SharedRootNode.IsValid() )
{
// @todo sizemap: When zoomed right into one asset, can we use the Class color for the node instead of grey?
FString OnlyAssetName = RootAssetPackageNames[ 0 ].ToString();
if( RootTreeMapNode->Children.Num() > 0 )
{
// The root will only have one child, so go ahead and use that child as the actual root
FTreeMapNodeDataPtr OnlyChild = RootTreeMapNode->Children[ 0 ];
OnlyChild->CopyNodeInto( *RootTreeMapNode );
RootTreeMapNode->Children = OnlyChild->Children;
RootTreeMapNode->Parent = nullptr;
for( const auto& ChildNode : RootTreeMapNode->Children )
{
ChildNode->Parent = RootTreeMapNode.Get();
}
OnlyAssetName = OnlyChild->Name;
}
// Use a more descriptive name for the root level node
RootTreeMapNode->Name = FString::Printf( TEXT( "%s %s (%i %s)" ),
*LOCTEXT( "RootNode_SizeMapForOneAsset", "Size map for" ).ToString(),
*OnlyAssetName,
TotalAssetCount,
*LOCTEXT( "RootNode_References", "total assets" ).ToString() );
}
else
{
// Multiple assets (or at least some shared assets) at the root level
RootTreeMapNode->BackgroundBrush = nullptr;
RootTreeMapNode->Size = 0.0f;
RootTreeMapNode->Parent = nullptr;
RootTreeMapNode->Name = FString::Printf( TEXT( "%s %i %s (%i %s, %s)" ),
*LOCTEXT( "RootNode_SizeMapForMultiple", "Size map for" ).ToString(),
RootAssetPackageNames.Num(),
*LOCTEXT( "RootNode_Assets", "assets" ).ToString(),
TotalAssetCount,
*LOCTEXT( "RootNode_References", "total assets" ).ToString(),
*SizeMapInternals::MakeBestSizeString( TotalSize, !bAnyUnknownSizes ) );
}
// OK, now refresh the actual tree map widget so our new tree will be displayed.
const bool bShouldPlayTransition = false;
TreeMapWidget->RebuildTreeMap( bShouldPlayTransition );
}
void SSizeMap::OnInitialAssetRegistrySearchComplete()
{
RefreshMap();
}
void SSizeMap::OnTreeMapNodeDoubleClicked( FTreeMapNodeData& TreeMapNodeData )
{
const FNodeSizeMapData* NodeSizeMapData = NodeSizeMapDataMap.Find( TreeMapNodeData.AsShared() );
if( NodeSizeMapData != nullptr )
{
TArray<FAssetData> Assets;
Assets.Add( NodeSizeMapData->AssetData );
GEditor->SyncBrowserToObjects( Assets );
}
}
// @todo sizemap urgent: We should add a spinner while we discover and load assets
// @todo sizemap urgent: Audit common asset types and make sure they have a useful GetResourceSize() implementation
// -> Some implementations are including the size of editor-only data (Static Mesh). This should be configurable!
// @todo sizemap urgent: It would be great to be able to see 0-sized/unknown-sized/tiny-sized nodes somehow, or at least a count of them
// - You'd almost want a way to zoom in super small. Or a little laserpointer effect that shows labels for super-tiny nodes
// @todo sizemap urgent: Need inline help to figure out mousewheel? Single click currently does nothing.
// @todo sizemap urgent: When an asset is not reporting a size, this tool should help to escalate that by making it very obvious that its missing (no GetResourceSize function)
// @todo sizemap: Can "zoom into" <Self> nodes which is a bit weird. (Asset name no longer draws). Maybe disallow zooming all the way in?
// --> Double-click current zooms directly into a single asset. Not super useful. Maybe only allow zooming down to the deepest nodes, not leaves
// @todo sizemap: When the tab is restored from layout, it will be totally empty. Instead it should probably have instructions for how to show the sizes for assets. (Same with Reference Viewer)
// @todo sizemap: We ideally want to replace the SReferenceTree code with SSizeMap
// @todo sizemap: Add a tree view that shows all of the references along with their sizes (so you can see Very Small references)
// @todo sizemap: It would be useful to be able to preview the sizes for specific platforms? (option in the UI)
// @todo sizemap: Should we show the percentage of total size as an option in the UI (though, the size of the boxes show this pretty well.)
// @todo sizemap: Should we show the folder part of the asset name as a tool-tip?
// @todo sizemap: It might be nice to unload assets that were loaded by us after the size map is built up
// @todo sizemap: Add a Refresh button. (Also, try to detect when objects change and auto-refresh, optionally)
#undef LOCTEXT_NAMESPACE