#if UNITY_2022_2_OR_NEWER using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.AddressableAssets.Build.Layout; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEngine; using UnityEngine.UIElements; namespace UnityEditor.AddressableAssets.BuildReportVisualizer { internal class MainPanelSummaryTab : IAddressableView, IBuildReportConsumer { BuildLayout m_Report; public VisualElement tabRootElement; internal ScrollView scrollbarElement; BuildReportWindow m_Window; class SummaryRow { private TextElement m_LabelElement; private TextElement m_SizeElement; private string m_TypeName; public SummaryRow(VisualElement root, string typeName) { m_TypeName = typeName; m_LabelElement = root.Q(string.Format(BuildReportUtility.SummaryTabLabelElementNameFormat, typeName)); m_SizeElement = root.Q(string.Format(BuildReportUtility.SummaryTabSizeElementNameFormat, typeName)); } public void Display(AssetSummary info) { m_LabelElement.text = $"{m_TypeName} ({info.Count})"; m_SizeElement.text = BuildReportUtility.GetDenominatedBytesString(info.SizeInBytes); } public void Display(BundleSummary info) { m_LabelElement.text = $"{m_TypeName} ({info.Count})"; m_SizeElement.text = BuildReportUtility.GetDenominatedBytesString(info.TotalCompressedSize); } public void Reset() { m_LabelElement.text = $"{m_TypeName} (0)"; m_SizeElement.text = "0 B"; } } BuildReportHelperConsumer m_HelperConsumer; internal MainPanelSummaryTab(BuildReportWindow window, BuildReportHelperConsumer helperConsumer) { m_HelperConsumer = helperConsumer; m_Window = window; } public void Consume(BuildLayout buildReport) { m_Report = buildReport; ClearGUI(); BuildLayoutSummary summary = BuildLayoutSummary.GetSummaryWithoutAssetTypes(buildReport); //This is a little overengineered, but if we do building multiple catalogs then this should help. HashSet remoteCatalogNames = new HashSet(); HashSet localCatalogNames = new HashSet(); foreach (var catalog in buildReport.AddressablesRuntimeSettings.CatalogLoadPaths) { string name = Path.GetFileName(catalog); if (name.EndsWith(".hash")) remoteCatalogNames.Add(name.Replace(".hash", ".json")); else localCatalogNames.Add(name); } SummaryRowBuilder generalInfo = new SummaryRowBuilder("General Information"); if (localCatalogNames.Count > 0) generalInfo.With("Local Catalog(s)", string.Join(", ", localCatalogNames)); if (remoteCatalogNames.Count > 0) generalInfo.With("Remote Catalog(s)", string.Join(", ", remoteCatalogNames)); generalInfo.With("Local catalog build location", buildReport.LocalCatalogBuildPath) .With("Remote catalog build location", string.IsNullOrEmpty(buildReport.RemoteCatalogBuildPath) ? "No remote catalog built" : buildReport.RemoteCatalogBuildPath) .With("Profile", buildReport.AddressablesEditorSettings.ActiveProfile.Name) .With("Platform", buildReport.BuildTarget.ToString()) .With("Player Build Version", buildReport.PlayerBuildVersion) .With("Build Duration", TimeSpan.FromSeconds(buildReport.Duration).ToString("g")) .With("Package Version", buildReport.PackageVersion) .With("Unity Editor Version", buildReport.UnityVersion); scrollbarElement.Add(generalInfo.Build()); if (buildReport.DuplicatedAssets.Count > 0) { ulong duplicatedSize = 0; foreach (var dupeAsset in m_HelperConsumer.GUIDToDuplicateAssets.Values) { int duplicationCount = dupeAsset.Bundles.Count; if (duplicationCount > 1) duplicatedSize += (ulong) (duplicationCount - 1) * dupeAsset.Asset.SerializedSize; } SummaryRowBuilder duplicatedAssets = new SummaryRowBuilder("Potential Issues") .With(new PotentialIssuesCard($"{buildReport.DuplicatedAssets.Count} Duplicate Assets were detected in the build. \n\n" + $"Removing duplicated Assets could result in up to {BuildReportUtility.GetDenominatedBytesString(duplicatedSize)} reduced build size.", () => m_Window.NavigateToView(BuildReportWindow.PotentialIssuesType.DuplicatedAssetsView))); scrollbarElement.Add(duplicatedAssets.Build()); } SummaryRowBuilder aggregateInfo = new SummaryRowBuilder("Aggregate Information") .With("Number of bundles", summary.BundleSummary.Count.ToString()) .With("Total size of all bundles", BuildReportUtility.GetDenominatedBytesString(summary.BundleSummary.TotalCompressedSize)) .With("Total number of assets", summary.TotalAssetCount.ToString()) .With("Addressables", $"{summary.ExplicitAssetCount} ({String.Format("{0:0.##}", ((float)summary.ExplicitAssetCount/(float)summary.TotalAssetCount) * 100f)}%)") .With("Assets pulled into a build by an Addressable", $"{summary.ImplicitAssetCount} ({String.Format("{0:0.##}", ((float)summary.ImplicitAssetCount/(float)summary.TotalAssetCount) * 100f)}%)", FontStyle.Italic); scrollbarElement.Add(aggregateInfo.Build()); tabRootElement.visible = true; tabRootElement.MarkDirtyRepaint(); scrollbarElement.visible = true; scrollbarElement.MarkDirtyRepaint(); } public void CreateGUI(VisualElement rootVisualElement) { tabRootElement = rootVisualElement.Q(BuildReportUtility.SummaryTab); // clear out the style sheets defined in the template uxml file so they can be applied from here in the order of: 1. theming, 2. base tabRootElement.styleSheets.Clear(); var themeStyle = AssetDatabase.LoadAssetAtPath(EditorGUIUtility.isProSkin ? BuildReportUtility.SummaryTabDarkUssPath : BuildReportUtility.SummaryTabLightUssPath, typeof(StyleSheet)) as StyleSheet; tabRootElement.styleSheets.Add(themeStyle); scrollbarElement = new ScrollView(); scrollbarElement.verticalScrollerVisibility = ScrollerVisibility.Auto; tabRootElement.Add(scrollbarElement); var ribbonStyle = AssetDatabase.LoadAssetAtPath(BuildReportUtility.SummaryTabUssPath, typeof(StyleSheet)) as StyleSheet; tabRootElement.styleSheets.Add(ribbonStyle); tabRootElement.visible = false; } public void ClearGUI() { scrollbarElement.Clear(); scrollbarElement.visible = false; } } } #endif