Adds ServerOptions, improve GameControllerOptions, fix engine integration tests (#1844)

* Adds ServerOptions, improve GameControllerOptions, fix engine integration tests

* Do component auto-registration in engine integration tests by default

* Fix integration tests on content, register components ONLY if not contentstarted or options are null

* Add integration test for engine integration tests working correctly

* Move cvar overrides out of content and into engine.
This commit is contained in:
Vera Aguilera Puerto
2021-07-03 15:19:46 +02:00
committed by GitHub
parent 63128324ab
commit c06707d519
15 changed files with 245 additions and 54 deletions

View File

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

View File

@@ -11,7 +11,7 @@ namespace Robust.Client
{
public void Main(IMainArgs args)
{
Start(args.Args, contentStart: false, args);
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
}
}
}

View File

@@ -22,10 +22,10 @@ namespace Robust.Client
public static void Main(string[] args)
{
Start(args);
Start(args, new GameControllerOptions());
}
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
{
if (_hasStarted)
{
@@ -40,7 +40,7 @@ namespace Robust.Client
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
{
IoCManager.InitThread();
@@ -51,15 +51,13 @@ namespace Robust.Client
var gc = IoCManager.Resolve<GameController>();
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.ContentStart = contentStart;
gc.Run(mode);
gc.Run(mode, options);
}
public void OverrideMainLoop(IGameLoop gameLoop)
@@ -67,9 +65,9 @@ namespace Robust.Client
_mainLoop = gameLoop;
}
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
if (!StartupSystemSplash(logHandlerFactory))
if (!StartupSystemSplash(options, logHandlerFactory))
{
Logger.Fatal("Failed to start game controller!");
return;

View File

@@ -75,8 +75,6 @@ namespace Robust.Client
public GameControllerOptions Options { get; private set; } = new();
public InitialLaunchState LaunchState { get; private set; } = default!;
public bool LoadConfigAndUserData { get; set; } = true;
public void SetCommandLineArgs(CommandLineArgs args)
{
_commandLineArgs = args;
@@ -95,7 +93,7 @@ namespace Robust.Client
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
@@ -196,8 +194,9 @@ namespace Robust.Client
return true;
}
internal bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
{
Options = options;
ReadInitialLaunchState();
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
@@ -234,7 +233,7 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
if (LoadConfigAndUserData)
if (Options.LoadConfigAndUserData)
{
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
if (File.Exists(configFile))
@@ -258,13 +257,13 @@ namespace Robust.Client
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
if (_loaderArgs != null)
{

View File

@@ -42,6 +42,11 @@ namespace Robust.Client
/// </summary>
public string ContentBuildDirectory { get; init; } = "Content.Client";
/// <summary>
/// Directory to load all assemblies from.
/// </summary>
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
@@ -52,6 +57,16 @@ namespace Robust.Client
/// </summary>
public bool ResourceMountDisabled { get; init; } = false;
/// <summary>
/// Whether to mount content resources when not on FULL_RELEASE.
/// </summary>
public bool LoadContentResources { get; init; } = true;
/// <summary>
/// Whether to load config and user data.
/// </summary>
public bool LoadConfigAndUserData { get; init; } = true;
/// <summary>
/// Whether to disable command line args server auto-connecting.
/// </summary>

View File

@@ -10,8 +10,7 @@ namespace Robust.Client
GameControllerOptions Options { get; }
bool ContentStart { get; set; }
void SetCommandLineArgs(CommandLineArgs args);
bool LoadConfigAndUserData { get; set; }
void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null);
void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null);
void KeyDown(KeyEventArgs keyEvent);
void KeyUp(KeyEventArgs keyEvent);
void TextEntered(TextEventArgs textEvent);

View File

@@ -102,6 +102,8 @@ namespace Robust.Server
private readonly ManualResetEventSlim _shutdownEvent = new(false);
public ServerOptions Options { get; private set; } = new();
/// <inheritdoc />
public int MaxPlayers => _config.GetCVar(CVars.GameMaxPlayers);
@@ -114,7 +116,7 @@ namespace Robust.Server
Logger.InfoS("srv", "Restarting Server...");
Cleanup();
Start(_logHandlerFactory);
Start(Options, _logHandlerFactory);
}
/// <inheritdoc />
@@ -141,15 +143,16 @@ namespace Robust.Server
}
/// <inheritdoc />
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
public bool Start(ServerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
Options = options;
var profilePath = Path.Join(Environment.CurrentDirectory, "AAAAAAAA");
ProfileOptimization.SetProfileRoot(profilePath);
ProfileOptimization.StartProfile("AAAAAAAAAA");
_config.Initialize(true);
if (LoadConfigAndUserData)
if (Options.LoadConfigAndUserData)
{
// Sets up the configMgr
// If a config file path was passed, use it literally.
@@ -269,22 +272,26 @@ namespace Robust.Server
return true;
}
var dataDir = LoadConfigAndUserData
var dataDir = Options.LoadConfigAndUserData
? _commandLineArgs?.DataDir ?? PathHelpers.ExecutableRelativeFile("data")
: null;
// Set up the VFS
_resources.Initialize(dataDir);
ProgramShared.DoMounts(_resources, _commandLineArgs?.MountOptions, "Content.Server", contentStart:ContentStart);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resources, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
Options.LoadContentResources, Options.ResourceMountDisabled, ContentStart);
// 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);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return true;
@@ -335,7 +342,7 @@ namespace Robust.Server
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes"));
prototypeManager.LoadDirectory(Options.PrototypeDirectory);
prototypeManager.Resync();
IoCManager.Resolve<IServerConsoleHost>().Initialize();
@@ -492,7 +499,6 @@ namespace Robust.Server
}
public bool ContentStart { get; set; }
public bool LoadConfigAndUserData { private get; set; } = true;
public void OverrideMainLoop(IGameLoop gameLoop)
{

View File

@@ -7,13 +7,13 @@ namespace Robust.Server
#if FULL_RELEASE
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
#else
Program.Start(args, true);
Program.Start(args, new ServerOptions(), true);
#endif
}
public static void StartLibrary(string[] args)
public static void StartLibrary(string[] args, ServerOptions options)
{
Program.Start(args, true);
Program.Start(args, options, true);
}
}
}

