Move MapGrid data from MapManager to MapGridComponent (#2430)

This commit is contained in:
Acruid
2022-02-02 18:15:06 -08:00
committed by GitHub
parent d716e1ff62
commit 8456cd90d1
26 changed files with 1227 additions and 1094 deletions

View File

@@ -43,9 +43,9 @@ namespace Robust.Client
IoCManager.Register<IGameTiming, ClientGameTiming>();
IoCManager.Register<IClientGameTiming, ClientGameTiming>();
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
IoCManager.Register<IMapManager, ClientMapManager>();
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IMapManager, NetworkedMapManager>();
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();

View File

@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
var sender = ev.Sender;
if (EntityManager.EntityExists(sender) &&
EntityManager.TryGetComponent(sender, out ClientOccluderComponent? iconSmooth)
&& iconSmooth.Running)
&& iconSmooth.Initialized)
{
var grid1 = _mapManager.GetGrid(EntityManager.GetComponent<TransformComponent>(sender).GridID);
var coords = EntityManager.GetComponent<TransformComponent>(sender).Coordinates;
@@ -85,7 +85,7 @@ namespace Robust.Client.GameObjects
}
// Entity is no longer valid, update around the last position it was at.
if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
else if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
{
var pos = ev.LastPosition.Value.pos;

View File

@@ -7,7 +7,6 @@ using System.Linq;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared;
@@ -50,7 +49,7 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientMapManager _mapManager = default!;
[Dependency] private readonly INetworkedMapManager _mapManager = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly INetConfigurationManager _config = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;

View File

@@ -1,187 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Map
{
internal class ClientMapManager : MapManager, IClientMapManager
{
public void ApplyGameStatePre(GameStateMapData? data, ReadOnlySpan<EntityState> entityStates)
{
// There was no map data this tick, so nothing to do.
if(data == null)
return;
// First we need to figure out all the NEW MAPS.
if(data.CreatedMaps != null)
{
DebugTools.Assert(!entityStates.IsEmpty, "Received new maps, but no entity state.");
foreach (var mapId in data.CreatedMaps)
{
// map already exists from a previous state.
if (_maps.Contains(mapId))
continue;
EntityUid mapEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
foreach (var compChange in entityState.ComponentChanges.Span)
{
if (compChange.State is not MapComponentState mapCompState || mapCompState.MapId != mapId)
continue;
mapEuid = entityState.Uid;
goto BreakMapEntSearch;
}
}
BreakMapEntSearch:
DebugTools.Assert(mapEuid != default, $"Could not find corresponding entity state for new map {mapId}.");
CreateMap(mapId, mapEuid);
}
}
// Then make all the grids.
if(data.CreatedGrids != null)
{
DebugTools.Assert(data.GridData is not null, "Received new grids, but GridData was null.");
foreach (var (gridId, creationDatum) in data.CreatedGrids)
{
if (_grids.ContainsKey(gridId))
continue;
EntityUid gridEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates!)
{
foreach (var compState in entityState.ComponentChanges.Span)
{
if (compState.State is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
continue;
gridEuid = entityState.Uid;
goto BreakGridEntSearch;
}
}
BreakGridEntSearch:
DebugTools.Assert(gridEuid != default, $"Could not find corresponding entity state for new grid {gridId}.");
MapId gridMapId = default;
foreach (var kvData in data.GridData!)
{
if (kvData.Key != gridId)
continue;
gridMapId = kvData.Value.Coordinates.MapId;
break;
}
DebugTools.Assert(gridMapId != default, $"Could not find corresponding gridData for new grid {gridId}.");
CreateGrid(gridMapId, gridId, creationDatum.ChunkSize, gridEuid);
}
}
// Process all grid updates.
if(data.GridData != null)
{
SuppressOnTileChanged = true;
// Ok good all the grids and maps exist now.
foreach (var (gridId, gridDatum) in data.GridData)
{
var grid = _grids[gridId];
if (grid.ParentMapId != gridDatum.Coordinates.MapId)
{
throw new NotImplementedException("Moving grids between maps is not yet implemented");
}
// I love mapmanager!!!
grid.WorldPosition = gridDatum.Coordinates.Position;
grid.WorldRotation = gridDatum.Angle;
var modified = new List<(Vector2i position, Tile tile)>();
foreach (var chunkData in gridDatum.ChunkData)
{
var chunk = grid.GetChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == grid.ChunkSize * grid.ChunkSize);
var counter = 0;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = chunkData.TileData[counter++];
if (chunk.GetTileRef(x, y).Tile != tile)
{
chunk.SetTile(x, y, tile);
modified.Add((new Vector2i(chunk.X * grid.ChunkSize + x, chunk.Y * grid.ChunkSize + y), tile));
}
}
}
}
if (modified.Count != 0)
{
InvokeGridChanged(this, new GridChangedEventArgs(grid, modified));
}
foreach (var chunkData in gridDatum.ChunkData)
{
var chunk = grid.GetChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
chunk.RegenerateCollision();
}
foreach (var chunkData in gridDatum.DeletedChunkData)
{
grid.RemoveChunk(chunkData.Index);
}
}
SuppressOnTileChanged = false;
}
}
public void ApplyGameStatePost(GameStateMapData? data)
{
if(data == null) // if there is no data, there is nothing to do!
return;
if(data.DeletedGrids != null)
{
foreach (var grid in data.DeletedGrids)
{
if (_grids.ContainsKey(grid))
{
DeleteGrid(grid);
}
}
}
if(data.DeletedMaps != null)
{
foreach (var map in data.DeletedMaps)
{
if (_maps.Contains(map))
{
DeleteMap(map);
}
}
}
}
}
}

View File

@@ -1,15 +0,0 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
namespace Robust.Client.Map
{
internal interface IClientMapManager : IMapManagerInternal
{
// 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);
void ApplyGameStatePost(GameStateMapData? data);
}
}

View File

@@ -232,8 +232,8 @@ internal partial class PVSSystem : EntitySystem
pvsCollection.AddGrid(gridId);
}
var uid = _mapManager.GetGrid(gridId).GridEntityId;
_entityPvsCollection.UpdateIndex(uid);
var euid = _mapManager.GetGridEuid(gridId);
_entityPvsCollection.UpdateIndex(euid);
}
private void OnMapDestroyed(object? sender, MapEventArgs e)

View File

@@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Map;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
@@ -35,7 +32,7 @@ namespace Robust.Server.GameStates
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IServerNetManager _networkManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerMapManager _mapManager = default!;
[Dependency] private readonly INetworkedMapManager _mapManager = default!;
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IServerEntityNetworkManager _entityNetworkManager = default!;

View File

@@ -1,12 +0,0 @@
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Robust.Server.Map
{
internal interface IServerMapManager : IMapManagerInternal
{
GameStateMapData? GetStateData(GameTick fromTick);
void CullDeletionHistory(GameTick uptoTick);
}
}

View File

