A game framework written with osu! in mind.
at master 217 lines 7.2 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System.Collections.Generic; 5using System.Collections.Specialized; 6using System.Linq; 7using osu.Framework.Allocation; 8using osu.Framework.Bindables; 9using osu.Framework.Graphics.Containers; 10using osu.Framework.Graphics.Sprites; 11using osu.Framework.Graphics.Visualisation; 12using osu.Framework.Platform; 13using osu.Framework.Statistics; 14 15namespace osu.Framework.Graphics.Performance 16{ 17 /// <summary> 18 /// Tracks global game statistics. 19 /// </summary> 20 internal class GlobalStatisticsDisplay : ToolWindow 21 { 22 private readonly FillFlowContainer<StatisticsGroup> groups; 23 24 private DotNetRuntimeListener listener; 25 26 private Bindable<bool> performanceLogging; 27 28 public GlobalStatisticsDisplay() 29 : base("Global Statistics", "(Ctrl+F2 to toggle)") 30 { 31 ScrollContent.Children = new Drawable[] 32 { 33 groups = new AlphabeticalFlow<StatisticsGroup> 34 { 35 Padding = new MarginPadding(5), 36 RelativeSizeAxes = Axes.X, 37 AutoSizeAxes = Axes.Y, 38 Direction = FillDirection.Vertical, 39 }, 40 }; 41 } 42 43 [BackgroundDependencyLoader] 44 private void load(GameHost host) 45 { 46 performanceLogging = host.PerformanceLogging.GetBoundCopy(); 47 } 48 49 protected override void LoadComplete() 50 { 51 base.LoadComplete(); 52 53 GlobalStatistics.StatisticsChanged += (_, e) => 54 { 55 switch (e.Action) 56 { 57 case NotifyCollectionChangedAction.Add: 58 add(e.NewItems.Cast<IGlobalStatistic>()); 59 break; 60 61 case NotifyCollectionChangedAction.Remove: 62 remove(e.OldItems.Cast<IGlobalStatistic>()); 63 break; 64 } 65 }; 66 67 add(GlobalStatistics.GetStatistics()); 68 69 State.BindValueChanged(visibilityChanged, true); 70 } 71 72 private void visibilityChanged(ValueChangedEvent<Visibility> state) 73 { 74 performanceLogging.Value = state.NewValue == Visibility.Visible; 75 76 if (state.NewValue == Visibility.Visible) 77 { 78 GlobalStatistics.OutputToLog(); 79 listener = new DotNetRuntimeListener(); 80 } 81 else 82 listener?.Dispose(); 83 } 84 85 private void remove(IEnumerable<IGlobalStatistic> stats) => Schedule(() => 86 { 87 foreach (var stat in stats) 88 groups.FirstOrDefault(g => g.GroupName == stat.Group)?.Remove(stat); 89 }); 90 91 private void add(IEnumerable<IGlobalStatistic> stats) => Schedule(() => 92 { 93 foreach (var stat in stats) 94 { 95 var group = groups.FirstOrDefault(g => g.GroupName == stat.Group); 96 97 if (group == null) 98 groups.Add(group = new StatisticsGroup(stat.Group)); 99 group.Add(stat); 100 } 101 }); 102 103 private class StatisticsGroup : CompositeDrawable, IAlphabeticalSort 104 { 105 public string GroupName { get; } 106 107 private readonly FillFlowContainer<StatisticsItem> items; 108 109 public StatisticsGroup(string groupName) 110 { 111 GroupName = groupName; 112 113 RelativeSizeAxes = Axes.X; 114 AutoSizeAxes = Axes.Y; 115 116 InternalChildren = new Drawable[] 117 { 118 new FillFlowContainer 119 { 120 RelativeSizeAxes = Axes.X, 121 AutoSizeAxes = Axes.Y, 122 Direction = FillDirection.Vertical, 123 Children = new Drawable[] 124 { 125 new SpriteText 126 { 127 Text = GroupName, 128 Font = FrameworkFont.Regular.With(weight: "Bold") 129 }, 130 items = new AlphabeticalFlow<StatisticsItem> 131 { 132 Padding = new MarginPadding { Left = 5 }, 133 RelativeSizeAxes = Axes.X, 134 AutoSizeAxes = Axes.Y, 135 Direction = FillDirection.Vertical, 136 }, 137 } 138 }, 139 }; 140 } 141 142 public void Add(IGlobalStatistic stat) 143 { 144 if (items.Any(s => s.Statistic == stat)) 145 return; 146 147 items.Add(new StatisticsItem(stat)); 148 } 149 150 public void Remove(IGlobalStatistic stat) 151 { 152 items.FirstOrDefault(s => s.Statistic == stat)?.Expire(); 153 } 154 155 private class StatisticsItem : CompositeDrawable, IAlphabeticalSort 156 { 157 public readonly IGlobalStatistic Statistic; 158 159 private readonly SpriteText valueText; 160 161 public StatisticsItem(IGlobalStatistic statistic) 162 { 163 Statistic = statistic; 164 165 RelativeSizeAxes = Axes.X; 166 AutoSizeAxes = Axes.Y; 167 168 InternalChildren = new Drawable[] 169 { 170 new SpriteText 171 { 172 Font = FrameworkFont.Regular, 173 Colour = FrameworkColour.Yellow, 174 Text = Statistic.Name, 175 RelativeSizeAxes = Axes.X, 176 Width = 0.68f, 177 }, 178 valueText = new SpriteText 179 { 180 Font = FrameworkFont.Condensed, 181 RelativePositionAxes = Axes.X, 182 X = 0.7f, 183 RelativeSizeAxes = Axes.X, 184 Width = 0.3f, 185 }, 186 }; 187 } 188 189 public string SortString => Statistic.Name; 190 191 protected override void Update() 192 { 193 base.Update(); 194 valueText.Text = Statistic.DisplayValue; 195 } 196 } 197 198 public string SortString => GroupName; 199 } 200 201 private interface IAlphabeticalSort 202 { 203 string SortString { get; } 204 } 205 206 private class AlphabeticalFlow<T> : FillFlowContainer<T> where T : Drawable, IAlphabeticalSort 207 { 208 public override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.Cast<T>().OrderBy(d => d.SortString); 209 } 210 211 protected override void Dispose(bool isDisposing) 212 { 213 base.Dispose(isDisposing); 214 listener?.Dispose(); 215 } 216 } 217}