Kill NetworkedMapManager (#3516)

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
Co-authored-by: Paul <ritter.paul1@googlemail.com>
This commit is contained in:
metalgearsloth
2022-12-12 08:00:31 +11:00
committed by GitHub
parent 445a3aa8fb
commit 6b076645db
14 changed files with 147 additions and 324 deletions

View File

@@ -604,11 +604,6 @@ namespace Robust.Client.GameStates
_config.TickProcessMessages();
}
using (_prof.Group("Map Pre"))
{
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates.Span);
}
(IEnumerable<EntityUid> Created, List<EntityUid> Detached) output;
using (_prof.Group("Entity"))
{
@@ -855,7 +850,7 @@ namespace Robust.Client.GameStates
SharedTransformSystem xformSys)
{
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
//
//
// Naively: easy, just apply server states to process any transform states before deleting, right? But now
// that PVS detach messages are sent separately & processed over time, the entity may have left our view,
// but not yet been moved to null-space. In that case, the server would not send us transform states, and
@@ -1226,7 +1221,7 @@ namespace Robust.Client.GameStates
}
/// <summary>
/// Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
/// Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
/// </summary>
private void ResetAllEnts(IConsoleShell shell, string argStr, string[] args)
{

View File

@@ -7,6 +7,7 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Graphics.Clyde
{
@@ -168,7 +169,7 @@ namespace Robust.Client.Graphics.Clyde
public void _setChunkDirty(MapGridComponent grid, Vector2i chunk)
{
var data = _mapChunkData[grid.GridEntityId];
var data = _mapChunkData.GetOrNew(grid.GridEntityId);
if (data.TryGetValue(chunk, out var datum))
{
datum.Dirty = true;
@@ -196,7 +197,7 @@ namespace Robust.Client.Graphics.Clyde
private void _updateOnGridCreated(GridStartupEvent ev)
{
var gridId = ev.EntityUid;
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
_mapChunkData.GetOrNew(gridId);
}
private void _updateOnGridRemoved(GridRemovalEvent ev)

View File

@@ -265,7 +265,6 @@ namespace Robust.Server.GameStates
playerChunks[sessionIndex], metadataQuery, transformQuery, viewerEntities[sessionIndex])
: _pvs.GetAllEntityStates(session, lastAck, _gameTiming.CurTick);
var playerStates = _playerManager.GetPlayerStates(lastAck);
var mapData = _mapManager.GetStateData(lastAck);
// lastAck varies with each client based on lag and such, we can't just make 1 global state and send it to everyone
var lastInputCommand = inputSystem.GetLastInputCommand(session);
@@ -277,8 +276,7 @@ namespace Robust.Server.GameStates
Math.Max(lastInputCommand, lastSystemMessage),
entStates,
playerStates,
deletions,
mapData);
deletions);
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);

View File

