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/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/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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 4d4634e31..00bb104ba 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,26 @@ namespace Barotrauma Character.Controlled?.UpdateLocalCursor(cam); #elif SERVER - if (Level.Loaded != null) Level.Loaded.Update((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(); - 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,7 +303,8 @@ namespace Barotrauma GameMain.PerformanceCounter.AddElapsedTicks("Update:Ragdolls", sw.ElapsedTicks); sw.Restart(); #endif - foreach (var sub in submarines) + + foreach(Submarine sub in submarines) { sub.Update((float)deltaTime); }