Adds GameController options for games using RobustToolbox as a library. (#1711)

This commit is contained in:
Vera Aguilera Puerto
2021-04-23 00:05:42 +02:00
committed by GitHub
parent 4ce6629ace
commit 8bd1e72e9f
13 changed files with 135 additions and 40 deletions

View File

@@ -5,10 +5,15 @@ namespace Robust.Client
public static void Start(string[] args)
{
#if FULL_RELEASE
throw new System.InvalidOperationException("ContentStart is not available on a full release.");
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
#else
GameController.Start(args, true);
#endif
}
public static void StartLibrary(string[] args, GameControllerOptions options)
{
GameController.Start(args, true, null, options);
}
}
}

View File

@@ -61,18 +61,18 @@ namespace Robust.Client
[Dependency] private readonly IFontManagerInternal _fontManager = default!;
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
[Dependency] private readonly IScriptClient _scriptClient = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
[Dependency] private readonly IAuthManager _authManager = default!;
[Dependency] private readonly IMidiManager _midiManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
private CommandLineArgs? _commandLineArgs;
private bool _disableAssemblyLoadContext;
// Arguments for loader-load. Not used otherwise.
private IMainArgs? _loaderArgs;
public bool ContentStart { get; set; } = false;
public GameControllerOptions Options { get; private set; } = new();
public InitialLaunchState LaunchState { get; private set; } = default!;
public bool LoadConfigAndUserData { get; set; } = true;
@@ -89,10 +89,10 @@ namespace Robust.Client
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
_modLoader.SetUseLoadContext(!_disableAssemblyLoadContext);
_modLoader.SetEnableSandboxing(true);
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
@@ -118,7 +118,7 @@ namespace Robust.Client
_inputManager.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.LoadDirectory(Options.PrototypeDirectory);
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
@@ -143,7 +143,8 @@ namespace Robust.Client
_clyde.Ready();
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
if (!Options.DisableCommandLineConnect &&
(_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
&& LaunchState.ConnectEndpoint != null)
{
_client.ConnectToServer(LaunchState.ConnectEndpoint);
@@ -172,7 +173,7 @@ namespace Robust.Client
if (LoadConfigAndUserData)
{
var configFile = Path.Combine(userDataDir, "client_config.toml");
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
if (File.Exists(configFile))
{
// Load config from user data if available.
@@ -196,7 +197,12 @@ namespace Robust.Client
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
if (_loaderArgs != null)
{
_stringSerializer.EnableCaching = false;
@@ -217,7 +223,7 @@ namespace Robust.Client
return false;
}
_clyde.SetWindowTitle("Space Station 14");
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
return true;

View File

@@ -19,7 +19,7 @@ namespace Robust.Client
Start(args);
}
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null)
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
{
if (_hasStarted)
{
@@ -30,11 +30,11 @@ namespace Robust.Client
if (CommandLineArgs.TryParse(args, out var parsed))
{
ParsedMain(parsed, contentStart, loaderArgs);
ParsedMain(parsed, contentStart, loaderArgs, options);
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
{
IoCManager.InitThread();
@@ -45,11 +45,13 @@ namespace Robust.Client
var gc = (GameController) IoCManager.Resolve<IGameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
if(options != null)
gc.Options = options;
// 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._disableAssemblyLoadContext = contentStart;
gc.ContentStart = contentStart;
if (!gc.Startup())
{
Logger.Fatal("Failed to start game controller!");

View File

@@ -0,0 +1,60 @@
using Robust.Shared;
using Robust.Shared.Utility;
namespace Robust.Client
{
public class GameControllerOptions
{
/// <summary>
/// Whether content sandboxing will be enabled & enforced.
/// </summary>
public bool Sandboxing { get; init; } = true;
// TODO: Expose mounting methods to games using Robust as a library.
/// <summary>
/// Lists of mount options to mount.
/// </summary>
public MountOptions MountOptions { get; init; } = new();
/// <summary>
/// Name the userdata directory will have.
/// </summary>
public string UserDataDirectoryName { get; init; } = "Space Station 14";
/// <summary>
/// Name of the configuration file in the user data directory.
/// </summary>
public string ConfigFileName { get; init; } = "client_config.toml";
// TODO: Define engine branding from json file in resources.
/// <summary>
/// Default window title.
/// </summary>
public string DefaultWindowTitle { get; init; } = "Space Station 14";
/// <summary>
/// Assemblies with this prefix will be loaded.
/// </summary>
public string ContentModulePrefix { get; init; } = "Content.";
/// <summary>
/// Name of the content build directory, for game pack mounting purposes.
/// </summary>
public string ContentBuildDirectory { get; init; } = "Content.Client";
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
/// <summary>
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
/// </summary>
public bool ResourceMountDisabled { get; init; } = false;
/// <summary>
/// Whether to disable command line args server auto-connecting.
/// </summary>
public bool DisableCommandLineConnect { get; init; } = false;
}
}

View File

@@ -7,6 +7,8 @@ namespace Robust.Client
{
internal interface IGameControllerInternal : IGameController
{
GameControllerOptions Options { get; }
bool ContentStart { get; set; }
void SetCommandLineArgs(CommandLineArgs args);
bool LoadConfigAndUserData { get; set; }
bool Startup(Func<ILogHandler>? logHandlerFactory = null);
@@ -18,4 +20,4 @@ namespace Robust.Client
void MouseWheel(MouseWheelEventArgs mouseWheelEventArgs);
void OverrideMainLoop(IGameLoop gameLoop);
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using JetBrains.Annotations;
using Robust.Shared.IoC;
namespace Robust.Client.Utility
{
@@ -29,7 +30,7 @@ namespace Robust.Client.Utility
appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
#endif
return Path.Combine(appDataDir, "Space Station 14", "data");
return Path.Combine(appDataDir, IoCManager.Resolve<IGameControllerInternal>().Options.UserDataDirectoryName, "data");
}
}

View File

@@ -254,9 +254,12 @@ namespace Robust.Server
// Set up the VFS
_resources.Initialize(dataDir);
ProgramShared.DoMounts(_resources, _commandLineArgs?.MountOptions, "Content.Server");
ProgramShared.DoMounts(_resources, _commandLineArgs?.MountOptions, "Content.Server", contentStart:ContentStart);
_modLoader.SetUseLoadContext(!DisableLoadContext);
// 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.
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(false);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
@@ -431,7 +434,7 @@ namespace Robust.Server
_shutdownEvent.Set();
}
public bool DisableLoadContext { private get; set; }
public bool ContentStart { get; set; }
public bool LoadConfigAndUserData { private get; set; } = true;
public void OverrideMainLoop(IGameLoop gameLoop)

View File

@@ -44,7 +44,7 @@ namespace Robust.Server
internal interface IBaseServerInternal : IBaseServer
{
bool DisableLoadContext { set; }
bool ContentStart { set; }
bool LoadConfigAndUserData { set; }
void OverrideMainLoop(IGameLoop gameLoop);

View File

@@ -62,10 +62,7 @@ namespace Robust.Server
var server = IoCManager.Resolve<IBaseServerInternal>();
// 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.
server.DisableLoadContext = contentStart;
server.ContentStart = contentStart;
server.SetCommandLineArgs(args);
Logger.Info("Server -> Starting");

View File

@@ -2,9 +2,32 @@
namespace Robust.Shared
{
internal sealed class MountOptions
public sealed class MountOptions
{
public List<string> ZipMounts = new();
public List<string> DirMounts = new();
public MountOptions()
{ }
public MountOptions(List<string> zipMounts, List<string> dirMounts)
{
ZipMounts = zipMounts;
DirMounts = dirMounts;
}
public static MountOptions Merge(MountOptions a, MountOptions b)
{
var zipMounts = new List<string>();
var dirMounts = new List<string>();
zipMounts.AddRange(a.ZipMounts);
zipMounts.AddRange(b.ZipMounts);
dirMounts.AddRange(a.DirMounts);
dirMounts.AddRange(b.DirMounts);
return new MountOptions(zipMounts, dirMounts);
}
}
}

View File

@@ -7,28 +7,19 @@ namespace Robust.Shared
internal static class ProgramShared
{
#if !FULL_RELEASE
private static string FindContentRootDir()
private static string FindContentRootDir(bool contentStart)
{
var contentPath = PathHelpers.ExecutableRelativeFile("Content.Shared.dll");
// If Content.Shared.dll is next to us,
// that means we've been executed from one of content's bin directories.
if (File.Exists(contentPath))
{
return "../../";
}
return "../../../";
return contentStart ? "../../" : "../../../";
}
#endif
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir, bool loader=false)
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir, bool loader=false, bool contentStart=false)
{
#if FULL_RELEASE
if (!loader)
res.MountContentDirectory(@"Resources/");
#else
var contentRootDir = FindContentRootDir();
var contentRootDir = FindContentRootDir(contentStart);
res.MountContentDirectory($@"{contentRootDir}RobustToolbox/Resources/");
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", new ResourcePath("/Assemblies/"));
res.MountContentDirectory($@"{contentRootDir}Resources/");

View File

@@ -9,6 +9,8 @@ namespace Robust.UnitTesting
internal sealed class GameControllerDummy : IGameControllerInternal
{
public InitialLaunchState LaunchState { get; } = new(false, null, null, null);
public GameControllerOptions Options { get; } = new();
public bool ContentStart { get; set; }
public void Shutdown(string? reason = null)
{

View File

@@ -356,6 +356,7 @@ namespace Robust.UnitTesting
cfg.OverrideConVars(new []{("log.runtimelog", "false"), (CVars.SysWinTickPeriod.Name, "-1")});
var failureLevel = _options == null ? LogLevel.Error : _options.FailureLogLevel;
server.ContentStart = _options?.ContentStart ?? false;
if (server.Start(() => new TestLogHandler("SERVER", failureLevel)))
{
throw new Exception("Server failed to start.");
@@ -449,6 +450,7 @@ namespace Robust.UnitTesting
cfg.OverrideConVars(new []{(CVars.NetPredictLagBias.Name, "0")});
var failureLevel = _options == null ? LogLevel.Error : _options.FailureLogLevel;
client.ContentStart = _options?.ContentStart ?? false;
client.Startup(() => new TestLogHandler("CLIENT", failureLevel));
var gameLoop = new IntegrationGameLoop(DependencyCollection.Resolve<IGameTiming>(),
@@ -566,6 +568,7 @@ namespace Robust.UnitTesting
public Assembly[]? ContentAssemblies { get; set; }
public string? ExtraPrototypes { get; set; }
public LogLevel? FailureLogLevel { get; set; } = LogLevel.Error;
public bool ContentStart { get; set; } = false;
public Dictionary<string, string> CVarOverrides { get; } = new();
}