Decal state handling (#12624)

This commit is contained in:
Leon Friedrich
2022-12-19 08:28:46 +13:00
committed by GitHub
parent 2759ef009e
commit 8f352f87c2
8 changed files with 312 additions and 99 deletions

View File

@@ -1,7 +1,9 @@
using Content.Shared.Decals; using Content.Shared.Decals;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Shared.GameStates;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using static Content.Shared.Decals.DecalGridComponent;
namespace Content.Client.Decals namespace Content.Client.Decals
{ {
@@ -21,6 +23,7 @@ namespace Content.Client.Decals
_overlay = new DecalOverlay(this, _sprites, EntityManager, PrototypeManager); _overlay = new DecalOverlay(this, _sprites, EntityManager, PrototypeManager);
_overlayManager.AddOverlay(_overlay); _overlayManager.AddOverlay(_overlay);
SubscribeLocalEvent<DecalGridComponent, ComponentHandleState>(OnHandleState);
SubscribeNetworkEvent<DecalChunkUpdateEvent>(OnChunkUpdate); SubscribeNetworkEvent<DecalChunkUpdateEvent>(OnChunkUpdate);
SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialize); SubscribeLocalEvent<GridInitializeEvent>(OnGridInitialize);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoval); SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoval);
@@ -73,6 +76,35 @@ namespace Content.Client.Decals
_decalZIndexIndex[gridId].Remove(uid); _decalZIndexIndex[gridId].Remove(uid);
} }
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args)
{
if (args.Current is not DecalGridState state)
return;
// is this a delta or full state?
var removedChunks = new List<Vector2i>();
if (!state.FullState)
{
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
{
if (!state.AllChunks!.Contains(key))
removedChunks.Add(key);
}
}
else
{
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
{
if (!state.Chunks.ContainsKey(key))
removedChunks.Add(key);
}
}
RemoveChunks(gridUid, gridComp, removedChunks);
UpdateChunks(gridUid, gridComp, state.Chunks);
return;
}
private void OnChunkUpdate(DecalChunkUpdateEvent ev) private void OnChunkUpdate(DecalChunkUpdateEvent ev)
{ {
foreach (var (gridId, updatedGridChunks) in ev.Data) foreach (var (gridId, updatedGridChunks) in ev.Data)
@@ -85,37 +117,7 @@ namespace Content.Client.Decals
continue; continue;
} }
var chunkCollection = gridComp.ChunkCollection.ChunkCollection; UpdateChunks(gridId, gridComp, updatedGridChunks);
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 updatedGridChunks)
{
if (chunkCollection.TryGetValue(indices, out var chunk))
{
var removedUids = new HashSet<uint>(chunk.Keys);
removedUids.ExceptWith(newChunkData.Keys);
foreach (var removedUid in removedUids)
{
RemoveDecalHook(gridId, removedUid);
chunkIndex.Remove(removedUid);
}
}
chunkCollection[indices] = newChunkData;
foreach (var (uid, decal) in newChunkData)
{
if (zIndexIndex.TryGetValue(uid, out var zIndex))
renderIndex[zIndex].Remove(uid);
renderIndex.GetOrNew(decal.ZIndex)[uid] = decal;
zIndexIndex[uid] = decal.ZIndex;
chunkIndex[uid] = indices;
}
}
} }
// Now we'll cull old chunks out of range as the server will send them to us anyway. // Now we'll cull old chunks out of range as the server will send them to us anyway.
@@ -129,6 +131,47 @@ namespace Content.Client.Decals
continue; continue;
} }
RemoveChunks(gridId, gridComp, chunks);
}
}
private void UpdateChunks(EntityUid gridId, DecalGridComponent gridComp, Dictionary<Vector2i, DecalChunk> updatedGridChunks)
{
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 updatedGridChunks)
{
if (chunkCollection.TryGetValue(indices, out var chunk))
{
var removedUids = new HashSet<uint>(chunk.Decals.Keys);
removedUids.ExceptWith(newChunkData.Decals.Keys);
foreach (var removedUid in removedUids)
{
RemoveDecalHook(gridId, removedUid);
chunkIndex.Remove(removedUid);
}
}
chunkCollection[indices] = newChunkData;
foreach (var (uid, decal) in newChunkData.Decals)
{
if (zIndexIndex.TryGetValue(uid, out var zIndex))
renderIndex[zIndex].Remove(uid);
renderIndex.GetOrNew(decal.ZIndex)[uid] = decal;
zIndexIndex[uid] = decal.ZIndex;
chunkIndex[uid] = indices;
}
}
}
private void RemoveChunks(EntityUid gridId, DecalGridComponent gridComp, IEnumerable<Vector2i> chunks)
{
var chunkCollection = gridComp.ChunkCollection.ChunkCollection; var chunkCollection = gridComp.ChunkCollection.ChunkCollection;
var chunkIndex = ChunkIndex[gridId]; var chunkIndex = ChunkIndex[gridId];
@@ -136,7 +179,7 @@ namespace Content.Client.Decals
{ {
if (!chunkCollection.TryGetValue(index, out var chunk)) continue; if (!chunkCollection.TryGetValue(index, out var chunk)) continue;
foreach (var (uid, _) in chunk) foreach (var uid in chunk.Decals.Keys)
{ {
RemoveDecalHook(gridId, uid); RemoveDecalHook(gridId, uid);
chunkIndex.Remove(uid); chunkIndex.Remove(uid);
@@ -147,4 +190,3 @@ namespace Content.Client.Decals
} }
} }
} }
}

