From f856ac0efa204300c25949674386ea5a9dbfa424 Mon Sep 17 00:00:00 2001
From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Date: Mon, 28 Nov 2022 14:11:44 +1300
Subject: [PATCH] Misc replay related engine changes (#3508)
---
.../GameObjects/ClientEntityManager.cs | 10 +-
Robust.Client/Player/LocalPlayer.cs | 5 +-
.../Configuration/ConfigurationManager.cs | 188 ++++++++++--------
.../Configuration/IConfigurationManager.cs | 8 +
.../IConfigurationManagerInternal.cs | 2 +-
.../ContentPack/IWritableDirProvider.cs | 2 +-
Robust.Shared/GameObjects/EntityManager.cs | 9 +-
Robust.Shared/GameObjects/IEntityManager.cs | 5 +
Robust.Shared/Network/IClientNetManager.cs | 5 +
Robust.Shared/Network/Messages/MsgConVars.cs | 2 +-
Robust.Shared/Network/NetManager.cs | 8 +
Robust.Shared/Utility/ZStd.cs | 4 +-
.../RobustIntegrationTest.NetManager.cs | 8 +
13 files changed, 161 insertions(+), 95 deletions(-)
diff --git a/Robust.Client/GameObjects/ClientEntityManager.cs b/Robust.Client/GameObjects/ClientEntityManager.cs
index db0d52d6c..c9c95ff07 100644
--- a/Robust.Client/GameObjects/ClientEntityManager.cs
+++ b/Robust.Client/GameObjects/ClientEntityManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
@@ -32,16 +33,11 @@ namespace Robust.Client.GameObjects
base.Initialize();
}
- public override void Shutdown()
- {
- using var _ = _gameTiming.StartStateApplicationArea();
- base.Shutdown();
- }
- public override void Cleanup()
+ public override void FlushEntities()
{
using var _ = _gameTiming.StartStateApplicationArea();
- base.Cleanup();
+ base.FlushEntities();
}
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid uid)
diff --git a/Robust.Client/Player/LocalPlayer.cs b/Robust.Client/Player/LocalPlayer.cs
index 86b718924..3a01d0af6 100644
--- a/Robust.Client/Player/LocalPlayer.cs
+++ b/Robust.Client/Player/LocalPlayer.cs
@@ -59,6 +59,9 @@ namespace Robust.Client.Player
/// Entity to attach the client to.
public void AttachEntity(EntityUid entity)
{
+ if (ControlledEntity == entity)
+ return;
+
// Detach and cleanup first
DetachEntity();
@@ -74,8 +77,8 @@ namespace Robust.Client.Player
if (IoCManager.Resolve().RunLevel != ClientRunLevel.SinglePlayerGame)
{
Logger.Warning($"Attaching local player to an entity {entMan.ToPrettyString(entity)} without an eye. This eye will not be netsynced and may cause issues.");
- eye.NetSyncEnabled = false;
}
+ eye.NetSyncEnabled = false;
}
eye.Current = true;
diff --git a/Robust.Shared/Configuration/ConfigurationManager.cs b/Robust.Shared/Configuration/ConfigurationManager.cs
index 5c94f6c8c..3d02bbb74 100644
--- a/Robust.Shared/Configuration/ConfigurationManager.cs
+++ b/Robust.Shared/Configuration/ConfigurationManager.cs
@@ -50,31 +50,50 @@ namespace Robust.Shared.Configuration
}
///
- public void LoadFromFile(string configFile)
+ public HashSet LoadFromTomlStream(Stream file)
{
+ var loaded = new HashSet();
try
{
- var tblRoot = Toml.ReadFile(configFile);
+ var tblRoot = Toml.ReadStream(file);
var callbackEvents = new ValueList();
// Ensure callbacks are raised OUTSIDE the write lock.
using (Lock.WriteGuard())
{
- ProcessTomlObject(tblRoot, ref callbackEvents);
+ ProcessTomlObject(tblRoot, ref callbackEvents, loaded);
}
foreach (var callback in callbackEvents)
{
InvokeValueChanged(callback);
}
+ }
+ catch (Exception e)
+ {
+ loaded.Clear();
+ Logger.WarningS("cfg", "Unable to load configuration from stream:\n{0}", e);
+ }
+ return loaded;
+ }
+
+ ///
+ public HashSet LoadFromFile(string configFile)
+ {
+ try
+ {
+ using var file = File.OpenRead(configFile);
+ var result = LoadFromTomlStream(file);
_configFile = configFile;
Logger.InfoS("cfg", $"Configuration Loaded from '{Path.GetFullPath(configFile)}'");
+ return result;
}
catch (Exception e)
{
Logger.WarningS("cfg", "Unable to load configuration file:\n{0}", e);
+ return new HashSet(0);
}
}
@@ -92,6 +111,7 @@ namespace Robust.Shared.Configuration
private void ProcessTomlObject(
TomlObject obj,
ref ValueList changedInvokes,
+ HashSet loadedCvars,
string tablePath = "")
{
if (obj is TomlTable table) // this is a table
@@ -105,7 +125,7 @@ namespace Robust.Shared.Configuration
else
newPath = tablePath + kvTml.Key;
- ProcessTomlObject(kvTml.Value, ref changedInvokes, newPath);
+ ProcessTomlObject(kvTml.Value, ref changedInvokes, loadedCvars, newPath);
}
}
else // this is a key, add CVar
@@ -116,6 +136,7 @@ namespace Robust.Shared.Configuration
{
// overwrite the value with the saved one
cfgVar.Value = tomlValue;
+ loadedCvars.Add(cfgVar.Name);
if (SetupInvokeValueChanged(cfgVar, tomlValue) is { } invoke)
changedInvokes.Add(invoke);
}
@@ -125,12 +146,89 @@ namespace Robust.Shared.Configuration
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
cfgVar = new ConfigVar(tablePath, 0, CVar.NONE) { Value = tomlValue };
_configVars.Add(tablePath, cfgVar);
+ loadedCvars.Add(cfgVar.Name);
}
cfgVar.ConfigModified = true;
}
}
+ ///
+ public void SaveToTomlStream(Stream stream, IEnumerable cvars)
+ {
+ var tblRoot = Toml.Create();
+
+ using (Lock.ReadGuard())
+ {
+ foreach (var name in cvars)
+ {
+ if (!_configVars.TryGetValue(name, out var cVar))
+ continue;
+
+ var value = cVar.Value;
+ if (value == null && cVar.Registered)
+ {
+ value = cVar.DefaultValue;
+ }
+
+ if (value == null)
+ {
+ Logger.ErrorS("cfg",
+ $"CVar {name} has no value or default value, was the default value registered as null?");
+ continue;
+ }
+
+ var keyIndex = name.LastIndexOf(TABLE_DELIMITER);
+ var tblPath = name.Substring(0, keyIndex).Split(TABLE_DELIMITER);
+ var keyName = name.Substring(keyIndex + 1);
+
+ // locate the Table in the config tree
+ var table = tblRoot;
+ foreach (var curTblName in tblPath)
+ {
+ if (!table.TryGetValue(curTblName, out TomlObject tblObject))
+ {
+ tblObject = table.Add(curTblName, new Dictionary()).Added;
+ }
+
+ table = tblObject as TomlTable ?? throw new InvalidConfigurationException(
+ $"[CFG] Object {curTblName} is being used like a table, but it is a {tblObject}. Are your CVar names formed properly?");
+ }
+
+ //runtime unboxing, either this or generic hell... ¯\_(ツ)_/¯
+ switch (value)
+ {
+ case Enum val:
+ table.Add(keyName, (int)(object)val); // asserts Enum value != (ulong || long)
+ break;
+ case int val:
+ table.Add(keyName, val);
+ break;
+ case long val:
+ table.Add(keyName, val);
+ break;
+ case bool val:
+ table.Add(keyName, val);
+ break;
+ case string val:
+ table.Add(keyName, val);
+ break;
+ case float val:
+ table.Add(keyName, val);
+ break;
+ case double val:
+ table.Add(keyName, val);
+ break;
+ default:
+ Logger.WarningS("cfg", $"Cannot serialize '{name}', unsupported type.");
+ break;
+ }
+ }
+ }
+
+ Toml.WriteStream(tblRoot, stream);
+ }
+
///
public void SaveToFile()
{
@@ -142,82 +240,14 @@ namespace Robust.Shared.Configuration
try
{
- var tblRoot = Toml.Create();
+ // Always write if it was present when reading from the config file, otherwise:
+ // Don't write if Archive flag is not set.
+ // Don't write if the cVar is the default value.
+ var cvars = _configVars.Where(x => x.Value.ConfigModified
+ || ((x.Value.Flags & CVar.ARCHIVE) == 0 && x.Value.Value != null && !x.Value.Value.Equals(x.Value.DefaultValue))).Select(x => x.Key);
- using (Lock.ReadGuard())
- {
- foreach (var (name, cVar) in _configVars)
- {
- var value = cVar.Value;
- if (value == null && cVar.Registered)
- {
- value = cVar.DefaultValue;
- }
-
- if (value == null)
- {
- Logger.ErrorS("cfg",
- $"CVar {name} has no value or default value, was the default value registered as null?");
- continue;
- }
-
- // Don't write if Archive flag is not set.
- // Don't write if the cVar is the default value.
- if (!cVar.ConfigModified &&
- (cVar.Flags & CVar.ARCHIVE) == 0 || value.Equals(cVar.DefaultValue))
- {
- continue;
- }
-
- var keyIndex = name.LastIndexOf(TABLE_DELIMITER);
- var tblPath = name.Substring(0, keyIndex).Split(TABLE_DELIMITER);
- var keyName = name.Substring(keyIndex + 1);
-
- // locate the Table in the config tree
- var table = tblRoot;
- foreach (var curTblName in tblPath)
- {
- if (!table.TryGetValue(curTblName, out TomlObject tblObject))
- {
- tblObject = table.Add(curTblName, new Dictionary()).Added;
- }
-
- table = tblObject as TomlTable ?? throw new InvalidConfigurationException(
- $"[CFG] Object {curTblName} is being used like a table, but it is a {tblObject}. Are your CVar names formed properly?");
- }
-
- //runtime unboxing, either this or generic hell... ¯\_(ツ)_/¯
- switch (value)
- {
- case Enum val:
- table.Add(keyName, (int)(object)val); // asserts Enum value != (ulong || long)
- break;
- case int val:
- table.Add(keyName, val);
- break;
- case long val:
- table.Add(keyName, val);
- break;
- case bool val:
- table.Add(keyName, val);
- break;
- case string val:
- table.Add(keyName, val);
- break;
- case float val:
- table.Add(keyName, val);
- break;
- case double val:
- table.Add(keyName, val);
- break;
- default:
- Logger.WarningS("cfg", $"Cannot serialize '{name}', unsupported type.");
- break;
- }
- }
- }
-
- Toml.WriteFile(tblRoot, _configFile);
+ using var file = File.OpenWrite(_configFile);
+ SaveToTomlStream(file, cvars);
Logger.InfoS("cfg", $"config saved to '{_configFile}'.");
}
catch (Exception e)
diff --git a/Robust.Shared/Configuration/IConfigurationManager.cs b/Robust.Shared/Configuration/IConfigurationManager.cs
index f25052ea4..4334282d1 100644
--- a/Robust.Shared/Configuration/IConfigurationManager.cs
+++ b/Robust.Shared/Configuration/IConfigurationManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using Robust.Shared.Timing;
namespace Robust.Shared.Configuration
@@ -44,6 +45,13 @@ namespace Robust.Shared.Configuration
///
void SaveToFile();
+ ///
+ /// Serializes a list of cvars to a toml.
+ ///
+ void SaveToTomlStream(Stream stream, IEnumerable cvars);
+
+ HashSet LoadFromTomlStream(Stream stream);
+
///
/// Register a CVar with the system. This must be done before the CVar is accessed.
///
diff --git a/Robust.Shared/Configuration/IConfigurationManagerInternal.cs b/Robust.Shared/Configuration/IConfigurationManagerInternal.cs
index 593b204c3..040dc1989 100644
--- a/Robust.Shared/Configuration/IConfigurationManagerInternal.cs
+++ b/Robust.Shared/Configuration/IConfigurationManagerInternal.cs
@@ -16,7 +16,7 @@ namespace Robust.Shared.Configuration
/// Sets up the ConfigurationManager and loads a TOML configuration file.
///
/// the full name of the config file.
- void LoadFromFile(string configFile);
+ HashSet LoadFromFile(string configFile);
///
/// Specifies the location where the config file should be saved, without trying to load from it.
diff --git a/Robust.Shared/ContentPack/IWritableDirProvider.cs b/Robust.Shared/ContentPack/IWritableDirProvider.cs
index dcb85f345..8fc3796cd 100644
--- a/Robust.Shared/ContentPack/IWritableDirProvider.cs
+++ b/Robust.Shared/ContentPack/IWritableDirProvider.cs
@@ -24,7 +24,7 @@ namespace Robust.Shared.ContentPack
void CreateDir(ResourcePath path);
///
- /// Deletes a file or empty directory. If the file or directory
+ /// Deletes a file or directory. If the file or directory
/// does not exist, does nothing.
///
/// Path of object to delete.
diff --git a/Robust.Shared/GameObjects/EntityManager.cs b/Robust.Shared/GameObjects/EntityManager.cs
index 4e45e84ad..11e4bdd73 100644
--- a/Robust.Shared/GameObjects/EntityManager.cs
+++ b/Robust.Shared/GameObjects/EntityManager.cs
@@ -10,6 +10,7 @@ using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
namespace Robust.Shared.GameObjects
{
@@ -480,15 +481,17 @@ namespace Robust.Shared.GameObjects
///
/// Disposes all entities and clears all lists.
///
- public void FlushEntities()
+ public virtual void FlushEntities()
{
QueuedDeletions.Clear();
QueuedDeletionsSet.Clear();
- foreach (var e in GetEntities())
+ foreach (var e in GetEntities().ToArray())
{
DeleteEntity(e);
}
- DebugTools.Assert(Entities.Count == 0);
+
+ if (Entities.Count != 0)
+ Logger.Error("Entities were spawned while flushing entities.");
}
///
diff --git a/Robust.Shared/GameObjects/IEntityManager.cs b/Robust.Shared/GameObjects/IEntityManager.cs
index 766118b25..33306731e 100644
--- a/Robust.Shared/GameObjects/IEntityManager.cs
+++ b/Robust.Shared/GameObjects/IEntityManager.cs
@@ -28,6 +28,11 @@ namespace Robust.Shared.GameObjects
///
void Cleanup();
+ ///
+ /// Deletes all entities.
+ ///
+ void FlushEntities();
+
///
/// Only run systems with set true.
///
diff --git a/Robust.Shared/Network/IClientNetManager.cs b/Robust.Shared/Network/IClientNetManager.cs
index ad5cd10c5..fc31c1ba5 100644
--- a/Robust.Shared/Network/IClientNetManager.cs
+++ b/Robust.Shared/Network/IClientNetManager.cs
@@ -42,5 +42,10 @@ namespace Robust.Shared.Network
void ClientDisconnect(string reason);
void Reset(string reason);
+
+ ///
+ /// Dispatches a messages as if it had just been received over the network.
+ ///
+ void DispatchLocalNetMessage(NetMessage message);
}
}
diff --git a/Robust.Shared/Network/Messages/MsgConVars.cs b/Robust.Shared/Network/Messages/MsgConVars.cs
index b4940894c..4c272277c 100644
--- a/Robust.Shared/Network/Messages/MsgConVars.cs
+++ b/Robust.Shared/Network/Messages/MsgConVars.cs
@@ -8,7 +8,7 @@ using Robust.Shared.Timing;
namespace Robust.Shared.Network.Messages
{
- internal sealed class MsgConVars : NetMessage
+ public sealed class MsgConVars : NetMessage
{
// Max buffer could potentially be 255 * 128 * 1024 = ~33MB, so if MaxMessageSize starts being a problem it can be increased.
private const int MaxMessageSize = 0x4000; // Arbitrarily chosen as a 'sane' value as the maximum size of the entire message.
diff --git a/Robust.Shared/Network/NetManager.cs b/Robust.Shared/Network/NetManager.cs
index ee5387ca5..f9acc0db7 100644
--- a/Robust.Shared/Network/NetManager.cs
+++ b/Robust.Shared/Network/NetManager.cs
@@ -922,6 +922,14 @@ namespace Robust.Shared.Network
return true;
}
+ public void DispatchLocalNetMessage(NetMessage message)
+ {
+ if (!_messages.TryGetValue(message.MsgName, out var msgDat))
+ return;
+
+ msgDat.Callback!.Invoke(message);
+ }
+
private void CacheNetMsgIndex(int id, string name)
{
if (!_messages.TryGetValue(name, out var msgDat))
diff --git a/Robust.Shared/Utility/ZStd.cs b/Robust.Shared/Utility/ZStd.cs
index 9cef68440..3030dcc0f 100644
--- a/Robust.Shared/Utility/ZStd.cs
+++ b/Robust.Shared/Utility/ZStd.cs
@@ -173,7 +173,7 @@ public sealed unsafe class ZStdCompressionContext : IDisposable
}
}
-internal sealed class ZStdDecompressStream : Stream
+public sealed class ZStdDecompressStream : Stream
{
private readonly Stream _baseStream;
private readonly bool _ownStream;
@@ -330,7 +330,7 @@ internal sealed class ZStdDecompressStream : Stream
}
}
-internal sealed class ZStdCompressStream : Stream
+public sealed class ZStdCompressStream : Stream
{
private readonly Stream _baseStream;
private readonly bool _ownStream;
diff --git a/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs b/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs
index 665d8f182..0c0d8954f 100644
--- a/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs
+++ b/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs
@@ -392,6 +392,14 @@ namespace Robust.UnitTesting
channel.OtherChannel.TryWrite(new DataMessage(message, channel.RemoteUid));
}
+ public void DispatchLocalNetMessage(NetMessage message)
+ {
+ if (_callbacks.TryGetValue(message.GetType(), out var callback))
+ {
+ callback(message);
+ }
+ }
+
private sealed class IntegrationNetChannel : INetChannel
{
private readonly IntegrationNetManager _owner;