@@ -1,8 +1,12 @@
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
@@ -24,11 +28,103 @@ public abstract partial class SharedMapSystem
return;
component.ChunkSize = state.ChunkSize;
if (state.ChunkData != null)
{
var modified = new List<(Vector2i position, Tile tile)>();
MapManager.SuppressOnTileChanged = true;
foreach (var chunkData in state.ChunkData)
{
if (chunkData.IsDeleted())
continue;
var chunk = component.GetOrAddChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == component.ChunkSize * component.ChunkSize);
var counter = 0;
for (ushort x = 0; x < component.ChunkSize; x++)
{
for (ushort y = 0; y < component.ChunkSize; y++)
{
var tile = chunkData.TileData[counter++];
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
modified.Add((new Vector2i(chunk.X * component.ChunkSize + x, chunk.Y * component.ChunkSize + y), tile));
}
}
}
foreach (var chunkData in state.ChunkData)
{
if (chunkData.IsDeleted())
{
component.RemoveChunk(chunkData.Index);
continue;
}
var chunk = component.GetOrAddChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
component.RegenerateCollision(chunk);
}
MapManager.SuppressOnTileChanged = false;
if (modified.Count != 0)
{
RaiseLocalEvent(uid, new GridModifiedEvent(component, modified), true);
}
}
}
private void OnGridGetState(EntityUid uid, MapGridComponent component, ref ComponentGetState args)
{
args.State = new MapGridComponentState(component.ChunkSize);
// TODO: Actual deltas.
List<ChunkDatum>? chunkData;
var fromTick = args.FromTick;
if (component.LastTileModifiedTick < fromTick)
{
chunkData = null;
}
else
{
chunkData = new List<ChunkDatum>();
var chunks = component.ChunkDeletionHistory;
foreach (var (tick, indices) in chunks)
{
if (tick < fromTick)
continue;
chunkData.Add(ChunkDatum.CreateDeleted(indices));
}
foreach (var (index, chunk) in component.GetMapChunks())
{
if (chunk.LastTileModifiedTick < fromTick)
continue;
var tileBuffer = new Tile[component.ChunkSize * (uint) component.ChunkSize];
// Flatten the tile array.
// NetSerializer doesn't do multi-dimensional arrays.
// This is probably really expensive.
for (var x = 0; x < component.ChunkSize; x++)
{
for (var y = 0; y < component.ChunkSize; y++)
{
tileBuffer[x * component.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
chunkData.Add(ChunkDatum.CreateModified(index, tileBuffer));
}
}
// TODO: Mark it as delta proper
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
}
private void OnGridAdd(EntityUid uid, MapGridComponent component, ComponentAdd args)

View File

@@ -21,6 +21,13 @@ public abstract partial class SharedMapSystem
return;
component.WorldMap = state.MapId;
if (!MapManager.MapExists(state.MapId))
{
var mapInternal = (IMapManagerInternal)MapManager;
mapInternal.CreateMap(state.MapId, uid);
}
component.LightingEnabled = state.LightingEnabled;
var xformQuery = GetEntityQuery<TransformComponent>();

View File

@@ -4,7 +4,6 @@ using System;
using System.Diagnostics;
using NetSerializer;
using Robust.Shared.Timing;
using System.Collections.Generic;
namespace Robust.Shared.GameStates
{
@@ -27,8 +26,7 @@ namespace Robust.Shared.GameStates
uint lastInput,
NetListAsArray<EntityState> entities,
NetListAsArray<PlayerState> players,
NetListAsArray<EntityUid> deletions,
GameStateMapData? mapData)
NetListAsArray<EntityUid> deletions)
{
FromSequence = fromSequence;
ToSequence = toSequence;
@@ -36,7 +34,6 @@ namespace Robust.Shared.GameStates
EntityStates = entities;
PlayerStates = players;
EntityDeletions = deletions;
MapData = mapData;
}
public readonly GameTick FromSequence;
@@ -47,6 +44,5 @@ namespace Robust.Shared.GameStates
public readonly NetListAsArray<EntityState> EntityStates;
public readonly NetListAsArray<PlayerState> PlayerStates;
public readonly NetListAsArray<EntityUid> EntityDeletions;
public readonly GameStateMapData? MapData;
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -8,63 +6,34 @@ using Robust.Shared.Serialization;
namespace Robust.Shared.GameStates
{
[Serializable, NetSerializable]
public sealed class GameStateMapData
public readonly struct ChunkDatum
{
public readonly KeyValuePair<EntityUid, GridDatum>[]? GridData;
public readonly Vector2i Index;
public GameStateMapData(KeyValuePair<EntityUid, GridDatum>[]? gridData)
// Definitely wasteful to send EVERY tile.
// Optimize away future coder.
// Also it's stored row-major.
public readonly Tile[] TileData;
public bool IsDeleted()
{
GridData = gridData;
return TileData == default;
}
[Serializable, NetSerializable]
public struct GridDatum
private ChunkDatum(Vector2i index, Tile[] tileData)
{
// TransformComponent State
public readonly MapCoordinates Coordinates;
public readonly Angle Angle;
// MapGridComponent State
public readonly ChunkDatum[] ChunkData;
public GridDatum(ChunkDatum[] chunkData, MapCoordinates coordinates, Angle angle)
{
ChunkData = chunkData;
Coordinates = coordinates;
Angle = angle;
}
Index = index;
TileData = tileData;
}
[Serializable, NetSerializable]
public readonly struct ChunkDatum
public static ChunkDatum CreateModified(Vector2i index, Tile[] tileData)
{
public readonly Vector2i Index;
return new ChunkDatum(index, tileData);
}
// Definitely wasteful to send EVERY tile.
// Optimize away future coder.
// Also it's stored row-major.
public readonly Tile[] TileData;
public bool IsDeleted()
{
return TileData == default;
}
private ChunkDatum(Vector2i index, Tile[] tileData)
{
Index = index;
TileData = tileData;
}
public static ChunkDatum CreateModified(Vector2i index, Tile[] tileData)
{
return new ChunkDatum(index, tileData);
}
public static ChunkDatum CreateDeleted(Vector2i index)
{
return new ChunkDatum(index, default!);
}
public static ChunkDatum CreateDeleted(Vector2i index)
{
return new ChunkDatum(index, default!);
}
}
}

View File

@@ -82,55 +82,6 @@ namespace Robust.Shared.Map.Components
_entMan.EventBus.RaiseLocalEvent(Owner, new EmptyGridEvent { GridId = Owner }, true);
}
internal static void ApplyMapGridState(NetworkedMapManager networkedMapManager, MapGridComponent gridComp,
GameStateMapData.ChunkDatum[] chunkUpdates)
{
networkedMapManager.SuppressOnTileChanged = true;
var modified = new List<(Vector2i position, Tile tile)>();
foreach (var chunkData in chunkUpdates)
{
if (chunkData.IsDeleted())
continue;
var chunk = gridComp.GetOrAddChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == gridComp.ChunkSize * gridComp.ChunkSize);
var counter = 0;
for (ushort x = 0; x < gridComp.ChunkSize; x++)
{
for (ushort y = 0; y < gridComp.ChunkSize; y++)
{
var tile = chunkData.TileData[counter++];
if (chunk.GetTile(x, y) == tile)
continue;
chunk.SetTile(x, y, tile);
modified.Add((new Vector2i(chunk.X * gridComp.ChunkSize + x, chunk.Y * gridComp.ChunkSize + y), tile));
}
}
}
if (modified.Count != 0)
MapManager.InvokeGridChanged(networkedMapManager, gridComp, modified);
foreach (var chunkData in chunkUpdates)
{
if (chunkData.IsDeleted())
{
gridComp.RemoveChunk(chunkData.Index);
continue;
}
var chunk = gridComp.GetOrAddChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
gridComp.RegenerateCollision(chunk);
}
networkedMapManager.SuppressOnTileChanged = false;
}
/// <summary>
/// Regenerate collision for multiple chunks at once; faster than doing it individually.
/// </summary>
@@ -923,8 +874,9 @@ namespace Robust.Shared.Map.Components
{
// As the collision regeneration can potentially delete the chunk we'll notify of the tile changed first.
var gridTile = mapChunk.ChunkTileToGridTile(tileIndices);
mapChunk.LastTileModifiedTick = _timing.CurTick;
LastTileModifiedTick = _timing.CurTick;
mapChunk.LastTileModifiedTick = _mapManager.GameTiming.CurTick;
LastTileModifiedTick = _mapManager.GameTiming.CurTick;
_entMan.Dirty(this);
// The map serializer currently sets tiles of unbound grids as part of the deserialization process
// It properly sets SuppressOnTileChanged so that the event isn't spammed for every tile on the grid.
@@ -951,15 +903,20 @@ namespace Robust.Shared.Map.Components
/// <summary>
/// The size of the chunks in the map grid.
/// </summary>
public ushort ChunkSize { get; }
public ushort ChunkSize;
/// <summary>
/// Networked chunk data.
/// </summary>
public List<ChunkDatum>? ChunkData;
/// <summary>
/// Constructs a new instance of <see cref="MapGridComponentState"/>.
/// </summary>
/// <param name="chunkSize">The size of the chunks in the map grid.</param>
public MapGridComponentState(ushort chunkSize)
public MapGridComponentState(ushort chunkSize, List<ChunkDatum>? chunkData)
{
ChunkSize = chunkSize;
ChunkData = chunkData;
}
}
}

