Sync with upstream
This commit is contained in:
22
.github/workflows/publish-release.yml
vendored
22
.github/workflows/publish-release.yml
vendored
@@ -4,6 +4,20 @@ name: Publish release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: "The git ref to checkout, build from and release"
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: "The tag of the release"
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: "Prerelease"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
workflow_call:
|
||||
inputs:
|
||||
target:
|
||||
@@ -23,13 +37,13 @@ on:
|
||||
env:
|
||||
CI_DIR: 2049ef39-42a2-46d2-b513-ee6d2e3a7b15
|
||||
RELEASES: |
|
||||
windows:client:Windows/Client
|
||||
windows:server:Windows/Server
|
||||
linux:client:Linux/Client
|
||||
linux:server:Linux/Server
|
||||
mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
||||
mac:server:Mac/Server
|
||||
ARCHIVE_BASE_NAME: luacsforbarotrauma
|
||||
windows:client:Windows/Client
|
||||
linux:client:Linux/Client
|
||||
mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
||||
ARCHIVE_BASE_NAME: luacsforbarotraumaEP
|
||||
# XXX: these file names are subject to shell expansion.
|
||||
# Be careful when using special characters.
|
||||
ARCHIVE_FILES_SERVER: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -60,3 +60,4 @@ Deploy/DeployAll/PrivateKey.*
|
||||
|
||||
#Rider
|
||||
*.DotSettings.user
|
||||
.vscode/settings.json
|
||||
|
||||
@@ -3557,6 +3557,11 @@ namespace Barotrauma
|
||||
ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray()
|
||||
}));
|
||||
|
||||
commands.Add(new Command("ShowServerPerf", "Immediately log server performance info", (string[] args) =>
|
||||
{
|
||||
// TODO: Not yet :)
|
||||
}));
|
||||
|
||||
#if WINDOWS
|
||||
commands.Add(new Command("startdedicatedserver", "", (string[] args) =>
|
||||
{
|
||||
@@ -3590,6 +3595,14 @@ namespace Barotrauma
|
||||
}
|
||||
}));*/
|
||||
|
||||
AssignOnClientExecute(
|
||||
"ShowServerPerf",
|
||||
(string[] args) =>
|
||||
{
|
||||
GameMain.Client?.SendConsoleCommand("ShowServerPerf");
|
||||
}
|
||||
);
|
||||
|
||||
AssignOnClientExecute(
|
||||
"giveperm",
|
||||
(string[] args) =>
|
||||
|
||||
@@ -49,6 +49,9 @@ namespace Barotrauma
|
||||
private GUITextBox serverNameBox, passwordBox, maxPlayersBox;
|
||||
private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
|
||||
private GUIDropDown languageDropdown, serverExecutableDropdown;
|
||||
#if DEBUG
|
||||
private GUITickBox lenientHandshakeBox;
|
||||
#endif
|
||||
private readonly GUIButton joinServerButton, hostServerButton;
|
||||
|
||||
private readonly GUIFrame modsButtonContainer;
|
||||
@@ -1127,6 +1130,13 @@ namespace Barotrauma
|
||||
int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
|
||||
arguments.Add("-ownerkey");
|
||||
arguments.Add(ownerKey.ToString());
|
||||
#if DEBUG
|
||||
if (lenientHandshakeBox.Selected)
|
||||
{
|
||||
arguments.Add("-lenienthandshake");
|
||||
NetConfig.UseLenientHandshake = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (NetConfig.UseLenientHandshake)
|
||||
{
|
||||
@@ -1601,6 +1611,14 @@ namespace Barotrauma
|
||||
ToolTip = TextManager.Get("hostserverkarmasettingtooltip")
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
lenientHandshakeBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), "DEBUG: Lenient server startup timeouts")
|
||||
{
|
||||
Selected = true,
|
||||
ToolTip = "Start with more lenient Lidgren handshake timeouts. The server is more likely to start even when running multiple instances on the same machine under heavy load."
|
||||
};
|
||||
#endif
|
||||
|
||||
tickboxAreaLower.RectTransform.IsFixedSize = true;
|
||||
|
||||
//spacing
|
||||
|
||||
@@ -2748,6 +2748,19 @@ namespace Barotrauma
|
||||
}
|
||||
);
|
||||
|
||||
commands.Add(new Command("ShowServerPerf", "Immediately log server performance info in ServerMessage", (string[] args) =>
|
||||
{
|
||||
GameServer.Log(PerformanceMonitor.PM.ToString(), ServerLog.MessageType.ServerMessage);
|
||||
}));
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"ShowServerPerf",
|
||||
(senderClient, cursorWorldPos, args) =>
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage(PerformanceMonitor.PM.ToString(), senderClient);
|
||||
}
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) =>
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Xml.Linq;
|
||||
using MoonSharp.Interpreter;
|
||||
using System.Net;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -329,8 +330,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Stopwatch performanceCounterTimer = Stopwatch.StartNew();
|
||||
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
|
||||
PerformanceMonitor PM = new PerformanceMonitor();
|
||||
|
||||
long prevTicks = stopwatch.ElapsedTicks;
|
||||
while (ShouldRun)
|
||||
{
|
||||
@@ -380,6 +383,7 @@ namespace Barotrauma
|
||||
|
||||
Timing.Accumulator -= Timing.Step;
|
||||
updateCount++;
|
||||
PM.Update();
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
@@ -427,6 +431,9 @@ namespace Barotrauma
|
||||
updateCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceMonitor.PM.Dispose();
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
CloseServer();
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
|
||||
if (luaPackage == null)
|
||||
{
|
||||
GameMain.Server.SendChatMessage("Couldn't find the LuaCsForBarotrauma content package.", ChatMessageType.ServerMessageBox);
|
||||
GameMain.Server.SendChatMessage("Couldn't find the ProjectEP package.", ChatMessageType.ServerMessageBox);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
LuaCsLogger.LogError($"Unauthorized file access exception. This usually means you already have LuaCs installed. ${e}", LuaCsMessageOrigin.LuaCs);
|
||||
LuaCsLogger.LogError($"Unauthorized file access exception. This usually means you already have ProjectEP installed. ${e}", LuaCsMessageOrigin.LuaCs);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
GameMain.Server.SendChatMessage("Client-Side LuaCs installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);
|
||||
GameMain.Server.SendChatMessage("Client-Side ProjectEP installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1153,7 +1153,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
KickClient(c, errorStr);
|
||||
//Is it necessary to kick a client for a non-existing entity?
|
||||
//there are plenty of things have been done if received an non-existing entity update.
|
||||
//KickClient(c, errorStr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1217,18 +1219,25 @@ namespace Barotrauma.Networking
|
||||
|
||||
errorLines.Add("");
|
||||
errorLines.Add("EntitySpawner events:");
|
||||
foreach (var entityEvent in entityEventManager.UniqueEvents)
|
||||
try
|
||||
{
|
||||
if (entityEvent.Entity is EntitySpawner)
|
||||
foreach (var entityEvent in entityEventManager.UniqueEvents.ToList())
|
||||
{
|
||||
var spawnData = entityEvent.Data as EntitySpawner.SpawnOrRemove;
|
||||
errorLines.Add(
|
||||
entityEvent.ID + ": " +
|
||||
(spawnData is EntitySpawner.RemoveEntity ? "Remove " : "Create ") +
|
||||
spawnData.Entity.ToString() +
|
||||
" (" + spawnData.ID + ", " + spawnData.Entity.ID + ")");
|
||||
if (entityEvent.Entity is EntitySpawner)
|
||||
{
|
||||
var spawnData = entityEvent.Data as EntitySpawner.SpawnOrRemove;
|
||||
errorLines.Add(
|
||||
entityEvent.ID + ": " +
|
||||
(spawnData is EntitySpawner.RemoveEntity ? "Remove " : "Create ") +
|
||||
spawnData.Entity.ToString() +
|
||||
" (" + spawnData.ID + ", " + spawnData.Entity.ID + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorLines.Add("Failed to write EntitySpawner events.");
|
||||
}
|
||||
|
||||
errorLines.Add("");
|
||||
errorLines.Add("Last debug messages:");
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static Barotrauma.EosInterface.Ownership;
|
||||
|
||||
|
||||
// DO NOT TOUCH ANYTHING HERE
|
||||
// OR EVERYTHING WILL FAIL
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class ServerEntityEvent : NetEntityEvent
|
||||
{
|
||||
private IServerSerializable serializable;
|
||||
|
||||
|
||||
#if DEBUG
|
||||
public string StackTrace;
|
||||
#endif
|
||||
@@ -44,6 +51,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
class ServerEntityEventManager : NetEntityEventManager
|
||||
{
|
||||
static public ServerEntityEventManager SEM;
|
||||
|
||||
private readonly List<ServerEntityEvent> events;
|
||||
|
||||
//list of unique events (i.e. !IsDuplicate) created during the round
|
||||
@@ -102,60 +111,137 @@ namespace Barotrauma.Networking
|
||||
private readonly GameServer server;
|
||||
|
||||
private double lastEventCountHighWarning;
|
||||
|
||||
public ServerEntityEventManager(GameServer server)
|
||||
private class PendingCreateEvent
|
||||
{
|
||||
events = new List<ServerEntityEvent>();
|
||||
|
||||
this.server = server;
|
||||
|
||||
bufferedEvents = new List<BufferedEvent>();
|
||||
|
||||
uniqueEvents = new List<ServerEntityEvent>();
|
||||
|
||||
lastWarningTime = -10.0;
|
||||
public IServerSerializable Entity;
|
||||
public NetEntityEvent.IData Data;
|
||||
public PendingCreateEvent(IServerSerializable entity, NetEntityEvent.IData data)
|
||||
{
|
||||
Entity = entity;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<PendingCreateEvent> pendingCreateQueue;
|
||||
|
||||
private readonly Task createEventTask;
|
||||
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly SemaphoreSlim eventSignal = new SemaphoreSlim(0);
|
||||
|
||||
public ServerEntityEventManager(GameServer server)
|
||||
{
|
||||
events = new List<ServerEntityEvent>();
|
||||
this.server = server;
|
||||
bufferedEvents = new List<BufferedEvent>();
|
||||
uniqueEvents = new List<ServerEntityEvent>();
|
||||
pendingCreateQueue = new ConcurrentQueue<PendingCreateEvent>();
|
||||
lastWarningTime = -10.0;
|
||||
SEM = this;
|
||||
|
||||
createEventTask = Task.Run(() => CreateEventProcessorLoop(cancellationTokenSource.Token));
|
||||
}
|
||||
|
||||
private async Task CreateEventProcessorLoop(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await eventSignal.WaitAsync(100, token);
|
||||
ProcessPendingCreateEvents();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPendingCreateEvents()
|
||||
{
|
||||
// Dequeue and process all pending events currently in the queue.
|
||||
// Use a lock to synchronize modifications to shared lists / ID.
|
||||
while (pendingCreateQueue.TryDequeue(out PendingCreateEvent pending))
|
||||
{
|
||||
// The original CreateEvent logic (mostly unchanged) but executed under a lock
|
||||
if (pending == null || pending.Entity == null) { continue; }
|
||||
|
||||
var entity = pending.Entity;
|
||||
var extraData = pending.Data;
|
||||
|
||||
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
||||
if (extraData != null) newEvent.SetData(extraData);
|
||||
|
||||
bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
|
||||
//remove old events that have been sent to all clients, they are redundant now
|
||||
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||
if (GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||
{
|
||||
events.RemoveAll(e =>
|
||||
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
||||
e.CreateTime < Timing.TotalTime - server.ServerSettings.EventRemovalTime);
|
||||
}
|
||||
|
||||
bool duplicateFound = false;
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
{
|
||||
//we already have an identical event that's waiting to be sent
|
||||
// -> no need to add a new one
|
||||
if (events[i].IsDuplicate(newEvent) && !events[i].Sent)
|
||||
{
|
||||
duplicateFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateFound) { continue; }
|
||||
|
||||
ID++;
|
||||
|
||||
events.Add(newEvent);
|
||||
|
||||
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
||||
{
|
||||
//create a copy of the event and give it a new ID
|
||||
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
||||
uniqueEvent.SetData(extraData);
|
||||
|
||||
uniqueEvents.Add(uniqueEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void CreateEvent(IServerSerializable entity, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
if (!ValidateEntity(entity)) { return; }
|
||||
|
||||
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
||||
if (extraData != null) newEvent.SetData(extraData);
|
||||
|
||||
bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
|
||||
|
||||
//remove old events that have been sent to all clients, they are redundant now
|
||||
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||
if (GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||
{
|
||||
events.RemoveAll(e =>
|
||||
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
||||
e.CreateTime < Timing.TotalTime - server.ServerSettings.EventRemovalTime);
|
||||
}
|
||||
// enqueue and let background task handle the rest
|
||||
pendingCreateQueue.Enqueue(new PendingCreateEvent(entity, extraData));
|
||||
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
if (eventSignal.CurrentCount == 0)
|
||||
{
|
||||
//we already have an identical event that's waiting to be sent
|
||||
// -> no need to add a new one
|
||||
if (events[i].IsDuplicate(newEvent) && !events[i].Sent) return;
|
||||
}
|
||||
|
||||
ID++;
|
||||
|
||||
events.Add(newEvent);
|
||||
|
||||
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
||||
{
|
||||
//create a copy of the event and give it a new ID
|
||||
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
||||
uniqueEvent.SetData(extraData);
|
||||
|
||||
uniqueEvents.Add(uniqueEvent);
|
||||
eventSignal.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
eventSignal.Release();
|
||||
try
|
||||
{
|
||||
createEventTask?.Wait(2000);
|
||||
}
|
||||
catch (AggregateException) { }
|
||||
finally
|
||||
{
|
||||
cancellationTokenSource.Dispose();
|
||||
eventSignal.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Due to intensive access demend and time it takes to refactor, we use try-catch when facing thread-safety issue to skip to next update :(
|
||||
public void Update(List<Client> clients)
|
||||
{
|
||||
foreach (BufferedEvent bufferedEvent in bufferedEvents)
|
||||
@@ -203,29 +289,58 @@ namespace Barotrauma.Networking
|
||||
bufferedEvent.IsProcessed = true;
|
||||
}
|
||||
|
||||
var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync);
|
||||
if (inGameClients.Count > 0)
|
||||
List<Client> inGameClients = null;
|
||||
List<Client> midRoundSyncClients = null;
|
||||
Client ownerClient = null;
|
||||
|
||||
foreach (var c in clients)
|
||||
{
|
||||
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
|
||||
lastSentToAll = inGameClients[0].LastRecvEntityEventID;
|
||||
|
||||
if (server.OwnerConnection != null)
|
||||
if (c.InGame)
|
||||
{
|
||||
var owner = clients.Find(c => c.Connection == server.OwnerConnection);
|
||||
if (owner != null)
|
||||
if (c.NeedsMidRoundSync)
|
||||
{
|
||||
lastSentToAll = owner.LastRecvEntityEventID;
|
||||
(midRoundSyncClients ??= new List<Client>()).Add(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
(inGameClients ??= new List<Client>()).Add(c);
|
||||
}
|
||||
}
|
||||
inGameClients.ForEach(c =>
|
||||
if (server.OwnerConnection != null && c.Connection == server.OwnerConnection)
|
||||
{
|
||||
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; }
|
||||
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; }
|
||||
});
|
||||
lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
|
||||
ownerClient = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
||||
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
||||
if (inGameClients != null && inGameClients.Count > 0)
|
||||
{
|
||||
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
|
||||
lastSentToAll = ownerClient?.LastRecvEntityEventID ?? inGameClients[0].LastRecvEntityEventID;
|
||||
|
||||
foreach (var c in inGameClients)
|
||||
{
|
||||
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID))
|
||||
{
|
||||
lastSentToAll = c.LastRecvEntityEventID;
|
||||
}
|
||||
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone))
|
||||
{
|
||||
lastSentToAnyone = c.LastRecvEntityEventID;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lastSentToAnyoneTime = events.ToList().Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
lastSentToAnyoneTime = Timing.TotalTime;
|
||||
}
|
||||
|
||||
|
||||
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
||||
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
||||
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||
{
|
||||
lastWarningTime = Timing.TotalTime;
|
||||
@@ -235,10 +350,19 @@ namespace Barotrauma.Networking
|
||||
events.ForEach(e => e.ResetCreateTime());
|
||||
//TODO: reset clients if this happens, maybe do it if a majority are behind rather than all of them?
|
||||
}
|
||||
|
||||
|
||||
clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
|
||||
|
||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
ServerEntityEvent firstEventToResend;
|
||||
try
|
||||
{
|
||||
firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
}
|
||||
catch
|
||||
{
|
||||
firstEventToResend = null;
|
||||
}
|
||||
|
||||
if (firstEventToResend != null &&
|
||||
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration &&
|
||||
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > server.ServerSettings.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > server.ServerSettings.OldEventKickTime))
|
||||
@@ -247,19 +371,19 @@ namespace Barotrauma.Networking
|
||||
// kick everyone that hasn't received it yet, this is way too old
|
||||
// UNLESS the event was created when the client was still midround syncing,
|
||||
// in which case we'll wait until the timeout runs out before kicking the client
|
||||
List<Client> toKick = inGameClients.FindAll(c =>
|
||||
List<Client> toKick = inGameClients.FindAll(c =>
|
||||
NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
|
||||
(!c.NeedsMidRoundSync || firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
|
||||
toKick.ForEach(c =>
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event ("
|
||||
+ (c.LastRecvEntityEventID + 1).ToString() +
|
||||
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
|
||||
}
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event ("
|
||||
+ (c.LastRecvEntityEventID + 1).ToString() +
|
||||
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -277,11 +401,21 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
var timedOutClients = clients.FindAll(c => c.Connection != GameMain.Server.OwnerConnection && c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
|
||||
foreach (Client timedOutClient in timedOutClients)
|
||||
if (midRoundSyncClients != null)
|
||||
{
|
||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(timedOutClient) + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error);
|
||||
GameMain.Server.DisconnectClient(timedOutClient, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
|
||||
foreach (var c in midRoundSyncClients)
|
||||
{
|
||||
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID))
|
||||
{
|
||||
lastSentToAll = (ushort)(c.FirstNewEventID - 1);
|
||||
}
|
||||
|
||||
if (c.Connection != GameMain.Server.OwnerConnection && Timing.TotalTime > c.MidRoundSyncTimeOut)
|
||||
{
|
||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + ". Syncing took too long.", ServerLog.MessageType.Error);
|
||||
GameMain.Server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bufferedEvents.RemoveAll(b => b.IsProcessed);
|
||||
@@ -346,7 +480,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
segmentTable.StartNewSegment(ServerNetSegment.EntityEventInitial);
|
||||
segmentTable.StartNewSegment(ServerNetSegment.EntityEventInitial);
|
||||
msg.WriteUInt16(client.UnreceivedEntityEventCount);
|
||||
msg.WriteUInt16(client.FirstNewEventID);
|
||||
|
||||
@@ -553,10 +687,10 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
var clientEntity = entity as IClientSerializable;
|
||||
if (clientEntity == null) return;
|
||||
|
||||
|
||||
clientEntity.ServerEventRead(buffer, sender);
|
||||
}
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ID = 0;
|
||||
|
||||
180
Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs
Normal file
180
Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class PerformanceMonitor
|
||||
{
|
||||
static public PerformanceMonitor PM;
|
||||
|
||||
private Stopwatch PMStopwatch = new Stopwatch();
|
||||
|
||||
private double tickratetimer = 0;
|
||||
|
||||
private double tickrate60stimer = 0;
|
||||
|
||||
private static Queue<double> tickrate60s = new Queue<double>(61);
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get{ return Item.ItemList.Count; }
|
||||
}
|
||||
|
||||
public int CharacterCount
|
||||
{
|
||||
get { return Character.CharacterList.Count; }
|
||||
}
|
||||
|
||||
public int PhysicsBodyCount
|
||||
{
|
||||
get { return PhysicsBody.List.Count; }
|
||||
}
|
||||
public int ConnectClients
|
||||
{
|
||||
get { return GameMain.Server.ConnectedClients.Count; }
|
||||
}
|
||||
|
||||
public double RealTickRate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long TotalTicks
|
||||
{
|
||||
get;set;
|
||||
}
|
||||
|
||||
public int LastSecondTicks
|
||||
{
|
||||
get; set;
|
||||
} = 0;
|
||||
|
||||
public float AverageTickRate
|
||||
{
|
||||
get
|
||||
{
|
||||
return TotalTicks / (float)TotalTimeElapsed * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
public double AverageTickRate10s
|
||||
{
|
||||
get
|
||||
{
|
||||
return tickrate60s.Count > 0 ? tickrate60s.Average() : 60;
|
||||
}
|
||||
}
|
||||
|
||||
public double TotalTimeElapsed
|
||||
{
|
||||
get
|
||||
{
|
||||
return PMStopwatch.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan TimeElapsed
|
||||
{
|
||||
get
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(TotalTimeElapsed);
|
||||
}
|
||||
}
|
||||
|
||||
public float MemoryUsage
|
||||
{
|
||||
get
|
||||
{
|
||||
Process proc = Process.GetCurrentProcess();
|
||||
float memory = MathF.Round(proc.PrivateMemorySize64 / (1024 * 1024), 2);
|
||||
proc.Dispose();
|
||||
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
|
||||
public double TickRateLow
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public double TickRateHigh
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public PerformanceMonitor()
|
||||
{
|
||||
PM = this;
|
||||
RealTickRate = 60;
|
||||
TotalTicks = 0;
|
||||
LastSecondTicks = 60;
|
||||
TickRateLow = 60;
|
||||
TickRateHigh = 60;
|
||||
PMStopwatch.Start();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
TotalTicks += 1;
|
||||
LastSecondTicks += 1;
|
||||
if (tickrate60s.Count > 60)
|
||||
{
|
||||
tickrate60s.Dequeue();
|
||||
}
|
||||
if (TotalTimeElapsed - 1000 >= tickratetimer)
|
||||
{
|
||||
RealTickRate = LastSecondTicks / (TotalTimeElapsed - tickratetimer) * 1000;
|
||||
tickrate60s.Enqueue(RealTickRate);
|
||||
tickratetimer = TotalTimeElapsed;
|
||||
LastSecondTicks = 0;
|
||||
}
|
||||
if (TotalTimeElapsed - 60000 >= tickrate60stimer)
|
||||
{
|
||||
GameServer.Log(PM.ToString(), ServerLog.MessageType.ServerMessage);
|
||||
TickRateLow = 60;
|
||||
TickRateHigh = 60;
|
||||
tickrate60stimer = TotalTimeElapsed;
|
||||
}
|
||||
if (RealTickRate > TickRateHigh)
|
||||
{
|
||||
TickRateHigh = RealTickRate;
|
||||
}
|
||||
if (RealTickRate < TickRateLow)
|
||||
{
|
||||
TickRateLow = RealTickRate;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PMStopwatch.Reset();
|
||||
PM = null;
|
||||
}
|
||||
override public string ToString()
|
||||
{
|
||||
return $"Server Performance Info \n" +
|
||||
$"Item Count: {ItemCount}\n" +
|
||||
$"Character Count: {CharacterCount}\n" +
|
||||
$"Clients Count {ConnectClients}\n " +
|
||||
$"PhysicsBody Count: {PhysicsBodyCount}\n" +
|
||||
$"Tick Rate: {RealTickRate}\n" +
|
||||
$"Min Tick Rate: {TickRateLow}\n" +
|
||||
$"Max Tick Rate: {TickRateHigh}\n" +
|
||||
$"Total Ticks: {TotalTicks}\n" +
|
||||
$"All time Average Tick Rate: {AverageTickRate}\n" +
|
||||
$"60s Average Tick Rate: {AverageTickRate10s}\n" +
|
||||
$"Server Run Time: {TimeElapsed}\n" +
|
||||
$"Memory Usage: {MemoryUsage}\n";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
GameMain.ShouldRun = false;
|
||||
};
|
||||
#endif
|
||||
Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
|
||||
Console.WriteLine("Barotrauma Dedicated Server(EP) " + GameMain.Version +
|
||||
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
|
||||
if (Console.IsOutputRedirected)
|
||||
{
|
||||
|
||||
@@ -1193,6 +1193,10 @@ namespace Barotrauma
|
||||
|
||||
public void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles = true)
|
||||
{
|
||||
// Hopefully this will fix some crashes :(
|
||||
// If Collider was null then no need to procced: nothing is there already
|
||||
if (Collider == null) { return; }
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
@@ -1214,6 +1218,7 @@ namespace Barotrauma
|
||||
|
||||
character.DisableImpactDamageTimer = 0.25f;
|
||||
|
||||
// Why they did null check below but didn't do it here????
|
||||
SetPosition(Collider.SimPosition + moveAmount);
|
||||
character.CursorPosition += moveAmount;
|
||||
|
||||
@@ -2235,8 +2240,9 @@ namespace Barotrauma
|
||||
if (limb == null)
|
||||
{
|
||||
// Didn't seek or find a (valid) limb of the matching type. If there's multiple limbs of the same type, check the other limbs.
|
||||
foreach (var l in limbs)
|
||||
foreach (var l in Limbs)
|
||||
{
|
||||
if (l == null) { continue; }
|
||||
if (l.Removed) { continue; }
|
||||
if (useSecondaryType)
|
||||
{
|
||||
|
||||
@@ -3442,21 +3442,9 @@ namespace Barotrauma
|
||||
|
||||
characterUpdateTick++;
|
||||
|
||||
if (characterUpdateTick % CharacterUpdateInterval == 0)
|
||||
for (int i = 0; i < CharacterList.Count; i++)
|
||||
{
|
||||
for (int i = 0; i < CharacterList.Count; i++)
|
||||
{
|
||||
if (LuaCsSetup.Instance.Game.UpdatePriorityCharacters.Contains(CharacterList[i])) continue;
|
||||
|
||||
CharacterList[i].Update(deltaTime * CharacterUpdateInterval, cam);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Character character in LuaCsSetup.Instance.Game.UpdatePriorityCharacters)
|
||||
{
|
||||
if (character.Removed) { continue; }
|
||||
Debug.Assert(character is { Removed: false });
|
||||
character.Update(deltaTime, cam);
|
||||
CharacterList[i].Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
|
||||
@@ -505,17 +505,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
|
||||
{
|
||||
// This is a % resistance (0 to 1.0)
|
||||
float resistance = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
||||
lock (afflictions) {
|
||||
// This is a % resistance (0 to 1.0)
|
||||
float resistance = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
||||
}
|
||||
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
||||
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
||||
// The returned value is calculated to be a % resistance again
|
||||
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
||||
}
|
||||
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
||||
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
||||
// The returned value is calculated to be a % resistance again
|
||||
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
||||
}
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
|
||||
@@ -282,16 +282,27 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && Entity.Spawner != null && createNetworkEvents)
|
||||
{
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
|
||||
try
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.ToList().Any(ev => ev.Entity == item))
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if SERVER
|
||||
Networking.GameServer.Log("Try making UniqueEvents snapshot failed", Networking.ServerLog.MessageType.Error);
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
#endif
|
||||
if (itemElement.GetAttributeBool("equip", false))
|
||||
|
||||
@@ -170,16 +170,28 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && Entity.Spawner != null)
|
||||
{
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
|
||||
try
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.ToList().Any(ev => ev.Entity == item))
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
}
|
||||
}
|
||||
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
catch
|
||||
{
|
||||
#if SERVER
|
||||
Networking.GameServer.Log("Try making UniqueEvents snapshot failed", Networking.ServerLog.MessageType.Error);
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ namespace Barotrauma.Items.Components
|
||||
amountMultiplier = (int)itemCreationMultiplier.Value;
|
||||
}
|
||||
|
||||
ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructed, 1f);
|
||||
|
||||
if (targetItem.Prefab.RandomDeconstructionOutput)
|
||||
{
|
||||
int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
|
||||
|
||||
@@ -7,6 +7,7 @@ using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MoonSharp.Interpreter;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -747,7 +748,6 @@ namespace Barotrauma
|
||||
waterFlowThisFrame = 0.0f;
|
||||
}
|
||||
|
||||
private static readonly HashSet<Hull> checkedHulls = new HashSet<Hull>();
|
||||
|
||||
/// <summary>
|
||||
/// Simulates water flow from the source to all the hulls it's connected to across the sub, as if the water was coming directly from outside.
|
||||
@@ -755,7 +755,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
void SimulateWaterFlowFromOutsideToConnectedHulls(Hull hull, float maxFlow, float deltaTime)
|
||||
{
|
||||
checkedHulls.Clear();
|
||||
List<Hull> checkedHulls = new List<Hull>();
|
||||
checkedHulls.Add(hull);
|
||||
foreach (var connectedGap in hull.ConnectedGaps)
|
||||
{
|
||||
@@ -766,7 +766,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
static void SimulateWaterFlowFromOutsideToConnectedHullsRecursive(Hull targetHull, Gap gap, HashSet<Hull> checkedHulls, Hull originHull, float maxFlow, float deltaTime)
|
||||
static void SimulateWaterFlowFromOutsideToConnectedHullsRecursive(Hull targetHull, Gap gap, List<Hull> checkedHulls, Hull originHull, float maxFlow, float deltaTime)
|
||||
{
|
||||
const float decay = 0.95f;
|
||||
|
||||
@@ -814,7 +814,12 @@ namespace Barotrauma
|
||||
if (outsideCollisionBlocker == null) { return false; }
|
||||
if (IsRoomToRoom || Submarine == null || open <= 0.0f || linkedTo.Count == 0 || linkedTo[0] is not Hull)
|
||||
{
|
||||
outsideCollisionBlocker.Enabled = false;
|
||||
SingleThreadWorker.GlobalWorker.AddAction(() =>
|
||||
{
|
||||
if (outsideCollisionBlocker == null) { return; }
|
||||
outsideCollisionBlocker.Enabled = false;
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -997,8 +1002,6 @@ namespace Barotrauma
|
||||
base.Remove();
|
||||
GapList.Remove(this);
|
||||
|
||||
checkedHulls.Clear();
|
||||
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
hull.ConnectedGaps.Remove(this);
|
||||
|
||||
@@ -479,7 +479,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList)
|
||||
{
|
||||
ContactEdge contactEdge = fixture.Body.ContactList;
|
||||
ContactEdge contactEdge = fixture.Body.ContactList == null ? null: fixture.Body.ContactList.CreateCopy();
|
||||
while (contactEdge != null)
|
||||
{
|
||||
if (contactEdge.Contact != null &&
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -29,7 +30,7 @@ namespace Barotrauma
|
||||
protected readonly List<Upgrade> Upgrades = new List<Upgrade>();
|
||||
|
||||
public readonly HashSet<Identifier> DisallowedUpgradeSet = new HashSet<Identifier>();
|
||||
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes)]
|
||||
public string DisallowedUpgrades
|
||||
{
|
||||
@@ -84,11 +85,11 @@ namespace Barotrauma
|
||||
public bool IsHighlighted
|
||||
{
|
||||
get { return isHighlighted || ExternalHighlight; }
|
||||
set
|
||||
set
|
||||
{
|
||||
if (value != isHighlighted)
|
||||
{
|
||||
isHighlighted = value;
|
||||
isHighlighted = value;
|
||||
CheckIsHighlighted();
|
||||
}
|
||||
}
|
||||
@@ -531,7 +532,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (originalWire.Connections.Any(c => c != null) &&
|
||||
(cloneWire.Connections[0] == null || cloneWire.Connections[1] == null) &&
|
||||
(cloneWire.Connections[0] == null || cloneWire.Connections[1] == null) &&
|
||||
cloneItem.GetComponent<DockingPort>() == null)
|
||||
{
|
||||
if (!clones.Any(c => (c as Item)?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(cloneWire) ?? false))
|
||||
@@ -639,98 +640,106 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Call Update() on every object in Entity.list
|
||||
/// </summary>
|
||||
public static void UpdateAll(float deltaTime, Camera cam)
|
||||
public static void UpdateAll(float deltaTime, Camera cam, ParallelOptions parallelOptions)
|
||||
{
|
||||
mapEntityUpdateTick++;
|
||||
|
||||
#if CLIENT
|
||||
var sw = new System.Diagnostics.Stopwatch();
|
||||
sw.Start();
|
||||
#endif
|
||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
||||
{
|
||||
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
// Buffer lists to avoid repeated allocations
|
||||
var hullList = Hull.HullList.ToList();
|
||||
var structureList = Structure.WallList.ToList();
|
||||
|
||||
List<Gap> gapList = Gap.GapList.ToList();
|
||||
List<Gap> shuffledGaps = new List<Gap>(gapList?.OrderBy(g => Rand.Int(int.MaxValue)));
|
||||
// In case if it failed, but why it would fail?
|
||||
shuffledGaps = shuffledGaps ?? gapList;
|
||||
|
||||
var itemList = Item.ItemList.ToList();
|
||||
|
||||
// First phase: parallel updates that have no order dependencies
|
||||
Parallel.Invoke(parallelOptions,
|
||||
() =>
|
||||
{
|
||||
hull.Update(deltaTime * MapEntityUpdateInterval, cam);
|
||||
// basically nothing here is thread-safe so
|
||||
foreach (var hull in hullList)
|
||||
{
|
||||
hull.Update(deltaTime, cam);
|
||||
}
|
||||
},
|
||||
// Structure parallel update
|
||||
() =>
|
||||
{
|
||||
Parallel.ForEach(structureList, parallelOptions, structure =>
|
||||
{
|
||||
structure.Update(deltaTime, cam);
|
||||
});
|
||||
},
|
||||
() =>
|
||||
//update gaps in random order, because otherwise in rooms with multiple gaps
|
||||
//the water/air will always tend to flow through the first gap in the list,
|
||||
//which may lead to weird behavior like water draining down only through
|
||||
//one gap in a room even if there are several
|
||||
|
||||
// moved waterflow reset here to see if we can reduce at least some time
|
||||
{
|
||||
// PLEASE WORK
|
||||
Parallel.ForEach(shuffledGaps, parallelOptions, gap =>
|
||||
{
|
||||
gap.ResetWaterFlowThisFrame();
|
||||
gap.Update(deltaTime, cam);
|
||||
});
|
||||
},
|
||||
// Powered components update
|
||||
() =>
|
||||
{
|
||||
Powered.UpdatePower(deltaTime);
|
||||
}
|
||||
);
|
||||
|
||||
SingleThreadWorker.GlobalWorker.RunActions();
|
||||
|
||||
#if CLIENT
|
||||
Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam);
|
||||
// Hull Cheats need to be executed after Hull update
|
||||
Hull.UpdateCheats(deltaTime, cam);
|
||||
#endif
|
||||
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
structure.Update(deltaTime * MapEntityUpdateInterval, cam);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
gap.ResetWaterFlowThisFrame();
|
||||
}
|
||||
//update gaps in random order, because otherwise in rooms with multiple gaps
|
||||
//the water/air will always tend to flow through the first gap in the list,
|
||||
//which may lead to weird behavior like water draining down only through
|
||||
//one gap in a room even if there are several
|
||||
foreach (Gap gap in Gap.GapList.OrderBy(g => Rand.Int(int.MaxValue)))
|
||||
{
|
||||
gap.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
if (mapEntityUpdateTick % PoweredUpdateInterval == 0)
|
||||
{
|
||||
Powered.UpdatePower(deltaTime * PoweredUpdateInterval);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Misc", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
#endif
|
||||
|
||||
// Item update (Item.Update() is not thread-safe and must be executed on the main thread)
|
||||
Item.UpdatePendingConditionUpdates(deltaTime);
|
||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
||||
{
|
||||
Item lastUpdatedItem = null;
|
||||
|
||||
try
|
||||
Item lastUpdatedItem = null;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (Item item in itemList)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (LuaCsSetup.Instance.Game.UpdatePriorityItems.Contains(item)) { continue; }
|
||||
lastUpdatedItem = item;
|
||||
item.Update(deltaTime * MapEntityUpdateInterval, cam);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"MapEntity.UpdateAll:ItemUpdateInvalidOperation",
|
||||
GameAnalyticsManager.ErrorSeverity.Critical,
|
||||
$"Error while updating item {lastUpdatedItem?.Name ?? "null"}: {e.Message}");
|
||||
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
|
||||
lastUpdatedItem = item;
|
||||
item.Update(deltaTime, cam);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in LuaCsSetup.Instance.Game.UpdatePriorityItems)
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
if (item.Removed) continue;
|
||||
|
||||
item.Update(deltaTime, cam);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"MapEntity.UpdateAll:ItemUpdateInvalidOperation",
|
||||
GameAnalyticsManager.ErrorSeverity.Critical,
|
||||
$"Error while updating item {lastUpdatedItem?.Name ?? "null"}: {e.Message}");
|
||||
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
|
||||
}
|
||||
|
||||
UpdateAllProjSpecific(deltaTime);
|
||||
Spawner?.Update();
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Items", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
#endif
|
||||
|
||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
||||
{
|
||||
UpdateAllProjSpecific(deltaTime * MapEntityUpdateInterval);
|
||||
|
||||
Spawner?.Update();
|
||||
}
|
||||
}
|
||||
|
||||
static partial void UpdateAllProjSpecific(float deltaTime);
|
||||
@@ -783,7 +792,7 @@ namespace Barotrauma
|
||||
var tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>());
|
||||
if (tags.Contains(Tags.HiddenItemContainer))
|
||||
{
|
||||
containsHiddenContainers = true;
|
||||
containsHiddenContainers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -828,7 +837,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (t == typeof(Item) && !containsHiddenContainers && identifier == "vent" &&
|
||||
else if (t == typeof(Item) && !containsHiddenContainers && identifier == "vent" &&
|
||||
submarine.Info.Type == SubmarineType.Player && !submarine.Info.HasTag(SubmarineTag.Shuttle))
|
||||
{
|
||||
if (!hiddenContainerCreated)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
@@ -426,7 +427,17 @@ namespace Barotrauma
|
||||
if (points[0].Y > Body.SimPosition.Y &&
|
||||
!Character.CharacterList.Any(c => c.Submarine == otherSubmarine && !c.IsIncapacitated && c.TeamID == otherSubmarine.TeamID))
|
||||
{
|
||||
otherSubmarine.GetConnectedSubs().ForEach(s => s.SubBody.forceUpwardsTimer += deltaTime);
|
||||
try
|
||||
{
|
||||
otherSubmarine.GetConnectedSubs().ToList().ForEach(s => s.SubBody.forceUpwardsTimer += deltaTime);
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if SERVER
|
||||
GameServer.Log("Try making UniqueEvents snapshot failed", ServerLog.MessageType.Error);
|
||||
#endif
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,9 @@ namespace Barotrauma.Networking
|
||||
exceptionMsg += " Child process has not exited.";
|
||||
}
|
||||
#endif
|
||||
#if !DEBUG
|
||||
throw new Exception(exceptionMsg);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
//#define RUN_PHYSICS_IN_SEPARATE_THREAD
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Threading;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
|
||||
#if DEBUG && CLIENT
|
||||
@@ -16,8 +18,11 @@ namespace Barotrauma
|
||||
{
|
||||
partial class GameScreen : Screen
|
||||
{
|
||||
private object updateLock = new object();
|
||||
private double physicsTime;
|
||||
// Use default instead. Hopefully this wont cause issues in long-running servers.
|
||||
private static readonly ParallelOptions parallelOptions = new ParallelOptions
|
||||
{
|
||||
//MaxDegreeOfParallelism = Math.Max(4, Environment.ProcessorCount - 1)
|
||||
};
|
||||
|
||||
#if CLIENT
|
||||
private readonly Camera cam;
|
||||
@@ -54,13 +59,13 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
cam.Position = Character.Controlled.WorldPosition;
|
||||
cam.UpdateTransform(true);
|
||||
Cam.Position = Character.Controlled.WorldPosition;
|
||||
Cam.UpdateTransform(true);
|
||||
}
|
||||
else if (Submarine.MainSub != null)
|
||||
{
|
||||
cam.Position = Submarine.MainSub.WorldPosition;
|
||||
cam.UpdateTransform(true);
|
||||
Cam.Position = Submarine.MainSub.WorldPosition;
|
||||
Cam.UpdateTransform(true);
|
||||
}
|
||||
GameMain.GameSession?.CrewManager?.ResetCrewListOpenState();
|
||||
ChatBox.ResetChatBoxOpenState();
|
||||
@@ -69,14 +74,6 @@ namespace Barotrauma
|
||||
|
||||
MapEntity.ClearHighlightedEntities();
|
||||
|
||||
#if RUN_PHYSICS_IN_SEPARATE_THREAD
|
||||
var physicsThread = new Thread(ExecutePhysics)
|
||||
{
|
||||
Name = "Physics thread",
|
||||
IsBackground = true
|
||||
};
|
||||
physicsThread.Start();
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Deselect()
|
||||
@@ -104,19 +101,16 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
#if RUN_PHYSICS_IN_SEPARATE_THREAD
|
||||
physicsTime += deltaTime;
|
||||
lock (updateLock)
|
||||
{
|
||||
#endif
|
||||
|
||||
var submarines = Submarine.Loaded.ToList();
|
||||
var physicsBodies = PhysicsBody.List.ToList();
|
||||
|
||||
#if DEBUG && CLIENT
|
||||
if (GameMain.GameSession != null && !DebugConsole.IsOpen && GUI.KeyboardDispatcher.Subscriber == null)
|
||||
{
|
||||
if (GameMain.GameSession.Level != null && GameMain.GameSession.Submarine != null)
|
||||
{
|
||||
Submarine closestSub = Submarine.FindClosest(cam.WorldViewCenter) ?? GameMain.GameSession.Submarine;
|
||||
Submarine closestSub = Submarine.FindClosest(Cam.WorldViewCenter) ?? GameMain.GameSession.Submarine;
|
||||
|
||||
Vector2 targetMovement = Vector2.Zero;
|
||||
if (PlayerInput.KeyDown(Keys.I)) { targetMovement.Y += 1.0f; }
|
||||
@@ -138,38 +132,38 @@ namespace Barotrauma
|
||||
|
||||
GameTime += deltaTime;
|
||||
|
||||
foreach (PhysicsBody body in PhysicsBody.List)
|
||||
Parallel.ForEach(physicsBodies, parallelOptions, body =>
|
||||
{
|
||||
//update character (colliders) regardless if they're enabled or not, so that the draw position is updated
|
||||
//necessary to sync the character's position even if the character is ragdolled and the collider is disabled
|
||||
if ((body.Enabled || body.UserData is Character) &&
|
||||
body.BodyType != BodyType.Static)
|
||||
{
|
||||
body.Update();
|
||||
}
|
||||
}
|
||||
if ((body.Enabled || body.UserData is Character) &&
|
||||
body.BodyType != BodyType.Static)
|
||||
{
|
||||
body.Update();
|
||||
}
|
||||
});
|
||||
|
||||
MapEntity.ClearHighlightedEntities();
|
||||
|
||||
#if CLIENT
|
||||
var sw = new System.Diagnostics.Stopwatch();
|
||||
sw.Start();
|
||||
#endif
|
||||
|
||||
GameMain.GameSession?.Update((float)deltaTime);
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:GameSession", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
sw.Restart();
|
||||
|
||||
GameMain.ParticleManager?.Update((float)deltaTime);
|
||||
|
||||
GameMain.ParticleManager.Update((float)deltaTime);
|
||||
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:Particles", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:Particle", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
|
||||
if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, cam);
|
||||
#endif
|
||||
|
||||
if (Level.Loaded != null) { Level.Loaded.Update((float)deltaTime, Cam); }
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:Level", sw.ElapsedTicks);
|
||||
|
||||
@@ -177,7 +171,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (controlled.SelectedItem != null && controlled.CanInteractWith(controlled.SelectedItem))
|
||||
{
|
||||
controlled.SelectedItem.UpdateHUD(cam, controlled, (float)deltaTime);
|
||||
controlled.SelectedItem.UpdateHUD(Cam, controlled, (float)deltaTime);
|
||||
}
|
||||
if (controlled.Inventory != null)
|
||||
{
|
||||
@@ -185,7 +179,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (controlled.HasEquippedItem(item))
|
||||
{
|
||||
item.UpdateHUD(cam, controlled, (float)deltaTime);
|
||||
item.UpdateHUD(Cam, controlled, (float)deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,12 +187,8 @@ namespace Barotrauma
|
||||
|
||||
sw.Restart();
|
||||
|
||||
Character.UpdateAll((float)deltaTime, cam);
|
||||
#elif SERVER
|
||||
if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance);
|
||||
Character.UpdateAll((float)deltaTime, Camera.Instance);
|
||||
#endif
|
||||
|
||||
Character.UpdateAll((float)deltaTime, Cam);
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
@@ -206,9 +196,11 @@ namespace Barotrauma
|
||||
sw.Restart();
|
||||
#endif
|
||||
|
||||
//StatusEffect.UpdateAll is not thread-safe and must be executed on the main thread
|
||||
StatusEffect.UpdateAll((float)deltaTime);
|
||||
|
||||
#if CLIENT
|
||||
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:StatusEffects", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
@@ -219,9 +211,6 @@ namespace Barotrauma
|
||||
Vector2 targetPos = Lights.LightManager.ViewTarget.WorldPosition;
|
||||
if (Lights.LightManager.ViewTarget == Character.Controlled)
|
||||
{
|
||||
//take the NetworkPositionErrorOffset into account, meaning the camera is positioned
|
||||
//where we've smoothed out the draw position of the character after a positional correction,
|
||||
//instead of where the character's collider actually is
|
||||
targetPos += ConvertUnits.ToDisplayUnits(Character.Controlled.AnimController.Collider.NetworkPositionErrorOffset);
|
||||
if (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen || ConversationAction.IsDialogOpen)
|
||||
{
|
||||
@@ -236,48 +225,43 @@ namespace Barotrauma
|
||||
}
|
||||
Vector2 screenOffset = screenTargetPos - new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight / 2);
|
||||
screenOffset.Y = -screenOffset.Y;
|
||||
targetPos -= screenOffset / cam.Zoom;
|
||||
targetPos -= screenOffset / Cam.Zoom;
|
||||
}
|
||||
}
|
||||
cam.TargetPos = targetPos;
|
||||
Cam.TargetPos = targetPos;
|
||||
}
|
||||
|
||||
cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory);
|
||||
Cam.MoveCamera((float)deltaTime, allowZoom: GUI.MouseOn == null && !Inventory.IsMouseOnInventory);
|
||||
|
||||
Character.Controlled?.UpdateLocalCursor(cam);
|
||||
Character.Controlled?.UpdateLocalCursor(Cam);
|
||||
#endif
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
foreach (Submarine sub in submarines)
|
||||
{
|
||||
sub.SetPrevTransform(sub.Position);
|
||||
}
|
||||
|
||||
foreach (PhysicsBody body in PhysicsBody.List)
|
||||
Parallel.ForEach(physicsBodies, parallelOptions, body =>
|
||||
{
|
||||
if (body.Enabled && body.BodyType != FarseerPhysics.BodyType.Static)
|
||||
{
|
||||
body.SetPrevTransform(body.SimPosition, body.Rotation);
|
||||
if (body.Enabled && body.BodyType != FarseerPhysics.BodyType.Static)
|
||||
{
|
||||
body.SetPrevTransform(body.SimPosition, body.Rotation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#if CLIENT
|
||||
MapEntity.UpdateAll((float)deltaTime, cam);
|
||||
#elif SERVER
|
||||
MapEntity.UpdateAll((float)deltaTime, Camera.Instance);
|
||||
#endif
|
||||
MapEntity.UpdateAll((float)deltaTime, Cam, parallelOptions);
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
#endif
|
||||
//Character.UpdateAnimAll is not thread-safe and must be executed on the main thread
|
||||
Character.UpdateAnimAll((float)deltaTime);
|
||||
|
||||
#if CLIENT
|
||||
Ragdoll.UpdateAll((float)deltaTime, cam);
|
||||
#elif SERVER
|
||||
Ragdoll.UpdateAll((float)deltaTime, Camera.Instance);
|
||||
#endif
|
||||
|
||||
Ragdoll.UpdateAll((float)deltaTime, Cam);
|
||||
SingleThreadWorker.GlobalWorker.RunActions();
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
@@ -285,7 +269,7 @@ namespace Barotrauma
|
||||
sw.Restart();
|
||||
#endif
|
||||
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
foreach (Submarine sub in submarines)
|
||||
{
|
||||
sub.Update((float)deltaTime);
|
||||
}
|
||||
@@ -295,8 +279,6 @@ namespace Barotrauma
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:Submarine", sw.ElapsedTicks);
|
||||
sw.Restart();
|
||||
#endif
|
||||
|
||||
#if !RUN_PHYSICS_IN_SEPARATE_THREAD
|
||||
try
|
||||
{
|
||||
GameMain.World.Step((float)Timing.Step);
|
||||
@@ -307,35 +289,14 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError(errorMsg, e);
|
||||
GameAnalyticsManager.AddErrorEventOnce("GameScreen.Update:WorldLockedException" + e.Message, GameAnalyticsManager.ErrorSeverity.Critical, errorMsg);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if CLIENT
|
||||
sw.Stop();
|
||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:Physics", sw.ElapsedTicks);
|
||||
#endif
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
#if RUN_PHYSICS_IN_SEPARATE_THREAD
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(double deltaTime);
|
||||
|
||||
private void ExecutePhysics()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (physicsTime >= Timing.Step)
|
||||
{
|
||||
lock (updateLock)
|
||||
{
|
||||
GameMain.World.Step((float)Timing.Step);
|
||||
physicsTime -= Timing.Step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class SingleThreadWorker
|
||||
{
|
||||
private ConcurrentQueue<Action> ActionQueue;
|
||||
|
||||
public static SingleThreadWorker GlobalWorker = new SingleThreadWorker();
|
||||
|
||||
/// <summary>
|
||||
/// Initilize a SingleThreadWorker
|
||||
/// SingleThreadWorker or STW for short is a FIFO queue ensure single-thread execution of a series of actions.
|
||||
/// </summary>
|
||||
public SingleThreadWorker()
|
||||
{
|
||||
ActionQueue = new ConcurrentQueue<Action>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a pending action in a STW queue
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
public void AddAction(Action action)
|
||||
{
|
||||
ActionQueue.Enqueue(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run all pending actions in the STW queue
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
public void RunActions()
|
||||
{
|
||||
while (ActionQueue.TryDequeue(out Action action))
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Just try-catch and do nothing but print errorlogs. We cannot afford crashing the game.
|
||||
ConsoleColor originalForeground = Console.ForegroundColor;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"WARNING: Error occurred when running Single Thread Actions. " +
|
||||
$"If the server didn't crash or stop responding then this should be fine \n{e}");
|
||||
Console.ForegroundColor = Console.ForegroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -349,6 +350,7 @@ namespace FarseerPhysics.Dynamics
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create all proxies.
|
||||
/// </summary>
|
||||
@@ -1409,4 +1411,4 @@ namespace FarseerPhysics.Dynamics
|
||||
return body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
75
README.md
75
README.md
@@ -1,11 +1,24 @@
|
||||
# LuaCsForBarotrauma
|
||||
This is a Barotrauma modification that adds Lua and Cs modding support.
|
||||
# LuaCsForBarotrauma Enhanced Performence Project
|
||||
|
||||
### Online Documentation: https://evilfactory.github.io/LuaCsForBarotrauma
|
||||
### VS Code Documentation: https://gitee.com/zhurengong/btlua-docs
|
||||
### Discord: https://discord.gg/f9zvNNuxu9
|
||||
> ⚠ **Warning:** This release is only available for server-side use and is not recommended to run on the client. Make sure that compatibility is adequately tested before deployment.
|
||||
|
||||
### This project uses a fork of Moonsharp: https://github.com/evilfactory/moonsharp
|
||||
This is a LuaCsForBarotrauma modification that adds Multi-Thread and Multi-Core support.
|
||||
|
||||
### This project uses a fork of Moonsharp from EvilFactory: https://github.com/evilfactory/moonsharp
|
||||
### This project WILL heavily modify original LuaCsForBarotrauma/Barotrauma code.
|
||||
|
||||
## Main Contributors
|
||||
|
||||
| Contributor | Role |
|
||||
|-------------|------|
|
||||
| [NotAlwaysTrue](https://github.com/NotAlwaysTrue) | Project Lead
|
||||
| [Eero](https://github.com/eiaol) | Project Engineer |
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
- [LuaCsForBarotrauma](https://github.com/evilfactory/LuaCsForBarotrauma) - Original Lua/C# modding framework
|
||||
- [EvilFactory](https://github.com/evilfactory) - Moonsharp fork maintainer
|
||||
- [FakeFish Ltd](https://www.barotraumagame.com/) - Barotrauma developers
|
||||
|
||||
# Barotrauma
|
||||
|
||||
@@ -34,3 +47,53 @@ If you're interested in working on the code, either to develop mods or to contri
|
||||
- [.NET 8 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux)
|
||||
### macOS
|
||||
- [Visual Studio 2022 for Mac](https://visualstudio.microsoft.com/vs/mac/)
|
||||
|
||||
# LuaCsForBarotrauma 性能增强项目
|
||||
|
||||
> ⚠ **重要警告:** 本版本仅适用于服务器端使用,不建议在客户端运行。请确保在部署前已充分测试兼容性。
|
||||
|
||||
这是一个 LuaCsForBarotrauma 的修改版本,添加了多线程和多核支持。
|
||||
|
||||
### 本项目使用 EvilFactory 的 Moonsharp 分支:https://github.com/evilfactory/moonsharp
|
||||
### 本项目将对原版 LuaCsForBarotrauma/Barotrauma 代码进行大量修改。
|
||||
|
||||
## 主要贡献者
|
||||
|
||||
| 贡献者 | 角色 |
|
||||
|--------|------|
|
||||
| [NotAlwaysTrue](https://github.com/NotAlwaysTrue) | 项目负责人 |
|
||||
| [Eero](https://github.com/eiaol) | 项目责任工程师 |
|
||||
|
||||
## 致谢
|
||||
|
||||
- [LuaCsForBarotrauma](https://github.com/evilfactory/LuaCsForBarotrauma) - 原版 Lua/C# 模组框架
|
||||
- [EvilFactory](https://github.com/evilfactory) - Moonsharp 分支维护者
|
||||
- [FakeFish Ltd](https://www.barotraumagame.com/) - Barotrauma 开发商
|
||||
|
||||
# Barotrauma
|
||||
|
||||
版权所有 © FakeFish Ltd 2017-2024
|
||||
|
||||
下载源代码前,请阅读 [最终用户许可协议](EULA.txt)。
|
||||
|
||||
如果您有问题或需要报告问题,请查看我们的[贡献指南](https://github.com/Regalis11/Barotrauma/blob/master/CONTRIBUTING.md)。
|
||||
|
||||
如果您有兴趣参与代码开发,无论是开发模组还是向仓库贡献内容,您也可以在[贡献指南](https://github.com/Regalis11/Barotrauma/blob/master/CONTRIBUTING.md)中找到相关说明。
|
||||
|
||||
## 链接:
|
||||
|
||||
**官方网站:** www.barotraumagame.com
|
||||
|
||||
**Steam 论坛:** https://steamcommunity.com/app/602960/discussions/
|
||||
|
||||
**Discord:** https://discordapp.com/invite/undertow
|
||||
|
||||
**Wiki:** https://barotraumagame.com/wiki/Main_Page
|
||||
|
||||
## 环境要求:
|
||||
### Windows
|
||||
- 支持 C# 10 的 [Visual Studio](https://www.visualstudio.com/vs/community/)(推荐 VS 2022 或更高版本)
|
||||
### Linux
|
||||
- [.NET 6 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux)
|
||||
### macOS
|
||||
- [Visual Studio 2022 for Mac](https://visualstudio.microsoft.com/vs/mac/)
|
||||
|
||||
Reference in New Issue
Block a user