From 81f44969eec1de1389e87286fce82f4579655345 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:47:40 -0300 Subject: [PATCH 01/13] Fix memory leak that happens when you press retry in singleplayer --- .../BarotraumaShared/SharedSource/GameSession/GameSession.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index ffe92f4fa..f3782f134 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -408,6 +408,7 @@ namespace Barotrauma public void LoadPreviousSave() { + GameMain.LuaCs.Hook.Call("roundEnd"); AchievementManager.OnRoundEnded(this, roundInterrupted: true); Submarine.Unload(); SaveUtil.LoadGame(DataPath); From e715fdc835d3312d23fa53a1c56baad5587d031a Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Fri, 26 Dec 2025 01:16:05 +0800 Subject: [PATCH 02/13] Fixed #10 Fixed #12 --- .../Map/Levels/LevelObjects/LevelTrigger.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 71 ++++++++----------- .../SharedSource/Screens/GameScreen.cs | 56 ++++++--------- .../Dynamics/Body.cs | 2 +- 4 files changed, 56 insertions(+), 75 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 21a71b22b..b05cc6be7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -479,7 +479,7 @@ namespace Barotrauma { foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList) { - ContactEdge contactEdge = fixture.Body.ContactList; + ContactEdge contactEdge = fixture.Body.ContactList.CreateCopy(); while (contactEdge != null) { if (contactEdge.Contact != null && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 48ea30861..91cfc04cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -652,34 +652,28 @@ namespace Barotrauma bool shouldUpdatePower = mapEntityUpdateTick % PoweredUpdateInterval == 0; // Buffer lists to avoid repeated allocations - var hullList = shouldUpdateMapEntities ? Hull.HullList.ToList() : null; - var structureList = shouldUpdateMapEntities ? Structure.WallList.ToList() : null; - var gapList = Gap.GapList.ToList(); - var itemList = shouldUpdateMapEntities ? Item.ItemList.ToList() : null; + List hullList = new List(Hull.HullList); + List structureList = new List(Structure.WallList); + List gapList = new List(Gap.GapList); + List itemList = new List(Item.ItemList); // First phase: parallel updates that have no order dependencies Parallel.Invoke(parallelOptions, // Hull parallel update () => { - if (shouldUpdateMapEntities && hullList != null) + Parallel.ForEach(hullList, parallelOptions, hull => { - Parallel.ForEach(hullList, parallelOptions, hull => - { - hull.Update(deltaTime * MapEntityUpdateInterval, cam); - }); - } + hull.Update(deltaTime, cam); + }); }, // Structure parallel update () => { - if (shouldUpdateMapEntities && structureList != null) + Parallel.ForEach(structureList, parallelOptions, structure => { - Parallel.ForEach(structureList, parallelOptions, structure => - { - structure.Update(deltaTime * MapEntityUpdateInterval, cam); - }); - } + structure.Update(deltaTime, cam); + }); }, // Gap reset (must be done before update) () => @@ -694,7 +688,7 @@ namespace Barotrauma { if (shouldUpdatePower) { - Powered.UpdatePower(deltaTime * PoweredUpdateInterval); + Powered.UpdatePower(deltaTime); } } ); @@ -721,33 +715,30 @@ namespace Barotrauma #endif // Item update (Item.Update() is not thread-safe and must be executed on the main thread) - if (shouldUpdateMapEntities && itemList != null) + Item.UpdatePendingConditionUpdates(deltaTime); + + float scaledDeltaTime = deltaTime; + Item lastUpdatedItem = null; + + try { - Item.UpdatePendingConditionUpdates(deltaTime); - - float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; - Item lastUpdatedItem = null; - - try + foreach (Item item in itemList) { - foreach (Item item in itemList) - { - lastUpdatedItem = item; - item.Update(scaledDeltaTime, cam); - } + lastUpdatedItem = item; + item.Update(scaledDeltaTime, 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); - } - - UpdateAllProjSpecific(scaledDeltaTime); - Spawner?.Update(); } + 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); + } + + UpdateAllProjSpecific(scaledDeltaTime); + Spawner?.Update(); #if CLIENT sw.Stop(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index e29da7114..4d4634e31 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -151,23 +151,16 @@ namespace Barotrauma var physicsBodies = PhysicsBody.List.ToList(); - Parallel.Invoke(parallelOptions, - () => + Parallel.ForEach(physicsBodies, parallelOptions, body => { - Parallel.ForEach(physicsBodies, parallelOptions, body => - { - if ((body.Enabled || body.UserData is Character) && - body.BodyType != BodyType.Static) - { - body.Update(); - } - }); - }, - () => - { - GameMain.GameSession?.Update((float)deltaTime); - } - ); + if ((body.Enabled || body.UserData is Character) && + body.BodyType != BodyType.Static) + { + body.Update(); + } + }); + + GameMain.GameSession?.Update((float)deltaTime); foreach (PhysicsBody body in physicsBodies) { @@ -183,10 +176,8 @@ namespace Barotrauma var sw = new System.Diagnostics.Stopwatch(); sw.Start(); - Parallel.Invoke(parallelOptions, - () => GameMain.ParticleManager.Update((float)deltaTime), - () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, cam); } - ); + GameMain.ParticleManager.Update((float)deltaTime); + if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, cam); sw.Stop(); GameMain.PerformanceCounter.AddElapsedTicks("Update:Particles+Level", sw.ElapsedTicks); @@ -252,25 +243,25 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - Parallel.Invoke(parallelOptions, - () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); }, - () => Character.UpdateAll((float)deltaTime, Camera.Instance) - ); + if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); + + Character.UpdateAll((float)deltaTime, Camera.Instance); #endif var submarines = Submarine.Loaded.ToList(); - Parallel.ForEach(submarines, parallelOptions, sub => + foreach(Submarine sub in submarines) { sub.SetPrevTransform(sub.Position); - }); + } - Parallel.ForEach(physicsBodies, parallelOptions, body => + // + foreach (PhysicsBody body in physicsBodies) { if (body.Enabled && body.BodyType != FarseerPhysics.BodyType.Static) - { - body.SetPrevTransform(body.SimPosition, body.Rotation); + { + body.SetPrevTransform(body.SimPosition, body.Rotation); } - }); + } #if CLIENT MapEntity.UpdateAll((float)deltaTime, cam, parallelOptions); @@ -302,11 +293,10 @@ namespace Barotrauma GameMain.PerformanceCounter.AddElapsedTicks("Update:Ragdolls", sw.ElapsedTicks); sw.Restart(); #endif - - Parallel.ForEach(submarines, parallelOptions, sub => + foreach (var sub in submarines) { sub.Update((float)deltaTime); - }); + } #if CLIENT sw.Stop(); diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.cs index 81faa0121..a35808d92 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/Body.cs @@ -1409,4 +1409,4 @@ namespace FarseerPhysics.Dynamics return body; } } -} \ No newline at end of file +} From 5446795196c4f176ce2cc781a81d7bba7bf2476c Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Fri, 26 Dec 2025 11:07:54 +0800 Subject: [PATCH 03/13] Fixed #7 (re-applied) --- .../SharedSource/Map/Submarine.cs | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 64f547fa3..f9e7a706d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -2,16 +2,17 @@ using Barotrauma.IO; using Barotrauma.Items.Components; using Barotrauma.Networking; +using Barotrauma.PerkBehaviors; using FarseerPhysics; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Xml.Linq; -using Barotrauma.PerkBehaviors; using Voronoi2; namespace Barotrauma @@ -484,9 +485,9 @@ namespace Barotrauma { Rectangle dockedBorders = Borders; checkSubmarineBorders.Add(this); - var connectedSubs = DockedTo.Where(s => - !checkSubmarineBorders.Contains(s) && - !s.Info.IsOutpost && + var connectedSubs = DockedTo.Where(s => + !checkSubmarineBorders.Contains(s) && + !s.Info.IsOutpost && (allowDifferentTeam || s.TeamID == TeamID)); foreach (Submarine dockedSub in connectedSubs) { @@ -508,23 +509,16 @@ namespace Barotrauma return dockedBorders; } - private readonly HashSet connectedSubs; + private readonly ConcurrentBag connectedSubs; /// /// Returns a list of all submarines that are connected to this one via docking ports, including this sub. /// - public IEnumerable GetConnectedSubs() + public ConcurrentBag GetConnectedSubs() { return connectedSubs; } - public void RefreshConnectedSubs() - { - connectedSubs.Clear(); - connectedSubs.Add(this); - GetConnectedSubsRecursive(connectedSubs); - } - - private void GetConnectedSubsRecursive(HashSet subs) + private void GetConnectedSubsRecursive(ConcurrentBag subs) { foreach (Submarine dockedSub in DockedTo) { @@ -534,6 +528,12 @@ namespace Barotrauma } } + public void RefreshConnectedSubs() + { + connectedSubs.Clear(); + connectedSubs.Add(this); + GetConnectedSubsRecursive(connectedSubs); + } /// /// Attempt to find a spawn position close to the specified position where the sub doesn't collide with walls/ruins /// @@ -551,7 +551,7 @@ namespace Barotrauma minWidth += padding; minHeight += padding; - int iterations = 0; + int iterations = 0; const int maxIterations = 5; do { @@ -580,9 +580,9 @@ namespace Barotrauma //if the raycast hit a wall, attempt to place the spawnpos there int offsetFromWall = 10 * -verticalMoveDir; float pickedPos = ConvertUnits.ToDisplayUnits(LastPickedPosition.Y) + offsetFromWall; - closestPickedPos.Y = - verticalMoveDir > 0 ? - Math.Min(closestPickedPos.Y, pickedPos) : + closestPickedPos.Y = + verticalMoveDir > 0 ? + Math.Min(closestPickedPos.Y, pickedPos) : Math.Max(closestPickedPos.Y, pickedPos); } } @@ -597,7 +597,7 @@ namespace Barotrauma bool couldMoveInVerticalMoveDir = Math.Sign(newSpawnPos.Y - spawnPos.Y) == Math.Sign(verticalMoveDir); if (!couldMoveInVerticalMoveDir) { break; } spawnPos = ClampToHorizontalLimits(newSpawnPos, limits); - } + } iterations++; } while (iterations < maxIterations); @@ -1001,7 +1001,7 @@ namespace Barotrauma /// Should plants' branches be ignored? /// If the predicate returns false, the fixture is ignored even if it would normally block visibility. /// A physics body that was between the points (or null) - public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel = false, bool ignoreSubs = false, bool ignoreSensors = true, bool ignoreDisabledWalls = true, bool ignoreBranches = true, + public static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel = false, bool ignoreSubs = false, bool ignoreSensors = true, bool ignoreDisabledWalls = true, bool ignoreBranches = true, Predicate blocksVisibilityPredicate = null) { Body closestBody = null; @@ -1160,10 +1160,10 @@ namespace Barotrauma { //a little hacky: undock and redock to ensure the hulls and gaps between docking ports are correct //after all the parts of the submarine have been flipped and moved to correct places. - if (dockingPort.DockingTarget is { } dockingTarget) + if (dockingPort.DockingTarget is { } dockingTarget) { - dockingPort.Undock(); - dockingPort.Dock(dockingTarget); + dockingPort.Undock(); + dockingPort.Dock(dockingTarget); } } @@ -1487,7 +1487,7 @@ namespace Barotrauma { if (ignoreOutposts && sub.Info.IsOutpost) { continue; } if (ignoreOutsideLevel && Level.Loaded != null && sub.IsAboveLevel) { continue; } - if (ignoreRespawnShuttle && sub.IsRespawnShuttle) { continue; } + if (ignoreRespawnShuttle && sub.IsRespawnShuttle) { continue; } if (teamType.HasValue && sub.TeamID != teamType) { continue; } float dist = Vector2.DistanceSquared(worldPosition, sub.WorldPosition); if (closest == null || dist < closestDist) @@ -1551,6 +1551,7 @@ namespace Barotrauma if (includingConnectedSubs) { // Performance-sensitive code -> implemented without Linq. + foreach (Submarine s in connectedSubs) { if (s == entity.Submarine && (allowDifferentTeam || entity.Submarine.TeamID == TeamID) && (allowDifferentType || entity.Submarine.Info.Type == Info.Type)) @@ -1600,7 +1601,7 @@ namespace Barotrauma Vector4 bounds = new Vector4(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue); foreach (XElement element in submarineElement.Elements()) { - if (element.Name == "Structure") + if (element.Name == "Structure") { string name = element.GetAttributeString("name", ""); Identifier identifier = element.GetAttributeIdentifier("identifier", ""); @@ -1642,7 +1643,7 @@ namespace Barotrauma { Stopwatch sw = Stopwatch.StartNew(); - connectedSubs = new HashSet(2) + connectedSubs = new ConcurrentBag { this }; @@ -1829,7 +1830,7 @@ namespace Barotrauma } } - if (Screen.Selected is { IsEditor : false }) + if (Screen.Selected is { IsEditor: false }) { foreach (Identifier layer in Info.LayersHiddenByDefault) { @@ -2011,7 +2012,7 @@ namespace Barotrauma Item itemToSwap = kvp.Key; ItemPrefab swapTo = kvp.Value; itemToSwap.PurchasedNewSwap = item.PurchasedNewSwap; - if (itemToSwap.Prefab != swapTo) { itemToSwap.PendingItemSwap = swapTo; } + if (itemToSwap.Prefab != swapTo) { itemToSwap.PendingItemSwap = swapTo; } } } @@ -2101,8 +2102,8 @@ namespace Barotrauma public static void Unload() { - if (Unloading) - { + if (Unloading) + { DebugConsole.AddWarning($"Called {nameof(Submarine.Unload)} when already unloading."); return; } @@ -2152,7 +2153,7 @@ namespace Barotrauma Ragdoll.RemoveAll(); PhysicsBody.RemoveAll(); - StatusEffect.StopAll(); + StatusEffect.StopAll(); GameMain.World = null; Powered.Grids.Clear(); @@ -2348,10 +2349,10 @@ namespace Barotrauma if (potentialContainer.Submarine == this && !isSecondary) { //valid primary container in the same sub -> perfect, let's use that one - return potentialContainer; + return potentialContainer; } selectedContainer = potentialContainer; - + } return selectedContainer; } From 603201084794ab2e0a60b6770094bb06324fad49 Mon Sep 17 00:00:00 2001 From: Eero Date: Fri, 26 Dec 2025 21:04:07 +0800 Subject: [PATCH 04/13] Improve parallelization in map and game screen updates Refactored update logic in MapEntity and GameScreen to use more granular and conditional parallelization, reducing unnecessary allocations and improving performance. Updates to hulls, structures, items, and physics bodies are now executed in parallel where safe, and item updates are only performed when necessary. Also parallelized submarine and physics body transform updates. --- .../SharedSource/Map/MapEntity.cs | 71 +++++++++++-------- .../SharedSource/Screens/GameScreen.cs | 56 +++++++++------ 2 files changed, 73 insertions(+), 54 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 91cfc04cc..48ea30861 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -652,28 +652,34 @@ namespace Barotrauma bool shouldUpdatePower = mapEntityUpdateTick % PoweredUpdateInterval == 0; // Buffer lists to avoid repeated allocations - List hullList = new List(Hull.HullList); - List structureList = new List(Structure.WallList); - List gapList = new List(Gap.GapList); - List itemList = new List(Item.ItemList); + var hullList = shouldUpdateMapEntities ? Hull.HullList.ToList() : null; + var structureList = shouldUpdateMapEntities ? Structure.WallList.ToList() : null; + var gapList = Gap.GapList.ToList(); + var itemList = shouldUpdateMapEntities ? Item.ItemList.ToList() : null; // First phase: parallel updates that have no order dependencies Parallel.Invoke(parallelOptions, // Hull parallel update () => { - Parallel.ForEach(hullList, parallelOptions, hull => + if (shouldUpdateMapEntities && hullList != null) { - hull.Update(deltaTime, cam); - }); + Parallel.ForEach(hullList, parallelOptions, hull => + { + hull.Update(deltaTime * MapEntityUpdateInterval, cam); + }); + } }, // Structure parallel update () => { - Parallel.ForEach(structureList, parallelOptions, structure => + if (shouldUpdateMapEntities && structureList != null) { - structure.Update(deltaTime, cam); - }); + Parallel.ForEach(structureList, parallelOptions, structure => + { + structure.Update(deltaTime * MapEntityUpdateInterval, cam); + }); + } }, // Gap reset (must be done before update) () => @@ -688,7 +694,7 @@ namespace Barotrauma { if (shouldUpdatePower) { - Powered.UpdatePower(deltaTime); + Powered.UpdatePower(deltaTime * PoweredUpdateInterval); } } ); @@ -715,30 +721,33 @@ namespace Barotrauma #endif // Item update (Item.Update() is not thread-safe and must be executed on the main thread) - Item.UpdatePendingConditionUpdates(deltaTime); - - float scaledDeltaTime = deltaTime; - Item lastUpdatedItem = null; - - try + if (shouldUpdateMapEntities && itemList != null) { - foreach (Item item in itemList) + Item.UpdatePendingConditionUpdates(deltaTime); + + float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; + Item lastUpdatedItem = null; + + try { - lastUpdatedItem = item; - item.Update(scaledDeltaTime, cam); + foreach (Item item in itemList) + { + lastUpdatedItem = item; + item.Update(scaledDeltaTime, 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); } - } - 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); - } - UpdateAllProjSpecific(scaledDeltaTime); - Spawner?.Update(); + UpdateAllProjSpecific(scaledDeltaTime); + Spawner?.Update(); + } #if CLIENT sw.Stop(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 4d4634e31..e29da7114 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -151,16 +151,23 @@ namespace Barotrauma var physicsBodies = PhysicsBody.List.ToList(); - Parallel.ForEach(physicsBodies, parallelOptions, body => + Parallel.Invoke(parallelOptions, + () => { - if ((body.Enabled || body.UserData is Character) && - body.BodyType != BodyType.Static) - { - body.Update(); - } - }); - - GameMain.GameSession?.Update((float)deltaTime); + Parallel.ForEach(physicsBodies, parallelOptions, body => + { + if ((body.Enabled || body.UserData is Character) && + body.BodyType != BodyType.Static) + { + body.Update(); + } + }); + }, + () => + { + GameMain.GameSession?.Update((float)deltaTime); + } + ); foreach (PhysicsBody body in physicsBodies) { @@ -176,8 +183,10 @@ namespace Barotrauma var sw = new System.Diagnostics.Stopwatch(); sw.Start(); - GameMain.ParticleManager.Update((float)deltaTime); - if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, cam); + Parallel.Invoke(parallelOptions, + () => GameMain.ParticleManager.Update((float)deltaTime), + () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, cam); } + ); sw.Stop(); GameMain.PerformanceCounter.AddElapsedTicks("Update:Particles+Level", sw.ElapsedTicks); @@ -243,25 +252,25 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); - - Character.UpdateAll((float)deltaTime, Camera.Instance); + Parallel.Invoke(parallelOptions, + () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); }, + () => Character.UpdateAll((float)deltaTime, Camera.Instance) + ); #endif var submarines = Submarine.Loaded.ToList(); - foreach(Submarine sub in submarines) + Parallel.ForEach(submarines, parallelOptions, sub => { sub.SetPrevTransform(sub.Position); - } + }); - // - foreach (PhysicsBody body in physicsBodies) + Parallel.ForEach(physicsBodies, parallelOptions, body => { if (body.Enabled && body.BodyType != FarseerPhysics.BodyType.Static) - { - body.SetPrevTransform(body.SimPosition, body.Rotation); + { + body.SetPrevTransform(body.SimPosition, body.Rotation); } - } + }); #if CLIENT MapEntity.UpdateAll((float)deltaTime, cam, parallelOptions); @@ -293,10 +302,11 @@ namespace Barotrauma GameMain.PerformanceCounter.AddElapsedTicks("Update:Ragdolls", sw.ElapsedTicks); sw.Restart(); #endif - foreach (var sub in submarines) + + Parallel.ForEach(submarines, parallelOptions, sub => { sub.Update((float)deltaTime); - } + }); #if CLIENT sw.Stop(); From 716a35701c5c03b0b6f2e356fd261c204e1a8464 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Sat, 27 Dec 2025 01:59:17 +0800 Subject: [PATCH 05/13] Revision 6032010 Removed a potential issue causing the server to stuck in GameScreen.cs (Internal reports) Added an Warning message to SEEM --- .../NetEntityEvent/ServerEntityEventManager.cs | 3 +++ .../BarotraumaShared/SharedSource/Screens/GameScreen.cs | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs index d74b71a55..81e2246ae 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -8,6 +8,9 @@ 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 diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index e29da7114..ea9964a80 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -252,10 +252,11 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - Parallel.Invoke(parallelOptions, - () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); }, - () => Character.UpdateAll((float)deltaTime, Camera.Instance) - ); + if (Level.Loaded != null) + { + Level.Loaded.Update((float)deltaTime, Camera.Instance); + } + Character.UpdateAll((float)deltaTime, Camera.Instance); #endif var submarines = Submarine.Loaded.ToList(); From f7650bd6df7b1372518b92bd4fde2892bf1ce180 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Sat, 27 Dec 2025 02:06:04 +0800 Subject: [PATCH 06/13] Re-applied multiple fixs --- .../BarotraumaShared/SharedSource/Screens/GameScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index ea9964a80..00bb104ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -304,10 +304,10 @@ namespace Barotrauma sw.Restart(); #endif - Parallel.ForEach(submarines, parallelOptions, sub => + foreach(Submarine sub in submarines) { sub.Update((float)deltaTime); - }); + } #if CLIENT sw.Stop(); From 7e899d900ac7300c77737629065d7190ea678061 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Sat, 27 Dec 2025 13:19:36 +0800 Subject: [PATCH 07/13] Added ClientCount for PerformenceMonitor Add a marker to help distinguish EP from other SV Executables Fixed a "Failed to copy object. Source is null." introduced by last update Uses dynamic ThreadCount instead of fixed 16 Re-Removed most PF support Re-Parallelzed Level update and Character Update(a conflict warning will be issued --- .../ServerSource/PerformenceMonitor.cs | 5 ++ .../BarotraumaServer/ServerSource/Program.cs | 2 +- .../Map/Levels/LevelObjects/LevelTrigger.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 85 +++++++------------ .../SharedSource/Screens/GameScreen.cs | 41 ++++----- 5 files changed, 57 insertions(+), 78 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs index d6d4c5d8d..9186bd504 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs @@ -37,6 +37,10 @@ namespace Barotrauma { get { return PhysicsBody.List.Count; } } + public int ConnectClients + { + get { return Client.ClientList.Count; } + } public double RealTickRate { @@ -160,6 +164,7 @@ namespace Barotrauma return $"Server Performence 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" + diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index 35e2503ef..1f5a9727b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index b05cc6be7..261a21706 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -479,7 +479,7 @@ namespace Barotrauma { foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList) { - ContactEdge contactEdge = fixture.Body.ContactList.CreateCopy(); + ContactEdge contactEdge = fixture.Body.ContactList == null ? null: fixture.Body.ContactList.CreateCopy(); while (contactEdge != null) { if (contactEdge.Contact != null && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 48ea30861..2634f9560 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -642,44 +642,34 @@ namespace Barotrauma /// public static void UpdateAll(float deltaTime, Camera cam , ParallelOptions parallelOptions) { - mapEntityUpdateTick++; #if CLIENT var sw = new System.Diagnostics.Stopwatch(); sw.Start(); #endif - bool shouldUpdateMapEntities = mapEntityUpdateTick % MapEntityUpdateInterval == 0; - bool shouldUpdatePower = mapEntityUpdateTick % PoweredUpdateInterval == 0; - // Buffer lists to avoid repeated allocations - var hullList = shouldUpdateMapEntities ? Hull.HullList.ToList() : null; - var structureList = shouldUpdateMapEntities ? Structure.WallList.ToList() : null; + var hullList = Hull.HullList.ToList(); + var structureList = Structure.WallList.ToList(); var gapList = Gap.GapList.ToList(); - var itemList = shouldUpdateMapEntities ? Item.ItemList.ToList() : null; + var itemList = Item.ItemList.ToList(); // First phase: parallel updates that have no order dependencies Parallel.Invoke(parallelOptions, // Hull parallel update () => { - if (shouldUpdateMapEntities && hullList != null) + Parallel.ForEach(hullList, parallelOptions, hull => { - Parallel.ForEach(hullList, parallelOptions, hull => - { - hull.Update(deltaTime * MapEntityUpdateInterval, cam); - }); - } + hull.Update(deltaTime, cam); + }); }, // Structure parallel update () => { - if (shouldUpdateMapEntities && structureList != null) + Parallel.ForEach(structureList, parallelOptions, structure => { - Parallel.ForEach(structureList, parallelOptions, structure => - { - structure.Update(deltaTime * MapEntityUpdateInterval, cam); - }); - } + structure.Update(deltaTime, cam); + }); }, // Gap reset (must be done before update) () => @@ -692,27 +682,21 @@ namespace Barotrauma // Powered components update () => { - if (shouldUpdatePower) - { - Powered.UpdatePower(deltaTime * PoweredUpdateInterval); - } + Powered.UpdatePower(deltaTime); } ); #if CLIENT // Hull Cheats need to be executed after Hull update - if (shouldUpdateMapEntities) - { - Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam); - } + Hull.UpdateCheats(deltaTime, cam); #endif // Gap update (has order dependencies, keep random order but execute sequentially) var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList(); - foreach (Gap gap in shuffledGaps) + Parallel.ForEach(gapList, parallelOptions, gap => { gap.Update(deltaTime, cam); - } + }); #if CLIENT sw.Stop(); @@ -721,33 +705,30 @@ namespace Barotrauma #endif // Item update (Item.Update() is not thread-safe and must be executed on the main thread) - if (shouldUpdateMapEntities && itemList != null) + Item.UpdatePendingConditionUpdates(deltaTime); + + float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; + Item lastUpdatedItem = null; + + try { - Item.UpdatePendingConditionUpdates(deltaTime); - - float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; - Item lastUpdatedItem = null; - - try + foreach (Item item in itemList) { - foreach (Item item in itemList) - { - lastUpdatedItem = item; - item.Update(scaledDeltaTime, cam); - } + lastUpdatedItem = item; + item.Update(scaledDeltaTime, 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); - } - - UpdateAllProjSpecific(scaledDeltaTime); - Spawner?.Update(); } + 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); + } + + UpdateAllProjSpecific(scaledDeltaTime); + Spawner?.Update(); #if CLIENT sw.Stop(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 00bb104ba..182873fc1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -7,6 +7,7 @@ using FarseerPhysics; using System.Threading.Tasks; using System.Linq; using System.Collections.Generic; +using System; #if DEBUG && CLIENT @@ -24,7 +25,7 @@ namespace Barotrauma private static readonly ParallelOptions parallelOptions = new ParallelOptions { - MaxDegreeOfParallelism = 16 + MaxDegreeOfParallelism = Environment.ProcessorCount * 2, }; #if CLIENT @@ -151,31 +152,23 @@ namespace Barotrauma var physicsBodies = PhysicsBody.List.ToList(); - Parallel.Invoke(parallelOptions, - () => + Parallel.ForEach(physicsBodies, parallelOptions, body => + { + if ((body.Enabled || body.UserData is Character) && + body.BodyType != BodyType.Static) { - Parallel.ForEach(physicsBodies, parallelOptions, body => - { - if ((body.Enabled || body.UserData is Character) && - body.BodyType != BodyType.Static) - { - body.Update(); - } - }); - }, - () => - { - GameMain.GameSession?.Update((float)deltaTime); + body.Update(); } - ); + }); + GameMain.GameSession?.Update((float)deltaTime); - foreach (PhysicsBody body in physicsBodies) + Parallel.ForEach(physicsBodies, parallelOptions, body => { if (body.Enabled && body.BodyType != BodyType.Static) { body.SetPrevTransform(body.SimPosition, body.Rotation); } - } + }); MapEntity.ClearHighlightedEntities(); @@ -252,14 +245,14 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - if (Level.Loaded != null) - { - Level.Loaded.Update((float)deltaTime, Camera.Instance); - } - Character.UpdateAll((float)deltaTime, Camera.Instance); + Parallel.Invoke(parallelOptions, + () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); }, + () => Character.UpdateAll((float)deltaTime, Camera.Instance) + ); #endif var submarines = Submarine.Loaded.ToList(); + Parallel.ForEach(submarines, parallelOptions, sub => { sub.SetPrevTransform(sub.Position); @@ -277,8 +270,8 @@ namespace Barotrauma MapEntity.UpdateAll((float)deltaTime, cam, parallelOptions); #elif SERVER - MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions); + //StatusEffect.UpdateAll is not thread-safe and must be executed on the main thread StatusEffect.UpdateAll((float)deltaTime); From b35eee55616526fc008d12539c653bd89b985359 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <77662224+NotAlwaysTrue@users.noreply.github.com> Date: Sat, 27 Dec 2025 15:32:16 +0800 Subject: [PATCH 08/13] Revert "OBT/1.0.4" --- .../ServerSource/PerformenceMonitor.cs | 5 -- .../BarotraumaServer/ServerSource/Program.cs | 2 +- .../Map/Levels/LevelObjects/LevelTrigger.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 85 ++++++++++++------- .../SharedSource/Screens/GameScreen.cs | 41 +++++---- 5 files changed, 78 insertions(+), 57 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs index 9186bd504..d6d4c5d8d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs @@ -37,10 +37,6 @@ namespace Barotrauma { get { return PhysicsBody.List.Count; } } - public int ConnectClients - { - get { return Client.ClientList.Count; } - } public double RealTickRate { @@ -164,7 +160,6 @@ namespace Barotrauma return $"Server Performence 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" + diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index 1f5a9727b..35e2503ef 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -64,7 +64,7 @@ namespace Barotrauma GameMain.ShouldRun = false; }; #endif - Console.WriteLine("Barotrauma Dedicated Server(EP) " + GameMain.Version + + Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")"); if (Console.IsOutputRedirected) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 261a21706..b05cc6be7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -479,7 +479,7 @@ namespace Barotrauma { foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList) { - ContactEdge contactEdge = fixture.Body.ContactList == null ? null: fixture.Body.ContactList.CreateCopy(); + ContactEdge contactEdge = fixture.Body.ContactList.CreateCopy(); while (contactEdge != null) { if (contactEdge.Contact != null && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 2634f9560..48ea30861 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -642,34 +642,44 @@ namespace Barotrauma /// public static void UpdateAll(float deltaTime, Camera cam , ParallelOptions parallelOptions) { + mapEntityUpdateTick++; #if CLIENT var sw = new System.Diagnostics.Stopwatch(); sw.Start(); #endif + bool shouldUpdateMapEntities = mapEntityUpdateTick % MapEntityUpdateInterval == 0; + bool shouldUpdatePower = mapEntityUpdateTick % PoweredUpdateInterval == 0; + // Buffer lists to avoid repeated allocations - var hullList = Hull.HullList.ToList(); - var structureList = Structure.WallList.ToList(); + var hullList = shouldUpdateMapEntities ? Hull.HullList.ToList() : null; + var structureList = shouldUpdateMapEntities ? Structure.WallList.ToList() : null; var gapList = Gap.GapList.ToList(); - var itemList = Item.ItemList.ToList(); + var itemList = shouldUpdateMapEntities ? Item.ItemList.ToList() : null; // First phase: parallel updates that have no order dependencies Parallel.Invoke(parallelOptions, // Hull parallel update () => { - Parallel.ForEach(hullList, parallelOptions, hull => + if (shouldUpdateMapEntities && hullList != null) { - hull.Update(deltaTime, cam); - }); + Parallel.ForEach(hullList, parallelOptions, hull => + { + hull.Update(deltaTime * MapEntityUpdateInterval, cam); + }); + } }, // Structure parallel update () => { - Parallel.ForEach(structureList, parallelOptions, structure => + if (shouldUpdateMapEntities && structureList != null) { - structure.Update(deltaTime, cam); - }); + Parallel.ForEach(structureList, parallelOptions, structure => + { + structure.Update(deltaTime * MapEntityUpdateInterval, cam); + }); + } }, // Gap reset (must be done before update) () => @@ -682,21 +692,27 @@ namespace Barotrauma // Powered components update () => { - Powered.UpdatePower(deltaTime); + if (shouldUpdatePower) + { + Powered.UpdatePower(deltaTime * PoweredUpdateInterval); + } } ); #if CLIENT // Hull Cheats need to be executed after Hull update - Hull.UpdateCheats(deltaTime, cam); + if (shouldUpdateMapEntities) + { + Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam); + } #endif // Gap update (has order dependencies, keep random order but execute sequentially) var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList(); - Parallel.ForEach(gapList, parallelOptions, gap => + foreach (Gap gap in shuffledGaps) { gap.Update(deltaTime, cam); - }); + } #if CLIENT sw.Stop(); @@ -705,30 +721,33 @@ namespace Barotrauma #endif // Item update (Item.Update() is not thread-safe and must be executed on the main thread) - Item.UpdatePendingConditionUpdates(deltaTime); - - float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; - Item lastUpdatedItem = null; - - try + if (shouldUpdateMapEntities && itemList != null) { - foreach (Item item in itemList) + Item.UpdatePendingConditionUpdates(deltaTime); + + float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; + Item lastUpdatedItem = null; + + try { - lastUpdatedItem = item; - item.Update(scaledDeltaTime, cam); + foreach (Item item in itemList) + { + lastUpdatedItem = item; + item.Update(scaledDeltaTime, 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); } - } - 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); - } - UpdateAllProjSpecific(scaledDeltaTime); - Spawner?.Update(); + UpdateAllProjSpecific(scaledDeltaTime); + Spawner?.Update(); + } #if CLIENT sw.Stop(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 182873fc1..00bb104ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -7,7 +7,6 @@ using FarseerPhysics; using System.Threading.Tasks; using System.Linq; using System.Collections.Generic; -using System; #if DEBUG && CLIENT @@ -25,7 +24,7 @@ namespace Barotrauma private static readonly ParallelOptions parallelOptions = new ParallelOptions { - MaxDegreeOfParallelism = Environment.ProcessorCount * 2, + MaxDegreeOfParallelism = 16 }; #if CLIENT @@ -152,23 +151,31 @@ namespace Barotrauma var physicsBodies = PhysicsBody.List.ToList(); - Parallel.ForEach(physicsBodies, parallelOptions, body => - { - if ((body.Enabled || body.UserData is Character) && - body.BodyType != BodyType.Static) + Parallel.Invoke(parallelOptions, + () => { - body.Update(); + Parallel.ForEach(physicsBodies, parallelOptions, body => + { + if ((body.Enabled || body.UserData is Character) && + body.BodyType != BodyType.Static) + { + body.Update(); + } + }); + }, + () => + { + GameMain.GameSession?.Update((float)deltaTime); } - }); - GameMain.GameSession?.Update((float)deltaTime); + ); - Parallel.ForEach(physicsBodies, parallelOptions, body => + foreach (PhysicsBody body in physicsBodies) { if (body.Enabled && body.BodyType != BodyType.Static) { body.SetPrevTransform(body.SimPosition, body.Rotation); } - }); + } MapEntity.ClearHighlightedEntities(); @@ -245,14 +252,14 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - Parallel.Invoke(parallelOptions, - () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); }, - () => Character.UpdateAll((float)deltaTime, Camera.Instance) - ); + if (Level.Loaded != null) + { + Level.Loaded.Update((float)deltaTime, Camera.Instance); + } + Character.UpdateAll((float)deltaTime, Camera.Instance); #endif var submarines = Submarine.Loaded.ToList(); - Parallel.ForEach(submarines, parallelOptions, sub => { sub.SetPrevTransform(sub.Position); @@ -270,8 +277,8 @@ namespace Barotrauma MapEntity.UpdateAll((float)deltaTime, cam, parallelOptions); #elif SERVER + MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions); - //StatusEffect.UpdateAll is not thread-safe and must be executed on the main thread StatusEffect.UpdateAll((float)deltaTime); From 3aadff7a3c8f63a824ebe4c4ef3cb63444332ae6 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Sat, 27 Dec 2025 15:51:04 +0800 Subject: [PATCH 09/13] Improved thread safety for Gap.update() --- Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 94b8bd03e..3fe156958 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -3,11 +3,12 @@ using Barotrauma.Items.Components; using FarseerPhysics; 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; -using MoonSharp.Interpreter; namespace Barotrauma { @@ -745,7 +746,7 @@ namespace Barotrauma waterFlowThisFrame = 0.0f; } - private static readonly HashSet checkedHulls = new HashSet(); + private static readonly ConcurrentBag checkedHulls = new ConcurrentBag(); /// /// 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. @@ -764,7 +765,7 @@ namespace Barotrauma } } - static void SimulateWaterFlowFromOutsideToConnectedHullsRecursive(Hull targetHull, Gap gap, HashSet checkedHulls, Hull originHull, float maxFlow, float deltaTime) + static void SimulateWaterFlowFromOutsideToConnectedHullsRecursive(Hull targetHull, Gap gap, ConcurrentBag checkedHulls, Hull originHull, float maxFlow, float deltaTime) { const float decay = 0.95f; From a44d89f9539729a5c25fe2f0b37285e2923e6ec8 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <77662224+NotAlwaysTrue@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:05:20 +0800 Subject: [PATCH 10/13] Revert "Revert back to 1.0.3" --- .../ServerSource/PerformenceMonitor.cs | 5 ++ .../BarotraumaServer/ServerSource/Program.cs | 2 +- .../Map/Levels/LevelObjects/LevelTrigger.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 85 +++++++------------ .../SharedSource/Screens/GameScreen.cs | 41 ++++----- 5 files changed, 57 insertions(+), 78 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs index d6d4c5d8d..9186bd504 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs @@ -37,6 +37,10 @@ namespace Barotrauma { get { return PhysicsBody.List.Count; } } + public int ConnectClients + { + get { return Client.ClientList.Count; } + } public double RealTickRate { @@ -160,6 +164,7 @@ namespace Barotrauma return $"Server Performence 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" + diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index 35e2503ef..1f5a9727b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -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) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index b05cc6be7..261a21706 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -479,7 +479,7 @@ namespace Barotrauma { foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList) { - ContactEdge contactEdge = fixture.Body.ContactList.CreateCopy(); + ContactEdge contactEdge = fixture.Body.ContactList == null ? null: fixture.Body.ContactList.CreateCopy(); while (contactEdge != null) { if (contactEdge.Contact != null && diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 48ea30861..2634f9560 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -642,44 +642,34 @@ namespace Barotrauma /// public static void UpdateAll(float deltaTime, Camera cam , ParallelOptions parallelOptions) { - mapEntityUpdateTick++; #if CLIENT var sw = new System.Diagnostics.Stopwatch(); sw.Start(); #endif - bool shouldUpdateMapEntities = mapEntityUpdateTick % MapEntityUpdateInterval == 0; - bool shouldUpdatePower = mapEntityUpdateTick % PoweredUpdateInterval == 0; - // Buffer lists to avoid repeated allocations - var hullList = shouldUpdateMapEntities ? Hull.HullList.ToList() : null; - var structureList = shouldUpdateMapEntities ? Structure.WallList.ToList() : null; + var hullList = Hull.HullList.ToList(); + var structureList = Structure.WallList.ToList(); var gapList = Gap.GapList.ToList(); - var itemList = shouldUpdateMapEntities ? Item.ItemList.ToList() : null; + var itemList = Item.ItemList.ToList(); // First phase: parallel updates that have no order dependencies Parallel.Invoke(parallelOptions, // Hull parallel update () => { - if (shouldUpdateMapEntities && hullList != null) + Parallel.ForEach(hullList, parallelOptions, hull => { - Parallel.ForEach(hullList, parallelOptions, hull => - { - hull.Update(deltaTime * MapEntityUpdateInterval, cam); - }); - } + hull.Update(deltaTime, cam); + }); }, // Structure parallel update () => { - if (shouldUpdateMapEntities && structureList != null) + Parallel.ForEach(structureList, parallelOptions, structure => { - Parallel.ForEach(structureList, parallelOptions, structure => - { - structure.Update(deltaTime * MapEntityUpdateInterval, cam); - }); - } + structure.Update(deltaTime, cam); + }); }, // Gap reset (must be done before update) () => @@ -692,27 +682,21 @@ namespace Barotrauma // Powered components update () => { - if (shouldUpdatePower) - { - Powered.UpdatePower(deltaTime * PoweredUpdateInterval); - } + Powered.UpdatePower(deltaTime); } ); #if CLIENT // Hull Cheats need to be executed after Hull update - if (shouldUpdateMapEntities) - { - Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam); - } + Hull.UpdateCheats(deltaTime, cam); #endif // Gap update (has order dependencies, keep random order but execute sequentially) var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList(); - foreach (Gap gap in shuffledGaps) + Parallel.ForEach(gapList, parallelOptions, gap => { gap.Update(deltaTime, cam); - } + }); #if CLIENT sw.Stop(); @@ -721,33 +705,30 @@ namespace Barotrauma #endif // Item update (Item.Update() is not thread-safe and must be executed on the main thread) - if (shouldUpdateMapEntities && itemList != null) + Item.UpdatePendingConditionUpdates(deltaTime); + + float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; + Item lastUpdatedItem = null; + + try { - Item.UpdatePendingConditionUpdates(deltaTime); - - float scaledDeltaTime = deltaTime * MapEntityUpdateInterval; - Item lastUpdatedItem = null; - - try + foreach (Item item in itemList) { - foreach (Item item in itemList) - { - lastUpdatedItem = item; - item.Update(scaledDeltaTime, cam); - } + lastUpdatedItem = item; + item.Update(scaledDeltaTime, 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); - } - - UpdateAllProjSpecific(scaledDeltaTime); - Spawner?.Update(); } + 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); + } + + UpdateAllProjSpecific(scaledDeltaTime); + Spawner?.Update(); #if CLIENT sw.Stop(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 00bb104ba..182873fc1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -7,6 +7,7 @@ using FarseerPhysics; using System.Threading.Tasks; using System.Linq; using System.Collections.Generic; +using System; #if DEBUG && CLIENT @@ -24,7 +25,7 @@ namespace Barotrauma private static readonly ParallelOptions parallelOptions = new ParallelOptions { - MaxDegreeOfParallelism = 16 + MaxDegreeOfParallelism = Environment.ProcessorCount * 2, }; #if CLIENT @@ -151,31 +152,23 @@ namespace Barotrauma var physicsBodies = PhysicsBody.List.ToList(); - Parallel.Invoke(parallelOptions, - () => + Parallel.ForEach(physicsBodies, parallelOptions, body => + { + if ((body.Enabled || body.UserData is Character) && + body.BodyType != BodyType.Static) { - Parallel.ForEach(physicsBodies, parallelOptions, body => - { - if ((body.Enabled || body.UserData is Character) && - body.BodyType != BodyType.Static) - { - body.Update(); - } - }); - }, - () => - { - GameMain.GameSession?.Update((float)deltaTime); + body.Update(); } - ); + }); + GameMain.GameSession?.Update((float)deltaTime); - foreach (PhysicsBody body in physicsBodies) + Parallel.ForEach(physicsBodies, parallelOptions, body => { if (body.Enabled && body.BodyType != BodyType.Static) { body.SetPrevTransform(body.SimPosition, body.Rotation); } - } + }); MapEntity.ClearHighlightedEntities(); @@ -252,14 +245,14 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - if (Level.Loaded != null) - { - Level.Loaded.Update((float)deltaTime, Camera.Instance); - } - Character.UpdateAll((float)deltaTime, Camera.Instance); + Parallel.Invoke(parallelOptions, + () => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); }, + () => Character.UpdateAll((float)deltaTime, Camera.Instance) + ); #endif var submarines = Submarine.Loaded.ToList(); + Parallel.ForEach(submarines, parallelOptions, sub => { sub.SetPrevTransform(sub.Position); @@ -277,8 +270,8 @@ namespace Barotrauma MapEntity.UpdateAll((float)deltaTime, cam, parallelOptions); #elif SERVER - MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions); + //StatusEffect.UpdateAll is not thread-safe and must be executed on the main thread StatusEffect.UpdateAll((float)deltaTime); From 16131e0accc4bef7d8b912a26796d9b8ab9a7e9c Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <77662224+NotAlwaysTrue@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:45:51 +0800 Subject: [PATCH 11/13] Fixed a typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eba4ae19a..0e6cfed9a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# LuaCsForBarotrauma Enhanced Performence Project +# LuaCsForBarotrauma Enhanced Performance Project > ⚠ **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. From 559aeb3c3f42380d6a99f33341de8c869dfcc1bb Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <2136846186@qq.com> Date: Sat, 27 Dec 2025 18:47:50 +0800 Subject: [PATCH 12/13] Fixed a typo using e instead of a :( --- Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs | 4 ++-- Barotrauma/BarotraumaServer/ServerSource/GameMain.cs | 4 ++-- .../BarotraumaServer/ServerSource/PerformenceMonitor.cs | 8 ++++---- .../BarotraumaShared/SharedSource/Screens/GameScreen.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 6814e1b41..063b61d08 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -2771,8 +2771,8 @@ namespace Barotrauma commands.Add(new Command("ShowServerPerf", "Immediately log server performance info in ServerMessage", (string[] args) => { - GameServer.Log(PerformenceMonitor.PM.ToString(), ServerLog.MessageType.ServerMessage); - NewMessage(PerformenceMonitor.PM.ToString(), Color.Green); + GameServer.Log(PerformanceMonitor.PM.ToString(), ServerLog.MessageType.ServerMessage); + NewMessage(PerformanceMonitor.PM.ToString(), Color.Green); })); #if DEBUG diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index b0c49b38a..516bbba8f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -330,7 +330,7 @@ namespace Barotrauma Stopwatch performanceCounterTimer = Stopwatch.StartNew(); stopwatch = Stopwatch.StartNew(); - PerformenceMonitor PM = new PerformenceMonitor(); + PerformanceMonitor PM = new PerformanceMonitor(); long prevTicks = stopwatch.ElapsedTicks; while (ShouldRun) @@ -426,7 +426,7 @@ namespace Barotrauma } } - PerformenceMonitor.PM.Dispose(); + PerformanceMonitor.PM.Dispose(); stopwatch.Stop(); diff --git a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs index 9186bd504..442b91315 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/PerformenceMonitor.cs @@ -11,9 +11,9 @@ using System.Threading.Tasks; namespace Barotrauma { - public class PerformenceMonitor + public class PerformanceMonitor { - static public PerformenceMonitor PM; + static public PerformanceMonitor PM; private Stopwatch PMStopwatch = new Stopwatch(); @@ -111,7 +111,7 @@ namespace Barotrauma get; set; } - public PerformenceMonitor() + public PerformanceMonitor() { PM = this; RealTickRate = 60; @@ -161,7 +161,7 @@ namespace Barotrauma } override public string ToString() { - return $"Server Performence Info \n" + + return $"Server Performance Info \n" + $"Item Count: {ItemCount}\n" + $"Character Count: {CharacterCount}\n" + $"Clients Count {ConnectClients}\n " + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 182873fc1..11b3ce9ad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -114,7 +114,7 @@ namespace Barotrauma public override void Update(double deltaTime) { -#warning For now CL side performence counter is partly useless bucz multiple changes on such things. Need time to take care of it +#warning For now CL side performance counter is partly useless bucz multiple changes on such things. Need time to take care of it #if RUN_PHYSICS_IN_SEPARATE_THREAD physicsTime += deltaTime; From edd50ef181fa57c267eda40c6b1811f97382fa01 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <77662224+NotAlwaysTrue@users.noreply.github.com> Date: Sat, 27 Dec 2025 20:54:06 +0800 Subject: [PATCH 13/13] Update publish-release.yml --- .github/workflows/publish-release.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 5d63b200d..d4283cdd3 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -23,13 +23,16 @@ 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 + ARCHIVE_BASE_NAME: luacsforbarotraumaEP + + # windows:client:Windows/Client + # linux:client:Linux/Client + # mac:client:Mac/Client/Barotrauma.app/Contents/MacOS + # we do not currently provide a CL + # XXX: these file names are subject to shell expansion. # Be careful when using special characters. ARCHIVE_FILES_SERVER: | @@ -211,4 +214,4 @@ jobs: files: | ${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_{build,patch}_{windows,linux,mac}_{client,server}.zip ${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_{build,patch}_linux_{client,server}.tar.gz - ${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_refs.zip \ No newline at end of file + ${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_refs.zip