@@ -1,180 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.Physics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Server.Map
{
[UsedImplicitly]
internal sealed class ServerMapManager : MapManager, IServerMapManager
{
private readonly Dictionary<GridId, List<(GameTick tick, Vector2i indices)>> _chunkDeletionHistory = new();
private readonly List<(GameTick tick, GridId gridId)> _gridDeletionHistory = new();
private readonly List<(GameTick tick, MapId mapId)> _mapDeletionHistory = new();
public override void DeleteMap(MapId mapID)
{
base.DeleteMap(mapID);
_mapDeletionHistory.Add((GameTiming.CurTick, mapID));
}
public override void DeleteGrid(GridId gridID)
{
base.DeleteGrid(gridID);
_gridDeletionHistory.Add((GameTiming.CurTick, gridID));
// No point syncing chunk removals anymore!
_chunkDeletionHistory.Remove(gridID);
}
public override void ChunkRemoved(MapChunk chunk)
{
base.ChunkRemoved(chunk);
if (!_chunkDeletionHistory.TryGetValue(chunk.GridId, out var chunks))
{
chunks = new List<(GameTick tick, Vector2i indices)>();
_chunkDeletionHistory[chunk.GridId] = chunks;
}
chunks.Add((GameTiming.CurTick, chunk.Indices));
// Seemed easier than having this method on GridFixtureSystem
if (!TryGetGrid(chunk.GridId, out var grid) ||
!EntityManager.TryGetComponent(grid.GridEntityId, out PhysicsComponent? body) ||
chunk.Fixtures.Count == 0) return;
// TODO: Like MapManager injecting this is a PITA so need to work out an easy way to do it.
// Maybe just add like a PostInject method that gets called way later?
var fixtureSystem = EntitySystem.Get<FixtureSystem>();
foreach (var fixture in chunk.Fixtures)
{
fixtureSystem.DestroyFixture(body, fixture);
}
}
public GameStateMapData? GetStateData(GameTick fromTick)
{
var gridDatums = new Dictionary<GridId, GameStateMapData.GridDatum>();
foreach (var grid in _grids.Values)
{
if (grid.LastTileModifiedTick < fromTick)
{
continue;
}
var deletedChunkData = new List<GameStateMapData.DeletedChunkDatum>();
if (_chunkDeletionHistory.TryGetValue(grid.Index, out var chunks))
{
foreach (var (tick, indices) in chunks)
{
if (tick < fromTick) continue;
deletedChunkData.Add(new GameStateMapData.DeletedChunkDatum(indices));
}
}
var chunkData = new List<GameStateMapData.ChunkDatum>();
foreach (var (index, chunk) in grid.GetMapChunks())
{
if (chunk.LastTileModifiedTick < fromTick)
{
continue;
}
var tileBuffer = new Tile[grid.ChunkSize * (uint) grid.ChunkSize];
// Flatten the tile array.
// NetSerializer doesn't do multi-dimensional arrays.
// This is probably really expensive.
for (var x = 0; x < grid.ChunkSize; x++)
{
for (var y = 0; y < grid.ChunkSize; y++)
{
tileBuffer[x * grid.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
chunkData.Add(new GameStateMapData.ChunkDatum(index, tileBuffer));
}
var gridDatum = new GameStateMapData.GridDatum(
chunkData.ToArray(),
deletedChunkData.ToArray(),
new MapCoordinates(grid.WorldPosition, grid.ParentMapId),
grid.WorldRotation);
gridDatums.Add(grid.Index, gridDatum);
}
// -- Map Deletion Data --
var mapDeletionsData = new List<MapId>();
foreach (var (tick, mapId) in _mapDeletionHistory)
{
if (tick < fromTick) continue;
mapDeletionsData.Add(mapId);
}
// -- Grid Deletion Data
var gridDeletionsData = new List<GridId>();
foreach (var (tick, gridId) in _gridDeletionHistory)
{
if (tick < fromTick) continue;
gridDeletionsData.Add(gridId);
}
// -- Map Creations --
var mapCreations = new List<MapId>();
foreach (var (mapId, tick) in _mapCreationTick)
{
if (tick < fromTick || mapId == MapId.Nullspace) continue;
mapCreations.Add(mapId);
}
// - Grid Creation data --
var gridCreations = new Dictionary<GridId, GameStateMapData.GridCreationDatum>();
foreach (var (gridId, grid) in _grids)
{
if (grid.CreatedTick < fromTick || grid.ParentMapId == MapId.Nullspace) continue;
gridCreations.Add(gridId, new GameStateMapData.GridCreationDatum(grid.ChunkSize));
}
// no point sending empty collections
if (gridDatums.Count == 0) gridDatums = default;
if (gridDeletionsData.Count == 0) gridDeletionsData = default;
if (mapDeletionsData.Count == 0) mapDeletionsData = default;
if (mapCreations.Count == 0) mapCreations = default;
if (gridCreations.Count == 0) gridCreations = default;
// no point even creating an empty map state if no data
if (gridDatums == null && gridDeletionsData == null && mapDeletionsData == null && mapCreations == null && gridCreations == null)
return default;
return new GameStateMapData(gridDatums?.ToArray<KeyValuePair<GridId, GameStateMapData.GridDatum>>(), gridDeletionsData?.ToArray(), mapDeletionsData?.ToArray(), mapCreations?.ToArray(), gridCreations?.ToArray<KeyValuePair<GridId, GameStateMapData.GridCreationDatum>>());
}
public void CullDeletionHistory(GameTick uptoTick)
{
foreach (var (gridId, chunks) in _chunkDeletionHistory.ToArray())
{
chunks.RemoveAll(t => t.tick < uptoTick);
if (chunks.Count == 0) _chunkDeletionHistory.Remove(gridId);
}
_mapDeletionHistory.RemoveAll(t => t.tick < uptoTick);
_gridDeletionHistory.RemoveAll(t => t.tick < uptoTick);
}
}
}

View File

@@ -231,7 +231,7 @@ namespace Robust.Server.Maps
private readonly MapLoadOptions? _loadOptions;
private readonly Dictionary<GridId, int> GridIDMap = new();
public readonly List<IMapGrid> Grids = new();
public readonly List<MapGrid> Grids = new();
private readonly Dictionary<EntityUid, int> EntityUidMap = new();
private readonly Dictionary<int, EntityUid> UidEntityMap = new();
@@ -330,6 +330,9 @@ namespace Robust.Server.Maps
// Actually instance components and run ExposeData on them.
FinishEntitiesLoad();
// Finish binding MapGrids to their respective MapGridComponents.
BindGridComponents();
// Clear the net tick numbers so that components from prototypes (not modified by map)
// aren't sent over the wire initially.
ResetNetTicks();
@@ -473,6 +476,43 @@ namespace Robust.Server.Maps
}
}
private void BindGridComponents()
{
// There were no new grids, nothing to do here.
if(Grids.Count == 0)
return;
// MapGrids already contain their assigned GridId from their ctor, and the MapComponents just got deserialized.
// Now we need to actually bind the MapGrids to their components so that you can resolve GridId -> EntityUid
// After doing this, it should be 100% safe to use the MapManager API like normal.
// get ents that the grids will bind to
var gridComps = new Dictionary<GridId, MapGridComponent>(Grids.Count);
// linear search for new grid comps
foreach (var tuple in _entitiesToDeserialize)
{
if (!_serverEntityManager.TryGetComponent(tuple.Item1, out MapGridComponent gridComp))
continue;
// These should actually be new, pre-init
DebugTools.Assert(gridComp.LifeStage == ComponentLifeStage.Added);
// yaml deserializer turns "null" into Invalid, this has been encountered by a customer from failed serialization.
DebugTools.Assert(gridComp.GridIndex != GridId.Invalid);
gridComps[gridComp.GridIndex] = gridComp;
}
// Actually bind them
foreach (var mapGrid in Grids)
{
// designed to throw if something is broken, every grid must map to an ent
var gridComp = gridComps[mapGrid.Index];
_mapManager.BindGrid(gridComp, mapGrid);
}
}
private void AttachMapEntities()
{
var mapEntity = _mapManager.GetMapEntityIdOrThrow(TargetMap);
@@ -496,8 +536,6 @@ namespace Robust.Server.Maps
{
if (_serverEntityManager.TryGetComponent(entity, out IMapGridComponent? grid))
{
var castGrid = (MapGrid) grid.Grid;
castGrid.GridEntityId = entity;
pvs?.EntityPVSCollection.UpdateIndex(entity);
// The problem here is that the grid is initialising at the same time as everything else which
// is bad for slothcoin because a bunch of components are only added
@@ -543,23 +581,19 @@ namespace Robust.Server.Maps
private void ReadGridSection()
{
var grids = RootNode.GetNode<YamlSequenceNode>("grids");
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
foreach (var grid in grids)
foreach (var yamlGrid in yamlGrids)
{
var newId = new GridId?();
YamlGridSerializer.DeserializeGrid(
_mapManager, TargetMap, ref newId,
(YamlMappingNode) grid["settings"],
(YamlSequenceNode) grid["chunks"],
var grid = YamlGridSerializer.DeserializeGrid(
_mapManager, TargetMap,
(YamlMappingNode) yamlGrid["settings"],
(YamlSequenceNode) yamlGrid["chunks"],
_tileMap!,
_tileDefinitionManager
);
if (newId != null)
{
Grids.Add(_mapManager.GetGrid(newId.Value));
}
Grids.Add(grid);
}
}
@@ -654,7 +688,7 @@ namespace Robust.Server.Maps
throw new InvalidOperationException();
}
Grids.Add(grid);
Grids.Add((MapGrid) grid);
GridIDMap.Add(grid.Index, GridIDMap.Count);
}
@@ -896,6 +930,9 @@ namespace Robust.Server.Maps
bool skipHook,
ISerializationContext? context = null)
{
// This is the code that deserializes the Grids index into the GridId. This has to happen between Grid allocation
// and when grids are bound to their entities.
if (node.Value == "null") return new DeserializedValue<GridId>(GridId.Invalid);
var val = int.Parse(node.Value);

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
using YamlDotNet.Core;
@@ -77,7 +76,7 @@ namespace Robust.Server.Maps
return Convert.ToBase64String(barr);
}
public static void DeserializeGrid(IMapManagerInternal mapMan, MapId mapId, ref GridId? gridId, YamlMappingNode info,
public static MapGrid DeserializeGrid(IMapManagerInternal mapMan, MapId mapId, YamlMappingNode info,
YamlSequenceNode chunks, IReadOnlyDictionary<ushort, string> tileDefMapping,
ITileDefinitionManager tileDefinitionManager)
{
@@ -97,14 +96,15 @@ namespace Robust.Server.Maps
sgsz = float.Parse(val, CultureInfo.InvariantCulture);
}
var grid = mapMan.CreateGridNoEntity(mapId, gridId);
gridId = grid.Index;
//TODO: Pass in options
var grid = mapMan.CreateUnboundGrid(mapId);
foreach (var chunkNode in chunks.Cast<YamlMappingNode>())
{
DeserializeChunk(mapMan, grid, chunkNode, tileDefMapping, tileDefinitionManager);
}
return grid;
}
private static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid, YamlMappingNode chunkData, IReadOnlyDictionary<ushort, string> tileDefMapping, ITileDefinitionManager tileDefinitionManager)

View File

@@ -4,7 +4,6 @@ using Robust.Server.DataMetrics;
using Robust.Server.Debugging;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Map;
using Robust.Server.Maps;
using Robust.Server.Placement;
using Robust.Server.Player;
@@ -47,9 +46,9 @@ namespace Robust.Server
IoCManager.Register<IServerConsoleHost, ServerConsoleHost>();
IoCManager.Register<IComponentFactory, ServerComponentFactory>();
IoCManager.Register<IConGroupController, ConGroupController>();
IoCManager.Register<IMapManager, ServerMapManager>();
IoCManager.Register<IMapManagerInternal, ServerMapManager>();
IoCManager.Register<IServerMapManager, ServerMapManager>();
IoCManager.Register<IMapManager, NetworkedMapManager>();
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ServerEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IEntityNetworkManager, ServerEntityManager>();

View File

@@ -16,7 +16,6 @@ namespace Robust.Shared.GameObjects
{
GridId GridIndex { get; }
IMapGrid Grid { get; }
void ClearGridId();
bool AnchorEntity(TransformComponent transform);
void UnanchorEntity(TransformComponent transform);
@@ -24,16 +23,19 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc cref="IMapGridComponent"/>
[ComponentReference(typeof(IMapGridComponent))]
[NetworkedComponent()]
[NetworkedComponent]
internal class MapGridComponent : Component, IMapGridComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
// This field is used for deserialization internally in the map loader.
[ViewVariables(VVAccess.ReadOnly)]
[DataField("index")]
private GridId _gridIndex = GridId.Invalid;
private IMapGrid? _mapGrid;
/// <inheritdoc />
public GridId GridIndex
{
@@ -43,12 +45,10 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[ViewVariables]
public IMapGrid Grid => _mapManager.GetGrid(_gridIndex);
/// <inheritdoc />
public void ClearGridId()
public IMapGrid Grid
{
_gridIndex = GridId.Invalid;
get => _mapGrid ?? throw new InvalidOperationException();
set => _mapGrid = value;
}
protected override void Initialize()
@@ -62,6 +62,13 @@ namespace Robust.Shared.GameObjects
}
}
protected override void OnRemove()
{
_mapManager.TrueGridDelete((MapGrid)_mapGrid!);
base.OnRemove();
}
/// <inheritdoc />
public bool AnchorEntity(TransformComponent transform)
{

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using Robust.Shared.Map;
@@ -16,7 +16,7 @@ namespace Robust.Shared.GameObjects
SubscribeLocalEvent<MapGridComponent, ComponentAdd>(OnGridAdd);
SubscribeLocalEvent<MapGridComponent, ComponentInit>(OnGridInit);
SubscribeLocalEvent<MapGridComponent, ComponentStartup>(OnGridStartup);
SubscribeLocalEvent<MapGridComponent, ComponentRemove>(OnGridRemove);
SubscribeLocalEvent<MapGridComponent, ComponentShutdown>(OnGridRemove);
}
private void OnGridAdd(EntityUid uid, MapGridComponent component, ComponentAdd args)
@@ -38,7 +38,7 @@ namespace Robust.Shared.GameObjects
EntityManager.EventBus.RaiseLocalEvent(uid, msg);
}
private void OnGridRemove(EntityUid uid, MapGridComponent component, ComponentRemove args)
private void OnGridRemove(EntityUid uid, MapGridComponent component, ComponentShutdown args)
{
EntityManager.EventBus.RaiseLocalEvent(uid, new GridRemovalEvent(uid, component.GridIndex));
MapManager.OnComponentRemoved(component);

View File

@@ -9,15 +9,16 @@ namespace Robust.Shared.Map
{
public struct FindGridsEnumerator : IDisposable
{
private IEntityManager _entityManager;
private readonly IEntityManager _entityManager;
private Dictionary<GridId, MapGrid>.Enumerator _enumerator;
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private IEnumerator<MapGrid> _enumerator;
private MapId _mapId;
private Box2 _worldAABB;
private bool _approx;
internal FindGridsEnumerator(IEntityManager entityManager, Dictionary<GridId, MapGrid>.Enumerator enumerator, MapId mapId, Box2 worldAABB, bool approx)
internal FindGridsEnumerator(IEntityManager entityManager, IEnumerator<MapGrid> enumerator, MapId mapId, Box2 worldAABB, bool approx)
{
_entityManager = entityManager;
_enumerator = enumerator;
@@ -36,7 +37,7 @@ namespace Robust.Shared.Map
return false;
}
var (_, nextGrid) = _enumerator.Current;
var nextGrid = _enumerator.Current;
if (nextGrid.ParentMapId != _mapId)
{

View File

@@ -1,4 +1,6 @@
namespace Robust.Shared.Map
using Robust.Shared.GameObjects;
namespace Robust.Shared.Map
{
/// <summary>
/// Invoked when a grid is altered.

View File

@@ -10,7 +10,7 @@ namespace Robust.Shared.Map
/// This is a collection of tiles in a grid format.
/// </summary>
[PublicAPI]
public interface IMapGrid : IDisposable
public interface IMapGrid
{
/// <summary>
/// The integer ID of the map this grid is currently located within.

View File

@@ -47,21 +47,21 @@ namespace Robust.Shared.Map
/// <summary>
/// Creates a new map.
/// </summary>
/// <param name="mapID">
/// <param name="mapId">
/// If provided, the new map will use this ID. If not provided, a new ID will be selected automatically.
/// </param>
/// <returns>The new map.</returns>
/// <exception cref="InvalidOperationException">
/// Throw if an explicit ID for the map or default grid is passed and a map or grid with the specified ID already exists, respectively.
/// </exception>
MapId CreateMap(MapId? mapID = null);
MapId CreateMap(MapId? mapId = null);
/// <summary>
/// Check whether a map with specified ID exists.
/// </summary>
/// <param name="mapID">The map ID to check existance of.</param>
/// <param name="mapId">The map ID to check existence of.</param>
/// <returns>True if the map exists, false otherwise.</returns>
bool MapExists(MapId mapID);
bool MapExists(MapId mapId);
/// <summary>
/// Creates a new entity, then sets it as the map entity.
@@ -87,12 +87,12 @@ namespace Robust.Shared.Map
IEnumerable<MapId> GetAllMapIds();
void DeleteMap(MapId mapID);
void DeleteMap(MapId mapId);
IMapGrid CreateGrid(MapId currentMapID, GridId? gridID = null, ushort chunkSize = 16);
IMapGrid GetGrid(GridId gridID);
IMapGrid CreateGrid(MapId currentMapId, GridId? gridId = null, ushort chunkSize = 16);
IMapGrid GetGrid(GridId gridId);
bool TryGetGrid(GridId gridId, [NotNullWhen(true)] out IMapGrid? grid);
bool GridExists(GridId gridID);
bool GridExists(GridId gridId);
IEnumerable<IMapGrid> GetAllMapGrids(MapId mapId);
/// <summary>
@@ -118,16 +118,16 @@ namespace Robust.Shared.Map
/// <returns>Returns true when a grid was found under the location.</returns>
bool TryFindGridAt(MapCoordinates mapCoordinates, [NotNullWhen(true)] out IMapGrid? grid);
void FindGridsIntersectingEnumerator(MapId mapId, Box2 worldAABB, out FindGridsEnumerator enumerator, bool approx = false);
void FindGridsIntersectingEnumerator(MapId mapId, Box2 worldAabb, out FindGridsEnumerator enumerator, bool approx = false);
/// <summary>
/// Returns the grids intersecting this AABB.
/// </summary>
/// <param name="mapId">The relevant MapID</param>
/// <param name="worldAABB">The AABB to intersect</param>
/// <param name="worldAabb">The AABB to intersect</param>
/// <param name="approx">Set to false if you wish to accurately get the grid bounds per-tile.</param>
/// <returns></returns>
IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldAABB, bool approx = false);
IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldAabb, bool approx = false);
/// <summary>
/// <see cref="FindGridsIntersecting(Robust.Shared.Map.MapId,Robust.Shared.Maths.Box2,bool)"/>
@@ -147,7 +147,7 @@ namespace Robust.Shared.Map
/// <param name="approx">Set to false if you wish to accurately get the grid bounds per-tile.</param>
IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2Rotated worldArea, bool approx = false);
void DeleteGrid(GridId gridID);
void DeleteGrid(GridId gridId);
/// <summary>
/// A tile is being modified.
@@ -180,5 +180,11 @@ namespace Robust.Shared.Map
MapId NextMapId();
GridId NextGridId();
EntityUid GetGridEuid(GridId id);
IMapGridComponent GetGridComp(GridId id);
IMapGridComponent GetGridComp(EntityUid euid);
IMapGrid GetGrid(EntityUid euid);
bool TryGetGrid(EntityUid euid, [NotNullWhen(true)] out IMapGrid? grid);
bool GridExists(EntityUid euid);
}
}

View File

@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
@@ -21,6 +23,10 @@ namespace Robust.Shared.Map
/// <param name="oldTile">The old tile that got replaced.</param>
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile);
IMapGridInternal CreateGridNoEntity(MapId currentMapID, GridId? gridID = null, ushort chunkSize = 16);
bool TryGetGridComp(GridId id, [MaybeNullWhen(false)]out IMapGridComponent comp);
bool TryGetGridEuid(GridId id, [MaybeNullWhen(false)]out EntityUid euid);
void TrueGridDelete(MapGrid grid);
MapGrid CreateUnboundGrid(MapId mapId);
void BindGrid(MapGridComponent gridComponent, MapGrid mapGrid);
}
}

