From 72599e528210a05b04f9e8e9aec7b9dfd368fcb2 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 15 Jul 2022 14:33:11 +1200 Subject: [PATCH] Fix disappearing decals bug (#9608) --- Content.Client/Commands/ZoomCommand.cs | 58 +++++++ Content.Client/Decals/DecalSystem.cs | 69 +++++---- Content.Server/Decals/DecalSystem.cs | 141 +++++++++++------- Content.Shared/Decals/SharedDecalSystem.cs | 22 ++- .../Locale/en-US/commands/zoom-command.ftl | 3 + Resources/clientCommandPerms.yml | 1 + 6 files changed, 204 insertions(+), 90 deletions(-) create mode 100644 Content.Client/Commands/ZoomCommand.cs create mode 100644 Resources/Locale/en-US/commands/zoom-command.ftl diff --git a/Content.Client/Commands/ZoomCommand.cs b/Content.Client/Commands/ZoomCommand.cs new file mode 100644 index 0000000000..f3adb61ae2 --- /dev/null +++ b/Content.Client/Commands/ZoomCommand.cs @@ -0,0 +1,58 @@ +using JetBrains.Annotations; +using Robust.Client.Graphics; +using Robust.Shared.Console; + +namespace Content.Client.Commands; + +[UsedImplicitly] +public sealed class ZoomCommand : IConsoleCommand +{ + [Dependency] private readonly IEyeManager _eyeMan = default!; + + public string Command => "zoom"; + public string Description => Loc.GetString("zoom-command-description"); + public string Help => Loc.GetString("zoom-command-help"); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + Vector2 zoom; + if (args.Length is not (1 or 2)) + { + shell.WriteLine(Help); + return; + } + + if (!float.TryParse(args[0], out var arg0)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[0]))); + return; + } + + if (arg0 > 0) + zoom = new(arg0, arg0); + else + { + shell.WriteError(Loc.GetString("zoom-command-error")); + return; + } + + if (args.Length == 2) + { + if (!float.TryParse(args[1], out var arg1)) + { + shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[1]))); + return; + } + + if (arg1 > 0) + zoom.Y = arg1; + else + { + shell.WriteError(Loc.GetString("zoom-command-error")); + return; + } + } + + _eyeMan.CurrentEye.Zoom = zoom; + } +} diff --git a/Content.Client/Decals/DecalSystem.cs b/Content.Client/Decals/DecalSystem.cs index 94827e3368..d70c5cf45b 100644 --- a/Content.Client/Decals/DecalSystem.cs +++ b/Content.Client/Decals/DecalSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Shared.Utility; namespace Content.Client.Decals { @@ -12,7 +13,7 @@ namespace Content.Client.Decals private DecalOverlay _overlay = default!; public Dictionary>> DecalRenderIndex = new(); - private Dictionary> DecalZIndexIndex = new(); + private Dictionary> _decalZIndexIndex = new(); public override void Initialize() { @@ -41,13 +42,13 @@ namespace Content.Client.Decals private void OnGridRemoval(GridRemovalEvent ev) { DecalRenderIndex.Remove(ev.EntityUid); - DecalZIndexIndex.Remove(ev.EntityUid); + _decalZIndexIndex.Remove(ev.EntityUid); } private void OnGridInitialize(GridInitializeEvent ev) { DecalRenderIndex[ev.EntityUid] = new(); - DecalZIndexIndex[ev.EntityUid] = new(); + _decalZIndexIndex[ev.EntityUid] = new(); } public override void Shutdown() @@ -64,25 +65,34 @@ namespace Content.Client.Decals private void RemoveDecalFromRenderIndex(EntityUid gridId, uint uid) { - var zIndex = DecalZIndexIndex[gridId][uid]; + var zIndex = _decalZIndexIndex[gridId][uid]; DecalRenderIndex[gridId][zIndex].Remove(uid); if (DecalRenderIndex[gridId][zIndex].Count == 0) DecalRenderIndex[gridId].Remove(zIndex); - DecalZIndexIndex[gridId].Remove(uid); + _decalZIndexIndex[gridId].Remove(uid); } private void OnChunkUpdate(DecalChunkUpdateEvent ev) { - foreach (var (gridId, gridChunks) in ev.Data) + foreach (var (gridId, updatedGridChunks) in ev.Data) { - if (gridChunks.Count == 0) continue; + if (updatedGridChunks.Count == 0) continue; - var chunkCollection = ChunkCollection(gridId); + if (!TryComp(gridId, out DecalGridComponent? gridComp)) + { + Logger.Error($"Received decal information for an entity without a decal component: {ToPrettyString(gridId)}"); + continue; + } + + var chunkCollection = gridComp.ChunkCollection.ChunkCollection; + var chunkIndex = ChunkIndex[gridId]; + var renderIndex = DecalRenderIndex[gridId]; + var zIndexIndex = _decalZIndexIndex[gridId]; // Update any existing data / remove decals we didn't receive data for. - foreach (var (indices, newChunkData) in gridChunks) + foreach (var (indices, newChunkData) in updatedGridChunks) { if (chunkCollection.TryGetValue(indices, out var chunk)) { @@ -90,30 +100,21 @@ namespace Content.Client.Decals removedUids.ExceptWith(newChunkData.Keys); foreach (var removedUid in removedUids) { - RemoveDecalInternal(gridId, removedUid); + RemoveDecalHook(gridId, removedUid); + chunkIndex.Remove(removedUid); } + } - chunkCollection[indices] = newChunkData; - } - else - { - chunkCollection.Add(indices, newChunkData); - } + chunkCollection[indices] = newChunkData; foreach (var (uid, decal) in newChunkData) - { - if (!DecalRenderIndex[gridId].ContainsKey(decal.ZIndex)) - DecalRenderIndex[gridId][decal.ZIndex] = new(); + { + if (zIndexIndex.TryGetValue(uid, out var zIndex)) + renderIndex[zIndex].Remove(uid); - if (DecalZIndexIndex.TryGetValue(gridId, out var values) && - values.TryGetValue(uid, out var zIndex)) - { - DecalRenderIndex[gridId][zIndex].Remove(uid); - } - - DecalRenderIndex[gridId][decal.ZIndex][uid] = decal; - DecalZIndexIndex[gridId][uid] = decal.ZIndex; - ChunkIndex[gridId][uid] = indices; + renderIndex.GetOrNew(decal.ZIndex)[uid] = decal; + zIndexIndex[uid] = decal.ZIndex; + chunkIndex[uid] = indices; } } } @@ -123,7 +124,14 @@ namespace Content.Client.Decals { if (chunks.Count == 0) continue; - var chunkCollection = ChunkCollection(gridId); + if (!TryComp(gridId, out DecalGridComponent? gridComp)) + { + Logger.Error($"Received decal information for an entity without a decal component: {ToPrettyString(gridId)}"); + continue; + } + + var chunkCollection = gridComp.ChunkCollection.ChunkCollection; + var chunkIndex = ChunkIndex[gridId]; foreach (var index in chunks) { @@ -131,7 +139,8 @@ namespace Content.Client.Decals foreach (var (uid, _) in chunk) { - RemoveDecalInternal(gridId, uid); + RemoveDecalHook(gridId, uid); + chunkIndex.Remove(uid); } chunkCollection.Remove(index); diff --git a/Content.Server/Decals/DecalSystem.cs b/Content.Server/Decals/DecalSystem.cs index a6d27172ff..b26a9323c5 100644 --- a/Content.Server/Decals/DecalSystem.cs +++ b/Content.Server/Decals/DecalSystem.cs @@ -53,6 +53,9 @@ namespace Content.Server.Decals var oldChunkCollection = DecalGridChunkCollection(ev.OldGrid); var chunkCollection = DecalGridChunkCollection(ev.Grid); + if (chunkCollection == null || oldChunkCollection == null) + return; + while (enumerator.MoveNext(out var tile)) { var tilePos = (Vector2) tile.Value.GridIndices; @@ -104,6 +107,9 @@ namespace Content.Server.Decals return; var chunkCollection = ChunkCollection(args.Entity); + if (chunkCollection == null) + return; + var indices = GetChunkIndices(args.NewTile.GridIndices); var toDelete = new HashSet(); if (chunkCollection.TryGetValue(indices, out var chunk)) @@ -213,6 +219,9 @@ namespace Content.Server.Decals return false; var chunkCollection = DecalGridChunkCollection(gridId.Value); + if (chunkCollection == null) + return false; + uid = chunkCollection.NextUid++; var chunkIndices = GetChunkIndices(decal.Coordinates); if(!chunkCollection.ChunkCollection.ContainsKey(chunkIndices)) @@ -231,7 +240,7 @@ namespace Content.Server.Decals var uids = new HashSet<(uint, Decal)>(); var chunkCollection = ChunkCollection(gridId); var chunkIndices = GetChunkIndices(position); - if (!chunkCollection.TryGetValue(chunkIndices, out var chunk)) + if (chunkCollection == null || !chunkCollection.TryGetValue(chunkIndices, out var chunk)) return uids; foreach (var (uid, decal) in chunk) @@ -266,6 +275,9 @@ namespace Content.Server.Decals DirtyChunk(gridId, indices); var chunkCollection = ChunkCollection(gridId); + if (chunkCollection == null) + return false; + var decal = chunkCollection[indices][uid]; if (newGridId == gridId) { @@ -276,6 +288,9 @@ namespace Content.Server.Decals RemoveDecalInternal(gridId, uid); var newChunkCollection = ChunkCollection(newGridId); + if (newChunkCollection == null) + return false; + var chunkIndices = GetChunkIndices(position); if(!newChunkCollection.ContainsKey(chunkIndices)) newChunkCollection[chunkIndices] = new(); @@ -293,8 +308,12 @@ namespace Content.Server.Decals } var chunkCollection = ChunkCollection(gridId); - var decal = chunkCollection[indices][uid]; - chunkCollection[indices][uid] = decal.WithColor(color); + if (chunkCollection == null) + return false; + + var chunk = chunkCollection[indices]; + var decal = chunk[uid]; + chunk[uid] = decal.WithColor(color); DirtyChunk(gridId, indices); return true; } @@ -310,8 +329,12 @@ namespace Content.Server.Decals throw new ArgumentOutOfRangeException($"Tried to set decal id to invalid prototypeid: {id}"); var chunkCollection = ChunkCollection(gridId); - var decal = chunkCollection[indices][uid]; - chunkCollection[indices][uid] = decal.WithId(id); + if (chunkCollection == null) + return false; + + var chunk = chunkCollection[indices]; + var decal = chunk[uid]; + chunk[uid] = decal.WithId(id); DirtyChunk(gridId, indices); return true; } @@ -324,8 +347,12 @@ namespace Content.Server.Decals } var chunkCollection = ChunkCollection(gridId); - var decal = chunkCollection[indices][uid]; - chunkCollection[indices][uid] = decal.WithRotation(angle); + if (chunkCollection == null) + return false; + + var chunk = chunkCollection[indices]; + var decal = chunk[uid]; + chunk[uid] = decal.WithRotation(angle); DirtyChunk(gridId, indices); return true; } @@ -338,8 +365,12 @@ namespace Content.Server.Decals } var chunkCollection = ChunkCollection(gridId); - var decal = chunkCollection[indices][uid]; - chunkCollection[indices][uid] = decal.WithZIndex(zIndex); + if (chunkCollection == null) + return false; + + var chunk = chunkCollection[indices]; + var decal = chunk[uid]; + chunk[uid] = decal.WithZIndex(zIndex); DirtyChunk(gridId, indices); return true; } @@ -352,8 +383,12 @@ namespace Content.Server.Decals } var chunkCollection = ChunkCollection(gridId); - var decal = chunkCollection[indices][uid]; - chunkCollection[indices][uid] = decal.WithCleanable(cleanable); + if (chunkCollection == null) + return false; + + var chunk = chunkCollection[indices]; + var decal = chunk[uid]; + chunk[uid] = decal.WithCleanable(cleanable); DirtyChunk(gridId, indices); return true; } @@ -368,24 +403,29 @@ namespace Content.Server.Decals continue; var chunksInRange = GetChunksForSession(playerSession); - var staleChunks = new Dictionary>(); + var staleChunks = _chunkViewerPool.Get(); + var previouslySent = _previousSentChunks[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. - var toRemoveGrids = new RemQueue(); - // Store the chunks for later to remove. - foreach (var (gridId, oldIndices) in _previousSentChunks[playerSession]) + foreach (var (gridId, oldIndices) in previouslySent) { // Mark the whole grid as stale and flag for removal. if (!chunksInRange.TryGetValue(gridId, out var chunks)) { - toRemoveGrids.Add(gridId); + previouslySent.Remove(gridId); - // If grid was deleted then don't worry about sending it to the client. - if (MapManager.TryGetGrid(gridId, out _)) + // 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; } @@ -412,46 +452,30 @@ namespace Content.Server.Decals foreach (var (gridId, gridChunks) in chunksInRange) { var newChunks = _chunkIndexPool.Get(); - newChunks.UnionWith(gridChunks); - if (_previousSentChunks[playerSession].TryGetValue(gridId, out var previousChunks)) + _dirtyChunks.TryGetValue(gridId, out var dirtyChunks); + + if (!previouslySent.TryGetValue(gridId, out var previousChunks)) + newChunks.UnionWith(gridChunks); + else { - newChunks.ExceptWith(previousChunks); + foreach (var index in gridChunks) + { + if (!previousChunks.Contains(index) || dirtyChunks != null && dirtyChunks.Contains(index)) + newChunks.Add(index); + } + + previousChunks.Clear(); + _chunkIndexPool.Return(previousChunks); } - if (_dirtyChunks.TryGetValue(gridId, out var dirtyChunks)) - { - var inRange = new HashSet(); - inRange.UnionWith(gridChunks); - inRange.IntersectWith(dirtyChunks); - newChunks.UnionWith(inRange); - } + previouslySent[gridId] = gridChunks; if (newChunks.Count == 0) - { _chunkIndexPool.Return(newChunks); - continue; - } - - // TODO: This is gonna have churn but mainly I want to fix the bugs rn. - _previousSentChunks[playerSession][gridId] = gridChunks; - updatedChunks[gridId] = newChunks; + else + updatedChunks[gridId] = newChunks; } - // We'll only remove stale grids after the above iteration. - foreach (var gridId in toRemoveGrids) - { - _previousSentChunks[playerSession].Remove(gridId); - } - - if (updatedChunks.Count == 0) - { - // ReturnToPool(chunksInRange); - // Even if updatedChunks is empty we'll still return it to the pool as it may have been allocated higher. - ReturnToPool(updatedChunks); - } - - if (updatedChunks.Count == 0 && staleChunks.Count == 0) continue; - //send all gridChunks to client SendChunkUpdates(playerSession, updatedChunks, staleChunks); } @@ -479,18 +503,25 @@ namespace Content.Server.Decals var updatedDecals = new Dictionary>>(); foreach (var (gridId, chunks) in updatedChunks) { + var collection = ChunkCollection(gridId); + if (collection == null) + continue; + var gridChunks = new Dictionary>(); foreach (var indices in chunks) { gridChunks.Add(indices, - ChunkCollection(gridId).TryGetValue(indices, out var chunk) + collection.TryGetValue(indices, out var chunk) ? chunk - : new Dictionary()); + : new Dictionary(0)); } updatedDecals[gridId] = gridChunks; } RaiseNetworkEvent(new DecalChunkUpdateEvent{Data = updatedDecals, RemovedChunks = staleChunks}, Filter.SinglePlayer(session)); + + ReturnToPool(updatedChunks); + ReturnToPool(staleChunks); } private HashSet GetSessionViewers(IPlayerSession session) @@ -528,14 +559,14 @@ namespace Content.Server.Decals foreach (var grid in MapManager.FindGridsIntersecting(mapId, bounds)) { - if (!chunks.ContainsKey(grid.GridEntityId)) - chunks[grid.GridEntityId] = _chunkIndexPool.Get(); + if (!chunks.TryGetValue(grid.GridEntityId, out var chunk)) + chunks[grid.GridEntityId] = chunk = _chunkIndexPool.Get(); var enumerator = new ChunkIndicesEnumerator(_transform.GetInvWorldMatrix(grid.GridEntityId, xformQuery).TransformBox(bounds), ChunkSize); while (enumerator.MoveNext(out var indices)) { - chunks[grid.GridEntityId].Add(indices.Value); + chunk.Add(indices.Value); } } } diff --git a/Content.Shared/Decals/SharedDecalSystem.cs b/Content.Shared/Decals/SharedDecalSystem.cs index ede333a8ba..c593b0fe9c 100644 --- a/Content.Shared/Decals/SharedDecalSystem.cs +++ b/Content.Shared/Decals/SharedDecalSystem.cs @@ -15,6 +15,8 @@ namespace Content.Shared.Decals protected readonly Dictionary> ChunkIndex = new(); + // Note that this constant is effectively baked into all map files, because of how they save the grid decal component. + // So if this ever needs changing, the maps need converting. public const int ChunkSize = 32; public static Vector2i GetChunkIndices(Vector2 coordinates) => new ((int) Math.Floor(coordinates.X / ChunkSize), (int) Math.Floor(coordinates.Y / ChunkSize)); @@ -52,9 +54,19 @@ namespace Content.Shared.Decals } } - protected DecalGridComponent.DecalGridChunkCollection DecalGridChunkCollection(EntityUid gridEuid) => - Comp(gridEuid).ChunkCollection; - protected Dictionary> ChunkCollection(EntityUid gridEuid) => DecalGridChunkCollection(gridEuid).ChunkCollection; + protected DecalGridComponent.DecalGridChunkCollection? DecalGridChunkCollection(EntityUid gridEuid, DecalGridComponent? comp = null) + { + if (!Resolve(gridEuid, ref comp)) + return null; + + return comp.ChunkCollection; + } + + protected Dictionary>? ChunkCollection(EntityUid gridEuid, DecalGridComponent? comp = null) + { + var collection = DecalGridChunkCollection(gridEuid, comp); + return collection?.ChunkCollection; + } protected virtual void DirtyChunk(EntityUid id, Vector2i chunkIndices) {} @@ -68,12 +80,12 @@ namespace Content.Shared.Decals } var chunkCollection = ChunkCollection(gridId); - if (!chunkCollection.TryGetValue(indices, out var chunk) || !chunk.Remove(uid)) + if (chunkCollection == null || !chunkCollection.TryGetValue(indices, out var chunk) || !chunk.Remove(uid)) { return false; } - if (chunkCollection[indices].Count == 0) + if (chunk.Count == 0) chunkCollection.Remove(indices); ChunkIndex[gridId].Remove(uid); diff --git a/Resources/Locale/en-US/commands/zoom-command.ftl b/Resources/Locale/en-US/commands/zoom-command.ftl new file mode 100644 index 0000000000..981ebe9490 --- /dev/null +++ b/Resources/Locale/en-US/commands/zoom-command.ftl @@ -0,0 +1,3 @@ +zoom-command-description = Sets the zoom of the main eye. +zoom-command-help = zoom ( | ) +zoom-command-error = scale has to be greater than 0 diff --git a/Resources/clientCommandPerms.yml b/Resources/clientCommandPerms.yml index a816953a4f..4d01cd7d4c 100644 --- a/Resources/clientCommandPerms.yml +++ b/Resources/clientCommandPerms.yml @@ -45,6 +45,7 @@ - showspread - showambient - showemergencyshuttle + - zoom - Flags: MAPPING Commands: