Unstable v0.15.17.0 (Hex is out of town edition)

This commit is contained in:
Juan Pablo Arce
2021-12-03 13:31:10 -03:00
parent cd5c8f3e13
commit 617d9ede88
245 changed files with 8088 additions and 5842 deletions
@@ -1,39 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Barotrauma.IO;
using System.IO.Pipes;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.Extensions;
#if SERVER
using PipeType = System.IO.Pipes.AnonymousPipeClientStream;
#else
using PipeType = System.IO.Pipes.AnonymousPipeServerStream;
#endif
namespace Barotrauma.Networking
{
static partial class ChildServerRelay
{
private static System.IO.Stream writeStream;
private static System.IO.Stream readStream;
private static volatile bool shutDown;
public static bool HasShutDown
{
get { return shutDown; }
}
private static PipeType writeStream;
private static PipeType readStream;
private static ManualResetEvent writeManualResetEvent;
private static byte[] tempBytes;
private enum ReadState
{
WaitingForPacketStart,
WaitingForPacketEnd
};
private static ReadState readState;
private static byte[] readIncBuf;
private static volatile bool shutDown;
public static bool HasShutDown => shutDown;
private const int ReadBufferSize = MsgConstants.MTU * 2;
private static byte[] readTempBytes;
private static int readIncOffset;
private static int readIncTotal;
private static Queue<byte[]> msgsToWrite;
private static Queue<byte[]> msgsToRead;
private static ConcurrentQueue<byte[]> msgsToWrite;
private static ConcurrentQueue<byte[]> msgsToRead;
private static Thread readThread;
private static Thread writeThread;
@@ -42,14 +38,13 @@ namespace Barotrauma.Networking
private static void PrivateStart()
{
readState = ReadState.WaitingForPacketStart;
readIncOffset = 0;
readIncTotal = 0;
tempBytes = new byte[MsgConstants.MTU * 2];
readTempBytes = new byte[ReadBufferSize];
msgsToWrite = new Queue<byte[]>();
msgsToRead = new Queue<byte[]>();
msgsToWrite = new ConcurrentQueue<byte[]>();
msgsToRead = new ConcurrentQueue<byte[]>();
shutDown = false;
@@ -88,113 +83,86 @@ namespace Barotrauma.Networking
private static int ReadIncomingMsgs()
{
Task<int> readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token);
Task<int> readTask = readStream?.ReadAsync(readTempBytes, 0, readTempBytes.Length, readCancellationToken.Token);
if (readTask is null) { return -1; }
TimeSpan timeOut = TimeSpan.FromMilliseconds(100);
for (int i = 0; i < 150; i++)
{
if (shutDown)
{
readCancellationToken?.Cancel();
shutDown = true;
return -1;
}
if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(timeOut) ?? true))
if (readTask.IsCompleted || readTask.Wait(timeOut))
{
break;
}
}
if (readTask == null || !readTask.IsCompleted)
{
readCancellationToken?.Cancel();
shutDown = true;
return -1;
}
if (readTask.Status != TaskStatus.RanToCompletion)
{
shutDown = true;
return -1;
bool swallowException = shutDown
&& ((readTask.Exception?.InnerException is ObjectDisposedException)
|| (readTask.Exception?.InnerException is System.IO.IOException));
if (swallowException)
{
readCancellationToken?.Cancel();
return -1;
}
throw new Exception(
$"ChildServerRelay readTask did not run to completion: status was {readTask.Status}.",
readTask.Exception);
}
return readTask.Result;
}
private static void CheckPipeConnected(string name, PipeType pipe)
{
if (!(pipe is { IsConnected: true }))
{
throw new Exception($"{name} was disconnected unexpectedly");
}
}
private static void UpdateRead()
{
Span<byte> msgLengthSpan = stackalloc byte[2];
while (!shutDown)
{
#if SERVER
if (!((readStream as AnonymousPipeClientStream)?.IsConnected ?? false))
CheckPipeConnected(nameof(readStream), readStream);
bool readBytes(Span<byte> readTo)
{
shutDown = true;
return;
}
#else
if (!((readStream as AnonymousPipeServerStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#endif
int readLen = ReadIncomingMsgs();
if (readLen < 0) { shutDown = true; return; }
int procIndex = 0;
while (procIndex < readLen)
{
if (readState == ReadState.WaitingForPacketStart)
for (int i = 0; i < readTo.Length; i++)
{
readIncTotal = tempBytes[procIndex];
procIndex++;
if (procIndex >= readLen)
if (readIncOffset >= readIncTotal)
{
readLen = ReadIncomingMsgs();
if (readLen < 0) { shutDown = true; return; }
procIndex = 0;
readIncTotal = ReadIncomingMsgs();
readIncOffset = 0;
if (readIncTotal == 0) { Thread.Yield(); continue; }
if (readIncTotal < 0) { return false; }
}
readIncTotal |= (tempBytes[procIndex] << 8);
procIndex++;
if (readIncTotal <= 0) { continue; }
readIncOffset = 0;
readIncBuf = new byte[readIncTotal];
readState = ReadState.WaitingForPacketEnd;
readTo[i] = readTempBytes[readIncOffset];
readIncOffset++;
}
else if (readState == ReadState.WaitingForPacketEnd)
{
if ((readIncTotal - readIncOffset) > (readLen - procIndex))
{
Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readLen - procIndex);
readIncOffset += (readLen - procIndex);
procIndex = readLen;
}
else
{
Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readIncTotal - readIncOffset);
procIndex += (readIncTotal - readIncOffset);
readIncOffset = readIncTotal;
lock (msgsToRead)
{
msgsToRead.Enqueue(readIncBuf);
}
readIncBuf = null;
readState = ReadState.WaitingForPacketStart;
}
}
if (shutDown) { break; }
return true;
}
if (!readBytes(msgLengthSpan)) { shutDown = true; break; }
int msgLength = msgLengthSpan[0] | (msgLengthSpan[1] << 8);
if (msgLength > 0)
{
byte[] msg = new byte[msgLength];
if (!readBytes(msg.AsSpan())) { shutDown = true; break; }
msgsToRead.Enqueue(msg);
}
Thread.Yield();
}
}
@@ -203,81 +171,62 @@ namespace Barotrauma.Networking
{
while (!shutDown)
{
#if SERVER
if (!((writeStream as AnonymousPipeClientStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#else
if (!((writeStream as AnonymousPipeServerStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#endif
bool msgAvailable; byte[] msg;
lock (msgsToWrite)
{
msgAvailable = msgsToWrite.TryDequeue(out msg);
}
while (msgAvailable)
{
byte[] lengthBytes = new byte[2];
lengthBytes[0] = (byte)(msg.Length & 0xFF);
lengthBytes[1] = (byte)((msg.Length >> 8) & 0xFF);
CheckPipeConnected(nameof(writeStream), writeStream);
msg = lengthBytes.Concat(msg).ToArray();
bool msgAvailable; byte[] msg;
void writeMsg()
{
// It's SUPER IMPORTANT that this stack allocation
// remains in this local function and is never inlined,
// because C# is stupid and only calls for deallocation
// when the function returns; placing it in the loop
// this method is based around would lead to a stack
// overflow real quick!
Span<byte> bytesToWrite = stackalloc byte[2 + msg.Length];
bytesToWrite[0] = (byte)(msg.Length & 0xFF);
bytesToWrite[1] = (byte)((msg.Length >> 8) & 0xFF);
Span<byte> msgSlice = bytesToWrite.Slice(2, msg.Length);
msg.AsSpan().CopyTo(msgSlice);
try
{
writeStream?.Write(msg, 0, msg.Length);
writeStream?.Write(bytesToWrite);
}
catch (ObjectDisposedException)
catch (Exception exception)
{
shutDown = true;
break;
}
catch (System.IO.IOException)
{
shutDown = true;
break;
switch (exception)
{
case ObjectDisposedException _:
case System.IO.IOException _:
if (!shutDown) { throw; }
break;
default:
throw;
};
}
}
msgAvailable = msgsToWrite.TryDequeue(out msg);
while (msgAvailable)
{
writeMsg();
if (shutDown) { break; }
lock (msgsToWrite)
{
msgAvailable = msgsToWrite.TryDequeue(out msg);
}
msgAvailable = msgsToWrite.TryDequeue(out msg);
}
if (!shutDown)
{
writeManualResetEvent.Reset();
if (!writeManualResetEvent.WaitOne(1000))
{
if (shutDown)
{
return;
}
try
{
//heartbeat to keep the other end alive
byte[] lengthBytes = new byte[2];
lengthBytes[0] = (byte)0;
lengthBytes[1] = (byte)0;
writeStream?.Write(lengthBytes, 0, 2);
}
catch (ObjectDisposedException)
{
shutDown = true;
break;
}
catch (System.IO.IOException)
{
shutDown = true;
break;
}
if (shutDown) { return; }
//heartbeat to keep the other end alive
msg = Array.Empty<byte>(); writeMsg();
}
}
}
@@ -287,21 +236,15 @@ namespace Barotrauma.Networking
{
if (shutDown) { return; }
lock (msgsToWrite)
{
msgsToWrite.Enqueue(msg);
writeManualResetEvent.Set();
}
msgsToWrite.Enqueue(msg);
writeManualResetEvent.Set();
}
public static bool Read(out byte[] msg)
{
if (shutDown) { msg = null; return false; }
lock (msgsToRead)
{
return msgsToRead.TryDequeue(out msg);
}
return msgsToRead.TryDequeue(out msg);
}
}
}
@@ -257,7 +257,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition, quality));
@@ -270,7 +270,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition, quality));
@@ -283,7 +283,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition, quality)
@@ -301,7 +301,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, onSpawn));
@@ -314,7 +314,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue5:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue5:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, position, sub, onSpawn));
@@ -327,7 +327,7 @@ namespace Barotrauma
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, characterInfo, onSpawn));
@@ -11,6 +11,7 @@ namespace Barotrauma.Networking
public const int MaxPlayers = 16;
public const int ServerNameMaxLength = 60;
public const int ServerMessageMaxLength = 2000;
public static string MasterServerUrl = GameMain.Config.MasterServerUrl;
@@ -31,7 +31,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception);
GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
GameAnalyticsManager.ErrorSeverity.Error,
"Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace.CleanupStackTrace());
//write an empty event to avoid messing up IDs
@@ -2,14 +2,14 @@
namespace Barotrauma.Networking
{
public enum DeliveryMethod : byte
public enum DeliveryMethod : int
{
Unreliable = 0x0,
Reliable = 0x1,
ReliableOrdered = 0x2
}
public enum ConnectionInitialization : byte
public enum ConnectionInitialization : int
{
//used by all peer implementations
SteamTicketAndVersion = 0x1,
@@ -22,7 +22,7 @@ namespace Barotrauma.Networking
}
[Flags]
public enum PacketHeader : byte
public enum PacketHeader : int
{
//used by all peer implementations
None = 0x0,
@@ -34,5 +34,23 @@ namespace Barotrauma.Networking
IsServerMessage = 0x8,
IsHeartbeatMessage = 0x10
}
public static class NetworkEnumExtensions
{
public static bool IsCompressed(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsCompressed);
public static bool IsConnectionInitializationStep(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsConnectionInitializationStep);
public static bool IsDisconnectMessage(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsDisconnectMessage);
public static bool IsServerMessage(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsServerMessage);
public static bool IsHeartbeatMessage(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsHeartbeatMessage);
}
}
@@ -219,7 +219,7 @@ namespace Barotrauma.Networking
internal static void EnsureBufferSize(ref byte[] buf, int numberOfBits)
{
int byteLen = ((numberOfBits + 7) >> 3);
int byteLen = (numberOfBits + 7) / 8;
if (buf == null)
{
buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount];
@@ -461,7 +461,7 @@ namespace Barotrauma.Networking
{
get
{
return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
return (LengthBits + 7) / 8;
}
}
@@ -45,6 +45,7 @@ namespace Barotrauma.Networking
[Flags]
public enum NetFlags : byte
{
None = 0x0,
Name = 0x1,
Message = 0x2,
Properties = 0x4,
@@ -342,8 +343,14 @@ namespace Barotrauma.Networking
get { return serverName; }
set
{
serverName = value;
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
string val = value;
if (val.Length > NetConfig.ServerNameMaxLength) { val = val.Substring(0, NetConfig.ServerNameMaxLength); }
if (serverName == val) { return; }
serverName = val;
ServerDetailsChanged = true;
#if SERVER
UpdateFlag(NetFlags.Name);
#endif
}
}
@@ -353,9 +360,14 @@ namespace Barotrauma.Networking
get { return serverMessageText; }
set
{
if (serverMessageText == value) { return; }
serverMessageText = value;
string val = value;
if (val.Length > NetConfig.ServerMessageMaxLength) { val = val.Substring(0, NetConfig.ServerMessageMaxLength); }
if (serverMessageText == val) { return; }
serverMessageText = val;
ServerDetailsChanged = true;
#if SERVER
UpdateFlag(NetFlags.Message);
#endif
}
}
@@ -371,6 +383,8 @@ namespace Barotrauma.Networking
public Dictionary<string, bool> MonsterEnabled { get; private set; }
public const int MaxExtraCargoItemsOfType = 10;
public const int MaxExtraCargoItemTypes = 20;
public Dictionary<ItemPrefab, int> ExtraCargo { get; private set; }
public HashSet<string> HiddenSubs { get; private set; }
@@ -1003,16 +1017,17 @@ namespace Barotrauma.Networking
{
bool changed = false;
UInt32 count = msg.ReadUInt32();
if (ExtraCargo == null || count != ExtraCargo.Count) changed = true;
if (ExtraCargo == null || count != ExtraCargo.Count) { changed = true; }
Dictionary<ItemPrefab, int> extraCargo = new Dictionary<ItemPrefab, int>();
for (int i = 0; i < count; i++)
{
string prefabIdentifier = msg.ReadString();
byte amount = msg.ReadByte();
var itemPrefab = MapEntityPrefab.Find(null, prefabIdentifier, showErrorMessages: false) as ItemPrefab;
if (itemPrefab != null && amount > 0)
if (MapEntityPrefab.Find(null, prefabIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab && amount > 0)
{
if (ExtraCargo.Keys.Count() >= MaxExtraCargoItemTypes) { continue; }
if (ExtraCargo.ContainsKey(itemPrefab) && ExtraCargo[itemPrefab] >= MaxExtraCargoItemsOfType) { continue; }
if (changed || !ExtraCargo.ContainsKey(itemPrefab) || ExtraCargo[itemPrefab] != amount) { changed = true; }
extraCargo.Add(itemPrefab, amount);
}
@@ -1039,11 +1054,15 @@ namespace Barotrauma.Networking
public void ReadHiddenSubs(IReadMessage msg)
{
var subList = GameMain.NetLobbyScreen.GetSubList();
HiddenSubs.Clear();
uint count = msg.ReadVariableUInt32();
for (int i = 0; i < count; i++)
{
string submarineName = msg.ReadString();
int index = msg.ReadUInt16();
if (index < 0 || index >= subList.Count) { continue; }
string submarineName = subList[index].Name;
HiddenSubs.Add(submarineName);
}
@@ -1054,10 +1073,12 @@ namespace Barotrauma.Networking
public void WriteHiddenSubs(IWriteMessage msg)
{
var subList = GameMain.NetLobbyScreen.GetSubList();
msg.WriteVariableUInt32((uint)HiddenSubs.Count);
foreach (string submarineName in HiddenSubs)
{
msg.Write(submarineName);
msg.Write((UInt16)subList.FindIndex(s => s.Name.Equals(submarineName, StringComparison.OrdinalIgnoreCase)));
}
}
}
@@ -74,6 +74,19 @@ namespace Barotrauma.Steam
return Steamworks.SteamClient.Name;
}
private static Steamworks.AuthTicket currentTicket = null;
public static Steamworks.AuthTicket GetAuthSessionTicket()
{
if (!isInitialized)
{
return null;
}
currentTicket?.Cancel();
currentTicket = Steamworks.SteamUser.GetAuthSessionTicket();
return currentTicket;
}
public static bool OverlayCustomURL(string url)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)