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 1/4] 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 2/4] 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 3/4] 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 4/4] 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();