View File

@@ -71,14 +71,6 @@ namespace Robust.Shared.Map
LastTileModifiedTick = CreatedTick = _mapManager.GameTiming.CurTick;
}
/// <summary>
/// Disposes the grid.
/// </summary>
public void Dispose()
{
// Nothing for now.
}
/// <inheritdoc />
[ViewVariables]
public Box2 WorldBounds =>

View File

@@ -0,0 +1,406 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Shared.Map;
/// <summary>
/// Arguments for when a one or more tiles on a grid is changed at once.
/// </summary>
public class GridChangedEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public GridChangedEventArgs(IMapGrid grid, IReadOnlyCollection<(Vector2i position, Tile tile)> modified)
{
Grid = grid;
Modified = modified;
}
/// <summary>
/// Grid being changed.
/// </summary>
public IMapGrid Grid { get; }
public IReadOnlyCollection<(Vector2i position, Tile tile)> Modified { get; }
}
/// <summary>
/// Arguments for when a single tile on a grid is changed locally or remotely.
/// </summary>
public class TileChangedEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public TileChangedEventArgs(TileRef newTile, Tile oldTile)
{
NewTile = newTile;
OldTile = oldTile;
}
/// <summary>
/// New tile that replaced the old one.
/// </summary>
public TileRef NewTile { get; }
/// <summary>
/// Old tile that was replaced.
/// </summary>
public Tile OldTile { get; }
}
internal partial class MapManager
{
private readonly Dictionary<GridId, EntityUid> _grids = new();
private GridId _highestGridId = GridId.Invalid;
public virtual void ChunkRemoved(MapChunk chunk) { }
public EntityUid GetGridEuid(GridId id)
{
DebugTools.Assert(id != GridId.Invalid);
//This turns into a linear search with EntityQuery without the _grids mapping
return _grids[id];
}
public bool TryGetGridEuid(GridId id, [MaybeNullWhen(false)] out EntityUid euid)
{
DebugTools.Assert(id != GridId.Invalid);
if (_grids.TryGetValue(id, out euid))
return true;
euid = default;
return false;
}
public IMapGridComponent GetGridComp(GridId id)
{
DebugTools.Assert(id != GridId.Invalid);
var euid = GetGridEuid(id);
return GetGridComp(euid);
}
public IMapGridComponent GetGridComp(EntityUid euid)
{
return EntityManager.GetComponent<IMapGridComponent>(euid);
}
public bool TryGetGridComp(GridId id, [MaybeNullWhen(false)] out IMapGridComponent comp)
{
DebugTools.Assert(id != GridId.Invalid);
var euid = GetGridEuid(id);
if (EntityManager.TryGetComponent(euid, out comp))
return true;
comp = default;
return false;
}
public IMapGridInternal CreateBoundGrid(MapId mapId, MapGridComponent gridComponent)
{
var mapGrid = CreateUnboundGrid(mapId);
BindGrid(gridComponent, mapGrid);
return mapGrid;
}
public void BindGrid(MapGridComponent gridComponent, MapGrid mapGrid)
{
gridComponent.Grid = mapGrid;
gridComponent.GridIndex = mapGrid.Index;
mapGrid.GridEntityId = gridComponent.Owner;
_grids.Add(mapGrid.Index, mapGrid.GridEntityId);
Logger.InfoS("map", $"Binding grid {mapGrid.Index} to entity {gridComponent.Owner}");
OnGridCreated?.Invoke(mapGrid.ParentMapId, mapGrid.Index);
}
public MapGrid CreateUnboundGrid(MapId mapId)
{
var newGrid = CreateGridImpl(mapId, null, 16, false, default);
Logger.InfoS("map", $"Creating unbound grid {newGrid.Index}");
return (MapGrid) newGrid;
}
public IEnumerable<IMapGrid> GetAllGrids()
{
return EntityManager.EntityQuery<IMapGridComponent>(true).Select(c => c.Grid);
}
public IMapGrid CreateGrid(MapId currentMapId, GridId? gridId = null, ushort chunkSize = 16)
{
DebugTools.Assert(gridId != GridId.Invalid);
var mapGrid = CreateGridImpl(currentMapId, gridId, chunkSize, true, default);
_grids.Add(mapGrid.Index, mapGrid.GridEntityId);
Logger.InfoS("map", $"Creating new grid {mapGrid.Index}");
EntityManager.InitializeComponents(mapGrid.GridEntityId);
EntityManager.StartComponents(mapGrid.GridEntityId);
OnGridCreated?.Invoke(currentMapId, mapGrid.Index);
return mapGrid;
}
public IMapGrid GetGrid(EntityUid euid)
{
return GetGridComp(euid).Grid;
}
public IMapGrid GetGrid(GridId gridId)
{
DebugTools.Assert(gridId != GridId.Invalid);
var euid = GetGridEuid(gridId);
return GetGridComp(euid).Grid;
}
public bool IsGrid(EntityUid uid)
{
return EntityManager.HasComponent<IMapGridComponent>(uid);
}
public bool TryGetGrid(EntityUid euid, [MaybeNullWhen(false)] out IMapGrid grid)
{
if (EntityManager.TryGetComponent(euid, out IMapGridComponent comp))
{
grid = comp.Grid;
return true;
}
grid = default;
return false;
}
public bool TryGetGrid(GridId gridId, [MaybeNullWhen(false)] out IMapGrid grid)
{
// grid 0 compatibility
if (gridId == GridId.Invalid)
{
grid = default;
return false;
}
if (!TryGetGridEuid(gridId, out var euid))
{
grid = default;
return false;
}
return TryGetGrid(euid, out grid);
}
public bool GridExists(GridId gridId)
{
// grid 0 compatibility
return gridId != GridId.Invalid && TryGetGridEuid(gridId, out var euid) && GridExists(euid);
}
public bool GridExists(EntityUid euid)
{
return EntityManager.EntityExists(euid) && EntityManager.HasComponent<IMapGridComponent>(euid);
}
public IEnumerable<IMapGrid> GetAllMapGrids(MapId mapId)
{
return EntityManager.EntityQuery<IMapGridComponent>(true)
.Where(c => c.Grid.ParentMapId == mapId)
.Select(c => c.Grid);
}
public void FindGridsIntersectingEnumerator(MapId mapId, Box2 worldAabb, out FindGridsEnumerator enumerator, bool approx = false)
{
enumerator = new FindGridsEnumerator(EntityManager, GetAllGrids().Cast<MapGrid>().GetEnumerator(), mapId, worldAabb, approx);
}
public virtual void DeleteGrid(GridId gridId)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
// Possible the grid was already deleted / is invalid
if (!TryGetGrid(gridId, out var iGrid))
{
DebugTools.Assert($"Calling {nameof(DeleteGrid)} with unknown id {gridId}.");
return; // Silently fail on release
}
var grid = (MapGrid)iGrid;
if (grid.Deleting)
{
DebugTools.Assert($"Calling {nameof(DeleteGrid)} multiple times for grid {gridId}.");
return; // Silently fail on release
}
var entityId = grid.GridEntityId;
if (!EntityManager.TryGetComponent(entityId, out MetaDataComponent metaComp))
{
DebugTools.Assert($"Calling {nameof(DeleteGrid)} with {gridId}, but there was no allocated entity.");
return; // Silently fail on release
}
// DeleteGrid may be triggered by the entity being deleted,
// so make sure that's not the case.
if (metaComp.EntityLifeStage < EntityLifeStage.Terminating)
EntityManager.DeleteEntity(entityId);
}
public void TrueGridDelete(MapGrid grid)
{
grid.Deleting = true;
var mapId = grid.ParentMapId;
var gridId = grid.Index;
_grids.Remove(grid.Index);
Logger.DebugS("map", $"Deleted grid {gridId}");
// TODO: Remove this trash
OnGridRemoved?.Invoke(mapId, gridId);
}
public GridId NextGridId()
{
return _highestGridId = new GridId(_highestGridId.Value + 1);
}
/// <inheritdoc />
public event EventHandler<TileChangedEventArgs>? TileChanged;
public event GridEventHandler? OnGridCreated;
public event GridEventHandler? OnGridRemoved;
/// <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; }
public void OnComponentRemoved(MapGridComponent comp)
{
var gridIndex = comp.GridIndex;
if (gridIndex == GridId.Invalid)
return;
if (!GridExists(gridIndex))
return;
Logger.DebugS("map", $"Entity {comp.Owner} removed grid component, removing bound grid {gridIndex}");
DeleteGrid(gridIndex);
}
/// <summary>
/// Raises the OnTileChanged event.
/// </summary>
/// <param name="tileRef">A reference to the new tile.</param>
/// <param name="oldTile">The old tile that got replaced.</param>
public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (SuppressOnTileChanged)
return;
TileChanged?.Invoke(this, new TileChangedEventArgs(tileRef, oldTile));
}
protected IMapGrid CreateGrid(MapId currentMapId, GridId gridId, ushort chunkSize, EntityUid euid)
{
var mapGrid = CreateGridImpl(currentMapId, gridId, chunkSize, true, euid);
_grids.Add(mapGrid.Index, mapGrid.GridEntityId);
Logger.InfoS("map", $"Creating new grid {mapGrid.Index}");
EntityManager.InitializeComponents(mapGrid.GridEntityId);
EntityManager.StartComponents(mapGrid.GridEntityId);
OnGridCreated?.Invoke(currentMapId, mapGrid.Index);
return mapGrid;
}
protected void InvokeGridChanged(object? sender, GridChangedEventArgs ev)
{
GridChanged?.Invoke(sender, ev);
}
private IMapGridInternal CreateGridImpl(MapId currentMapId, GridId? gridId, ushort chunkSize, bool createEntity,
EntityUid euid)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
var actualId = gridId ?? new GridId(_highestGridId.Value + 1);
DebugTools.Assert(actualId != GridId.Invalid);
if (GridExists(actualId))
throw new InvalidOperationException($"A grid with ID {actualId} already exists");
if (_highestGridId.Value < actualId.Value)
_highestGridId = actualId;
var grid = new MapGrid(this, EntityManager, actualId, chunkSize, currentMapId);
if (actualId != GridId.Invalid && createEntity) // nullspace default grid is not bound to an entity
{
// the entity may already exist from map deserialization
IMapGridComponent? result = null;
foreach (var comp in EntityManager.EntityQuery<MapGridComponent>(true))
{
if (comp.GridIndex != actualId)
continue;
result = comp;
break;
}
if (result != null)
{
grid.GridEntityId = result.Owner;
((MapGridComponent)result).Grid = grid;
Logger.DebugS("map", $"Rebinding grid {actualId} to entity {grid.GridEntityId}");
}
else
{
var gridEnt = EntityManager.CreateEntityUninitialized(null, euid);
grid.GridEntityId = gridEnt;
Logger.DebugS("map", $"Binding grid {actualId} to entity {grid.GridEntityId}");
var gridComp = EntityManager.AddComponent<MapGridComponent>(gridEnt);
gridComp.GridIndex = grid.Index;
gridComp.Grid = grid;
//TODO: This is a hack to get TransformComponent.MapId working before entity states
//are applied. After they are applied the parent may be different, but the MapId will
//be the same. This causes TransformComponent.ParentUid of a grid to be unsafe to
//use in transform states anytime before the state parent is properly set.
EntityManager.GetComponent<TransformComponent>(gridEnt).AttachParent(GetMapEntityIdOrThrow(currentMapId));
}
}
else
Logger.DebugS("map", $"Skipping entity binding for gridId {actualId}");
return grid;
}
}

