using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor.AddressableAssets.Build.Layout;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.AddressableAssets.BuildReportVisualizer
{
///
/// UI item used to display assets in the Addressables Build Report
///
public class AssetsViewBuildReportItem : IAddressablesBuildReportItem
{
///
/// The name of the asset
///
public string Name { get; set; }
///
/// The name of the AssetBundle where the asset is stored
///
public string BundleName { get; set; }
///
/// The Addressable Group the asset belonged to
///
public string GroupName { get; set; }
///
/// The total size of the asset and its dependencies
///
public ulong FileSizePlusRefs { get; set; }
///
/// The size of the uncompressed asset
///
public ulong FileSizeUncompressed { get; set; }
///
/// The file size of the AssetBundle where the asset is stored
///
public ulong BundleSize { get; set; }
///
/// The ids of assets this asset references
///
public int RefsTo { get; set; }
///
/// The ids that reference this asset
///
public int RefsBy { get; set; }
///
/// Data store for Assets explicitly defined in an AssetBundle
///
public BuildLayout.ExplicitAsset ExplicitAsset { get; set; }
///
/// Data store for implicit Asset references
///
public BuildLayout.DataFromOtherAsset DataFromOtherAsset { get; set; }
///
/// Data store for the AssetBundle the asset is stored in
///
public BuildLayout.Bundle Bundle { get; set; }
///
/// If this asset is an implicit dependecy, this is the list of all the AssetBundles where the asset was duplicated
///
public List Bundles { get; set; }
///
/// The total size with dependencies
///
public ulong SizeWDependencies => 9999;
///
/// Used to build the GUI for the asset data
///
/// The visual element container
public virtual void CreateGUI(VisualElement rootVisualElement) { }
///
/// Get specific data for a given column
///
/// The name of the column to get data for
/// The display string for a GUI element
public virtual string GetCellContent(string colName)
{
if (colName == BuildReportUtility.AssetsContentViewColAssetName)
return Name;
else if (colName == BuildReportUtility.AssetsContentViewColSizePlusRefs)
{
if (FileSizePlusRefs == 0)
return "--";
return BuildReportUtility.GetDenominatedBytesString(FileSizePlusRefs);
}
else if (colName == BuildReportUtility.AssetsContentViewColSizeUncompressed)
{
if (FileSizeUncompressed == 0)
return "--";
return BuildReportUtility.GetDenominatedBytesString(FileSizeUncompressed);
}
else if (colName == BuildReportUtility.AssetsContentViewColBundleSize)
{
if (BundleSize == 0)
return "--";
return BuildReportUtility.GetDenominatedBytesString(BundleSize);
}
else if (colName == BuildReportUtility.AssetsContentViewColRefsTo)
{
if (RefsTo == -1)
return "--";
return RefsTo.ToString();
}
else if (colName == BuildReportUtility.AssetsContentViewColRefsBy)
{
if (RefsBy == -1)
return "--";
return RefsBy.ToString();
}
return "";
}
///
/// Get sortable content data for a column
///
/// The name of the column to get data for
/// The display string for a GUI element
public string GetSortContent(string colName)
{
if (colName == BuildReportUtility.AssetsContentViewColSizePlusRefs)
return FileSizePlusRefs.ToString();
if (colName == BuildReportUtility.AssetsContentViewColSizeUncompressed)
return FileSizeUncompressed.ToString();
if (colName == BuildReportUtility.AssetsContentViewColBundleSize)
return BundleSize.ToString();
return GetCellContent(colName);
}
}
///
/// The type header for the asset view
///
public class AssetsViewTypeHeader : AssetsViewBuildReportItem
{
///
/// Create a new type header object
///
/// The title name to use
public AssetsViewTypeHeader(string name)
{
Name = name;
}
///
/// Get the content for a given cell
///
/// The name of the column to get data for
/// The display data for the cell
public override string GetCellContent(string colName)
{
if (colName == BuildReportUtility.AssetsContentViewColAssetName)
return Name;
else
{
return "";
}
}
///
/// Build the GUI for a given asset
///
/// The visual element container
public override void CreateGUI(VisualElement rootVisualElement)
{
}
}
///
/// Container for the UI data for an AssetBundle
///
public class AssetsViewBuildReportBundle : AssetsViewBuildReportItem, IAddressablesBuildReportBundle
{
///
/// Create a UI element data container for a Bundle
///
/// The AssetBundle you're creating data for
public AssetsViewBuildReportBundle(BuildLayout.Bundle bundle)
{
Name = bundle.Name;
Bundle = bundle;
BundleSize = bundle.FileSize;
GroupName = bundle.Group.Name;
BundleName = "";
FileSizePlusRefs = bundle.FileSize;
foreach (var b in Bundle.ExpandedDependencies)
FileSizePlusRefs += b.FileSize;
RefsTo = Bundle.ExpandedDependencies.Count;
RefsBy = Bundle.DependentBundles.Count;
}
}
///
/// Container for UI element (Unrelated Assets)
///
public class AssetsViewBuildReportUnrelatedAssets : AssetsViewBuildReportItem
{
///
/// Create a UI container for Unrelated Assets
///
/// The file size of the assets
/// The number of assets
public AssetsViewBuildReportUnrelatedAssets(ulong assetSize, int assetCount)
{
Name = $"({assetCount} unrelated assets)";
FileSizeUncompressed = assetSize;
GroupName = "";
BundleName = "";
}
}
///
/// Container for UI element (Assets)
///
public class AssetsViewBuildReportAsset : AssetsViewBuildReportItem, IAddressablesBuildReportAsset
{
///
/// For a given asset, the assets in the same AssetBundle it references
///
public List InternallyReferencedAssets { get; }
///
/// For a given asset, the assets in different AssetBundles it references
///
public List ExternallyReferencedAssets { get; }
///
/// Asset data from implicit (non-Addressable) dependencies
///
public List ImplicitDependencies { get; }
///
/// Asset data for explicit (Addressable) dependencies
///
List ReferencingAssets { get; }
///
/// Create the UI container for an Addressable asset
///
/// The Addressable asset
public AssetsViewBuildReportAsset(BuildLayout.ExplicitAsset asset)
{
ExplicitAsset = asset;
Name = asset.AddressableName;
Bundle = asset.Bundle;
Bundles = new List(){ Bundle };
BundleName = asset.Bundle?.Name;
ExternallyReferencedAssets = asset.ExternallyReferencedAssets;
InternallyReferencedAssets = asset.InternalReferencedExplicitAssets;
ImplicitDependencies = asset.InternalReferencedOtherAssets;
ReferencingAssets = asset.ReferencingAssets;
FileSizeUncompressed = asset.SerializedSize + asset.StreamedSize;
FileSizePlusRefs = FileSizeUncompressed;
foreach (var r in asset.ExternallyReferencedAssets)
if (r != null)
FileSizePlusRefs += r.SerializedSize + asset.StreamedSize;
foreach (var r in asset.InternalReferencedExplicitAssets)
if (r != null)
FileSizePlusRefs += r.SerializedSize + asset.StreamedSize;
foreach (var r in asset.InternalReferencedOtherAssets)
if (r != null)
FileSizePlusRefs += r.SerializedSize + asset.StreamedSize;
RefsTo = asset.ExternallyReferencedAssets.Count + asset.InternalReferencedExplicitAssets.Count + asset.InternalReferencedOtherAssets.Count;
RefsBy = asset.ReferencingAssets != null ? asset.ReferencingAssets.Count : -1;
}
///
/// Create a UI container for a non-Addressable asset
///
/// The non-Addressable asset
public AssetsViewBuildReportAsset(BuildLayout.DataFromOtherAsset asset)
{
DataFromOtherAsset = asset;
Bundles = new List() { asset.File.Bundle };
Name = asset.AssetPath;
RefsBy = asset.ReferencingAssets.Count;
RefsTo = -1;
FileSizeUncompressed = asset.SerializedSize + asset.StreamedSize;
FileSizePlusRefs = asset.SerializedSize + asset.StreamedSize;
}
}
class AssetsContentView : ContentView
{
public AssetsContentView(BuildReportHelperConsumer helperConsumer, DetailsView detailsView)
: base(helperConsumer, detailsView)
{
m_DataHashtoReportItem = new Dictionary();
}
internal override ContentViewColumnData[] ColumnDataForView
{
get
{
return new ContentViewColumnData[]
{
new ContentViewColumnData(BuildReportUtility.AssetsContentViewColAssetName, this, true, "Asset Name"),
new ContentViewColumnData(BuildReportUtility.AssetsContentViewColSizePlusRefs, this, false, "Total Size (+ refs)"),
new ContentViewColumnData(BuildReportUtility.AssetsContentViewColSizeUncompressed, this, false, "Uncompressed Size"),
new ContentViewColumnData(BuildReportUtility.AssetsContentViewColBundleSize, this, false, "Bundle File Size"),
new ContentViewColumnData(BuildReportUtility.AssetsContentViewColRefsTo, this, false, "Refs To"),
new ContentViewColumnData(BuildReportUtility.AssetsContentViewColRefsBy, this, false, "Refs By")
};
}
}
// Data about assets from our currently selected build report.
public override IList CreateTreeViewItems(BuildLayout report)
{
List buildReportAssets = new List();
if (report == null)
return buildReportAssets;
foreach (BuildLayout.ExplicitAsset asset in BuildLayoutHelpers.EnumerateAssets(report))
buildReportAssets.Add(new AssetsViewBuildReportAsset(asset));
foreach (BuildLayout.DataFromOtherAsset implicitAsset in BuildLayoutHelpers.EnumerateImplicitAssets(report))
buildReportAssets.Add(new AssetsViewBuildReportAsset(implicitAsset));
return buildReportAssets;
}
public override void Consume(BuildLayout buildReport)
{
if (buildReport == null)
return;
m_Report = buildReport;
m_TreeItems = CreateTreeViewItems(m_Report);
#if UNITY_6000_0_OR_NEWER
m_TreeView.sortingMode = ColumnSortingMode.Custom;
#endif
m_TreeView.columnSortingChanged += ColumnSortingChanged;
BuildTree();
}
private void BuildTree()
{
LoadTree(builder =>
{
int id = 0;
foreach (AssetsViewBuildReportItem item in m_TreeItems)
{
AssetsViewBuildReportAsset asset = item as AssetsViewBuildReportAsset;
if (asset == null)
continue;
bool assetIsExplicitAsset = asset.ExplicitAsset != null;
if (assetIsExplicitAsset)
{
var children = CreateChildrenOfAsset(asset, ref id, true);
var treeItem = new TreeViewItemData(++id, item, children);
builder.Add(treeItem);
}
else
{
builder.Add(new TreeViewItemData(++id, item));
}
}
});
}
private void ColumnSortingChanged()
{
var columnList = m_TreeView.sortedColumns;
IList sortedRootList = new List();
foreach (var col in columnList)
{
sortedRootList = SortByColumnDescription(col);
}
m_DataHashtoReportItem = new Dictionary();
m_TreeView.SetRootItems(CreateTreeRootsNestedList(sortedRootList));
m_TreeView.Rebuild();
}
public override void CreateGUI(VisualElement rootVisualElement)
{
VisualElement view = rootVisualElement.Q(BuildReportUtility.ContentView);
TreeBuilder tb = new TreeBuilder()
.With(ColumnDataForView)
.With((items) => ItemsSelected.Invoke(items));
m_TreeView = tb.Build();
SetCallbacksForColumns(m_TreeView.columns, ColumnDataForView);
m_SearchField = rootVisualElement.Q(BuildReportUtility.SearchField);
RegisterSearchCallback();
// Create loading indicator
CreateLoadingIndicator(rootVisualElement);
}
protected override async Task PerformSearchAsync(string searchValue, CancellationToken cancellationToken)
{
await PerformSearchAsyncImpl(searchValue, cancellationToken);
}
public IList> CreateTreeRootsNestedList(IList items)
{
int id = 0;
var roots = new List>(items.Count);
foreach (AssetsViewBuildReportItem item in items)
{
AssetsViewBuildReportAsset asset = item as AssetsViewBuildReportAsset;
if (asset == null)
continue;
bool includeAllDependencies = EntryAppearsInSearch(asset, m_SearchValue);
bool assetIsExplicitAsset = asset.ExplicitAsset != null;
if (assetIsExplicitAsset)
{
var children = CreateChildrenOfAsset(asset, ref id, includeAllDependencies);
if (children.Count > 0 || EntryAppearsInSearch(asset, m_SearchValue))
roots.Add(new TreeViewItemData(++id, item, children));
}
else if (includeAllDependencies)
{
roots.Add(new TreeViewItemData(++id, item));
}
}
return roots;
}
List> CreateChildrenOfAsset(AssetsViewBuildReportAsset asset, ref int id, bool includeAllDependencies)
{
var children = new List>();
PopulateInternallyReferencedEntries(children, asset, ref id, includeAllDependencies);
PopulateExternallyReferencedEntries(children, asset, ref id, includeAllDependencies);
PopulateImplicitEntries(children, asset, ref id, includeAllDependencies);
return children;
}
void PopulateInternallyReferencedEntries(List> children, AssetsViewBuildReportAsset asset, ref int id, bool includeAllDependencies)
{
foreach (var dep in asset.InternallyReferencedAssets)
{
var buildReportAsset = new AssetsViewBuildReportAsset(dep);
if (EntryAppearsInSearch(buildReportAsset, m_SearchValue) || includeAllDependencies)
children.Add(new TreeViewItemData(++id, buildReportAsset));
}
}
void PopulateExternallyReferencedEntries(List> children, AssetsViewBuildReportAsset asset, ref int id, bool includeAllDependencies)
{
Dictionary> bundleToAssetList = new Dictionary>();
foreach (var dep in asset.ExternallyReferencedAssets)
{
if (!bundleToAssetList.ContainsKey(dep.Bundle))
bundleToAssetList.Add(dep.Bundle, new List());
bundleToAssetList[dep.Bundle].Add(dep);
}
foreach (var bundle in bundleToAssetList.Keys)
{
var assetTreeViewItems = new List>();
var assetList = bundleToAssetList[bundle];
ulong unrelatedAssetSize = bundle.FileSize;
foreach (var bundleAsset in assetList)
{
var bundleReportAsset = new AssetsViewBuildReportAsset(bundleAsset);
if (includeAllDependencies || EntryAppearsInSearch(bundleReportAsset, m_SearchValue))
assetTreeViewItems.Add(new TreeViewItemData(++id, bundleReportAsset));
unrelatedAssetSize -= (bundleAsset.SerializedSize + bundleAsset.StreamedSize);
}
int unrelatedAssetCount = bundle.AssetCount - assetList.Count;
if (unrelatedAssetCount > 0 && includeAllDependencies)
assetTreeViewItems.Add(new TreeViewItemData(++id, new AssetsViewBuildReportUnrelatedAssets(unrelatedAssetSize, unrelatedAssetCount)));
if (includeAllDependencies || assetTreeViewItems.Count > 0)
children.Add(new TreeViewItemData(++id, new AssetsViewBuildReportBundle(bundle), assetTreeViewItems));
}
}
void PopulateImplicitEntries(List> children, AssetsViewBuildReportAsset asset, ref int id, bool includeAllDependencies)
{
foreach (var dep in asset.ImplicitDependencies)
{
var reportImplicitAsset = new AssetsViewBuildReportAsset(dep);
if (EntryAppearsInSearch(reportImplicitAsset, m_SearchValue) || includeAllDependencies)
children.Add(new TreeViewItemData(++id, reportImplicitAsset));
}
}
}
}