View File

@@ -43,6 +43,8 @@ namespace Robust.Shared.Map
/// <param name="oldTile">The old tile that got replaced.</param>
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile);
MapId CreateMap(MapId? mapId, EntityUid euid);
void TrueDeleteMap(MapId mapId);
void OnGridBoundsChange(EntityUid uid, MapGridComponent grid);
}

View File

@@ -159,13 +159,6 @@ internal partial class MapManager
/// <inheritdoc />
public event EventHandler<TileChangedEventArgs>? TileChanged;
/// <summary>
/// Should the OnTileChanged event be suppressed? This is useful for initially loading the map
/// so that you don't spam an event for each of the million station tiles.
/// </summary>
/// <inheritdoc />
public event EventHandler<GridChangedEventArgs>? GridChanged;
/// <inheritdoc />
public bool SuppressOnTileChanged { get; set; }
@@ -208,10 +201,4 @@ internal partial class MapManager
EntityManager.StartComponents(gridEnt);
return grid;
}
protected internal static void InvokeGridChanged(MapManager mapManager, MapGridComponent mapGrid, IReadOnlyCollection<(Vector2i position, Tile tile)> changedTiles)
{
mapManager.GridChanged?.Invoke(mapManager, new GridChangedEventArgs(mapGrid, changedTiles));
mapManager.EntityManager.EventBus.RaiseLocalEvent(mapGrid.GridEntityId, new GridModifiedEvent(mapGrid, changedTiles), true);
}
}