View File

@@ -26,12 +26,12 @@ internal partial class MapManager
private void InitializeGridTrees()
{
_entityManager.EventBus.SubscribeEvent<GridInitializeEvent>(EventSource.Local, this, OnGridInit);
_entityManager.EventBus.SubscribeEvent<GridRemovalEvent>(EventSource.Local, this, OnGridRemove);
_entityManager.EventBus.SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
_entityManager.EventBus.SubscribeLocalEvent<MapGridComponent, RotateEvent>(OnGridRotate);
_entityManager.EventBus.SubscribeLocalEvent<MapGridComponent, EntMapIdChangedMessage>(OnGridMapChange);
_entityManager.EventBus.SubscribeLocalEvent<MapGridComponent, GridFixtureChangeEvent>(OnGridBoundsChange);
EntityManager.EventBus.SubscribeEvent<GridInitializeEvent>(EventSource.Local, this, OnGridInit);
EntityManager.EventBus.SubscribeEvent<GridRemovalEvent>(EventSource.Local, this, OnGridRemove);
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, RotateEvent>(OnGridRotate);
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, EntMapIdChangedMessage>(OnGridMapChange);
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, GridFixtureChangeEvent>(OnGridBoundsChange);
}
private void OnMapCreatedGridTree(MapEventArgs e)

View File

@@ -0,0 +1,282 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Map;
/// <summary>
/// Arguments for when a map is created or deleted locally ore remotely.
/// </summary>
public class MapEventArgs : EventArgs
{
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public MapEventArgs(MapId map)
{
Map = map;
}
/// <summary>
/// Map that is being modified.
/// </summary>
public MapId Map { get; }
}
internal partial class MapManager
{
private readonly Dictionary<MapId, EntityUid> _mapEntities = new();
private readonly HashSet<MapId> _maps = new();
protected readonly Dictionary<MapId, GameTick> MapCreationTick = new(); //TODO: MapComponent.CreationTick?
private MapId _highestMapId = MapId.Nullspace;
/// <inheritdoc />
public virtual void DeleteMap(MapId mapId)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (!_maps.Contains(mapId))
throw new InvalidOperationException($"Attempted to delete nonexistant map '{mapId}'");
// grids are cached because Delete modifies collection
foreach (var grid in GetAllMapGrids(mapId).ToList())
{
DeleteGrid(grid.Index);
}
if (mapId != MapId.Nullspace)
{
var args = new MapEventArgs(mapId);
OnMapDestroyedGridTree(args);
MapDestroyed?.Invoke(this, args);
_maps.Remove(mapId);
MapCreationTick.Remove(mapId);
}
if (_mapEntities.TryGetValue(mapId, out var ent))
{
EntityManager.DeleteEntity(ent);
_mapEntities.Remove(mapId);
}
Logger.InfoS("map", $"Deleting map {mapId}");
}
public MapId CreateMap(MapId? mapId = null)
{
return CreateMap(mapId, default);
}
/// <inheritdoc />
public bool MapExists(MapId mapId)
{
return _maps.Contains(mapId);
}
public EntityUid CreateNewMapEntity(MapId mapId)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
var newEntity = EntityManager.CreateEntityUninitialized(null);
SetMapEntity(mapId, newEntity);
EntityManager.InitializeComponents(newEntity);
EntityManager.StartComponents(newEntity);
return newEntity;
}
/// <inheritdoc />
public void SetMapEntity(MapId mapId, EntityUid newMapEntity)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (!_maps.Contains(mapId))
throw new InvalidOperationException($"Map {mapId} does not exist.");
foreach (var kvEntity in _mapEntities)
{
if (kvEntity.Value == newMapEntity)
{
throw new InvalidOperationException(
$"Entity {newMapEntity} is already the root node of map {kvEntity.Key}.");
}
}
// remove existing graph
if (_mapEntities.TryGetValue(mapId, out var oldEntId))
{
//Note: This prevents setting a subgraph as the root, since the subgraph will be deleted
EntityManager.DeleteEntity(oldEntId);
}
else
_mapEntities.Add(mapId, EntityUid.Invalid);
// re-use or add map component
if (!EntityManager.TryGetComponent(newMapEntity, out MapComponent? mapComp))
mapComp = EntityManager.AddComponent<MapComponent>(newMapEntity);
else
{
if (mapComp.WorldMap != mapId)
{
Logger.WarningS("map",
$"Setting map {mapId} root to entity {newMapEntity}, but entity thinks it is root node of map {mapComp.WorldMap}.");
}
}
Logger.DebugS("map", $"Setting map {mapId} entity to {newMapEntity}");
// set as new map entity
mapComp.WorldMap = mapId;
_mapEntities[mapId] = newMapEntity;
}
public EntityUid GetMapEntityId(MapId mapId)
{
if (_mapEntities.TryGetValue(mapId, out var entId))
return entId;
return EntityUid.Invalid;
}
/// <summary>
/// Replaces GetMapEntity()'s throw-on-failure semantics.
/// </summary>
public EntityUid GetMapEntityIdOrThrow(MapId mapId)
{
return _mapEntities[mapId];
}
public bool HasMapEntity(MapId mapId)
{
return _mapEntities.ContainsKey(mapId);
}
public IEnumerable<MapId> GetAllMapIds()
{
return _maps;
}
public bool IsMap(EntityUid uid)
{
return EntityManager.HasComponent<IMapComponent>(uid);
}
public MapId NextMapId()
{
return _highestMapId = new MapId(_highestMapId.Value + 1);
}
/// <inheritdoc />
public MapId DefaultMap => MapId.Nullspace;
/// <inheritdoc />
public event EventHandler<MapEventArgs>? MapCreated;
/// <inheritdoc />
public event EventHandler<MapEventArgs>? MapDestroyed;
protected MapId CreateMap(MapId? mapId, EntityUid entityUid)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
var actualId = mapId ?? new MapId(_highestMapId.Value + 1);
if (MapExists(actualId))
throw new InvalidOperationException($"A map with ID {actualId} already exists");
if (_highestMapId.Value < actualId.Value)
_highestMapId = actualId;
_maps.Add(actualId);
MapCreationTick.Add(actualId, GameTiming.CurTick);
Logger.InfoS("map", $"Creating new map {actualId}");
if (actualId != MapId.Nullspace) // nullspace isn't bound to an entity
{
var mapComps = EntityManager.EntityQuery<MapComponent>(true);
IMapComponent? result = null;
foreach (var mapComp in mapComps)
{
if (mapComp.WorldMap != actualId)
continue;
result = mapComp;
break;
}
if (result != null)
{
_mapEntities.Add(actualId, result.Owner);
Logger.DebugS("map", $"Rebinding map {actualId} to entity {result.Owner}");
}
else
{
var newEnt = EntityManager.CreateEntityUninitialized(null, entityUid);
_mapEntities.Add(actualId, newEnt);
var mapComp = EntityManager.AddComponent<MapComponent>(newEnt);
mapComp.WorldMap = actualId;
EntityManager.InitializeComponents(newEnt);
EntityManager.StartComponents(newEnt);
Logger.DebugS("map", $"Binding map {actualId} to entity {newEnt}");
}
}
var args = new MapEventArgs(actualId);
OnMapCreatedGridTree(args);
MapCreated?.Invoke(this, args);
return actualId;
}
private void EnsureNullspaceExistsAndClear()
{
if (!_maps.Contains(MapId.Nullspace))
CreateMap(MapId.Nullspace);
else
{
if (!_mapEntities.TryGetValue(MapId.Nullspace, out var mapEntId))
return;
// Notice we do not clear off any added comps to the nullspace map entity, would it be better to just delete
// and recreate the entity, letting recursive entity deletion perform this foreach loop?
foreach (var childEuid in EntityManager.GetComponent<TransformComponent>(mapEntId).ChildEntities)
{
EntityManager.DeleteEntity(childEuid);
}
}
}
private void DeleteAllMaps()
{
foreach (var map in _maps.ToArray())
{
if (map != MapId.Nullspace)
DeleteMap(map);
}
if (_mapEntities.TryGetValue(MapId.Nullspace, out var entId))
{
Logger.InfoS("map", $"Deleting map entity {entId}");
EntityManager.DeleteEntity(entId);
if (_mapEntities.Remove(MapId.Nullspace))
Logger.InfoS("map", "Removing nullspace map entity.");
}
}
}

View File

@@ -1,642 +1,72 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Map
namespace Robust.Shared.Map;
/// <inheritdoc cref="IMapManager" />
internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
{
/// <inheritdoc cref="IMapManager"/>
internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
[field: Dependency] public IGameTiming GameTiming { get; } = default!;
[field: Dependency] public IEntityManager EntityManager { get; } = default!;
/// <inheritdoc />
public void Initialize()
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public IGameTiming GameTiming => _gameTiming;
public IEntityManager EntityManager => _entityManager;
/// <inheritdoc />
public MapId DefaultMap => MapId.Nullspace;
/// <inheritdoc />
public event EventHandler<TileChangedEventArgs>? TileChanged;
public event GridEventHandler? OnGridCreated;
public event GridEventHandler? OnGridRemoved;
/// <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 event EventHandler<MapEventArgs>? MapCreated;
/// <inheritdoc />
public event EventHandler<MapEventArgs>? MapDestroyed;
/// <inheritdoc />
public bool SuppressOnTileChanged { get; set; }
private MapId HighestMapID = MapId.Nullspace;
private GridId HighestGridID = GridId.Invalid;
private protected readonly HashSet<MapId> _maps = new();
private protected readonly Dictionary<MapId, GameTick> _mapCreationTick = new();
private protected readonly Dictionary<GridId, MapGrid> _grids = new();
private protected readonly Dictionary<MapId, EntityUid> _mapEntities = new();
InitializeGridTrees();
#if DEBUG
private bool _dbgGuardInit = false;
private bool _dbgGuardRunning = false;
DebugTools.Assert(!_dbgGuardInit);
DebugTools.Assert(!_dbgGuardRunning);
_dbgGuardInit = true;
#endif
/// <inheritdoc />
public void Initialize()
{
InitializeGridTrees();
#if DEBUG
DebugTools.Assert(!_dbgGuardInit);
DebugTools.Assert(!_dbgGuardRunning);
_dbgGuardInit = true;
#endif
}
/// <inheritdoc />
public void Startup()
{
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
_dbgGuardRunning = true;
#endif
Logger.DebugS("map", "Starting...");
if (!_maps.Contains(MapId.Nullspace))
{
CreateMap(MapId.Nullspace);
}
else if (_mapEntities.TryGetValue(MapId.Nullspace, out var mapEntId))
{
var mapEnt = mapEntId;
foreach (var childTransform in _entityManager.GetComponent<TransformComponent>(mapEnt).Children.ToArray())
{
_entityManager.DeleteEntity(childTransform.Owner);
}
}
DebugTools.Assert(_grids.Count == 0);
DebugTools.Assert(!GridExists(GridId.Invalid));
}
public void OnComponentRemoved(MapGridComponent comp)
{
var gridIndex = comp.GridIndex;
if (gridIndex != GridId.Invalid)
{
if (GridExists(gridIndex))
{
Logger.DebugS("map",
$"Entity {comp.Owner} removed grid component, removing bound grid {gridIndex}");
DeleteGrid(gridIndex);
}
}
}
public virtual void ChunkRemoved(MapChunk chunk)
{
return;
}
/// <inheritdoc />
public void Shutdown()
{
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
#endif
Logger.DebugS("map", "Stopping...");
foreach (var map in _maps.ToArray())
{
if (map != MapId.Nullspace)
{
DeleteMap(map);
}
}
if (_mapEntities.TryGetValue(MapId.Nullspace, out var entId))
{
Logger.InfoS("map", $"Deleting map entity {entId}");
_entityManager.DeleteEntity(entId);
if (_mapEntities.Remove(MapId.Nullspace))
Logger.InfoS("map", "Removing nullspace map entity.");
}
#if DEBUG
DebugTools.Assert(_grids.Count == 0);
DebugTools.Assert(!GridExists(GridId.Invalid));
_dbgGuardRunning = false;
#endif
}
/// <inheritdoc />
public void Restart()
{
Logger.DebugS("map", "Restarting...");
Shutdown();
Startup();
}
/// <summary>
/// Raises the OnTileChanged event.
/// </summary>
/// <param name="tileRef">A reference to the new tile.</param>
/// <param name="oldTile">The old tile that got replaced.</param>
public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (SuppressOnTileChanged)
return;
TileChanged?.Invoke(this, new TileChangedEventArgs(tileRef, oldTile));
}
/// <inheritdoc />
public virtual void DeleteMap(MapId mapID)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (!_maps.Contains(mapID))
{
throw new InvalidOperationException($"Attempted to delete nonexistant map '{mapID}'");
}
// grids are cached because Delete modifies collection
foreach (var grid in GetAllMapGrids(mapID).ToList())
{
DeleteGrid(grid.Index);
}
if (mapID != MapId.Nullspace)
{
var args = new MapEventArgs(mapID);
OnMapDestroyedGridTree(args);
MapDestroyed?.Invoke(this, args);
_maps.Remove(mapID);
_mapCreationTick.Remove(mapID);
}
if (_mapEntities.TryGetValue(mapID, out var ent))
{
_entityManager.DeleteEntity(ent);
_mapEntities.Remove(mapID);
}
Logger.InfoS("map", $"Deleting map {mapID}");
}
public MapId CreateMap(MapId? mapID = null)
{
return CreateMap(mapID, default);
}
public MapId CreateMap(MapId? mapID, EntityUid entityUid)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
MapId actualID;
if (mapID != null)
{
actualID = mapID.Value;
}
else
{
actualID = new MapId(HighestMapID.Value + 1);
}
if (MapExists(actualID))
{
throw new InvalidOperationException($"A map with ID {actualID} already exists");
}
if (HighestMapID.Value < actualID.Value)
{
HighestMapID = actualID;
}
_maps.Add(actualID);
_mapCreationTick.Add(actualID, _gameTiming.CurTick);
Logger.InfoS("map", $"Creating new map {actualID}");
if (actualID != MapId.Nullspace) // nullspace isn't bound to an entity
{
var mapComps = _entityManager.EntityQuery<MapComponent>(true);
IMapComponent? result = null;
foreach (var mapComp in mapComps)
{
if (mapComp.WorldMap != actualID)
continue;
result = mapComp;
break;
}
if (result != null)
{
_mapEntities.Add(actualID, result.Owner);
Logger.DebugS("map", $"Rebinding map {actualID} to entity {result.Owner}");
}
else
{
var newEnt = _entityManager.CreateEntityUninitialized(null, entityUid);
_mapEntities.Add(actualID, newEnt);
var mapComp = _entityManager.AddComponent<MapComponent>(newEnt);
mapComp.WorldMap = actualID;
_entityManager.InitializeComponents(newEnt);
_entityManager.StartComponents(newEnt);
Logger.DebugS("map", $"Binding map {actualID} to entity {newEnt}");
}
}
var args = new MapEventArgs(actualID);
OnMapCreatedGridTree(args);
MapCreated?.Invoke(this, args);
return actualID;
}
/// <inheritdoc />
public bool MapExists(MapId mapID)
{
return _maps.Contains(mapID);
}
public EntityUid CreateNewMapEntity(MapId mapId)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
var newEntity = _entityManager.CreateEntityUninitialized(null);
SetMapEntity(mapId, newEntity);
_entityManager.InitializeComponents(newEntity);
_entityManager.StartComponents(newEntity);
return newEntity;
}
/// <inheritdoc />
public void SetMapEntity(MapId mapId, EntityUid newMapEntity)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (!_maps.Contains(mapId))
throw new InvalidOperationException($"Map {mapId} does not exist.");
foreach (var kvEntity in _mapEntities)
{
if (kvEntity.Value == newMapEntity)
{
throw new InvalidOperationException(
$"Entity {newMapEntity} is already the root node of map {kvEntity.Key}.");
}
}
// remove existing graph
if (_mapEntities.TryGetValue(mapId, out var oldEntId))
{
//Note: This prevents setting a subgraph as the root, since the subgraph will be deleted
_entityManager.DeleteEntity(oldEntId);
}
else
{
_mapEntities.Add(mapId, EntityUid.Invalid);
}
// re-use or add map component
if (!_entityManager.TryGetComponent(newMapEntity, out MapComponent? mapComp))
{
mapComp = _entityManager.AddComponent<MapComponent>(newMapEntity);
}
else
{
if (mapComp.WorldMap != mapId)
Logger.WarningS("map",
$"Setting map {mapId} root to entity {newMapEntity}, but entity thinks it is root node of map {mapComp.WorldMap}.");
}
Logger.DebugS("map", $"Setting map {mapId} entity to {newMapEntity}");
// set as new map entity
mapComp.WorldMap = mapId;
_mapEntities[mapId] = newMapEntity;
}
public EntityUid GetMapEntityId(MapId mapId)
{
if (_mapEntities.TryGetValue(mapId, out var entId))
return entId;
return EntityUid.Invalid;
}
/// <summary>
/// Replaces GetMapEntity()'s throw-on-failure semantics.
/// </summary>
public EntityUid GetMapEntityIdOrThrow(MapId mapId)
{
return _mapEntities[mapId];
}
public bool HasMapEntity(MapId mapId)
{
return _mapEntities.ContainsKey(mapId);
}
public IEnumerable<MapId> GetAllMapIds()
{
return _maps;
}
public IEnumerable<IMapGrid> GetAllGrids()
{
return _grids.Values;
}
public IMapGrid CreateGrid(MapId currentMapID, GridId? gridID = null, ushort chunkSize = 16)
{
return CreateGridImpl(currentMapID, gridID, chunkSize, true, default);
}
public IMapGrid CreateGrid(MapId currentMapID, GridId gridID, ushort chunkSize, EntityUid euid)
{
return CreateGridImpl(currentMapID, gridID, chunkSize, true, euid);
}
private IMapGridInternal CreateGridImpl(MapId currentMapID, GridId? gridID, ushort chunkSize, bool createEntity,
EntityUid euid)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
GridId actualID;
if (gridID != null)
{
actualID = gridID.Value;
}
else
{
actualID = new GridId(HighestGridID.Value + 1);
}
DebugTools.Assert(actualID != GridId.Invalid);
if (GridExists(actualID))
{
throw new InvalidOperationException($"A grid with ID {actualID} already exists");
}
if (HighestGridID.Value < actualID.Value)
{
HighestGridID = actualID;
}
var grid = new MapGrid(this, _entityManager, actualID, chunkSize, currentMapID);
_grids.Add(actualID, grid);
Logger.InfoS("map", $"Creating new grid {actualID}");
if (actualID != GridId.Invalid && createEntity) // nullspace default grid is not bound to an entity
{
// the entity may already exist from map deserialization
IMapGridComponent? result = null;
foreach (var comp in _entityManager.EntityQuery<MapGridComponent>(true))
{
if (comp.GridIndex != actualID)
continue;
result = comp;
break;
}
if (result != null)
{
grid.GridEntityId = result.Owner;
Logger.DebugS("map", $"Rebinding grid {actualID} to entity {grid.GridEntityId}");
}
else
{
var gridEnt = EntityManager.CreateEntityUninitialized(null, euid);
grid.GridEntityId = gridEnt;
Logger.DebugS("map", $"Binding grid {actualID} to entity {grid.GridEntityId}");
var gridComp = _entityManager.AddComponent<MapGridComponent>(gridEnt);
gridComp.GridIndex = grid.Index;
//TODO: This is a hack to get TransformComponent.MapId working before entity states
//are applied. After they are applied the parent may be different, but the MapId will
//be the same. This causes TransformComponent.ParentUid of a grid to be unsafe to
//use in transform states anytime before the state parent is properly set.
_entityManager.GetComponent<TransformComponent>(gridEnt).AttachParent(GetMapEntityIdOrThrow(currentMapID));
_entityManager.InitializeComponents(gridEnt);
_entityManager.StartComponents(gridEnt);
}
}
else
{
Logger.DebugS("map", $"Skipping entity binding for gridId {actualID}");
}
OnGridCreated?.Invoke(currentMapID, actualID);
return grid;
}
public IMapGridInternal CreateGridNoEntity(MapId currentMapID, GridId? gridID = null, ushort chunkSize = 16)
{
return CreateGridImpl(currentMapID, gridID, chunkSize, false, default);
}
public IMapGrid GetGrid(GridId gridID)
{
return _grids[gridID];
}
public bool IsGrid(EntityUid uid)
{
return _grids.Any(x => x.Value.GridEntityId == uid);
}
public bool IsMap(EntityUid uid)
{
return _mapEntities.Any(x => x.Value == uid);
}
public bool TryGetGrid(GridId gridId, [NotNullWhen(true)] out IMapGrid? grid)
{
if (_grids.TryGetValue(gridId, out var gridinterface))
{
grid = gridinterface;
return true;
}
grid = null;
return false;
}
public bool GridExists(GridId gridID)
{
return _grids.ContainsKey(gridID);
}
public IEnumerable<IMapGrid> GetAllMapGrids(MapId mapId)
{
return _grids.Values.Where(m => m.ParentMapId == mapId);
}
public void FindGridsIntersectingEnumerator(MapId mapId, Box2 worldAABB, out FindGridsEnumerator enumerator, bool approx = false)
{
enumerator = new FindGridsEnumerator(_entityManager, _grids.GetEnumerator(), mapId, worldAABB, approx);
}
public virtual void DeleteGrid(GridId gridID)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
// Possible the grid was already deleted / is invalid
if (!_grids.TryGetValue(gridID, out var grid) || grid.Deleting)
return;
grid.Deleting = true;
var mapId = grid.ParentMapId;
var entityId = grid.GridEntityId;
if (_entityManager.EntityExists(entityId))
{
// DeleteGrid may be triggered by the entity being deleted,
// so make sure that's not the case.
if (_entityManager.GetComponent<MetaDataComponent>(entityId).EntityLifeStage <= EntityLifeStage.MapInitialized)
_entityManager.DeleteEntity(entityId);
}
grid.Dispose();
_grids.Remove(grid.Index);
Logger.DebugS("map", $"Deleted grid {gridID}");
OnGridRemoved?.Invoke(mapId, gridID);
}
public MapId NextMapId()
{
return HighestMapID = new MapId(HighestMapID.Value + 1);
}
public GridId NextGridId()
{
return HighestGridID = new GridId(HighestGridID.Value + 1);
}
protected void InvokeGridChanged(object? sender, GridChangedEventArgs ev)
{
GridChanged?.Invoke(sender, ev);
}
}
/// <summary>
/// Arguments for when a map is created or deleted locally ore remotely.
/// </summary>
public class MapEventArgs : EventArgs
/// <inheritdoc />
public void Startup()
{
/// <summary>
/// Map that is being modified.
/// </summary>
public MapId Map { get; }
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
_dbgGuardRunning = true;
#endif
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public MapEventArgs(MapId map)
{
Map = map;
}
Logger.DebugS("map", "Starting...");
EnsureNullspaceExistsAndClear();
DebugTools.Assert(_grids.Count == 0);
DebugTools.Assert(!GridExists(GridId.Invalid));
}
/// <summary>
/// Arguments for when a single tile on a grid is changed locally or remotely.
/// </summary>
public class TileChangedEventArgs : EventArgs
/// <inheritdoc />
public void Shutdown()
{
/// <summary>
/// New tile that replaced the old one.
/// </summary>
public TileRef NewTile { get; }
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
#endif
Logger.DebugS("map", "Stopping...");
/// <summary>
/// Old tile that was replaced.
/// </summary>
public Tile OldTile { get; }
DeleteAllMaps();
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public TileChangedEventArgs(TileRef newTile, Tile oldTile)
{
NewTile = newTile;
OldTile = oldTile;
}
#if DEBUG
DebugTools.Assert(_grids.Count == 0);
DebugTools.Assert(!GridExists(GridId.Invalid));
_dbgGuardRunning = false;
#endif
}
/// <summary>
/// Arguments for when a one or more tiles on a grid is changed at once.
/// </summary>
public class GridChangedEventArgs : EventArgs
/// <inheritdoc />
public void Restart()
{
/// <summary>
/// Grid being changed.
/// </summary>
public IMapGrid Grid { get; }
Logger.DebugS("map", "Restarting...");
public IReadOnlyCollection<(Vector2i position, Tile tile)> Modified { get; }
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public GridChangedEventArgs(IMapGrid grid, IReadOnlyCollection<(Vector2i position, Tile tile)> modified)
{
Grid = grid;
Modified = modified;
}
Shutdown();
Startup();
}
#if DEBUG
private bool _dbgGuardInit;
private bool _dbgGuardRunning;
#endif
}

