diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 169bb03e4..9775a0f5a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -1,4 +1,4 @@ -//#define RUN_PHYSICS_IN_SEPARATE_THREAD +#define RUN_PHYSICS_IN_SEPARATE_THREAD using Microsoft.Xna.Framework; using System.Threading; @@ -6,10 +6,10 @@ using FarseerPhysics.Dynamics; using FarseerPhysics; using System.Threading.Tasks; using System.Linq; +using System; #if DEBUG && CLIENT -using System; using Barotrauma.Sounds; using Microsoft.Xna.Framework.Input; #endif @@ -18,9 +18,15 @@ namespace Barotrauma { partial class GameScreen : Screen { - private object updateLock = new object(); + private readonly object updateLock = new object(); private double physicsTime; +#if RUN_PHYSICS_IN_SEPARATE_THREAD + private CancellationTokenSource physicsCancellation; + private readonly AutoResetEvent physicsEvent = new AutoResetEvent(false); + private const int PHYSICS_WAIT_TIMEOUT_MS = 2; +#endif + #if CLIENT private readonly Camera cam; @@ -71,12 +77,13 @@ namespace Barotrauma MapEntity.ClearHighlightedEntities(); - Task PhysicsTask = Task.Factory.StartNew(() => ExecutePhysics()); #if RUN_PHYSICS_IN_SEPARATE_THREAD + physicsCancellation = new CancellationTokenSource(); var physicsThread = new Thread(ExecutePhysics) { Name = "Physics thread", - IsBackground = true + IsBackground = true, + Priority = ThreadPriority.AboveNormal }; physicsThread.Start(); #endif @@ -85,6 +92,12 @@ namespace Barotrauma public override void Deselect() { base.Deselect(); + +#if RUN_PHYSICS_IN_SEPARATE_THREAD + physicsCancellation?.Cancel(); + physicsEvent?.Set(); +#endif + #if CLIENT var config = GameSettings.CurrentConfig; config.CrewMenuOpen = CrewManager.PreferCrewMenuOpen; @@ -101,6 +114,7 @@ namespace Barotrauma #endif #endif } + /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. @@ -111,9 +125,11 @@ namespace Barotrauma #warning For now CL side performence 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; lock (updateLock) - { + { + physicsTime += deltaTime; + } + physicsEvent?.Set(); #endif @@ -334,27 +350,78 @@ namespace Barotrauma GameMain.PerformanceCounter.AddElapsedTicks("Update:Physics", sw.ElapsedTicks); #endif UpdateProjSpecific(deltaTime); - -#if RUN_PHYSICS_IN_SEPARATE_THREAD - } -#endif } partial void UpdateProjSpecific(double deltaTime); private void ExecutePhysics() { - while (true) +#if RUN_PHYSICS_IN_SEPARATE_THREAD + var token = physicsCancellation.Token; + + try { - while (physicsTime >= Timing.Step) + while (!token.IsCancellationRequested) { + bool hasWork = false; + lock (updateLock) { - GameMain.World.Step((float)Timing.Step); - physicsTime -= Timing.Step; + while (physicsTime >= Timing.Step) + { + try + { + GameMain.World.Step((float)Timing.Step); + physicsTime -= Timing.Step; + hasWork = true; + } + catch (WorldLockedException e) + { + string errorMsg = $"Physics thread WorldLockedException: {e.Message}\n{e.StackTrace}"; + DebugConsole.ThrowError(errorMsg, e); + GameAnalyticsManager.AddErrorEventOnce( + "GameScreen.ExecutePhysics:WorldLockedException" + e.Message, + GameAnalyticsManager.ErrorSeverity.Error, + errorMsg); + break; + } + catch (Exception e) + { + string errorMsg = $"Physics step error: {e.Message}\n{e.StackTrace}"; + DebugConsole.ThrowError(errorMsg, e); + GameAnalyticsManager.AddErrorEventOnce( + "GameScreen.ExecutePhysics:PhysicsStepError" + e.GetType().Name, + GameAnalyticsManager.ErrorSeverity.Error, + errorMsg); + break; + } + } + } + + if (!hasWork) + { + physicsEvent.WaitOne(PHYSICS_WAIT_TIMEOUT_MS); } } } + catch (ThreadAbortException) + { + DebugConsole.Log("Physics thread aborted."); + } + catch (Exception e) + { + string errorMsg = $"Fatal error in physics thread: {e.Message}\n{e.StackTrace}"; + DebugConsole.ThrowError(errorMsg, e); + GameAnalyticsManager.AddErrorEventOnce( + "GameScreen.ExecutePhysics:FatalError", + GameAnalyticsManager.ErrorSeverity.Critical, + errorMsg); + } + finally + { + DebugConsole.Log("Physics thread terminated."); + } +#endif } } }