View File

@@ -23,7 +23,7 @@ namespace Robust.Server
/// Sets up the server, loads the game, gets ready for client connections.
/// </summary>
/// <returns></returns>
bool Start(Func<ILogHandler>? logHandler = null);
bool Start(ServerOptions options, Func<ILogHandler>? logHandler = null);
/// <summary>
/// Hard restarts the server, shutting it down, kicking all players, and starting the server again.
@@ -44,11 +44,10 @@ namespace Robust.Server
internal interface IBaseServerInternal : IBaseServer
{
ServerOptions Options { get; }
bool ContentStart { set; }
bool LoadConfigAndUserData { set; }
void OverrideMainLoop(IGameLoop gameLoop);
void SetCommandLineArgs(CommandLineArgs args);
}
}

View File

@@ -21,10 +21,10 @@ namespace Robust.Server
internal static void Main(string[] args)
{
Start(args);
Start(args, new ServerOptions());
}
internal static void Start(string[] args, bool contentStart = false)
internal static void Start(string[] args, ServerOptions options, bool contentStart = false)
{
if (_hasStarted)
{
@@ -47,11 +47,11 @@ namespace Robust.Server
TaskCreationOptions.LongRunning,
TaskContinuationOptions.None,
RobustTaskScheduler.Instance
).StartNew(() => ParsedMain(parsed, contentStart))
).StartNew(() => ParsedMain(parsed, contentStart, options))
.GetAwaiter().GetResult();
}
private static void ParsedMain(CommandLineArgs args, bool contentStart)
private static void ParsedMain(CommandLineArgs args, bool contentStart, ServerOptions options)
{
Thread.CurrentThread.Name = "Main Thread";
IoCManager.InitThread();
@@ -67,7 +67,7 @@ namespace Robust.Server
Logger.Info("Server -> Starting");
if (server.Start())
if (server.Start(options))
{
Logger.Fatal("Server -> Can not start server");
//Not like you'd see this, haha. Perhaps later for logging.

View File

@@ -0,0 +1,54 @@
using Robust.Shared;
using Robust.Shared.Utility;
namespace Robust.Server
{
public class ServerOptions
{
/// <summary>
/// Whether content sandboxing will be enabled & enforced.
/// </summary>
public bool Sandboxing { get; init; } = false;
// 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>
/// 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.Server";
/// <summary>
/// Directory to load all assemblies from.
/// </summary>
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
/// <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 mount content resources when not on FULL_RELEASE.
/// </summary>
public bool LoadContentResources { get; init; } = true;
/// <summary>
/// Whether to load config and user data.
/// </summary>
public bool LoadConfigAndUserData { get; init; } = true;
}
}

View File

@@ -12,6 +12,11 @@ namespace Robust.Shared
{
return contentStart ? "../../" : "../../../";
}
private static string FindEngineRootDir(bool contentStart)
{
return contentStart ? "../../RobustToolbox/" : "../../";
}
#endif
internal static void PrintRuntimeInfo(ISawmill sawmill)
@@ -20,17 +25,21 @@ namespace Robust.Shared
sawmill.Debug($"OS: {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}");
}
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir,
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir, ResourcePath assembliesPath, bool loadContentResources = true,
bool loader = false, bool contentStart = false)
{
#if FULL_RELEASE
if (!loader)
res.MountContentDirectory(@"Resources/");
#else
var contentRootDir = FindContentRootDir(contentStart);
res.MountContentDirectory($@"{contentRootDir}RobustToolbox/Resources/");
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", new ResourcePath("/Assemblies/"));
res.MountContentDirectory($@"{contentRootDir}Resources/");
res.MountContentDirectory($@"{FindEngineRootDir(contentStart)}Resources/");
if (loadContentResources)
{
var contentRootDir = FindContentRootDir(contentStart);
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", assembliesPath);
res.MountContentDirectory($@"{contentRootDir}Resources/");
}
#endif
if (options == null)

View File

@@ -33,7 +33,7 @@ namespace Robust.UnitTesting
public string? ContentRootDir { get; set; }
public void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
public void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
{
}

View File

@@ -17,6 +17,7 @@ using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
@@ -422,7 +423,15 @@ namespace Robust.UnitTesting
var server = DependencyCollection.Resolve<BaseServer>();
server.LoadConfigAndUserData = false;
var serverOptions = _options != null ? _options.Options : new ServerOptions()
{
LoadConfigAndUserData = false,
LoadContentResources = false,
};
// Autoregister components if options are null or we're NOT starting from content.
if(!_options?.ContentStart ?? true)
IoCManager.Resolve<IComponentFactory>().DoAutoRegistrations();
if (_options?.ContentAssemblies != null)
{
@@ -447,7 +456,7 @@ namespace Robust.UnitTesting
var failureLevel = _options == null ? LogLevel.Error : _options.FailureLogLevel;
server.ContentStart = _options?.ContentStart ?? false;
if (server.Start(() => new TestLogHandler("SERVER", failureLevel)))
if (server.Start(serverOptions, () => new TestLogHandler("SERVER", failureLevel)))
{
throw new Exception("Server failed to start.");
}
@@ -535,13 +544,21 @@ namespace Robust.UnitTesting
var client = DependencyCollection.Resolve<GameController>();
var clientOptions = _options != null ? _options.Options : new GameControllerOptions()
{
LoadContentResources = false,
LoadConfigAndUserData = false,
};
// Autoregister components if options are null or we're NOT starting from content.
if(!_options?.ContentStart ?? true)
IoCManager.Resolve<IComponentFactory>().DoAutoRegistrations();
if (_options?.ContentAssemblies != null)
{
IoCManager.Resolve<TestingModLoader>().Assemblies = _options.ContentAssemblies;
}
client.LoadConfigAndUserData = false;
var cfg = IoCManager.Resolve<IConfigurationManagerInternal>();
if (_options != null)
@@ -556,15 +573,25 @@ namespace Robust.UnitTesting
}
}
cfg.OverrideConVars(new[] {(CVars.NetPredictLagBias.Name, "0")});
cfg.OverrideConVars(new[]
{
(CVars.NetPredictLagBias.Name, "0"),
// Connecting to Discord is a massive waste of time.
// Basically just makes the CI logs a mess.
(CVars.DiscordEnabled.Name, "false"),
// Avoid preloading textures.
(CVars.TexturePreloadingEnabled.Name, "false"),
});
GameLoop = new IntegrationGameLoop(DependencyCollection.Resolve<IGameTiming>(),
_fromInstanceWriter, _toInstanceReader);
var failureLevel = _options == null ? LogLevel.Error : _options.FailureLogLevel;
client.OverrideMainLoop(GameLoop);
client.ContentStart = true;
client.StartupSystemSplash(() => new TestLogHandler("CLIENT", failureLevel));
client.ContentStart = _options?.ContentStart ?? false;
client.StartupSystemSplash(clientOptions, () => new TestLogHandler("CLIENT", failureLevel));
client.StartupContinue(GameController.DisplayMode.Headless);
GameLoop.RunInit();
@@ -674,10 +701,20 @@ namespace Robust.UnitTesting
public class ServerIntegrationOptions : IntegrationOptions
{
public virtual ServerOptions Options { get; set; } = new()
{
LoadConfigAndUserData = false,
LoadContentResources = false,
};
}
public class ClientIntegrationOptions : IntegrationOptions
{
public virtual GameControllerOptions Options { get; set; } = new()
{
LoadContentResources = false,
LoadConfigAndUserData = false,
};
}
public abstract class IntegrationOptions

