diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 0f09fa7e5..e554960ee 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -13,6 +13,7 @@ using System.Xml.Linq; using MoonSharp.Interpreter; using System.Net; using Barotrauma.Extensions; +using System.Threading.Tasks; namespace Barotrauma { @@ -327,11 +328,16 @@ namespace Barotrauma } Stopwatch performanceCounterTimer = Stopwatch.StartNew(); - stopwatch = Stopwatch.StartNew(); + + PerformenceMonitor PM = new PerformenceMonitor(); + long prevTicks = stopwatch.ElapsedTicks; while (ShouldRun) { +#if DEBUG + Console.WriteLine(PM.ToString()); +#endif long currTicks = stopwatch.ElapsedTicks; double elapsedTime = Math.Max(currTicks - prevTicks, 0) / frequency; Timing.Accumulator += elapsedTime; @@ -374,6 +380,7 @@ namespace Barotrauma Timing.Accumulator -= Timing.Step; updateCount++; + PM.Update(); } #if !DEBUG @@ -421,6 +428,9 @@ namespace Barotrauma updateCount = 0; } } + + PerformenceMonitor.PM.Dispose(); + stopwatch.Stop(); CloseServer(); diff --git a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs new file mode 100644 index 000000000..20d726ec6 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs @@ -0,0 +1,164 @@ +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 PerformenceMonitor + { + static public PerformenceMonitor PM; + + private Stopwatch PMStopwatch = new Stopwatch(); + + private double tickratetimer = 0; + + private double tickrate60stimer = 0; + + private static Queue tickrate10s = new Queue(10); + + 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 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 tickrate10s.Count > 0 ? tickrate10s.Average() : 60; + } + } + + public double TotalTimeElapsed + { + get + { + return PMStopwatch.Elapsed.TotalMilliseconds; + } + } + + 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 PerformenceMonitor() + { + PM = this; + RealTickRate = 60; + TotalTicks = 0; + LastSecondTicks = 60; + TickRateLow = 60; + TickRateHigh = 60; + PMStopwatch.Start(); + } + + public void Update() + { + TotalTicks += 1; + LastSecondTicks += 1; + if(tickrate10s.Count >= 10) + { + tickrate10s.Dequeue(); + } + if (TotalTimeElapsed - 1000 >= tickratetimer) + { + RealTickRate = LastSecondTicks / (TotalTimeElapsed - tickratetimer) * 1000; + tickrate10s.Enqueue(RealTickRate); + tickratetimer = TotalTimeElapsed; + LastSecondTicks = 0; + } + if (TotalTimeElapsed - 60000 >= tickrate60stimer) + { + 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 $"Item Count: {ItemCount}\n" + + $"Character Count: {CharacterCount}\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" + + $"10s Average Tick Rate: {AverageTickRate10s}\n" + + $"TotalTimeElapsed: {TotalTimeElapsed}\n" + + $"Memory Usage: {MemoryUsage}"; + } + + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 64f01bb58..9de7c093e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -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 @@ -647,35 +648,40 @@ namespace Barotrauma var sw = new System.Diagnostics.Stopwatch(); sw.Start(); #endif - if (mapEntityUpdateTick % MapEntityUpdateInterval == 0) + Task StructuralTask = Task.Factory.StartNew(() => { - - foreach (Hull hull in Hull.HullList) + if (mapEntityUpdateTick % MapEntityUpdateInterval == 0) { - hull.Update(deltaTime * MapEntityUpdateInterval, cam); - } + foreach (Hull hull in Hull.HullList) + { + hull.Update(deltaTime * MapEntityUpdateInterval, cam); + } #if CLIENT - Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam); + Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam); #endif - foreach (Structure structure in Structure.WallList) - { - structure.Update(deltaTime * MapEntityUpdateInterval, cam); + foreach (Structure structure in Structure.WallList) + { + structure.Update(deltaTime * MapEntityUpdateInterval, cam); + } } - } + }); - foreach (Gap gap in Gap.GapList) + Task GapTask = Task.Factory.StartNew(() => { - 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); - } + 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) { @@ -687,7 +693,6 @@ namespace Barotrauma GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Misc", sw.ElapsedTicks); sw.Restart(); #endif - Item.UpdatePendingConditionUpdates(deltaTime); if (mapEntityUpdateTick % MapEntityUpdateInterval == 0) { @@ -705,32 +710,38 @@ namespace Barotrauma catch (InvalidOperationException e) { GameAnalyticsManager.AddErrorEventOnce( - "MapEntity.UpdateAll:ItemUpdateInvalidOperation", - GameAnalyticsManager.ErrorSeverity.Critical, + "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); } } - foreach (var item in GameMain.LuaCs.Game.UpdatePriorityItems) + Task PItemTask = Task.Factory.StartNew(() => { - if (item.Removed) continue; + foreach (var item in GameMain.LuaCs.Game.UpdatePriorityItems) + { + if (item.Removed) continue; - item.Update(deltaTime, cam); - } + item.Update(deltaTime, cam); + } + }); #if CLIENT sw.Stop(); GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Items", sw.ElapsedTicks); sw.Restart(); #endif - - if (mapEntityUpdateTick % MapEntityUpdateInterval == 0) + Task SpawnerTask = Task.Factory.StartNew(() => { - UpdateAllProjSpecific(deltaTime * MapEntityUpdateInterval); + if (mapEntityUpdateTick % MapEntityUpdateInterval == 0) + { + UpdateAllProjSpecific(deltaTime * MapEntityUpdateInterval); - Spawner?.Update(); - } + Spawner?.Update(); + } + }); + Task.WaitAll(SpawnerTask, PItemTask, GapTask, StructuralTask); } static partial void UpdateAllProjSpecific(float deltaTime); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs index 865280f5f..7ddfe996a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs @@ -174,7 +174,9 @@ namespace Barotrauma.Networking exceptionMsg += " Child process has not exited."; } #endif +#if !DEBUG throw new Exception(exceptionMsg); +#endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 754bb6d26..171550c36 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -4,6 +4,8 @@ using Microsoft.Xna.Framework; using System.Threading; using FarseerPhysics.Dynamics; using FarseerPhysics; +using System.Threading.Tasks; +using System.Linq; #if DEBUG && CLIENT @@ -69,6 +71,7 @@ namespace Barotrauma MapEntity.ClearHighlightedEntities(); + Task PhysicsTask = Task.Factory.StartNew(() => ExecutePhysics()); #if RUN_PHYSICS_IN_SEPARATE_THREAD var physicsThread = new Thread(ExecutePhysics) { @@ -138,6 +141,7 @@ namespace Barotrauma GameTime += deltaTime; + //Physics Update; wait for changes. foreach (PhysicsBody body in PhysicsBody.List) { //update character (colliders) regardless if they're enabled or not, so that the draw position is updated @@ -146,15 +150,20 @@ namespace Barotrauma body.BodyType != BodyType.Static) { body.Update(); - } + } + if (body.Enabled && body.BodyType != FarseerPhysics.BodyType.Static) + { + body.SetPrevTransform(body.SimPosition, body.Rotation); + } } + MapEntity.ClearHighlightedEntities(); #if CLIENT var sw = new System.Diagnostics.Stopwatch(); sw.Start(); #endif - + //This is not changed yet. Will be back for it. GameMain.GameSession?.Update((float)deltaTime); #if CLIENT @@ -195,8 +204,14 @@ namespace Barotrauma Character.UpdateAll((float)deltaTime, cam); #elif SERVER - if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); - Character.UpdateAll((float)deltaTime, Camera.Instance); + Task LevelTask = Task.Factory.StartNew(() => + { + if (Level.Loaded != null) + { + Level.Loaded.Update((float)deltaTime, Camera.Instance); + } + }); + Task CharacterTask = Task.Factory.StartNew(() => Character.UpdateAll((float)deltaTime, Camera.Instance)); #endif @@ -206,7 +221,7 @@ namespace Barotrauma sw.Restart(); #endif - StatusEffect.UpdateAll((float)deltaTime); + Task SETask = Task.Factory.StartNew(() => StatusEffect.UpdateAll((float)deltaTime)); #if CLIENT sw.Stop(); @@ -259,10 +274,12 @@ namespace Barotrauma body.SetPrevTransform(body.SimPosition, body.Rotation); } } - #if CLIENT MapEntity.UpdateAll((float)deltaTime, cam); #elif SERVER + Task.WaitAll(LevelTask, CharacterTask, SETask); + + //This is internally multi-threaded MapEntity.UpdateAll((float)deltaTime, Camera.Instance); #endif @@ -284,7 +301,7 @@ namespace Barotrauma GameMain.PerformanceCounter.AddElapsedTicks("Update:Ragdolls", sw.ElapsedTicks); sw.Restart(); #endif - + //Sub update. Wait for change foreach (Submarine sub in Submarine.Loaded) { sub.Update((float)deltaTime);