mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Move MapGrid data from MapManager to MapGridComponent (#2430)
This commit is contained in:
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Robust.Shared.Map
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when a grid is altered.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
406
Robust.Shared/Map/MapManager.GridCollection.cs
Normal file
406
Robust.Shared/Map/MapManager.GridCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
282
Robust.Shared/Map/MapManager.MapCollection.cs
Normal file
282
Robust.Shared/Map/MapManager.MapCollection.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
363
Robust.Shared/Map/NetworkedMapManager.cs
Normal file
363
Robust.Shared/Map/NetworkedMapManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user