View File

@@ -68,7 +68,6 @@ internal partial class MapManager
{
var args = new MapEventArgs(mapId);
OnMapDestroyedGridTree(args);
MapDestroyed?.Invoke(this, args);
_mapEntities.Remove(mapId);
}
@@ -78,7 +77,7 @@ internal partial class MapManager
/// <inheritdoc />
public MapId CreateMap(MapId? mapId = null)
{
return CreateMap(mapId, default);
return ((IMapManagerInternal) this).CreateMap(mapId, default);
}
/// <inheritdoc />
@@ -223,13 +222,7 @@ internal partial class MapManager
return _highestMapId = new MapId(_highestMapId.Value + 1);
}
/// <inheritdoc />
public event EventHandler<MapEventArgs>? MapCreated;
/// <inheritdoc />
public event EventHandler<MapEventArgs>? MapDestroyed;
protected MapId CreateMap(MapId? mapId, EntityUid entityUid)
MapId IMapManagerInternal.CreateMap(MapId? mapId, EntityUid entityUid)
{
if (mapId == MapId.Nullspace)
throw new InvalidOperationException("Attempted to create a null-space map.");
@@ -283,8 +276,6 @@ internal partial class MapManager
var args = new MapEventArgs(actualId);
OnMapCreatedGridTree(args);
MapCreated?.Invoke(this, args);
return actualId;
}
}

View File

