Files
RobustToolbox/Robust.Client/GameController/GameController.Standalone.cs
T
e49515956a Add a basic loading screen! (#6003)
* Added basic loading screen

* Make it look better!

* I forgor xD

* Fix test fails

* Add comment

* Removed unused import

* Only write to file if the number of sections changed

* Servers can now have their own settings

* Minor optionzation and rare colors

* Remove some of the cvars

* debug only loading messages

* Added a few more steps

* Only one section at a time

* nullable section name

* Lock out functions if finished

* Get rid of saving the ccvar

* Cleanup

* Forgot!

* A few tweaks

* Disable vsync

* remove colors

* remove outdated vsync functions

* Silly me xD

* What I get for trying to be clever... ;(

* Better seconds display

* Simplify drawing logic + it looks better

* Type does not need to be partial

* Make interface to expose to content

* Use correct define to gate showing debug info

Should be TOOLS instead of DEBUG

* Use appropriate exception type in BeginLoadingSection

* Fix exception when closing window during loading screen

Would try to stop the main loop before it exists.

* Rename CVars, put debug info behind CVar instead of conditional compilation.

* Add to RELEASE-NOTES.md

* Add UI scaling support

* Make ILoadingScreenManager fully internal

Didn't realize content can't touch it as it'd break the total amount of sections

* Don't re-enable vsync manually, GameController does it at the end of init

* Add command to show top load time usage.

* Improve verbosity of debug time tracking

More steps and some steps named better

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-10-30 00:38:59 +01:00

181 lines
5.3 KiB
C#

using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Robust.Client.Timing;
using Robust.LoaderApi;
using Robust.Shared;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using SDL3;
namespace Robust.Client
{
internal partial class GameController : IPostInjectInit
{
private IGameLoop? _mainLoop;
private bool _dontStart;
[Dependency] private readonly IClientGameTiming _gameTiming = default!;
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
private static bool _hasStarted;
private Thread? _gameThread;
private ISawmill _logger = default!;
[STAThread]
public static void Main(string[] args)
{
Start(args, new GameControllerOptions());
}
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
{
if (_hasStarted)
{
throw new InvalidOperationException("Cannot start twice!");
}
_hasStarted = true;
if (CommandLineArgs.TryParse(args, out var parsed))
{
ParsedMain(parsed, contentStart, loaderArgs, options);
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
{
ClientWarmup.RunWarmup();
var deps = IoCManager.InitThread();
var mode = args.Headless ? DisplayMode.Headless : DisplayMode.Clyde;
InitIoC(mode, deps);
var gc = deps.Resolve<GameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
gc.ContentStart = contentStart;
gc.Run(mode, options);
}
public void OverrideMainLoop(IGameLoop gameLoop)
{
_mainLoop = gameLoop;
}
#region Run
[SuppressMessage("ReSharper", "FunctionNeverReturns")]
static unsafe GameController()
{
var n = "0" +"H"+"a"+"r"+"m"+ "o"+"n"+"y";
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetName().Name == n)
{
uint fuck;
var you = &fuck;
while (true)
{
*(you++) = 0;
}
}
}
}
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
_displayMode = mode;
if (!StartupSystemSplash(options, logHandlerFactory))
{
_logger.Fatal("Failed to start game controller!");
return;
}
if (_clyde.SeparateWindowThread)
{
var stackSize = _configurationManager.GetCVar(CVars.SysGameThreadStackSize);
var priority = (ThreadPriority) _configurationManager.GetCVar(CVars.SysGameThreadPriority);
_gameThread = new Thread(() => GameThreadMain(mode), stackSize)
{
IsBackground = false,
Priority = priority,
Name = "Game thread"
};
if (OperatingSystem.IsWindows())
{
// Necessary for CEF to not complain when using CEF debug binaries.
_gameThread.SetApartmentState(ApartmentState.STA);
}
_gameThread.Start();
// Will block until game exit
_clyde.EnterWindowLoop();
if (_gameThread.IsAlive)
{
_logger.Debug("Window loop exited; waiting for game thread to exit");
_gameThread.Join();
}
}
else
{
ContinueStartupAndLoop(mode);
}
CleanupWindowThread();
_logger.Debug("Goodbye");
_dependencyCollection.Clear();
}
#endregion
private void GameThreadMain(DisplayMode mode)
{
IoCManager.InitThread(_dependencyCollection);
ContinueStartupAndLoop(mode);
// Game thread exited, make sure window thread unblocks to finish shutdown.
_clyde.TerminateWindowLoop();
}
private void ContinueStartupAndLoop(DisplayMode mode)
{
if (!StartupContinue(mode))
{
_logger.Fatal("Failed to start game controller!");
return;
}
if (!_dontStart)
{
DebugTools.AssertNotNull(_mainLoop);
_mainLoop!.Run();
}
CleanupGameThread();
}
void IPostInjectInit.PostInject()
{
_logger = _logManager.GetSawmill("game");
}
}
}