View File

@@ -0,0 +1,363 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
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);
void ApplyGameStatePost(GameStateMapData? data);
}
internal class NetworkedMapManager : MapManager, INetworkedMapManager
{
private readonly Dictionary<GridId, List<(GameTick tick, Vector2i indices)>> _chunkDeletionHistory = new();
private readonly List<(GameTick tick, GridId gridId)> _gridDeletionHistory = new();
private readonly List<(GameTick tick, MapId mapId)> _mapDeletionHistory = new();
public override void DeleteMap(MapId mapId)
{
base.DeleteMap(mapId);
_mapDeletionHistory.Add((GameTiming.CurTick, mapId));
}
public override void DeleteGrid(GridId gridId)
{
base.DeleteGrid(gridId);
_gridDeletionHistory.Add((GameTiming.CurTick, gridId));
// No point syncing chunk removals anymore!
_chunkDeletionHistory.Remove(gridId);
}
public override void ChunkRemoved(MapChunk chunk)
{
base.ChunkRemoved(chunk);
if (!_chunkDeletionHistory.TryGetValue(chunk.GridId, out var chunks))
{
chunks = new List<(GameTick tick, Vector2i indices)>();
_chunkDeletionHistory[chunk.GridId] = chunks;
}
chunks.Add((GameTiming.CurTick, chunk.Indices));
// Seemed easier than having this method on GridFixtureSystem
if (!TryGetGrid(chunk.GridId, out var grid) ||
!EntityManager.TryGetComponent(grid.GridEntityId, out PhysicsComponent? body) ||
chunk.Fixtures.Count == 0)
return;
// TODO: Like MapManager injecting this is a PITA so need to work out an easy way to do it.
// Maybe just add like a PostInject method that gets called way later?
var fixtureSystem = EntitySystem.Get<FixtureSystem>();
foreach (var fixture in chunk.Fixtures)
{
fixtureSystem.DestroyFixture(body, fixture);
}
}
public GameStateMapData? GetStateData(GameTick fromTick)
{
var gridDatums = new Dictionary<GridId, GameStateMapData.GridDatum>();
foreach (MapGrid grid in GetAllGrids())
{
if (grid.LastTileModifiedTick < fromTick)
continue;
var deletedChunkData = new List<GameStateMapData.DeletedChunkDatum>();
if (_chunkDeletionHistory.TryGetValue(grid.Index, out var chunks))
{
foreach (var (tick, indices) in chunks)
{
if (tick < fromTick)
continue;
deletedChunkData.Add(new GameStateMapData.DeletedChunkDatum(indices));
}
}
var chunkData = new List<GameStateMapData.ChunkDatum>();
foreach (var (index, chunk) in grid.GetMapChunks())
{
if (chunk.LastTileModifiedTick < fromTick)
continue;
var tileBuffer = new Tile[grid.ChunkSize * (uint)grid.ChunkSize];
// Flatten the tile array.
// NetSerializer doesn't do multi-dimensional arrays.
// This is probably really expensive.
for (var x = 0; x < grid.ChunkSize; x++)
{
for (var y = 0; y < grid.ChunkSize; y++)
{
tileBuffer[x * grid.ChunkSize + y] = chunk.GetTile((ushort)x, (ushort)y);
}
}
chunkData.Add(new GameStateMapData.ChunkDatum(index, tileBuffer));
}
var gridDatum = new GameStateMapData.GridDatum(
chunkData.ToArray(),
deletedChunkData.ToArray(),
new MapCoordinates(grid.WorldPosition, grid.ParentMapId),
grid.WorldRotation);
gridDatums.Add(grid.Index, gridDatum);
}
// -- Map Deletion Data --
var mapDeletionsData = new List<MapId>();
foreach (var (tick, mapId) in _mapDeletionHistory)
{
if (tick < fromTick)
continue;
mapDeletionsData.Add(mapId);
}
// -- Grid Deletion Data
var gridDeletionsData = new List<GridId>();
foreach (var (tick, gridId) in _gridDeletionHistory)
{
if (tick < fromTick)
continue;
gridDeletionsData.Add(gridId);
}
// -- Map Creations --
var mapCreations = new List<MapId>();
foreach (var (mapId, tick) in MapCreationTick)
{
if (tick < fromTick || mapId == MapId.Nullspace)
continue;
mapCreations.Add(mapId);
}
// - Grid Creation data --
var gridCreations = new Dictionary<GridId, GameStateMapData.GridCreationDatum>();
foreach (MapGrid grid in GetAllGrids())
{
if (grid.CreatedTick < fromTick || grid.ParentMapId == MapId.Nullspace)
continue;
gridCreations.Add(grid.Index, new GameStateMapData.GridCreationDatum(grid.ChunkSize));
}
// no point sending empty collections
if (gridDatums.Count == 0)
gridDatums = default;
if (gridDeletionsData.Count == 0)
gridDeletionsData = default;
if (mapDeletionsData.Count == 0)
mapDeletionsData = default;
if (mapCreations.Count == 0)
mapCreations = default;
if (gridCreations.Count == 0)
gridCreations = default;
// no point even creating an empty map state if no data
if (gridDatums == null && gridDeletionsData == null && mapDeletionsData == null && mapCreations == null && gridCreations == null)
return default;
return new GameStateMapData(gridDatums?.ToArray<KeyValuePair<GridId, GameStateMapData.GridDatum>>(), gridDeletionsData?.ToArray(),
mapDeletionsData?.ToArray(), mapCreations?.ToArray(),
gridCreations?.ToArray<KeyValuePair<GridId, GameStateMapData.GridCreationDatum>>());
}
public void CullDeletionHistory(GameTick upToTick)
{
foreach (var (gridId, chunks) in _chunkDeletionHistory.ToArray())
{
chunks.RemoveAll(t => t.tick < upToTick);
if (chunks.Count == 0)
_chunkDeletionHistory.Remove(gridId);
}
_mapDeletionHistory.RemoveAll(t => t.tick < upToTick);
_gridDeletionHistory.RemoveAll(t => t.tick < upToTick);
}
public void ApplyGameStatePre(GameStateMapData? data, ReadOnlySpan<EntityState> entityStates)
{
// There was no map data this tick, so nothing to do.
if (data == null)
return;
// First we need to figure out all the NEW MAPS.
if (data.CreatedMaps != null)
{
DebugTools.Assert(!entityStates.IsEmpty, "Received new maps, but no entity state.");
foreach (var mapId in data.CreatedMaps)
{
// map already exists from a previous state.
if (MapExists(mapId))
continue;
EntityUid mapEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates)
{
foreach (var compChange in entityState.ComponentChanges.Span)
{
if (compChange.State is not MapComponentState mapCompState || mapCompState.MapId != mapId)
continue;
mapEuid = entityState.Uid;
goto BreakMapEntSearch;
}
}
BreakMapEntSearch:
DebugTools.Assert(mapEuid != default, $"Could not find corresponding entity state for new map {mapId}.");
CreateMap(mapId, mapEuid);
}
}
// Then make all the grids.
if (data.CreatedGrids != null)
{
DebugTools.Assert(data.GridData is not null, "Received new grids, but GridData was null.");
foreach (var (gridId, creationDatum) in data.CreatedGrids)
{
if (GridExists(gridId))
continue;
EntityUid gridEuid = default;
//get shared euid of map comp entity
foreach (var entityState in entityStates)
{
foreach (var compState in entityState.ComponentChanges.Span)
{
if (compState.State is not MapGridComponentState gridCompState || gridCompState.GridIndex != gridId)
continue;
gridEuid = entityState.Uid;
goto BreakGridEntSearch;
}
}
BreakGridEntSearch:
DebugTools.Assert(gridEuid != default, $"Could not find corresponding entity state for new grid {gridId}.");
MapId gridMapId = default;
foreach (var kvData in data.GridData!)
{
if (kvData.Key != gridId)
continue;
gridMapId = kvData.Value.Coordinates.MapId;
break;
}
DebugTools.Assert(gridMapId != default, $"Could not find corresponding gridData for new grid {gridId}.");
CreateGrid(gridMapId, gridId, creationDatum.ChunkSize, gridEuid);
}
}
// Process all grid updates.
if (data.GridData != null)
{
SuppressOnTileChanged = true;
// Ok good all the grids and maps exist now.
foreach (var (gridId, gridDatum) in data.GridData)
{
var grid = (MapGrid)GetGrid(gridId);
if (grid.ParentMapId != gridDatum.Coordinates.MapId)
throw new NotImplementedException("Moving grids between maps is not yet implemented");
// I love mapmanager!!!
grid.WorldPosition = gridDatum.Coordinates.Position;
grid.WorldRotation = gridDatum.Angle;
var modified = new List<(Vector2i position, Tile tile)>();
foreach (var chunkData in gridDatum.ChunkData)
{
var chunk = grid.GetChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(chunkData.TileData.Length == grid.ChunkSize * grid.ChunkSize);
var counter = 0;
for (ushort x = 0; x < grid.ChunkSize; x++)
{
for (ushort y = 0; y < grid.ChunkSize; y++)
{
var tile = chunkData.TileData[counter++];
if (chunk.GetTileRef(x, y).Tile != tile)
{
chunk.SetTile(x, y, tile);
modified.Add((new Vector2i(chunk.X * grid.ChunkSize + x, chunk.Y * grid.ChunkSize + y), tile));
}
}
}
}
if (modified.Count != 0)
InvokeGridChanged(this, new GridChangedEventArgs(grid, modified));
foreach (var chunkData in gridDatum.ChunkData)
{
var chunk = grid.GetChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
chunk.RegenerateCollision();
}
foreach (var chunkData in gridDatum.DeletedChunkData)
{
grid.RemoveChunk(chunkData.Index);
}
}
SuppressOnTileChanged = false;
}
}
public void ApplyGameStatePost(GameStateMapData? data)
{
if (data == null) // if there is no data, there is nothing to do!
return;
if (data.DeletedGrids != null)
{
foreach (var grid in data.DeletedGrids)
{
if (GridExists(grid))
DeleteGrid(grid);
}
}
if (data.DeletedMaps != null)
{
foreach (var map in data.DeletedMaps)
{
if (MapExists(map))
DeleteMap(map);
}
}
}
}