| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using Avalonia;
- using Avalonia.Controls;
- using Avalonia.Layout;
- using Avalonia.Media;
- using Avalonia.Threading;
- namespace ControlCatalog.Pages
- {
- /// <summary>
- /// Shared helpers for the performance-monitor demo pages
- /// (NavigationPage, TabbedPage, DrawerPage, ContentPage).
- /// </summary>
- internal sealed class NavigationPerformanceMonitorHelper
- {
- internal static readonly IBrush PositiveDeltaBrush = new SolidColorBrush(Color.Parse("#D32F2F"));
- internal static readonly IBrush NegativeDeltaBrush = new SolidColorBrush(Color.Parse("#388E3C"));
- internal static readonly IBrush ZeroDeltaBrush = new SolidColorBrush(Color.Parse("#757575"));
- internal static readonly IBrush CurrentBorderBrush = new SolidColorBrush(Color.Parse("#0078D4"));
- internal static readonly IBrush DefaultBorderBrush = new SolidColorBrush(Color.Parse("#CCCCCC"));
- private readonly List<WeakReference<Page>> _trackedPages = new();
- private double _previousHeapMB;
- private DispatcherTimer? _autoRefreshTimer;
- internal readonly Stopwatch OpStopwatch = new();
- internal int TotalCreated;
- /// <summary>
- /// Track a newly-created page via WeakReference and increment TotalCreated.
- /// </summary>
- internal void TrackPage(Page page)
- {
- TotalCreated++;
- _trackedPages.Add(new WeakReference<Page>(page));
- }
- /// <summary>
- /// Count live (not yet GC'd) tracked page instances.
- /// </summary>
- internal int CountLiveInstances()
- {
- int alive = 0;
- for (int i = _trackedPages.Count - 1; i >= 0; i--)
- {
- if (_trackedPages[i].TryGetTarget(out _))
- alive++;
- else
- _trackedPages.RemoveAt(i);
- }
- return alive;
- }
- /// <summary>
- /// Update heap and delta text blocks. Call from RefreshAll().
- /// </summary>
- internal void UpdateHeapDelta(TextBlock heapText, TextBlock deltaText)
- {
- var heapMB = GC.GetTotalMemory(false) / (1024.0 * 1024.0);
- heapText.Text = $"Managed Heap: {heapMB:##0.0} MB";
- var delta = heapMB - _previousHeapMB;
- if (Math.Abs(delta) < 0.05)
- {
- deltaText.Text = "(no change)";
- deltaText.Foreground = ZeroDeltaBrush;
- }
- else
- {
- var sign = delta > 0 ? "+" : "";
- deltaText.Text = $"({sign}{delta:0.0} MB)";
- deltaText.Foreground = delta > 0 ? PositiveDeltaBrush : NegativeDeltaBrush;
- }
- _previousHeapMB = heapMB;
- }
- /// <summary>
- /// Initialize previous heap baseline.
- /// </summary>
- internal void InitHeap()
- {
- _previousHeapMB = GC.GetTotalMemory(false) / (1024.0 * 1024.0);
- }
- /// <summary>
- /// Stop the stopwatch and write elapsed ms to the given TextBlock.
- /// </summary>
- internal void StopMetrics(TextBlock lastOpText)
- {
- if (!OpStopwatch.IsRunning) return;
- OpStopwatch.Stop();
- lastOpText.Text = $"Last Op: {OpStopwatch.ElapsedMilliseconds} ms";
- }
- /// <summary>
- /// Force full GC, then invoke the refresh callback.
- /// </summary>
- internal void ForceGC(Action refresh)
- {
- GC.Collect();
- GC.WaitForPendingFinalizers();
- GC.Collect();
- refresh();
- }
- /// <summary>
- /// Start a 2-second auto-refresh timer.
- /// </summary>
- internal void StartAutoRefresh(Action refresh)
- {
- if (_autoRefreshTimer != null) return;
- _autoRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
- _autoRefreshTimer.Tick += (_, _) => refresh();
- _autoRefreshTimer.Start();
- }
- /// <summary>
- /// Stop the auto-refresh timer.
- /// </summary>
- internal void StopAutoRefresh()
- {
- _autoRefreshTimer?.Stop();
- _autoRefreshTimer = null;
- }
- /// <summary>
- /// Toggle auto-refresh based on a CheckBox.
- /// </summary>
- internal void OnAutoRefreshChanged(CheckBox check, Action refresh)
- {
- if (check.IsChecked == true)
- StartAutoRefresh(refresh);
- else
- StopAutoRefresh();
- }
- /// <summary>
- /// Append a timestamped log entry to a StackPanel inside a ScrollViewer.
- /// </summary>
- internal void LogOperation(string action, string detail,
- StackPanel logPanel, ScrollViewer logScroll, string? extraInfo = null)
- {
- var heapMB = GC.GetTotalMemory(false) / (1024.0 * 1024.0);
- var timing = OpStopwatch.ElapsedMilliseconds;
- var extra = extraInfo != null ? $" {extraInfo}," : "";
- logPanel.Children.Add(new TextBlock
- {
- Text = $"{DateTime.Now:HH:mm:ss} [{action}] {detail} —{extra} heap {heapMB:##0.0} MB, {timing} ms",
- FontSize = 10,
- FontFamily = new FontFamily("Cascadia Mono,Consolas,Menlo,monospace"),
- Padding = new Thickness(6, 2),
- TextTrimming = TextTrimming.CharacterEllipsis,
- });
- logScroll.ScrollToEnd();
- }
- /// <summary>
- /// Build a tracked ContentPage with a 50 KB dummy allocation.
- /// </summary>
- internal ContentPage BuildTrackedPage(string title, int index, int allocBytes = 51200)
- {
- var page = NavigationDemoHelper.MakePage(title,
- $"Stack position #{index}\nPush more pages ...", index);
- page.Tag = new byte[allocBytes];
- TrackPage(page);
- return page;
- }
- /// <summary>
- /// Create a reusable stack/history row (badge + title + label).
- /// </summary>
- internal static (Border Container, Border Badge, TextBlock IndexText,
- TextBlock TitleText, TextBlock BadgeText) CreateStackRow()
- {
- var indexText = new TextBlock
- {
- FontSize = 10, FontWeight = FontWeight.SemiBold,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- };
- var badge = new Border
- {
- Width = 22, Height = 22,
- CornerRadius = new CornerRadius(11),
- VerticalAlignment = VerticalAlignment.Center,
- Child = indexText,
- };
- var titleText = new TextBlock
- {
- VerticalAlignment = VerticalAlignment.Center,
- TextTrimming = TextTrimming.CharacterEllipsis,
- Margin = new Thickness(6, 0, 0, 0),
- };
- var badgeText = new TextBlock
- {
- FontSize = 10, Opacity = 0.5,
- VerticalAlignment = VerticalAlignment.Center,
- Margin = new Thickness(4, 0, 0, 0),
- IsVisible = false,
- };
- var row = new DockPanel();
- row.Children.Add(badge);
- row.Children.Add(titleText);
- row.Children.Add(badgeText);
- var container = new Border
- {
- CornerRadius = new CornerRadius(6),
- Padding = new Thickness(8, 6),
- Child = row,
- };
- return (container, badge, indexText, titleText, badgeText);
- }
- /// <summary>
- /// Update a stack row with page data.
- /// </summary>
- internal static void UpdateStackRow(
- (Border Container, Border Badge, TextBlock IndexText,
- TextBlock TitleText, TextBlock BadgeText) row,
- int stackIndex, string title, bool isCurrent, bool isRoot)
- {
- row.Badge.Background = NavigationDemoHelper.GetPageBrush(stackIndex);
- row.IndexText.Text = (stackIndex + 1).ToString();
- row.TitleText.Text = title;
- row.TitleText.FontWeight = isCurrent ? FontWeight.SemiBold : FontWeight.Normal;
- string? label = isCurrent ? "current" : (isRoot ? "root" : null);
- row.BadgeText.Text = label ?? "";
- row.BadgeText.IsVisible = label != null;
- row.Container.BorderBrush = isCurrent ? CurrentBorderBrush : DefaultBorderBrush;
- row.Container.BorderThickness = new Thickness(isCurrent ? 2 : 1);
- }
- /// <summary>
- /// Sync a StackPanel of stack rows with data, growing/shrinking the row cache as needed.
- /// </summary>
- internal static void RefreshStackPanel(
- StackPanel panel,
- List<(Border Container, Border Badge, TextBlock IndexText,
- TextBlock TitleText, TextBlock BadgeText)> rowCache,
- IReadOnlyList<Page> stack, Page? currentPage)
- {
- int count = stack.Count;
- while (rowCache.Count < count)
- rowCache.Add(CreateStackRow());
- while (panel.Children.Count > count)
- panel.Children.RemoveAt(panel.Children.Count - 1);
- while (panel.Children.Count < count)
- panel.Children.Add(rowCache[panel.Children.Count].Container);
- for (int displayIdx = 0; displayIdx < count; displayIdx++)
- {
- int stackIdx = count - 1 - displayIdx;
- var page = stack[stackIdx];
- bool isCurrent = ReferenceEquals(page, currentPage);
- bool isRoot = stackIdx == 0;
- var row = rowCache[displayIdx];
- if (!ReferenceEquals(panel.Children[displayIdx], row.Container))
- panel.Children[displayIdx] = row.Container;
- UpdateStackRow(row, stackIdx, page.Header?.ToString() ?? "(untitled)", isCurrent, isRoot);
- }
- }
- }
- }
|