Files
RobustToolbox/Robust.Server/BaseServer.cs
Paul Ritter 80f9f24243 Serialization v3 aka constant suffering (#1606)
* oops

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

* adds more serializers, actually implements them & removes most of the last of the old system

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

* Temporary fix for SERV3: Turn populate delegate into regular code

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

* Convert every non generic serializer to the new format, general fixes

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

* Create more deserialized types and store prototypes with their deserialized results

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

* Update all prototypes to have a parent and have consistent id/parent properties

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

* Add copying for value types and arrays. Fix copy internal for primitives, enums and strings

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

* Add checks for the serialization manager initializing and already being initialized

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

* Fix sprite component norot not being default true like in latest master

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

* Make yaml linter 5 times faster (~80% less execution time)

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

* Revert "Make yaml linter 5 times faster (~80% less execution time)"

This reverts commit 2ee4cc2c26.

* Add serialization to RobustServerSimulation and mock reflection methods
Fixes container tests

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

* Add IEquatable, IComparable, ToString and operators to DataPosition
Rename it to NodeMark
Make it a readonly struct

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

* Make dependencies an argument instead of a property on the serialization manager

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Vera Aguilera Puerto <zddm@outlook.es>
2021-03-04 15:59:14 -08:00

583 lines
21 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
using Robust.Server.Console;
using Robust.Server.DataMetrics;
using Robust.Server.Debugging;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Log;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Server.Utility;
using Robust.Server.ViewVariables;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Serilog.Debugging;
using Serilog.Sinks.Loki;
using Stopwatch = Robust.Shared.Timing.Stopwatch;
namespace Robust.Server
{
/// <summary>
/// The master class that runs the rest of the engine.
/// </summary>
internal sealed class BaseServer : IBaseServerInternal
{
private static readonly Gauge ServerUpTime = Metrics.CreateGauge(
"robust_server_uptime",
"The real time the server main loop has been running.");
private static readonly Gauge ServerCurTime = Metrics.CreateGauge(
"robust_server_curtime",
"The IGameTiming.CurTime of the server.");
private static readonly Gauge ServerCurTick = Metrics.CreateGauge(
"robust_server_curtick",
"The IGameTiming.CurTick of the server.");
private static readonly Histogram TickUsage = Metrics.CreateHistogram(
"robust_server_update_usage",
"Time usage of the main loop Update()s",
new HistogramConfiguration
{
LabelNames = new[] {"area"},
Buckets = Histogram.ExponentialBuckets(0.000_01, 2, 13)
});
[Dependency] private readonly IConfigurationManagerInternal _config = default!;
[Dependency] private readonly IComponentManager _components = default!;
[Dependency] private readonly IServerEntityManager _entities = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IGameTiming _time = default!;
[Dependency] private readonly IResourceManagerInternal _resources = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITimerManager timerManager = default!;
[Dependency] private readonly IServerGameStateManager _stateManager = default!;
[Dependency] private readonly IServerNetManager _network = default!;
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly IRuntimeLog runtimeLog = default!;
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
[Dependency] private readonly IScriptHost _scriptHost = default!;
[Dependency] private readonly IMetricsManager _metricsManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
[Dependency] private readonly ILocalizationManagerInternal _loc = default!;
private readonly Stopwatch _uptimeStopwatch = new();
private CommandLineArgs? _commandLineArgs;
private Func<ILogHandler>? _logHandlerFactory;
private ILogHandler? _logHandler;
private IGameLoop _mainLoop = default!;
private TimeSpan _lastTitleUpdate;
private int _lastReceivedBytes;
private int _lastSentBytes;
private string? _shutdownReason;
private readonly ManualResetEventSlim _shutdownEvent = new(false);
/// <inheritdoc />
public int MaxPlayers => _config.GetCVar(CVars.GameMaxPlayers);
/// <inheritdoc />
public string ServerName => _config.GetCVar(CVars.GameHostName);
/// <inheritdoc />
public void Restart()
{
Logger.InfoS("srv", "Restarting Server...");
Cleanup();
Start(_logHandlerFactory);
}
/// <inheritdoc />
public void Shutdown(string? reason)
{
if (string.IsNullOrWhiteSpace(reason))
Logger.InfoS("srv", "Shutting down...");
else
Logger.InfoS("srv", $"{reason}, shutting down...");
_shutdownReason = reason;
_mainLoop.Running = false;
if (_logHandler != null)
{
_log.RootSawmill.RemoveHandler(_logHandler);
(_logHandler as IDisposable)?.Dispose();
}
}
public void SetCommandLineArgs(CommandLineArgs args)
{
_commandLineArgs = args;
}
/// <inheritdoc />
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
{
_config.Initialize(true);
if (LoadConfigAndUserData)
{
// Sets up the configMgr
// If a config file path was passed, use it literally.
// This ensures it's working-directory relative
// (for people passing config file through the terminal or something).
// Otherwise use the one next to the executable.
if (_commandLineArgs?.ConfigFile != null)
{
_config.LoadFromFile(_commandLineArgs.ConfigFile);
}
else
{
var path = PathHelpers.ExecutableRelativeFile("server_config.toml");
if (File.Exists(path))
{
_config.LoadFromFile(path);
}
else
{
_config.SetSaveFile(path);
}
}
}
_config.LoadCVarsFromAssembly(typeof(BaseServer).Assembly); // Robust.Server
_config.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Robust.Shared
_config.OverrideConVars(EnvironmentVariables.GetEnvironmentCVars());
if (_commandLineArgs != null)
{
_config.OverrideConVars(_commandLineArgs.CVars);
}
//Sets up Logging
_logHandlerFactory = logHandlerFactory;
var logHandler = logHandlerFactory?.Invoke() ?? null;
var logEnabled = _config.GetCVar(CVars.LogEnabled);
if (logEnabled && logHandler == null)
{
var logPath = _config.GetCVar(CVars.LogPath);
var logFormat = _config.GetCVar(CVars.LogFormat);
var logFilename = logFormat.Replace("%(date)s", DateTime.Now.ToString("yyyy-MM-dd"))
.Replace("%(time)s", DateTime.Now.ToString("hh-mm-ss"));
var fullPath = Path.Combine(logPath, logFilename);
if (!Path.IsPathRooted(fullPath))
{
logPath = PathHelpers.ExecutableRelativeFile(fullPath);
}
logHandler = new FileLogHandler(logPath);
}
_log.RootSawmill.Level = _config.GetCVar(CVars.LogLevel);
if (logEnabled && logHandler != null)
{
_logHandler = logHandler;
_log.RootSawmill.AddHandler(_logHandler!);
}
SelfLog.Enable(s => { System.Console.WriteLine("SERILOG ERROR: {0}", s); });
if (!SetupLoki())
{
return true;
}
// Has to be done early because this guy's in charge of the main thread Synchronization Context.
_taskManager.Initialize();
LoadSettings();
// Load metrics really early so that we can profile startup times in the future maybe.
_metricsManager.Initialize();
var netMan = IoCManager.Resolve<IServerNetManager>();
try
{
netMan.Initialize(true);
netMan.StartServer();
}
catch (Exception e)
{
var port = netMan.Port;
Logger.Fatal(
"Unable to setup networking manager. Check port {0} is not already in use and that all binding addresses are correct!\n{1}",
port, e);
return true;
}
var dataDir = LoadConfigAndUserData
? _commandLineArgs?.DataDir ?? PathHelpers.ExecutableRelativeFile("data")
: null;
// Set up the VFS
_resources.Initialize(dataDir);
ProgramShared.DoMounts(_resources, _commandLineArgs?.MountOptions, "Content.Server");
_modLoader.SetUseLoadContext(!DisableLoadContext);
_modLoader.SetEnableSandboxing(false);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
{
Logger.Fatal("Errors while loading content assemblies.");
return true;
}
foreach (var loadedModule in _modLoader.LoadedModules)
{
_config.LoadCVarsFromAssembly(loadedModule);
}
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
// HAS to happen after content gets loaded.
// Else the content types won't be included.
// TODO: solve this properly.
_serializer.Initialize();
_loc.AddLoadedToStringSerializer(_stringSerializer);
//IoCManager.Resolve<IMapLoader>().LoadedMapData +=
// IoCManager.Resolve<IRobustMappedStringSerializer>().AddStrings;
IoCManager.Resolve<IPrototypeManager>().LoadedData += (yaml, name) =>
{
if (!_stringSerializer.Locked)
{
_stringSerializer.AddStrings(yaml);
}
};
// Initialize Tier 2 services
IoCManager.Resolve<IGameTiming>().InSimulation = true;
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_stateManager.Initialize();
IoCManager.Resolve<IPlayerManager>().Initialize(MaxPlayers);
_mapManager.Initialize();
_mapManager.Startup();
IoCManager.Resolve<IPlacementManager>().Initialize();
IoCManager.Resolve<IViewVariablesHost>().Initialize();
IoCManager.Resolve<IDebugDrawingManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_entities.Initialize();
IoCManager.Resolve<ISerializationManager>().Initialize();
// because of 'reasons' this has to be called after the last assembly is loaded
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes"));
prototypeManager.Resync();
IoCManager.Resolve<IServerConsoleHost>().Initialize();
_entities.Startup();
_scriptHost.Initialize();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
IoCManager.Resolve<IStatusHost>().Start();
AppDomain.CurrentDomain.ProcessExit += ProcessExiting;
_watchdogApi.Initialize();
_stringSerializer.LockStrings();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
{
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
}
return false;
}
private bool SetupLoki()
{
var enabled = _config.GetCVar(CVars.LokiEnabled);
if (!enabled)
{
return true;
}
var serverName = _config.GetCVar(CVars.LokiName);
var address = _config.GetCVar(CVars.LokiAddress);
var username = _config.GetCVar(CVars.LokiUsername);
var password = _config.GetCVar(CVars.LokiPassword);
if (string.IsNullOrWhiteSpace(serverName))
{
Logger.FatalS("loki", "Misconfiguration: Server name is not specified/empty.");
return false;
}
if (string.IsNullOrWhiteSpace(address))
{
Logger.FatalS("loki", "Misconfiguration: Loki address is not specified/empty.");
return false;
}
LokiCredentials credentials;
if (string.IsNullOrWhiteSpace(username))
{
credentials = new NoAuthCredentials(address);
}
else
{
if (string.IsNullOrWhiteSpace(password))
{
Logger.FatalS("loki", "Misconfiguration: Loki password is not specified/empty but username is.");
return false;
}
credentials = new BasicAuthCredentials(address, username, password);
}
Logger.DebugS("loki", "Loki enabled for server {ServerName} loki address {LokiAddress}.", serverName,
address);
var handler = new LokiLogHandler(serverName, credentials);
_log.RootSawmill.AddHandler(handler);
return true;
}
private void ProcessExiting(object? sender, EventArgs e)
{
_taskManager.RunOnMainThread(() => Shutdown("ProcessExited"));
// Give the server 10 seconds to shut down.
// If it still hasn't managed to assume it's stuck or something.
if (!_shutdownEvent.Wait(10_000))
{
System.Console.WriteLine("ProcessExited timeout (10s) has been passed; killing server.");
// This kills the server right? Returning?
}
}
/// <inheritdoc />
public void MainLoop()
{
if (_mainLoop == null)
{
_mainLoop = new GameLoop(_time)
{
SleepMode = SleepMode.Delay,
DetectSoftLock = true,
EnableMetrics = true
};
}
_uptimeStopwatch.Start();
_mainLoop.Input += (sender, args) => Input(args);
_mainLoop.Tick += (sender, args) => Update(args);
_mainLoop.Update += (sender, args) => { ServerUpTime.Set(_uptimeStopwatch.Elapsed.TotalSeconds); };
// set GameLoop.Running to false to return from this function.
_time.Paused = false;
_mainLoop.Run();
_time.InSimulation = true;
Cleanup();
_shutdownEvent.Set();
}
public bool DisableLoadContext { private get; set; }
public bool LoadConfigAndUserData { private get; set; } = true;
public void OverrideMainLoop(IGameLoop gameLoop)
{
_mainLoop = gameLoop;
}
/// <summary>
/// Updates the console window title with performance statistics.
/// </summary>
private void UpdateTitle()
{
if (!Environment.UserInteractive || System.Console.IsInputRedirected)
{
return;
}
// every 1 second update stats in the console window title
if ((_time.RealTime - _lastTitleUpdate).TotalSeconds < 1.0)
return;
var netStats = UpdateBps();
System.Console.Title = string.Format("FPS: {0:N2} SD: {1:N2}ms | Net: ({2}) | Memory: {3:N0} KiB",
Math.Round(_time.FramesPerSecondAvg, 2),
_time.RealFrameTimeStdDev.TotalMilliseconds,
netStats,
Process.GetCurrentProcess().PrivateMemorySize64 >> 10);
_lastTitleUpdate = _time.RealTime;
}
/// <summary>
/// Loads the server settings from the ConfigurationManager.
/// </summary>
private void LoadSettings()
{
var cfgMgr = IoCManager.Resolve<IConfigurationManager>();
cfgMgr.OnValueChanged(CVars.NetTickrate, i =>
{
var b = (byte) i;
_time.TickRate = b;
Logger.InfoS("game", $"Tickrate changed to: {b} on tick {_time.CurTick}");
});
_time.TickRate = (byte) _config.GetCVar(CVars.NetTickrate);
Logger.InfoS("srv", $"Name: {ServerName}");
Logger.InfoS("srv", $"TickRate: {_time.TickRate}({_time.TickPeriod.TotalMilliseconds:0.00}ms)");
Logger.InfoS("srv", $"Max players: {MaxPlayers}");
}
// called right before main loop returns, do all saving/cleanup in here
private void Cleanup()
{
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
// shut down networking, kicking all players.
_network.Shutdown($"Server shutting down: {_shutdownReason}");
// shutdown entities
_entities.Shutdown();
if (_config.GetCVar(CVars.LogRuntimeLog))
{
// Wrtie down exception log
var logPath = _config.GetCVar(CVars.LogPath);
var relPath = PathHelpers.ExecutableRelativeFile(logPath);
Directory.CreateDirectory(relPath);
var pathToWrite = Path.Combine(relPath,
"Runtime-" + DateTime.Now.ToString("yyyy-MM-dd-THH-mm-ss") + ".txt");
File.WriteAllText(pathToWrite, runtimeLog.Display(), EncodingHelpers.UTF8);
}
AppDomain.CurrentDomain.ProcessExit -= ProcessExiting;
//TODO: This should prob shutdown all managers in a loop.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
{
WindowsTickPeriod.TimeEndPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
}
}
private string UpdateBps()
{
var stats = IoCManager.Resolve<IServerNetManager>().Statistics;
var bps =
$"Send: {(stats.SentBytes - _lastSentBytes) >> 10:N0} KiB/s, Recv: {(stats.ReceivedBytes - _lastReceivedBytes) >> 10:N0} KiB/s";
_lastSentBytes = stats.SentBytes;
_lastReceivedBytes = stats.ReceivedBytes;
return bps;
}
private void Input(FrameEventArgs args)
{
_systemConsole.Update();
_network.ProcessPackets();
_taskManager.ProcessPendingTasks();
}
private void Update(FrameEventArgs frameEventArgs)
{
ServerCurTick.Set(_time.CurTick.Value);
ServerCurTime.Set(_time.CurTime.TotalSeconds);
// These are always the same on the server, there is no prediction.
_time.LastRealTick = _time.CurTick;
UpdateTitle();
using (TickUsage.WithLabels("PreEngine").NewTimer())
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
}
using (TickUsage.WithLabels("NetworkedCVar").NewTimer())
{
IoCManager.Resolve<INetConfigurationManager>().TickProcessMessages();
}
using (TickUsage.WithLabels("Timers").NewTimer())
{
timerManager.UpdateTimers(frameEventArgs);
}
using (TickUsage.WithLabels("AsyncTasks").NewTimer())
{
_taskManager.ProcessPendingTasks();
}
using (TickUsage.WithLabels("ComponentCull").NewTimer())
{
_components.CullRemovedComponents();
}
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
_entities.Update(frameEventArgs.DeltaSeconds, TickUsage);
using (TickUsage.WithLabels("PostEngine").NewTimer())
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
}
using (TickUsage.WithLabels("GameState").NewTimer())
{
_stateManager.SendGameStateUpdate();
}
_watchdogApi.Heartbeat();
}
}
}