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)); } } } }