View File

@@ -122,9 +122,9 @@ namespace Content.MapRenderer.Painters
// for some reason decal moment i guess. // for some reason decal moment i guess.
if (_sEntityManager.TryGetComponent<DecalGridComponent>(grid.Owner, out var comp)) if (_sEntityManager.TryGetComponent<DecalGridComponent>(grid.Owner, out var comp))
{ {
foreach (var (_, list) in comp.ChunkCollection.ChunkCollection) foreach (var chunk in comp.ChunkCollection.ChunkCollection.Values)
{ {
foreach (var (_, decal) in list) foreach (var decal in chunk.Decals.Values)
{ {
var (x, y) = TransformLocalPosition(decal.Coordinates, grid); var (x, y) = TransformLocalPosition(decal.Coordinates, grid);
decals.GetOrNew(grid.Owner).Add(new DecalData(decal, x, y)); decals.GetOrNew(grid.Owner).Add(new DecalData(decal, x, y));

View File

@@ -8,11 +8,15 @@ using Content.Shared.Decals;
using Content.Shared.Maps; using Content.Shared.Maps;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.GameStates;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Threading; using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using static Content.Shared.Decals.DecalGridComponent;
namespace Content.Server.Decals namespace Content.Server.Decals
{ {
@@ -23,6 +27,8 @@ namespace Content.Server.Decals
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
[Dependency] private readonly IParallelManager _parMan = default!; [Dependency] private readonly IParallelManager _parMan = default!;
[Dependency] private readonly ChunkingSystem _chunking = default!; [Dependency] private readonly ChunkingSystem _chunking = default!;
[Dependency] private readonly IConfigurationManager _conf = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IDependencyCollection _dependencies = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!;
private readonly Dictionary<EntityUid, HashSet<Vector2i>> _dirtyChunks = new(); private readonly Dictionary<EntityUid, HashSet<Vector2i>> _dirtyChunks = new();
@@ -36,6 +42,7 @@ namespace Content.Server.Decals
private ObjectPool<Dictionary<EntityUid, HashSet<Vector2i>>> _chunkViewerPool = private ObjectPool<Dictionary<EntityUid, HashSet<Vector2i>>> _chunkViewerPool =
new DefaultObjectPool<Dictionary<EntityUid, HashSet<Vector2i>>>( new DefaultObjectPool<Dictionary<EntityUid, HashSet<Vector2i>>>(
new DefaultPooledObjectPolicy<Dictionary<EntityUid, HashSet<Vector2i>>>(), 64); new DefaultPooledObjectPolicy<Dictionary<EntityUid, HashSet<Vector2i>>>(), 64);
private bool _pvsEnabled;
public override void Initialize() public override void Initialize()
{ {
@@ -43,10 +50,57 @@ namespace Content.Server.Decals
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged); SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
SubscribeLocalEvent<DecalGridComponent, ComponentGetState>(OnGetState);
SubscribeNetworkEvent<RequestDecalPlacementEvent>(OnDecalPlacementRequest); SubscribeNetworkEvent<RequestDecalPlacementEvent>(OnDecalPlacementRequest);
SubscribeNetworkEvent<RequestDecalRemovalEvent>(OnDecalRemovalRequest); SubscribeNetworkEvent<RequestDecalRemovalEvent>(OnDecalRemovalRequest);
SubscribeLocalEvent<PostGridSplitEvent>(OnGridSplit); SubscribeLocalEvent<PostGridSplitEvent>(OnGridSplit);
_conf.OnValueChanged(CVars.NetPVS, OnPvsToggle, true);
}
private void OnPvsToggle(bool value)
{
if (value == _pvsEnabled)
return;
_pvsEnabled = value;
if (value)
return;
foreach (var playerData in _previousSentChunks.Values)
{
playerData.Clear();
}
foreach (var (grid, meta) in EntityQuery<DecalGridComponent, MetaDataComponent>(true))
{
grid.ForceTick = _timing.CurTick;
Dirty(grid, meta);
}
}
private void OnGetState(EntityUid uid, DecalGridComponent component, ref ComponentGetState args)
{
if (_pvsEnabled && !args.ReplayState)
return;
// Should this be a full component state or a delta-state?
if (args.FromTick <= component.CreationTick && args.FromTick <= component.ForceTick)
{
args.State = new DecalGridState(component.ChunkCollection.ChunkCollection);
return;
}
var data = new Dictionary<Vector2i, DecalChunk>();
foreach (var (index, chunk) in component.ChunkCollection.ChunkCollection)
{
if (chunk.LastModified >= args.FromTick)
data[index] = chunk;
}
args.State = new DecalGridState(data) { AllChunks = new(component.ChunkCollection.ChunkCollection.Keys) };
} }
private void OnGridSplit(ref PostGridSplitEvent ev) private void OnGridSplit(ref PostGridSplitEvent ev)
@@ -69,16 +123,16 @@ namespace Content.Server.Decals
var bounds = new Box2(tilePos - 0.01f, tilePos + 1.01f); var bounds = new Box2(tilePos - 0.01f, tilePos + 1.01f);
var toRemove = new RemQueue<uint>(); var toRemove = new RemQueue<uint>();
foreach (var (oldUid, decal) in oldChunk) foreach (var (oldUid, decal) in oldChunk.Decals)
{ {
if (!bounds.Contains(decal.Coordinates)) continue; if (!bounds.Contains(decal.Coordinates)) continue;
var uid = chunkCollection.NextUid++; var uid = chunkCollection.NextUid++;
var chunk = chunkCollection.ChunkCollection.GetOrNew(chunkIndices); var chunk = chunkCollection.ChunkCollection.GetOrNew(chunkIndices);
chunk[uid] = decal; chunk.Decals[uid] = decal;
ChunkIndex[ev.Grid][uid] = chunkIndices; ChunkIndex[ev.Grid][uid] = chunkIndices;
DirtyChunk(ev.Grid, chunkIndices); DirtyChunk(ev.Grid, chunkIndices, chunk);
toRemove.Add(oldUid); toRemove.Add(oldUid);
ChunkIndex[ev.OldGrid].Remove(oldUid); ChunkIndex[ev.OldGrid].Remove(oldUid);
@@ -86,14 +140,14 @@ namespace Content.Server.Decals
foreach (var uid in toRemove) foreach (var uid in toRemove)
{ {
oldChunk.Remove(uid); oldChunk.Decals.Remove(uid);
} }
if (oldChunk.Count == 0) if (oldChunk.Decals.Count == 0)
oldChunkCollection.ChunkCollection.Remove(chunkIndices); oldChunkCollection.ChunkCollection.Remove(chunkIndices);
if (toRemove.List?.Count > 0) if (toRemove.List?.Count > 0)
DirtyChunk(ev.OldGrid, chunkIndices); DirtyChunk(ev.OldGrid, chunkIndices, oldChunk);
} }
} }
@@ -102,6 +156,7 @@ namespace Content.Server.Decals
base.Shutdown(); base.Shutdown();
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged; _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
_conf.UnsubValueChanged(CVars.NetPVS, OnPvsToggle);
} }
private void OnTileChanged(TileChangedEvent args) private void OnTileChanged(TileChangedEvent args)
@@ -117,7 +172,7 @@ namespace Content.Server.Decals
var toDelete = new HashSet<uint>(); var toDelete = new HashSet<uint>();
if (chunkCollection.TryGetValue(indices, out var chunk)) if (chunkCollection.TryGetValue(indices, out var chunk))
{ {
foreach (var (uid, decal) in chunk) foreach (var (uid, decal) in chunk.Decals)
{ {
if (new Vector2((int) Math.Floor(decal.Coordinates.X), (int) Math.Floor(decal.Coordinates.Y)) == if (new Vector2((int) Math.Floor(decal.Coordinates.X), (int) Math.Floor(decal.Coordinates.Y)) ==
args.NewTile.GridIndices) args.NewTile.GridIndices)
@@ -134,7 +189,7 @@ namespace Content.Server.Decals
RemoveDecalInternal( args.NewTile.GridUid, uid); RemoveDecalInternal( args.NewTile.GridUid, uid);
} }
DirtyChunk(args.NewTile.GridUid, indices); DirtyChunk(args.NewTile.GridUid, indices, chunk!);
} }
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
@@ -190,8 +245,9 @@ namespace Content.Server.Decals
} }
} }
protected override void DirtyChunk(EntityUid id, Vector2i chunkIndices) protected override void DirtyChunk(EntityUid id, Vector2i chunkIndices, DecalChunk chunk)
{ {
chunk.LastModified = _timing.CurTick;
if(!_dirtyChunks.ContainsKey(id)) if(!_dirtyChunks.ContainsKey(id))
_dirtyChunks[id] = new HashSet<Vector2i>(); _dirtyChunks[id] = new HashSet<Vector2i>();
_dirtyChunks[id].Add(chunkIndices); _dirtyChunks[id].Add(chunkIndices);
@@ -227,11 +283,10 @@ namespace Content.Server.Decals
uid = chunkCollection.NextUid++; uid = chunkCollection.NextUid++;
var chunkIndices = GetChunkIndices(decal.Coordinates); var chunkIndices = GetChunkIndices(decal.Coordinates);
if(!chunkCollection.ChunkCollection.ContainsKey(chunkIndices)) var chunk = chunkCollection.ChunkCollection.GetOrNew(chunkIndices);
chunkCollection.ChunkCollection[chunkIndices] = new(); chunk.Decals[uid.Value] = decal;
chunkCollection.ChunkCollection[chunkIndices][uid.Value] = decal;
ChunkIndex[gridId.Value][uid.Value] = chunkIndices; ChunkIndex[gridId.Value][uid.Value] = chunkIndices;
DirtyChunk(gridId.Value, chunkIndices); DirtyChunk(gridId.Value, chunkIndices, chunk);
return true; return true;
} }
@@ -246,7 +301,7 @@ namespace Content.Server.Decals
if (chunkCollection == null || !chunkCollection.TryGetValue(chunkIndices, out var chunk)) if (chunkCollection == null || !chunkCollection.TryGetValue(chunkIndices, out var chunk))
return uids; return uids;
foreach (var (uid, decal) in chunk) foreach (var (uid, decal) in chunk.Decals)
{ {
if ((position - decal.Coordinates-new Vector2(0.5f, 0.5f)).Length > distance) if ((position - decal.Coordinates-new Vector2(0.5f, 0.5f)).Length > distance)
continue; continue;
@@ -276,15 +331,16 @@ namespace Content.Server.Decals
return false; return false;
} }
DirtyChunk(gridId, indices);
var chunkCollection = ChunkCollection(gridId); var chunkCollection = ChunkCollection(gridId);
if (chunkCollection == null) if (chunkCollection == null)
return false; return false;
var decal = chunkCollection[indices][uid]; var decal = chunkCollection[indices].Decals[uid];
if (newGridId == gridId) if (newGridId == gridId)
{ {
chunkCollection[indices][uid] = decal.WithCoordinates(position); var existingChunk = chunkCollection[indices];
existingChunk.Decals[uid] = decal.WithCoordinates(position);
DirtyChunk(gridId, indices, existingChunk);
return true; return true;
} }
@@ -297,9 +353,11 @@ namespace Content.Server.Decals
var chunkIndices = GetChunkIndices(position); var chunkIndices = GetChunkIndices(position);
if(!newChunkCollection.ContainsKey(chunkIndices)) if(!newChunkCollection.ContainsKey(chunkIndices))
newChunkCollection[chunkIndices] = new(); newChunkCollection[chunkIndices] = new();
newChunkCollection[chunkIndices][uid] = decal.WithCoordinates(position);
var chunk = newChunkCollection[chunkIndices];
chunk.Decals[uid] = decal.WithCoordinates(position);
ChunkIndex[newGridId][uid] = chunkIndices; ChunkIndex[newGridId][uid] = chunkIndices;
DirtyChunk(newGridId, chunkIndices); DirtyChunk(newGridId, chunkIndices, chunk);
return true; return true;
} }
@@ -315,9 +373,9 @@ namespace Content.Server.Decals
return false; return false;
var chunk = chunkCollection[indices]; var chunk = chunkCollection[indices];
var decal = chunk[uid]; var decal = chunk.Decals[uid];
chunk[uid] = decal.WithColor(color); chunk.Decals[uid] = decal.WithColor(color);
DirtyChunk(gridId, indices); DirtyChunk(gridId, indices, chunk);
return true; return true;
} }
@@ -336,9 +394,9 @@ namespace Content.Server.Decals
return false; return false;
var chunk = chunkCollection[indices]; var chunk = chunkCollection[indices];
var decal = chunk[uid]; var decal = chunk.Decals[uid];
chunk[uid] = decal.WithId(id); chunk.Decals[uid] = decal.WithId(id);
DirtyChunk(gridId, indices); DirtyChunk(gridId, indices , chunk);
return true; return true;
} }
@@ -354,9 +412,9 @@ namespace Content.Server.Decals
return false; return false;
var chunk = chunkCollection[indices]; var chunk = chunkCollection[indices];
var decal = chunk[uid]; var decal = chunk.Decals[uid];
chunk[uid] = decal.WithRotation(angle); chunk.Decals[uid] = decal.WithRotation(angle);
DirtyChunk(gridId, indices); DirtyChunk(gridId, indices, chunk);
return true; return true;
} }
@@ -372,9 +430,9 @@ namespace Content.Server.Decals
return false; return false;
var chunk = chunkCollection[indices]; var chunk = chunkCollection[indices];
var decal = chunk[uid]; var decal = chunk.Decals[uid];
chunk[uid] = decal.WithZIndex(zIndex); chunk.Decals[uid] = decal.WithZIndex(zIndex);
DirtyChunk(gridId, indices); DirtyChunk(gridId, indices, chunk);
return true; return true;
} }
@@ -390,9 +448,9 @@ namespace Content.Server.Decals
return false; return false;
var chunk = chunkCollection[indices]; var chunk = chunkCollection[indices];
var decal = chunk[uid]; var decal = chunk.Decals[uid];
chunk[uid] = decal.WithCleanable(cleanable); chunk.Decals[uid] = decal.WithCleanable(cleanable);
DirtyChunk(gridId, indices); DirtyChunk(gridId, indices, chunk);
return true; return true;
} }
@@ -400,9 +458,25 @@ namespace Content.Server.Decals
{ {
base.Update(frameTime); base.Update(frameTime);
foreach (var ent in _dirtyChunks.Keys)
{
if (TryComp(ent, out DecalGridComponent? decals))
Dirty(decals);
}
if (!_pvsEnabled)
{
_dirtyChunks.Clear();
return;
}
if (_pvsEnabled)
{
var players = _playerManager.ServerSessions.Where(x => x.Status == SessionStatus.InGame).ToArray(); var players = _playerManager.ServerSessions.Where(x => x.Status == SessionStatus.InGame).ToArray();
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount }; var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
Parallel.ForEach(players, opts, UpdatePlayer); Parallel.ForEach(players, opts, UpdatePlayer);
}
_dirtyChunks.Clear(); _dirtyChunks.Clear();
} }
@@ -506,20 +580,20 @@ namespace Content.Server.Decals
Dictionary<EntityUid, HashSet<Vector2i>> updatedChunks, Dictionary<EntityUid, HashSet<Vector2i>> updatedChunks,
Dictionary<EntityUid, HashSet<Vector2i>> staleChunks) Dictionary<EntityUid, HashSet<Vector2i>> staleChunks)
{ {
var updatedDecals = new Dictionary<EntityUid, Dictionary<Vector2i, Dictionary<uint, Decal>>>(); var updatedDecals = new Dictionary<EntityUid, Dictionary<Vector2i, DecalChunk>>();
foreach (var (gridId, chunks) in updatedChunks) foreach (var (gridId, chunks) in updatedChunks)
{ {
var collection = ChunkCollection(gridId); var collection = ChunkCollection(gridId);
if (collection == null) if (collection == null)
continue; continue;
var gridChunks = new Dictionary<Vector2i, Dictionary<uint, Decal>>(); var gridChunks = new Dictionary<Vector2i, DecalChunk>();
foreach (var indices in chunks) foreach (var indices in chunks)
{ {
gridChunks.Add(indices, gridChunks.Add(indices,
collection.TryGetValue(indices, out var chunk) collection.TryGetValue(indices, out var chunk)
? chunk ? chunk
: new Dictionary<uint, Decal>(0)); : new());
} }
updatedDecals[gridId] = gridChunks; updatedDecals[gridId] = gridChunks;
} }

View File

@@ -6,12 +6,13 @@ namespace Content.Shared.Decals
[DataDefinition] [DataDefinition]
public sealed class Decal public sealed class Decal
{ {
// if these are made not-readonly, then decal grid state handling needs to be updated to clone decals.
[DataField("coordinates")] public readonly Vector2 Coordinates = Vector2.Zero; [DataField("coordinates")] public readonly Vector2 Coordinates = Vector2.Zero;
[DataField("id")] public readonly string Id = string.Empty; [DataField("id")] public readonly string Id = string.Empty;
[DataField("color")] public readonly Color? Color; [DataField("color")] public readonly Color? Color;
[DataField("angle")] public readonly Angle Angle = Angle.Zero; [DataField("angle")] public readonly Angle Angle = Angle.Zero;
[DataField("zIndex")] public readonly int ZIndex; [DataField("zIndex")] public readonly int ZIndex;
[DataField("cleanable")] public bool Cleanable; [DataField("cleanable")] public readonly bool Cleanable;
public Decal() {} public Decal() {}

View File

@@ -1,12 +1,12 @@
using Robust.Shared.Map;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using static Content.Shared.Decals.DecalGridComponent;
namespace Content.Shared.Decals namespace Content.Shared.Decals
{ {
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class DecalChunkUpdateEvent : EntityEventArgs public sealed class DecalChunkUpdateEvent : EntityEventArgs
{ {
public Dictionary<EntityUid, Dictionary<Vector2i, Dictionary<uint, Decal>>> Data = new(); public Dictionary<EntityUid, Dictionary<Vector2i, DecalChunk>> Data = new();
public Dictionary<EntityUid, HashSet<Vector2i>> RemovedChunks = new(); public Dictionary<EntityUid, HashSet<Vector2i>> RemovedChunks = new();
} }
} }

View File

@@ -3,6 +3,7 @@ using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation; using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces; using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using static Content.Shared.Decals.DecalGridComponent;
namespace Content.Shared.Decals namespace Content.Shared.Decals
{ {
@@ -20,13 +21,13 @@ namespace Content.Shared.Decals
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null,
ISerializationManager.InstantiationDelegate<DecalGridComponent.DecalGridChunkCollection>? _ = default) ISerializationManager.InstantiationDelegate<DecalGridComponent.DecalGridChunkCollection>? _ = default)
{ {
var dictionary = serializationManager.Read<Dictionary<Vector2i, Dictionary<uint, Decal>>>(node, context, skipHook, notNullableOverride: true); var dictionary = serializationManager.Read<Dictionary<Vector2i, DecalChunk>>(node, context, skipHook: skipHook, notNullableOverride: true);
var uids = new SortedSet<uint>(); var uids = new SortedSet<uint>();
var uidChunkMap = new Dictionary<uint, Vector2i>(); var uidChunkMap = new Dictionary<uint, Vector2i>();
foreach (var (indices, decals) in dictionary) foreach (var (indices, decals) in dictionary)
{ {
foreach (var (uid, _) in decals) foreach (var uid in decals.Decals.Keys)
{ {
uids.Add(uid); uids.Add(uid);
uidChunkMap[uid] = indices; uidChunkMap[uid] = indices;
@@ -40,13 +41,13 @@ namespace Content.Shared.Decals
uidMap[uid] = nextIndex++; uidMap[uid] = nextIndex++;
} }
var newDict = new Dictionary<Vector2i, Dictionary<uint, Decal>>(); var newDict = new Dictionary<Vector2i, DecalChunk>();
foreach (var (oldUid, newUid) in uidMap) foreach (var (oldUid, newUid) in uidMap)
{ {
var indices = uidChunkMap[oldUid]; var indices = uidChunkMap[oldUid];
if(!newDict.ContainsKey(indices)) if(!newDict.ContainsKey(indices))
newDict[indices] = new(); newDict[indices] = new();
newDict[indices][newUid] = dictionary[indices][oldUid]; newDict[indices].Decals[newUid] = dictionary[indices].Decals[oldUid];
} }
return new DecalGridComponent.DecalGridChunkCollection(newDict){NextUid = nextIndex}; return new DecalGridComponent.DecalGridChunkCollection(newDict){NextUid = nextIndex};

View File

@@ -1,16 +1,110 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Content.Shared.Decals.DecalGridComponent;
namespace Content.Shared.Decals namespace Content.Shared.Decals
{ {
[RegisterComponent] [RegisterComponent]
[Access(typeof(SharedDecalSystem))] [Access(typeof(SharedDecalSystem))]
[NetworkedComponent]
public sealed class DecalGridComponent : Component public sealed class DecalGridComponent : Component
{ {
[DataField("chunkCollection", serverOnly: true)] [DataField("chunkCollection", serverOnly: true)]
public DecalGridChunkCollection ChunkCollection = new(new ()); public DecalGridChunkCollection ChunkCollection = new(new ());
[DataRecord] /// <summary>
public record DecalGridChunkCollection(Dictionary<Vector2i, Dictionary<uint, Decal>> ChunkCollection) /// Tick at which PVS was last toggled. Ensures that all players receive a full update when toggling PVS.
/// </summary>
public GameTick ForceTick { get; set; }
[DataDefinition]
[Serializable, NetSerializable]
public sealed class DecalChunk
{
[DataField("decals")]
public Dictionary<uint, Decal> Decals;
[NonSerialized]
public GameTick LastModified;
public DecalChunk()
{
Decals = new();
}
public DecalChunk(Dictionary<uint, Decal> decals)
{
Decals = decals;
}
public DecalChunk(DecalChunk chunk)
{
// decals are readonly, so this should be fine.
Decals = chunk.Decals.ShallowClone();
LastModified = chunk.LastModified;
}
}
[DataRecord, Serializable, NetSerializable]
public record DecalGridChunkCollection(Dictionary<Vector2i, DecalChunk> ChunkCollection)
{ {
public uint NextUid; public uint NextUid;
} }
} }
[Serializable, NetSerializable]
public sealed class DecalGridState : ComponentState, IComponentDeltaState
{
public Dictionary<Vector2i, DecalChunk> Chunks;
public bool FullState => AllChunks == null;
// required to infer deleted/missing chunks for delta states
public HashSet<Vector2i>? AllChunks;
public DecalGridState(Dictionary<Vector2i, DecalChunk> chunks)
{
Chunks = chunks;
}
public void ApplyToFullState(ComponentState fullState)
{
DebugTools.Assert(!FullState);
var state = (DecalGridState) fullState;
DebugTools.Assert(state.FullState);
foreach (var key in state.Chunks.Keys)
{
if (!AllChunks!.Contains(key))
state.Chunks.Remove(key);
}
foreach (var (chunk, data) in Chunks)
{
state.Chunks[chunk] = new(data);
}
}
public ComponentState CreateNewFullState(ComponentState fullState)
{
DebugTools.Assert(!FullState);
var state = (DecalGridState) fullState;
DebugTools.Assert(state.FullState);
var chunks = new Dictionary<Vector2i, DecalChunk>(state.Chunks.Count);
foreach (var (chunk, data) in Chunks)
{
chunks[chunk] = new(data);
}
foreach (var (chunk, data) in state.Chunks)
{
if (AllChunks!.Contains(chunk))
chunks.TryAdd(chunk, new(data));
}
return new DecalGridState(chunks);
}
}
} }

View File

@@ -4,6 +4,7 @@ using Robust.Shared.Configuration;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using static Content.Shared.Decals.DecalGridComponent;
namespace Content.Shared.Decals namespace Content.Shared.Decals
{ {
@@ -32,7 +33,7 @@ namespace Content.Shared.Decals
ChunkIndex[msg.EntityUid] = new(); ChunkIndex[msg.EntityUid] = new();
foreach (var (indices, decals) in comp.ChunkCollection.ChunkCollection) foreach (var (indices, decals) in comp.ChunkCollection.ChunkCollection)
{ {
foreach (var uid in decals.Keys) foreach (var uid in decals.Decals.Keys)
{ {
ChunkIndex[msg.EntityUid][uid] = indices; ChunkIndex[msg.EntityUid][uid] = indices;
} }
@@ -47,13 +48,13 @@ namespace Content.Shared.Decals
return comp.ChunkCollection; return comp.ChunkCollection;
} }
protected Dictionary<Vector2i, Dictionary<uint, Decal>>? ChunkCollection(EntityUid gridEuid, DecalGridComponent? comp = null) protected Dictionary<Vector2i, DecalChunk>? ChunkCollection(EntityUid gridEuid, DecalGridComponent? comp = null)
{ {
var collection = DecalGridChunkCollection(gridEuid, comp); var collection = DecalGridChunkCollection(gridEuid, comp);
return collection?.ChunkCollection; return collection?.ChunkCollection;
} }
protected virtual void DirtyChunk(EntityUid id, Vector2i chunkIndices) {} protected virtual void DirtyChunk(EntityUid id, Vector2i chunkIndices, DecalChunk chunk) {}
protected bool RemoveDecalInternal(EntityUid gridId, uint uid) protected bool RemoveDecalInternal(EntityUid gridId, uint uid)
{ {
@@ -65,16 +66,16 @@ namespace Content.Shared.Decals
} }
var chunkCollection = ChunkCollection(gridId); var chunkCollection = ChunkCollection(gridId);
if (chunkCollection == null || !chunkCollection.TryGetValue(indices, out var chunk) || !chunk.Remove(uid)) if (chunkCollection == null || !chunkCollection.TryGetValue(indices, out var chunk) || !chunk.Decals.Remove(uid))
{ {
return false; return false;
} }
if (chunk.Count == 0) if (chunk.Decals.Count == 0)
chunkCollection.Remove(indices); chunkCollection.Remove(indices);
ChunkIndex[gridId].Remove(uid); ChunkIndex[gridId].Remove(uid);
DirtyChunk(gridId, indices); DirtyChunk(gridId, indices, chunk);
return true; return true;
} }