View File

@@ -0,0 +1,75 @@
using System.Threading.Tasks;
using NUnit.Framework;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using IPlayerManager = Robust.Server.Player.IPlayerManager;
namespace Robust.UnitTesting.Shared
{
[TestFixture]
public class EngineIntegrationTest_Test : RobustIntegrationTest
{
[Test]
public void ServerStartsCorrectlyTest()
{
ServerIntegrationInstance? server = null;
Assert.DoesNotThrow(() => server = StartServer());
Assert.That(server, Is.Not.Null);
}
[Test]
public void ClientStartsCorrectlyTest()
{
ClientIntegrationInstance? client = null;
Assert.DoesNotThrow(() => client = StartClient());
Assert.That(client, Is.Not.Null);
}
[Test]
public async Task ServerClientPairConnectCorrectlyTest()
{
var server = StartServer();
var client = StartClient();
Assert.That(server, Is.Not.Null);
Assert.That(client, Is.Not.Null);
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
// Connect client to the server...
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => IoCManager.Resolve<IClientNetManager>().ClientConnect(null!, 0, null!));
// Run 10 synced ticks...
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
await server.WaitAssertion(() =>
{
var playerManager = IoCManager.Resolve<IPlayerManager>();
// There must be a player connected.
Assert.That(playerManager.PlayerCount, Is.EqualTo(1));
// Get the only player...
var player = playerManager.GetAllPlayers()[0];
Assert.That(player.Status, Is.EqualTo(SessionStatus.Connected));
Assert.That(player.ConnectedClient.IsConnected, Is.True);
});
await client.WaitAssertion(() =>
{
var netManager = IoCManager.Resolve<IClientNetManager>();
Assert.That(netManager.IsConnected, Is.True);
Assert.That(netManager.ServerChannel, Is.Not.Null);
Assert.That(netManager.ServerChannel!.IsConnected, Is.True);
});
}
}
}