mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 09:37:03 +02:00
* [Dependency] source generator No more reflection, no more codegen at runtime Also various changes to Roslyn helpers to make this easier to write. Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback. No fallback is available for [field: Dependency] properties, due to a Roslyn bug. Code Fixes exist. We love Roslyn * Apply dependencies generator changes to all code * Release notes * Preprocessor got hands * Handle nullable dependencies These are bad but gotta deal with it. * Apply suggestions from code review Co-authored-by: Moony <moony@hellomouse.net> * Fine, let's not use collection expressions --------- Co-authored-by: Moony <moony@hellomouse.net>
319 lines
9.7 KiB
C#
319 lines
9.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Shared;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Console;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Timing;
|
|
using Stopwatch = Robust.Shared.Timing.Stopwatch;
|
|
|
|
namespace Robust.Client.Graphics;
|
|
|
|
internal interface ILoadingScreenManager
|
|
{
|
|
void BeginLoadingSection(string sectionName);
|
|
|
|
/// <summary>
|
|
/// Start a loading bar "section" for the given method.
|
|
/// Must be ended with EndSection.
|
|
/// </summary>
|
|
void BeginLoadingSection(object method);
|
|
|
|
void EndLoadingSection();
|
|
|
|
/// <summary>
|
|
/// Will run the giving function and add a custom "section" for it on the loading screen.
|
|
/// </summary>
|
|
void LoadingStep(Action action, object method);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Manager that creates and displays a basic splash screen and loading bar.
|
|
/// </summary>
|
|
internal sealed partial class LoadingScreenManager : ILoadingScreenManager
|
|
{
|
|
[Dependency] private IResourceCache _resourceCache = default!;
|
|
[Dependency] private IClydeInternal _clyde = default!;
|
|
[Dependency] private IConfigurationManager _cfg = default!;
|
|
[Dependency] private ILogManager _logManager = default!;
|
|
|
|
private ISawmill _sawmill = default!;
|
|
|
|
private readonly Stopwatch _sw = new();
|
|
|
|
#region UI constants
|
|
|
|
private const int LoadingBarWidth = 250;
|
|
private const int LoadingBarHeight = 20;
|
|
private const int LoadingBarOutlineOffset = 5;
|
|
private static readonly Vector2i LogoLoadingBarOffset = (0, 20);
|
|
private static readonly Vector2i LoadTimesIndent = (20, 0);
|
|
|
|
private const int NumLongestLoadTimes = 5;
|
|
|
|
private static readonly Color LoadingBarColor = Color.White;
|
|
|
|
#endregion
|
|
|
|
#region Cvars
|
|
|
|
private string _splashLogo = "";
|
|
private bool _showLoadingBar;
|
|
private bool _showDebug;
|
|
|
|
#endregion
|
|
|
|
private const string FontLocation = "/EngineFonts/NotoSans/NotoSans-Regular.ttf";
|
|
private const int FontSize = 11;
|
|
private VectorFont? _font;
|
|
|
|
// Number of loading sections for the loading bar. This has to be manually set!
|
|
private int _numberOfLoadingSections;
|
|
|
|
// The name of the section and how much time it took to load
|
|
internal readonly List<(string Name, TimeSpan LoadTime)> Times = [];
|
|
|
|
private int _currentSection;
|
|
private string? _currentSectionName;
|
|
|
|
private bool _currentlyInSection;
|
|
private bool _finished;
|
|
|
|
public void Initialize(int sections)
|
|
{
|
|
if (_finished)
|
|
return;
|
|
|
|
_clyde.VsyncEnabled = false;
|
|
|
|
_numberOfLoadingSections = sections;
|
|
|
|
_sawmill = _logManager.GetSawmill("loading");
|
|
|
|
_splashLogo = _cfg.GetCVar(CVars.DisplaySplashLogo);
|
|
_showLoadingBar = _cfg.GetCVar(CVars.LoadingShowBar);
|
|
_showDebug = _cfg.GetCVar(CVars.LoadingShowDebug);
|
|
|
|
if (_resourceCache.TryGetResource<FontResource>(FontLocation, out var fontResource))
|
|
_font = new VectorFont(fontResource, FontSize);
|
|
else
|
|
_sawmill.Error($"Could not load font: {FontLocation}");
|
|
}
|
|
|
|
public void BeginLoadingSection(string sectionName) => BeginLoadingSection(sectionName, false);
|
|
public void BeginLoadingSection(string sectionName, bool dontRender)
|
|
{
|
|
if (_finished)
|
|
return;
|
|
|
|
if (_currentlyInSection)
|
|
throw new InvalidOperationException("You cannot begin more than one section at a time!");
|
|
|
|
_currentlyInSection = true;
|
|
|
|
_currentSectionName = sectionName;
|
|
|
|
if (_clyde.IsInitialized)
|
|
{
|
|
_clyde.MainWindow.SetWindowProgress(
|
|
WindowProgressState.Normal,
|
|
_currentSection / (float)_numberOfLoadingSections);
|
|
}
|
|
|
|
if (!dontRender)
|
|
{
|
|
// This ensures that if the screen was resized or something the new size is properly updated to clyde.
|
|
_clyde.ProcessInput(new FrameEventArgs((float)_sw.Elapsed.TotalSeconds));
|
|
_sw.Restart();
|
|
_clyde.Render();
|
|
}
|
|
else
|
|
{
|
|
_sw.Restart();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a loading bar "section" for the given method.
|
|
/// Must be ended with EndSection.
|
|
/// </summary>
|
|
public void BeginLoadingSection(object method)
|
|
{
|
|
if (_finished)
|
|
return;
|
|
|
|
BeginLoadingSection(method.GetType().Name);
|
|
}
|
|
|
|
public void EndLoadingSection()
|
|
{
|
|
if (_finished)
|
|
return;
|
|
|
|
var time = _sw.Elapsed;
|
|
if (_currentSectionName != null)
|
|
Times.Add((_currentSectionName, time));
|
|
_currentSection++;
|
|
_currentlyInSection = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Will run the giving function and add a custom "section" for it on the loading screen.
|
|
/// </summary>
|
|
public void LoadingStep(Action action, object method)
|
|
{
|
|
if (_finished)
|
|
return;
|
|
|
|
BeginLoadingSection(method as string ?? method.GetType().Name);
|
|
action();
|
|
EndLoadingSection();
|
|
}
|
|
|
|
public void Finish()
|
|
{
|
|
if (_finished)
|
|
return;
|
|
|
|
if (_currentSection != _numberOfLoadingSections)
|
|
_sawmill.Error($"The number of seen loading sections isn't equal to the total number of loading sections! Seen: {_currentSection}, Total: {_numberOfLoadingSections}");
|
|
|
|
_clyde.MainWindow.SetWindowProgress(WindowProgressState.None, 1);
|
|
|
|
_finished = true;
|
|
}
|
|
|
|
#region Drawing functions
|
|
|
|
/// <summary>
|
|
/// Draw out the splash and loading screen.
|
|
/// </summary>
|
|
public void DrawLoadingScreen(IRenderHandle handle, Vector2i screenSize)
|
|
{
|
|
if (_finished)
|
|
return;
|
|
|
|
var scale = UserInterfaceManager.CalculateUIScale(_clyde.MainWindow.ContentScale.X, _cfg);
|
|
|
|
// Start at the center!
|
|
var location = screenSize / 2;
|
|
|
|
DrawSplash(handle, ref location, scale);
|
|
|
|
DrawLoadingBar(handle, ref location, scale);
|
|
|
|
if (_showDebug)
|
|
{
|
|
DrawCurrentLoading(handle, ref location, scale);
|
|
|
|
DrawTopTimes(handle, ref location, scale);
|
|
}
|
|
}
|
|
|
|
private void DrawSplash(IRenderHandle handle, ref Vector2i startLocation, float scale)
|
|
{
|
|
if (string.IsNullOrEmpty(_splashLogo))
|
|
return;
|
|
|
|
if (!_resourceCache.TryGetResource<TextureResource>(_splashLogo, out var textureResource))
|
|
return;
|
|
|
|
var drawSize = textureResource.Texture.Size * scale;
|
|
|
|
handle.DrawingHandleScreen.DrawTextureRect(textureResource.Texture, UIBox2.FromDimensions(startLocation - drawSize / 2, drawSize));
|
|
startLocation += Vector2i.Up * (int)drawSize.Y / 2;
|
|
}
|
|
|
|
private void DrawLoadingBar(IRenderHandle handle, ref Vector2i location, float scale)
|
|
{
|
|
var barWidth = (int)(LoadingBarWidth * scale);
|
|
var barHeight = (int)(LoadingBarHeight * scale);
|
|
var outlineOffset = (int)(LoadingBarOutlineOffset * scale);
|
|
|
|
// Always do the offsets, it looks a lot better!
|
|
location.X -= barWidth / 2;
|
|
location += (Vector2i)(LogoLoadingBarOffset * scale);
|
|
|
|
if (!_showLoadingBar)
|
|
return;
|
|
|
|
var sectionWidth = barWidth / _numberOfLoadingSections;
|
|
|
|
var barTopLeft = location;
|
|
var barBottomRight = new Vector2i(_currentSection * sectionWidth % barWidth, barHeight);
|
|
var barBottomRightMax = new Vector2i(barWidth, barHeight);
|
|
|
|
var outlinePosition = barTopLeft + Vector2i.DownLeft * outlineOffset;
|
|
var outlineSize = barBottomRightMax + Vector2i.UpRight * 2 * outlineOffset;
|
|
|
|
// Outline
|
|
handle.DrawingHandleScreen.DrawRect(UIBox2.FromDimensions(outlinePosition, outlineSize), LoadingBarColor, false);
|
|
|
|
// Progress bar
|
|
handle.DrawingHandleScreen.DrawRect(UIBox2.FromDimensions(barTopLeft, barBottomRight), LoadingBarColor);
|
|
|
|
location += Vector2i.Up * outlineSize;
|
|
}
|
|
|
|
// Draw the currently loading section to the screen.
|
|
private void DrawCurrentLoading(IRenderHandle handle, ref Vector2i location, float scale)
|
|
{
|
|
if (_font == null || _currentSectionName == null)
|
|
return;
|
|
|
|
handle.DrawingHandleScreen.DrawString(_font, location, _currentSectionName, scale, Color.White);
|
|
location += Vector2i.Up * _font.GetLineHeight(scale);
|
|
}
|
|
|
|
// Draw the slowest loading times to the screen.
|
|
private void DrawTopTimes(IRenderHandle handle, ref Vector2i location, float scale)
|
|
{
|
|
if (_font == null)
|
|
return;
|
|
|
|
location += (Vector2i)(LoadTimesIndent * scale);
|
|
|
|
var offset = 0;
|
|
var x = 0;
|
|
Times.Sort((a, b) => b.LoadTime.CompareTo(a.LoadTime));
|
|
|
|
foreach (var (name, time) in Times)
|
|
{
|
|
if (x >= NumLongestLoadTimes)
|
|
break;
|
|
|
|
var entry = $"{time.TotalSeconds:F2} - {name}";
|
|
handle.DrawingHandleScreen.DrawString(_font, location + new Vector2i(0, offset), entry, scale, Color.White);
|
|
offset += _font.GetLineHeight(scale);
|
|
x++;
|
|
}
|
|
|
|
location += Vector2i.Up * offset;
|
|
}
|
|
|
|
#endregion // Drawing functions
|
|
}
|
|
|
|
internal sealed partial class ShowTopLoadingTimesCommand : IConsoleCommand
|
|
{
|
|
[Dependency] private LoadingScreenManager _mgr = default!;
|
|
|
|
public string Command => "loading_top";
|
|
public string Description => "";
|
|
public string Help => "";
|
|
|
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
{
|
|
var sorted = _mgr.Times.Where(x => x.LoadTime > TimeSpan.FromSeconds(0.01)).OrderByDescending(x => x.LoadTime);
|
|
foreach (var (name, time) in sorted)
|
|
{
|
|
shell.WriteLine($"{time.TotalSeconds:F2} - {name}");
|
|
}
|
|
}
|
|
}
|