// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Text; using FlaxEditor.GUI; using FlaxEngine; using FlaxEngine.Json; using FlaxEngine.GUI; using FlaxEditor.GUI.ContextMenu; using System.Linq; namespace FlaxEditor.Windows.Profiler { /// /// The Assets profiling mode. /// /// internal sealed class Assets : ProfilerMode { private class Resource { public string Name; public string TypeName; public int ReferencesCount; public ulong MemoryUsage; public Guid AssetId; } private readonly SingleChart _memoryUsageChart; private readonly Table _table; private SamplesBuffer _resources; private List _tableRowsCache; private Dictionary _resourceCache; private StringBuilder _stringBuilder; public Assets() : base("Assets") { // Layout var mainPanel = new Panel(ScrollBars.None) { AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, Parent = this, }; // Chart _memoryUsageChart = new SingleChart { Title = "Assets Memory Usage (CPU)", AnchorPreset = AnchorPresets.HorizontalStretchTop, FormatSample = v => Utilities.Utils.FormatBytesCount((int)v), Parent = mainPanel, }; _memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged; var panel = new Panel(ScrollBars.Vertical) { AnchorPreset = AnchorPresets.StretchAll, Offsets = new Margin(0, 0, _memoryUsageChart.Height + 2, 0), Parent = mainPanel, }; var layout = new VerticalPanel { AnchorPreset = AnchorPresets.HorizontalStretchTop, Offsets = Margin.Zero, IsScrollable = true, Parent = panel, }; // Table var style = Style.Current; var headerColor = style.LightBackground; var textColor = style.Foreground; _table = new Table { Columns = new[] { new ColumnDefinition { UseExpandCollapseMode = true, CellAlignment = TextAlignment.Near, Title = "Resource", TitleBackgroundColor = headerColor, TitleColor = textColor, }, new ColumnDefinition { Title = "Type", CellAlignment = TextAlignment.Center, TitleBackgroundColor = headerColor, TitleColor = textColor, }, new ColumnDefinition { Title = "References", TitleBackgroundColor = headerColor, TitleColor = textColor, }, new ColumnDefinition { Title = "Memory Usage", TitleBackgroundColor = headerColor, TitleColor = textColor, FormatValue = v => Utilities.Utils.FormatBytesCount((ulong)v), }, }, Parent = layout, }; _table.Splits = new[] { 0.6f, 0.2f, 0.08f, 0.12f, }; } /// public override void Clear() { _memoryUsageChart.Clear(); _resources?.Clear(); _resourceCache?.Clear(); } /// public override void Update(ref SharedUpdateData sharedData) { if (_resourceCache == null) _resourceCache = new Dictionary(); if (_stringBuilder == null) _stringBuilder = new StringBuilder(); // Capture current assets usage info var assets = FlaxEngine.Content.Assets; var resources = new Resource[assets.Length]; ulong totalMemoryUsage = 0; for (int i = 0; i < resources.Length; i++) { var asset = assets[i]; ref var resource = ref resources[i]; if (!asset) continue; // Try to reuse cached resource info var assetId = asset.ID; if (!_resourceCache.TryGetValue(assetId, out resource)) { resource = new Resource { Name = asset.Path, TypeName = asset.TypeName, AssetId = assetId, }; var typeNameEnding = asset.TypeName.LastIndexOf('.'); if (typeNameEnding != -1) resource.TypeName = resource.TypeName.Substring(typeNameEnding + 1); var assetItem = Editor.Instance.ContentDatabase.FindAsset(assetId); if (assetItem != null) { resource.Name = assetItem.NamePath; } if (string.IsNullOrEmpty(resource.Name) && asset.IsVirtual) resource.Name = ""; _resourceCache.Add(assetId, resource); } resource.MemoryUsage = asset.MemoryUsage; resource.ReferencesCount = asset.ReferencesCount; totalMemoryUsage += resource.MemoryUsage; } _memoryUsageChart.AddSample((float)totalMemoryUsage); if (_resources == null) _resources = new SamplesBuffer(); _resources.Add(resources); } /// public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents) { _memoryUsageChart.SelectedSampleIndex = selectedFrame; if (_resources == null) return; if (_tableRowsCache == null) _tableRowsCache = new List(); UpdateTable(); } /// public override void OnDestroy() { _resources?.Clear(); _resourceCache?.Clear(); _tableRowsCache?.Clear(); _stringBuilder?.Clear(); base.OnDestroy(); } private void UpdateTable() { _table.IsLayoutLocked = true; int idx = 0; while (_table.Children.Count > idx) { var child = _table.Children[idx]; if (child is ClickableRow row) { _tableRowsCache.Add(row); child.Parent = null; } else { idx++; } } _table.LockChildrenRecursive(); UpdateTableInner(); _table.UnlockChildrenRecursive(); _table.PerformLayout(); } private void UpdateTableInner() { if (_resources.Count == 0) return; var resources = _resources.Get(_memoryUsageChart.SelectedSampleIndex); if (resources == null || resources.Length == 0) return; var resourcesOrdered = resources.OrderByDescending(x => x.MemoryUsage); // Add rows var rowColor2 = Style.Current.Background * 1.4f; int rowIndex = 0; foreach (var e in resourcesOrdered) { ClickableRow row; if (_tableRowsCache.Count != 0) { // Reuse row var last = _tableRowsCache.Count - 1; row = _tableRowsCache[last]; _tableRowsCache.RemoveAt(last); } else { // Allocate new row row = new ClickableRow { Values = new object[4] }; row.RowDoubleClick = OnRowDoubleClick; row.RowRightClick = OnRowRightClick; } // Setup row data row.Tag = e.AssetId; row.Values[0] = e.Name; row.Values[1] = e.TypeName; row.Values[2] = e.ReferencesCount; row.Values[3] = e.MemoryUsage; // Add row to the table row.Width = _table.Width; row.BackgroundColor = rowIndex % 2 == 0 ? rowColor2 : Color.Transparent; row.Parent = _table; rowIndex++; } } private void OnRowDoubleClick(ClickableRow row) { var assetId = (Guid)row.Tag; var assetItem = Editor.Instance.ContentDatabase.FindAsset(assetId); if (assetItem != null) Editor.Instance.ContentEditing.Open(assetItem); } private void OnRowRightClick(ClickableRow row) { var assetId = (Guid)row.Tag; var assetItem = Editor.Instance.ContentDatabase.FindAsset(assetId); if (assetItem != null) { var cm = new ContextMenu(); ContextMenuButton b; b = cm.AddButton("Open", () => Editor.Instance.ContentEditing.Open(assetItem)); cm.AddButton("Show in content window", () => Editor.Instance.Windows.ContentWin.Select(assetItem)); cm.AddButton(Utilities.Constants.ShowInExplorer, () => FileSystem.ShowFileExplorer(System.IO.Path.GetDirectoryName(assetItem.Path))); cm.AddButton("Select actors using this asset", () => Editor.Instance.SceneEditing.SelectActorsUsingAsset(assetItem.ID)); cm.AddButton("Show asset references graph", () => Editor.Instance.Windows.Open(new AssetReferencesGraphWindow(Editor.Instance, assetItem))); cm.AddButton("Copy name", () => Clipboard.Text = assetItem.NamePath); cm.AddButton("Copy path", () => Clipboard.Text = assetItem.Path); cm.AddButton("Copy asset ID", () => Clipboard.Text = JsonSerializer.GetStringID(assetItem.ID)); cm.Show(row, row.PointFromScreen(Input.MouseScreenPosition)); } } } }