Refactor MapEntity.UpdateAll for improved parallelism
Rewrites MapEntity.UpdateAll to use more granular parallelism, reducing contention and improving performance by parallelizing hull, structure, and gap resets, while keeping order-dependent and non-thread-safe updates sequential. Updates GameScreen to pass ParallelOptions to UpdateAll.
This commit is contained in:
@@ -638,53 +638,80 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call Update() on every object in Entity.list
|
/// Call Update() on every object in Entity.list - 完全并行版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void UpdateAll(float deltaTime, Camera cam)
|
public static void UpdateAll(float deltaTime, Camera cam , ParallelOptions parallelOptions)
|
||||||
{
|
{
|
||||||
mapEntityUpdateTick++;
|
mapEntityUpdateTick++;
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
var sw = new System.Diagnostics.Stopwatch();
|
var sw = new System.Diagnostics.Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
#endif
|
#endif
|
||||||
Task StructuralTask = Task.Factory.StartNew(() =>
|
|
||||||
{
|
bool shouldUpdateMapEntities = mapEntityUpdateTick % MapEntityUpdateInterval == 0;
|
||||||
if (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 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
|
||||||
|
() =>
|
||||||
{
|
{
|
||||||
foreach (Hull hull in Hull.HullList)
|
if (shouldUpdateMapEntities && hullList != null)
|
||||||
{
|
{
|
||||||
hull.Update(deltaTime * MapEntityUpdateInterval, cam);
|
Parallel.ForEach(hullList, parallelOptions, hull =>
|
||||||
|
{
|
||||||
|
hull.Update(deltaTime * MapEntityUpdateInterval, cam);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Structure parallel update
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
if (shouldUpdateMapEntities && structureList != null)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(structureList, parallelOptions, structure =>
|
||||||
|
{
|
||||||
|
structure.Update(deltaTime * MapEntityUpdateInterval, cam);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Gap reset (must be done before update)
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
Parallel.ForEach(gapList, parallelOptions, gap =>
|
||||||
|
{
|
||||||
|
gap.ResetWaterFlowThisFrame();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Powered components update
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
if (shouldUpdatePower)
|
||||||
|
{
|
||||||
|
Powered.UpdatePower(deltaTime * PoweredUpdateInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
|
// Hull Cheats need to be executed after Hull update
|
||||||
|
if (shouldUpdateMapEntities)
|
||||||
|
{
|
||||||
Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam);
|
Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
foreach (Structure structure in Structure.WallList)
|
// Gap update (has order dependencies, keep random order but execute sequentially)
|
||||||
{
|
var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList();
|
||||||
structure.Update(deltaTime * MapEntityUpdateInterval, cam);
|
foreach (Gap gap in shuffledGaps)
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Task GapTask = Task.Factory.StartNew(() =>
|
|
||||||
{
|
{
|
||||||
foreach (Gap gap in Gap.GapList)
|
gap.Update(deltaTime, cam);
|
||||||
{
|
|
||||||
gap.ResetWaterFlowThisFrame();
|
|
||||||
}
|
|
||||||
//update gaps in random order, because otherwise in rooms with multiple gaps
|
|
||||||
//the water/air will always tend to flow through the first gap in the list,
|
|
||||||
//which may lead to weird behavior like water draining down only through
|
|
||||||
//one gap in a room even if there are several
|
|
||||||
foreach (Gap gap in Gap.GapList.OrderBy(g => Rand.Int(int.MaxValue)))
|
|
||||||
{
|
|
||||||
gap.Update(deltaTime, cam);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mapEntityUpdateTick % PoweredUpdateInterval == 0)
|
|
||||||
{
|
|
||||||
Powered.UpdatePower(deltaTime * PoweredUpdateInterval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
@@ -692,45 +719,39 @@ namespace Barotrauma
|
|||||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Misc", sw.ElapsedTicks);
|
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Misc", sw.ElapsedTicks);
|
||||||
sw.Restart();
|
sw.Restart();
|
||||||
#endif
|
#endif
|
||||||
//TODO: Divide ItemList into different parts to update
|
|
||||||
Task ItemTask = Task.Factory.StartNew(() =>
|
|
||||||
{
|
|
||||||
Item.UpdatePendingConditionUpdates(deltaTime);
|
|
||||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
|
||||||
{
|
|
||||||
Item lastUpdatedItem = null;
|
|
||||||
|
|
||||||
try
|
// Item update (Item.Update() is not thread-safe and must be executed on the main thread)
|
||||||
{
|
if (shouldUpdateMapEntities && itemList != null)
|
||||||
foreach (Item item in Item.ItemList)
|
|
||||||
{
|
|
||||||
lastUpdatedItem = item;
|
|
||||||
item.Update(deltaTime * MapEntityUpdateInterval, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Task.WaitAll(ItemTask, GapTask, StructuralTask);
|
|
||||||
|
|
||||||
if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
|
|
||||||
{
|
{
|
||||||
UpdateAllProjSpecific(deltaTime * MapEntityUpdateInterval);
|
Item.UpdatePendingConditionUpdates(deltaTime);
|
||||||
|
|
||||||
|
float scaledDeltaTime = deltaTime * MapEntityUpdateInterval;
|
||||||
|
Item lastUpdatedItem = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAllProjSpecific(scaledDeltaTime);
|
||||||
Spawner?.Update();
|
Spawner?.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Items", sw.ElapsedTicks);
|
GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Items", sw.ElapsedTicks);
|
||||||
sw.Restart();
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,11 +273,11 @@ namespace Barotrauma
|
|||||||
});
|
});
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
MapEntity.UpdateAll((float)deltaTime, cam);
|
MapEntity.UpdateAll((float)deltaTime, cam, parallelOptions);
|
||||||
#elif SERVER
|
#elif SERVER
|
||||||
|
|
||||||
// TODO: Move both UpdateAll() method to this file
|
// TODO: Move both UpdateAll() method to this file
|
||||||
MapEntity.UpdateAll((float)deltaTime, Camera.Instance);
|
MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions);
|
||||||
StatusEffect.UpdateAll((float)deltaTime);
|
StatusEffect.UpdateAll((float)deltaTime);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user