Enable and improve threaded physics execution

Activated the RUN_PHYSICS_IN_SEPARATE_THREAD directive and refactored the physics threading logic. Added cancellation support, improved error handling, and used synchronization primitives to coordinate physics updates. These changes aim to improve performance and stability when running physics in a separate thread.
This commit is contained in:
eero
2025-12-22 00:16:25 +08:00
parent dc08b6b92a
commit 7d9642a5b1
@@ -1,4 +1,4 @@
//#define RUN_PHYSICS_IN_SEPARATE_THREAD #define RUN_PHYSICS_IN_SEPARATE_THREAD
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Threading; using System.Threading;
@@ -6,10 +6,10 @@ using FarseerPhysics.Dynamics;
using FarseerPhysics; using FarseerPhysics;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq; using System.Linq;
using System;
#if DEBUG && CLIENT #if DEBUG && CLIENT
using System;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
#endif #endif
@@ -18,9 +18,15 @@ namespace Barotrauma
{ {
partial class GameScreen : Screen partial class GameScreen : Screen
{ {
private object updateLock = new object(); private readonly object updateLock = new object();
private double physicsTime; 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 #if CLIENT
private readonly Camera cam; private readonly Camera cam;
@@ -71,12 +77,13 @@ namespace Barotrauma
MapEntity.ClearHighlightedEntities(); MapEntity.ClearHighlightedEntities();
Task PhysicsTask = Task.Factory.StartNew(() => ExecutePhysics());
#if RUN_PHYSICS_IN_SEPARATE_THREAD #if RUN_PHYSICS_IN_SEPARATE_THREAD
physicsCancellation = new CancellationTokenSource();
var physicsThread = new Thread(ExecutePhysics) var physicsThread = new Thread(ExecutePhysics)
{ {
Name = "Physics thread", Name = "Physics thread",
IsBackground = true IsBackground = true,
Priority = ThreadPriority.AboveNormal
}; };
physicsThread.Start(); physicsThread.Start();
#endif #endif
@@ -85,6 +92,12 @@ namespace Barotrauma
public override void Deselect() public override void Deselect()
{ {
base.Deselect(); base.Deselect();
#if RUN_PHYSICS_IN_SEPARATE_THREAD
physicsCancellation?.Cancel();
physicsEvent?.Set();
#endif
#if CLIENT #if CLIENT
var config = GameSettings.CurrentConfig; var config = GameSettings.CurrentConfig;
config.CrewMenuOpen = CrewManager.PreferCrewMenuOpen; config.CrewMenuOpen = CrewManager.PreferCrewMenuOpen;
@@ -101,6 +114,7 @@ namespace Barotrauma
#endif #endif
#endif #endif
} }
/// <summary> /// <summary>
/// Allows the game to run logic such as updating the world, /// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio. /// 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 #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 #if RUN_PHYSICS_IN_SEPARATE_THREAD
physicsTime += deltaTime;
lock (updateLock) lock (updateLock)
{ {
physicsTime += deltaTime;
}
physicsEvent?.Set();
#endif #endif
@@ -334,27 +350,78 @@ namespace Barotrauma
GameMain.PerformanceCounter.AddElapsedTicks("Update:Physics", sw.ElapsedTicks); GameMain.PerformanceCounter.AddElapsedTicks("Update:Physics", sw.ElapsedTicks);
#endif #endif
UpdateProjSpecific(deltaTime); UpdateProjSpecific(deltaTime);
#if RUN_PHYSICS_IN_SEPARATE_THREAD
}
#endif
} }
partial void UpdateProjSpecific(double deltaTime); partial void UpdateProjSpecific(double deltaTime);
private void ExecutePhysics() 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) lock (updateLock)
{ {
GameMain.World.Step((float)Timing.Step); while (physicsTime >= Timing.Step)
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
} }
} }
} }