From 237b842d651682f1007a9f279ee8c363d55704a1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 2 Nov 2022 20:23:26 +1300 Subject: [PATCH] Parallelize gas and decal systems. (#12349) --- .../EntitySystems/GasTileOverlaySystem.cs | 68 ++++---- Content.Server/Decals/DecalSystem.cs | 156 +++++++++--------- 2 files changed, 110 insertions(+), 114 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs index 46444087b9..218df29627 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -1,4 +1,3 @@ -using System.Runtime.CompilerServices; using Content.Server.Atmos.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.EntitySystems; @@ -12,9 +11,12 @@ using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Map; -using Robust.Shared.Player; +using Robust.Shared.Threading; using Robust.Shared.Timing; using Robust.Shared.Utility; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute; // ReSharper disable once RedundantUsingDirective @@ -28,6 +30,7 @@ namespace Content.Server.Atmos.EntitySystems [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IConfigurationManager _confMan = default!; + [Dependency] private readonly IParallelManager _parMan = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly ChunkingSystem _chunkingSys = default!; @@ -96,30 +99,22 @@ namespace Content.Server.Atmos.EntitySystems { if (e.NewStatus != SessionStatus.InGame) { - if (_lastSentChunks.Remove(e.Session, out var set)) - ReturnToPool(set); - return; + if (_lastSentChunks.Remove(e.Session, out var sets)) + { + foreach (var set in sets.Values) + { + set.Clear(); + _chunkIndexPool.Return(set); + } + } } if (!_lastSentChunks.ContainsKey(e.Session)) { - _lastSentChunks[e.Session] = _chunkViewerPool.Get(); - DebugTools.Assert(_lastSentChunks[e.Session].Count == 0); + _lastSentChunks[e.Session] = new(); } } - private void ReturnToPool(Dictionary> chunks) - { - foreach (var (_, previous) in chunks) - { - previous.Clear(); - _chunkIndexPool.Return(previous); - } - - chunks.Clear(); - _chunkViewerPool.Return(chunks); - } - /// /// Updates the visuals for a tile on some grid chunk. /// @@ -164,6 +159,7 @@ namespace Content.Server.Atmos.EntitySystems private void UpdateOverlayData(GameTick curTick) { + // TODO parallelize? foreach (var (gridId, invalidIndices) in _invalidTiles) { if (!TryComp(gridId, out GridAtmosphereComponent? gam)) @@ -203,24 +199,16 @@ namespace Content.Server.Atmos.EntitySystems // Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range // If they are, check if they need the new data to send (i.e. if there's an overlay for the gas). // Afterwards we reset all the chunk data for the next time we tick. - var xformQuery = GetEntityQuery(); - - foreach (var session in Filter.GetAllPlayers(_playerManager)) - { - if (session is IPlayerSession { Status: SessionStatus.InGame } playerSession) - UpdatePlayer(playerSession, xformQuery, curTick); - } + var players = _playerManager.ServerSessions.Where(x => x.Status == SessionStatus.InGame).ToArray(); + var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount }; + Parallel.ForEach(players, opts, p => UpdatePlayer(p, curTick)); } - private void UpdatePlayer(IPlayerSession playerSession, EntityQuery xformQuery, GameTick curTick) + private void UpdatePlayer(IPlayerSession playerSession, GameTick curTick) { + var xformQuery = GetEntityQuery(); var chunksInRange = _chunkingSys.GetChunksForSession(playerSession, ChunkSize, xformQuery, _chunkIndexPool, _chunkViewerPool); - - if (!_lastSentChunks.TryGetValue(playerSession, out var previouslySent)) - { - _lastSentChunks[playerSession] = previouslySent = _chunkViewerPool.Get(); - DebugTools.Assert(previouslySent.Count == 0); - } + var previouslySent = _lastSentChunks[playerSession]; var ev = new GasOverlayUpdateEvent(); @@ -289,8 +277,8 @@ namespace Content.Server.Atmos.EntitySystems } } - RaiseNetworkEvent(ev, playerSession.ConnectedClient); - ReturnToPool(ev.RemovedChunks); + if (ev.UpdatedChunks.Count != 0 || ev.RemovedChunks.Count != 0) + RaiseNetworkEvent(ev, playerSession.ConnectedClient); } public override void Reset(RoundRestartCleanupEvent ev) @@ -300,10 +288,14 @@ namespace Content.Server.Atmos.EntitySystems foreach (var data in _lastSentChunks.Values) { - ReturnToPool(data); - } + foreach (var previous in data.Values) + { + previous.Clear(); + _chunkIndexPool.Return(previous); + } - _lastSentChunks.Clear(); + data.Clear(); + } } } } diff --git a/Content.Server/Decals/DecalSystem.cs b/Content.Server/Decals/DecalSystem.cs index ffbd269f03..e590c6245a 100644 --- a/Content.Server/Decals/DecalSystem.cs +++ b/Content.Server/Decals/DecalSystem.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; using Content.Server.Administration.Managers; using Content.Shared.Administration; using Content.Shared.Chunking; @@ -9,6 +11,7 @@ using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Threading; using Robust.Shared.Utility; namespace Content.Server.Decals @@ -18,6 +21,7 @@ namespace Content.Server.Decals [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; + [Dependency] private readonly IParallelManager _parMan = default!; [Dependency] private readonly ChunkingSystem _chunking = default!; private readonly Dictionary> _dirtyChunks = new(); @@ -395,92 +399,91 @@ namespace Content.Server.Decals { base.Update(frameTime); + var players = _playerManager.ServerSessions.Where(x => x.Status == SessionStatus.InGame).ToArray(); + var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount }; + Parallel.ForEach(players, opts, UpdatePlayer); + _dirtyChunks.Clear(); + } + + public void UpdatePlayer(IPlayerSession player) + { var xformQuery = GetEntityQuery(); + var chunksInRange = _chunking.GetChunksForSession(player, ChunkSize, xformQuery, _chunkIndexPool, _chunkViewerPool); + var staleChunks = _chunkViewerPool.Get(); + var previouslySent = _previousSentChunks[player]; - foreach (var session in Filter.GetAllPlayers(_playerManager)) + // Get any chunks not in range anymore + // Then, remove them from previousSentChunks (for stuff like grids out of range) + // and also mark them as stale for networking. + + foreach (var (gridId, oldIndices) in previouslySent) { - if (session is not IPlayerSession { Status: SessionStatus.InGame } playerSession) + // Mark the whole grid as stale and flag for removal. + if (!chunksInRange.TryGetValue(gridId, out var chunks)) + { + previouslySent.Remove(gridId); + + // Was the grid deleted? + if (MapManager.IsGrid(gridId)) + staleChunks[gridId] = oldIndices; + else + { + // If grid was deleted then don't worry about telling the client to delete the chunk. + oldIndices.Clear(); + _chunkIndexPool.Return(oldIndices); + } + continue; - - var chunksInRange = _chunking.GetChunksForSession(playerSession, ChunkSize, xformQuery, _chunkIndexPool, _chunkViewerPool); - var staleChunks = _chunkViewerPool.Get(); - var previouslySent = _previousSentChunks.GetOrNew(playerSession); - - // Get any chunks not in range anymore - // Then, remove them from previousSentChunks (for stuff like grids out of range) - // and also mark them as stale for networking. - - foreach (var (gridId, oldIndices) in previouslySent) - { - // Mark the whole grid as stale and flag for removal. - if (!chunksInRange.TryGetValue(gridId, out var chunks)) - { - previouslySent.Remove(gridId); - - // Was the grid deleted? - if (MapManager.IsGrid(gridId)) - staleChunks[gridId] = oldIndices; - else - { - // If grid was deleted then don't worry about telling the client to delete the chunk. - oldIndices.Clear(); - _chunkIndexPool.Return(oldIndices); - } - - continue; - } - - var elmo = _chunkIndexPool.Get(); - - // Get individual stale chunks. - foreach (var chunk in oldIndices) - { - if (chunks.Contains(chunk)) continue; - elmo.Add(chunk); - } - - if (elmo.Count == 0) - { - _chunkIndexPool.Return(elmo); - continue; - } - - staleChunks.Add(gridId, elmo); } - var updatedChunks = _chunkViewerPool.Get(); - foreach (var (gridId, gridChunks) in chunksInRange) + var elmo = _chunkIndexPool.Get(); + + // Get individual stale chunks. + foreach (var chunk in oldIndices) { - var newChunks = _chunkIndexPool.Get(); - _dirtyChunks.TryGetValue(gridId, out var dirtyChunks); - - if (!previouslySent.TryGetValue(gridId, out var previousChunks)) - newChunks.UnionWith(gridChunks); - else - { - foreach (var index in gridChunks) - { - if (!previousChunks.Contains(index) || dirtyChunks != null && dirtyChunks.Contains(index)) - newChunks.Add(index); - } - - previousChunks.Clear(); - _chunkIndexPool.Return(previousChunks); - } - - previouslySent[gridId] = gridChunks; - - if (newChunks.Count == 0) - _chunkIndexPool.Return(newChunks); - else - updatedChunks[gridId] = newChunks; + if (chunks.Contains(chunk)) continue; + elmo.Add(chunk); } - //send all gridChunks to client - SendChunkUpdates(playerSession, updatedChunks, staleChunks); + if (elmo.Count == 0) + { + _chunkIndexPool.Return(elmo); + continue; + } + + staleChunks.Add(gridId, elmo); } - _dirtyChunks.Clear(); + var updatedChunks = _chunkViewerPool.Get(); + foreach (var (gridId, gridChunks) in chunksInRange) + { + var newChunks = _chunkIndexPool.Get(); + _dirtyChunks.TryGetValue(gridId, out var dirtyChunks); + + if (!previouslySent.TryGetValue(gridId, out var previousChunks)) + newChunks.UnionWith(gridChunks); + else + { + foreach (var index in gridChunks) + { + if (!previousChunks.Contains(index) || dirtyChunks != null && dirtyChunks.Contains(index)) + newChunks.Add(index); + } + + previousChunks.Clear(); + _chunkIndexPool.Return(previousChunks); + } + + previouslySent[gridId] = gridChunks; + + if (newChunks.Count == 0) + _chunkIndexPool.Return(newChunks); + else + updatedChunks[gridId] = newChunks; + } + + //send all gridChunks to client + SendChunkUpdates(player, updatedChunks, staleChunks); } private void ReturnToPool(Dictionary> chunks) @@ -518,7 +521,8 @@ namespace Content.Server.Decals updatedDecals[gridId] = gridChunks; } - RaiseNetworkEvent(new DecalChunkUpdateEvent{Data = updatedDecals, RemovedChunks = staleChunks}, Filter.SinglePlayer(session)); + if (updatedDecals.Count != 0 || staleChunks.Count != 0) + RaiseNetworkEvent(new DecalChunkUpdateEvent{Data = updatedDecals, RemovedChunks = staleChunks}, Filter.SinglePlayer(session)); ReturnToPool(updatedChunks); ReturnToPool(staleChunks);