@@ -1,88 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Map;
internal interface INetworkedMapManager : IMapManagerInternal
{
GameStateMapData? GetStateData(GameTick fromTick);
void CullDeletionHistory(GameTick upToTick);
// Two methods here, so that new grids etc can be made BEFORE entities get states applied,
// but old ones can be deleted after.
void ApplyGameStatePre(GameStateMapData? data, ReadOnlySpan<EntityState> entityStates);
}
internal sealed class NetworkedMapManager : MapManager, INetworkedMapManager
{
public GameStateMapData? GetStateData(GameTick fromTick)
{
var gridDatums = new Dictionary<EntityUid, GameStateMapData.GridDatum>();
var enumerator = EntityManager.AllEntityQueryEnumerator<MapGridComponent>();
while (enumerator.MoveNext(out var iGrid))
{
if (iGrid.LastTileModifiedTick < fromTick)
continue;
var chunkData = new List<GameStateMapData.ChunkDatum>();
var chunks = iGrid.ChunkDeletionHistory;
foreach (var (tick, indices) in chunks)
{
if (tick < fromTick)
continue;
chunkData.Add(GameStateMapData.ChunkDatum.CreateDeleted(indices));
}
foreach (var (index, chunk) in iGrid.GetMapChunks())
{
if (chunk.LastTileModifiedTick < fromTick)
continue;
var tileBuffer = new Tile[iGrid.ChunkSize * (uint) iGrid.ChunkSize];
// Flatten the tile array.
// NetSerializer doesn't do multi-dimensional arrays.
// This is probably really expensive.
for (var x = 0; x < iGrid.ChunkSize; x++)
{
for (var y = 0; y < iGrid.ChunkSize; y++)
{
tileBuffer[x * iGrid.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
chunkData.Add(GameStateMapData.ChunkDatum.CreateModified(index, tileBuffer));
}
var gridXform = EntityManager.GetComponent<TransformComponent>(iGrid.GridEntityId);
var (worldPos, worldRot) = gridXform.GetWorldPositionRotation();
var gridDatum = new GameStateMapData.GridDatum(
chunkData.ToArray(),
new MapCoordinates(worldPos, gridXform.MapID),
worldRot);
gridDatums.Add(iGrid.GridEntityId, gridDatum);
}
// no point sending empty collections
if (gridDatums.Count == 0)
return default;
return new GameStateMapData(gridDatums.ToArray<KeyValuePair<EntityUid, GameStateMapData.GridDatum>>());
}
public void CullDeletionHistory(GameTick upToTick)
{
var query = EntityManager.EntityQueryEnumerator<MapGridComponent>();
var query = EntityManager.AllEntityQueryEnumerator<MapGridComponent>();
while (query.MoveNext(out var grid))
{
@@ -90,110 +20,4 @@ internal sealed class NetworkedMapManager : MapManager, INetworkedMapManager
chunks.RemoveAll(t => t.tick < upToTick);
}
}
private readonly List<(MapId mapId, EntityUid euid)> _newMaps = new();
private List<(MapId mapId, EntityUid euid, ushort chunkSize)> _newGrids = new();
public void ApplyGameStatePre(GameStateMapData? data, ReadOnlySpan<EntityState> entityStates)
{
// Setup new maps and grids
{
// search for any newly created map components
foreach (var entityState in entityStates)
{
foreach (var compChange in entityState.ComponentChanges.Span)
{
if (compChange.State is MapComponentState mapCompState)
{
var mapEuid = entityState.Uid;
var mapId = mapCompState.MapId;
// map already exists from a previous state.
if (MapExists(mapId))
continue;
_newMaps.Add((mapId, mapEuid));
}
else if (data != null && data.GridData != null && compChange.State is MapGridComponentState gridCompState)
{
var gridEuid = entityState.Uid;
var chunkSize = gridCompState.ChunkSize;
// grid already exists from a previous state
if(GridExists(gridEuid))
continue;
// Existing ent?
// I love NetworkedMapManager
if (EntityManager.EntityExists(gridEuid))
{
EntityManager.AddComponent<MapGridComponent>(gridEuid);
continue;
}
DebugTools.Assert(chunkSize > 0, $"Invalid chunk size in entity state for new grid {gridEuid}.");
MapId gridMapId = default;
foreach (var kvData in data.GridData)
{
if (kvData.Key != gridEuid)
continue;
gridMapId = kvData.Value.Coordinates.MapId;
break;
}
DebugTools.Assert(gridMapId != default, $"Could not find corresponding gridData for new grid {gridEuid}.");
_newGrids.Add((gridMapId, gridEuid, chunkSize));
}
}
}
// create all the new maps
foreach (var (mapId, euid) in _newMaps)
{
CreateMap(mapId, euid);
}
_newMaps.Clear();
// create all the new grids
foreach (var (mapId, euid, chunkSize) in _newGrids)
{
CreateGrid(mapId, chunkSize, euid);
}
_newGrids.Clear();
}
// Process all grid updates.
if (data != null && data.GridData != null)
{
// Ok good all the grids and maps exist now.
foreach (var (gridId, gridDatum) in data.GridData)
{
var xformComp = EntityManager.GetComponent<TransformComponent>(gridId);
ApplyTransformState(xformComp, gridDatum);
var gridComp = EntityManager.GetComponent<MapGridComponent>(gridId);
MapGridComponent.ApplyMapGridState(this, gridComp, gridDatum.ChunkData);
}
}
}
private static void ApplyTransformState(TransformComponent xformComp, GameStateMapData.GridDatum gridDatum)
{
if (xformComp.MapID != gridDatum.Coordinates.MapId)
throw new NotImplementedException("Moving grids between maps is not yet implemented");
// TODO: SHITCODE ALERT -> When we get proper ECS we can delete this.
if (xformComp.WorldPosition != gridDatum.Coordinates.Position)
{
xformComp.WorldPosition = gridDatum.Coordinates.Position;
}
if (xformComp.WorldRotation != gridDatum.Angle)
{
xformComp.WorldRotation = gridDatum.Angle;
}
}
}

View File

@@ -161,7 +161,7 @@ namespace Robust.UnitTesting.Client.GameStates
/// </summary>
private static GameState GameStateFactory(uint from, uint to)
{
return new(new GameTick(@from), new GameTick(to), 0, default, default, default, null);
return new(new GameTick(@from), new GameTick(to), 0, default, default, default);
}
/// <summary>

View File

@@ -63,7 +63,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
new EntityUid(512),
new []
{
new ComponentChange(0, new MapGridComponentState(16), default)
new ComponentChange(0, new MapGridComponentState(16, null), default)
}, default);
serializer.Serialize(stream, payload);