Compare commits

...

22 Commits

Author SHA1 Message Date
Paul
27a94384d0 v0.8.56 2022-02-15 18:16:07 +01:00
Paul Ritter
4b8f5815db pvs caching (#2547)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-15 18:14:44 +01:00
metalgearsloth
d830eef435 Version: 0.8.55 2022-02-15 21:16:35 +11:00
metalgearsloth
e7c4bf7341 Don't use worldbounds for PVS (#2473) 2022-02-15 21:07:41 +11:00
ike709
162e646404 Fix a weird issue with config (#2546)
Co-authored-by: ike709 <ike709@github.com>
2022-02-15 20:57:32 +11:00
metalgearsloth
93049fcacf More transform optimisations (#2519) 2022-02-15 20:43:49 +11:00
Kara D
f25ad8ece9 Version: 0.8.54 2022-02-13 20:25:00 -07:00
mirrorcult
1aaf3b9250 Merge pull request #2521 from ElectroJr/public-AppearanceSystem 2022-02-13 20:24:23 -07:00
mirrorcult
11f6cff6df Merge pull request #2539 from Acruid/vv_fix_entName 2022-02-13 20:01:53 -07:00
mirrorcult
c2ea57c95a Merge pull request #2503 from Ygg01/linguini-multiline-error-fix 2022-02-13 20:00:47 -07:00
Ygg01
f61ae5da6b Update Linguini and line info in errors 2022-02-14 01:50:11 +01:00
Acruid
8ae9a5f2da Fix a typo where setting the description of an entity in VV sets the name. 2022-02-13 16:35:51 -08:00
Ygg01
3f7e89e006 Merge remote-tracking branch 'origin/master' into linguini-multiline-error-fix 2022-02-13 23:35:28 +01:00
Ygg01
cee4e4d62e Enable Fluent logs for only .ftl files (#2532) 2022-02-12 21:59:08 +11:00
metalgearsloth
14a3783760 Version: 0.8.53 2022-02-12 15:56:21 +11:00
mirrorcult
b4607f7b1f Draw effects below FOV (#2534) 2022-02-12 15:54:50 +11:00
Acruid
5a28c16cae Map Pausing Fixes (#2520) 2022-02-12 15:54:03 +11:00
ElectroJr
9e8bf861ea Merge remote-tracking branch 'upstream/master' into public-AppearanceSystem 2022-02-11 20:43:19 +13:00
ElectroJr
4cfb9210d0 Make AppearanceSystem public 2022-02-09 04:55:31 +13:00
Ygg01
681f77a796 Change newline append 2022-02-04 02:01:52 +01:00
Ygg01
2a7f1cbf48 Ensure that tests work regardless of platform 2022-02-03 04:06:53 +01:00
Ygg01
c4e63cfdc7 Fix multiline errors, add some tests 2022-02-03 01:43:45 +01:00
33 changed files with 1094 additions and 378 deletions

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<PropertyGroup><Version>0.8.52</Version></PropertyGroup>
<PropertyGroup><Version>0.8.56</Version></PropertyGroup>
</Project>

View File

@@ -510,7 +510,7 @@ namespace Robust.Client
logManager.GetSawmill("discord").Level = LogLevel.Warning;
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
logManager.GetSawmill("szr").Level = LogLevel.Info;
logManager.GetSawmill("loc").Level = LogLevel.Error;
logManager.GetSawmill("loc").Level = LogLevel.Warning;
#if DEBUG_ONLY_FCE_INFO
#if DEBUG_ONLY_FCE_LOG

View File

@@ -6,7 +6,7 @@ using Robust.Shared.GameStates;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
internal sealed class AppearanceSystem : SharedAppearanceSystem
public sealed class AppearanceSystem : SharedAppearanceSystem
{
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();

View File

@@ -323,7 +323,7 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
private readonly ShaderInstance _unshadedShader;
private readonly EffectSystem _owner;

View File

@@ -29,14 +29,13 @@ namespace Robust.Server.Console.Commands
var mapId = new MapId(int.Parse(args[0]));
var mapMgr = IoCManager.Resolve<IMapManager>();
var pauseMgr = IoCManager.Resolve<IPauseManager>();
if (!mapMgr.MapExists(mapId))
{
mapMgr.CreateMap(mapId);
if (args.Length >= 2 && args[1] == "false")
{
pauseMgr.AddUninitializedMap(mapId);
mapMgr.AddUninitializedMap(mapId);
}
shell.WriteLine($"Map with ID {mapId} created.");
@@ -318,7 +317,6 @@ namespace Robust.Server.Console.Commands
}
var mapManager = IoCManager.Resolve<IMapManager>();
var pauseManager = IoCManager.Resolve<IPauseManager>();
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
@@ -329,13 +327,13 @@ namespace Robust.Server.Console.Commands
return;
}
if (pauseManager.IsMapInitialized(mapId))
if (mapManager.IsMapInitialized(mapId))
{
shell.WriteError("Map is already initialized!");
return;
}
pauseManager.DoMapInitialize(mapId);
mapManager.DoMapInitialize(mapId);
}
}
@@ -348,15 +346,14 @@ namespace Robust.Server.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var pauseManager = IoCManager.Resolve<IPauseManager>();
var msg = new StringBuilder();
foreach (var mapId in mapManager.GetAllMapIds().OrderBy(id => id.Value))
{
msg.AppendFormat("{0}: init: {1}, paused: {2}, ent: {3}, grids: {4}\n",
mapId, pauseManager.IsMapInitialized(mapId),
pauseManager.IsMapPaused(mapId),
mapId, mapManager.IsMapInitialized(mapId),
mapManager.IsMapPaused(mapId),
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)),
mapManager.GetMapEntityId(mapId));
}

View File

@@ -109,9 +109,8 @@ namespace Robust.Server.Console.Commands
private void SetupPlayer(MapId mapId, IConsoleShell shell, IPlayerSession? player, IMapManager mapManager)
{
if (mapId == MapId.Nullspace) return;
var pauseManager = IoCManager.Resolve<IPauseManager>();
pauseManager.SetMapPaused(mapId, false);
var mapUid = IoCManager.Resolve<IMapManager>().GetMapEntityIdOrThrow(mapId);
mapManager.SetMapPaused(mapId, false);
var mapUid = mapManager.GetMapEntityIdOrThrow(mapId);
IoCManager.Resolve<IEntityManager>().GetComponent<SharedPhysicsMapComponent>(mapUid).Gravity = new Vector2(0, -9.8f);
return;

View File

@@ -3,7 +3,7 @@ using Robust.Shared.GameStates;
namespace Robust.Server.GameObjects;
internal sealed class AppearanceSystem : SharedAppearanceSystem
public sealed class AppearanceSystem : SharedAppearanceSystem
{
public override void Initialize()
{

View File

@@ -5,30 +5,31 @@ namespace Robust.Server.GameStates;
public struct ChunkIndicesEnumerator
{
private Vector2i _topLeft;
private Vector2i _bottomRight;
private Vector2i _bottomLeft;
private Vector2i _topRight;
private int _x;
private int _y;
public ChunkIndicesEnumerator(Box2 viewBox, float chunkSize)
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
{
_topLeft = (viewBox.TopLeft / chunkSize).Floored();
_bottomRight = (viewBox.BottomRight / chunkSize).Floored();
_bottomLeft = ((viewPos - range) / chunkSize).Floored();
// Also floor this as we get the whole chunk anyway.
_topRight = ((viewPos + range) / chunkSize).Floored();
_x = _topLeft.X;
_y = _bottomRight.Y;
_x = _bottomLeft.X;
_y = _bottomLeft.Y;
}
public bool MoveNext([NotNullWhen(true)] out Vector2i? chunkIndices)
{
if (_y > _topLeft.Y)
if (_y > _topRight.Y)
{
_x++;
_y = _bottomRight.Y;
_y = _bottomLeft.Y;
}
if (_x > _bottomRight.X)
if (_x > _topRight.X)
{
chunkIndices = null;
return false;

View File

@@ -81,14 +81,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
private readonly List<(GameTick tick, TIndex index)> _deletionHistory = new();
/// <summary>
/// An index containing the <see cref="IndexLocation"/>s of all <see cref="TIndex"/>.
/// An index containing the <see cref="IIndexLocation"/>s of all <see cref="TIndex"/>.
/// </summary>
private readonly Dictionary<TIndex, IndexLocation> _indexLocations = new();
private readonly Dictionary<TIndex, IIndexLocation> _indexLocations = new();
/// <summary>
/// Buffer of all locationchanges since the last process call
/// </summary>
private readonly Dictionary<TIndex, IndexLocation> _locationChangeBuffer = new();
private readonly Dictionary<TIndex, IIndexLocation> _locationChangeBuffer = new();
/// <summary>
/// Buffer of all indexremovals since the last process call
/// </summary>
@@ -103,7 +103,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
{
var changedIndices = new HashSet<TIndex>(_locationChangeBuffer.Keys);
var changedChunkLocations = new HashSet<IndexLocation>();
var changedChunkLocations = new HashSet<IIndexLocation>();
foreach (var (index, tick) in _removalBuffer)
{
//changes dont need to be computed if we are removing the index anyways
@@ -154,7 +154,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
public HashSet<TIndex>.Enumerator GetElementsForSession(ICommonSession session) => _localOverrides[session].GetEnumerator();
private void AddIndexInternal(TIndex index, IndexLocation location)
private void AddIndexInternal(TIndex index, IIndexLocation location)
{
switch (location)
{
@@ -186,7 +186,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
_indexLocations.Add(index, location);
}
private IndexLocation? RemoveIndexInternal(TIndex index)
private IIndexLocation? RemoveIndexInternal(TIndex index)
{
// the index might be gone due to disconnects/grid-/map-deletions
if (!_indexLocations.TryGetValue(index, out var location))
@@ -392,7 +392,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
}
private void RegisterUpdate(TIndex index, IndexLocation location)
private void RegisterUpdate(TIndex index, IIndexLocation location)
{
if(_indexLocations.TryGetValue(index, out var oldLocation) && oldLocation == location) return;
@@ -400,14 +400,78 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
}
#endregion
#region IndexLocations
private abstract record IndexLocation;
private record MapChunkLocation(MapId MapId, Vector2i ChunkIndices) : IndexLocation;
private record GridChunkLocation(GridId GridId, Vector2i ChunkIndices) : IndexLocation;
private record GlobalOverride : IndexLocation;
private record LocalOverride(ICommonSession Session) : IndexLocation;
#endregion
}
#region IndexLocations
public interface IIndexLocation {};
public interface IChunkIndexLocation{ };
public struct MapChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<MapChunkLocation>
{
public MapChunkLocation(MapId mapId, Vector2i chunkIndices)
{
MapId = mapId;
ChunkIndices = chunkIndices;
}
public MapId MapId { get; init; }
public Vector2i ChunkIndices { get; init; }
public bool Equals(MapChunkLocation other)
{
return MapId.Equals(other.MapId) && ChunkIndices.Equals(other.ChunkIndices);
}
public override bool Equals(object? obj)
{
return obj is MapChunkLocation other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(MapId, ChunkIndices);
}
}
public struct GridChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<GridChunkLocation>
{
public GridChunkLocation(GridId gridId, Vector2i chunkIndices)
{
GridId = gridId;
ChunkIndices = chunkIndices;
}
public GridId GridId { get; init; }
public Vector2i ChunkIndices { get; init; }
public bool Equals(GridChunkLocation other)
{
return GridId.Equals(other.GridId) && ChunkIndices.Equals(other.ChunkIndices);
}
public override bool Equals(object? obj)
{
return obj is GridChunkLocation other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(GridId, ChunkIndices);
}
}
public struct GlobalOverride : IIndexLocation { }
public struct LocalOverride : IIndexLocation
{
public LocalOverride(ICommonSession session)
{
Session = session;
}
public ICommonSession Session { get; init; }
}
#endregion

View File

@@ -35,7 +35,7 @@ internal sealed partial class PVSSystem : EntitySystem
/// <summary>
/// Is view culling enabled, or will we send the whole map?
/// </summary>
private bool _cullingEnabled;
public bool CullingEnabled { get; private set; }
/// <summary>
/// How many new entities we can send per tick (dont wanna nuke the clients mailbox).
@@ -56,17 +56,28 @@ internal sealed partial class PVSSystem : EntitySystem
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw last iteration.
/// </summary>
private readonly Dictionary<ICommonSession, Dictionary<EntityUid, PVSEntityVisiblity>> _playerVisibleSets = new();
/// <summary>
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw along its entire connection.
/// </summary>
private readonly Dictionary<ICommonSession, HashSet<EntityUid>> _playerSeenSets = new();
private PVSCollection<EntityUid> _entityPvsCollection = default!;
public PVSCollection<EntityUid> EntityPVSCollection => _entityPvsCollection;
private readonly List<IPVSCollection> _pvsCollections = new();
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool =
new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
new DefaultPooledObjectPolicy<Dictionary<EntityUid, PVSEntityVisiblity>>(), MaxVisPoolSize);
private readonly ObjectPool<HashSet<EntityUid>> _viewerEntsPool
= new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool
= new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
new DictPolicy<EntityUid, PVSEntityVisiblity>(), MaxVisPoolSize);
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<EntityUid, MetaDataComponent>> _chunkCachePool =
new DefaultObjectPool<Dictionary<EntityUid, MetaDataComponent>>(
new DictPolicy<EntityUid, MetaDataComponent>(), MaxVisPoolSize);
private readonly ObjectPool<HashSet<int>> _playerChunkPool =
new DefaultObjectPool<HashSet<int>>(new SetPolicy<int>(), MaxVisPoolSize);
public override void Initialize()
{
@@ -121,7 +132,7 @@ internal sealed partial class PVSSystem : EntitySystem
private void SetPvs(bool value)
{
_cullingEnabled = value;
CullingEnabled = value;
}
private void OnNewEntityBudgetChanged(int obj)
@@ -260,115 +271,213 @@ internal sealed partial class PVSSystem : EntitySystem
#endregion
public (List<EntityState>? updates, List<EntityUid>? deletions) CalculateEntityStates(ICommonSession session,
GameTick fromTick, GameTick toTick)
public (List<(uint, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(IPlayerSession[] sessions)
{
var chunkList = new List<(uint, IChunkIndexLocation)>();
var playerChunks = new HashSet<int>[sessions.Length];
var eyeQuery = EntityManager.GetEntityQuery<EyeComponent>();
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var viewerEntities = new EntityUid[sessions.Length][];
// Keep track of the index of each chunk we use for a faster index lookup.
var mapIndices = new Dictionary<uint, Dictionary<MapChunkLocation, int>>(4);
var gridIndices = new Dictionary<uint, Dictionary<GridChunkLocation, int>>(4);
for (int i = 0; i < sessions.Length; i++)
{
var session = sessions[i];
playerChunks[i] = _playerChunkPool.Get();
var viewers = GetSessionViewers(session);
viewerEntities[i] = new EntityUid[viewers.Count];
viewers.CopyTo(viewerEntities[i]);
foreach (var eyeEuid in viewers)
{
var (viewPos, range, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
uint visMask = EyeComponent.DefaultVisibilityMask;
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
visMask = eyeComp.VisibilityMask;
// Get the nyoom dictionary for index lookups.
if (!mapIndices.TryGetValue(visMask, out var mapDict))
{
mapDict = new Dictionary<MapChunkLocation, int>(32);
mapIndices[visMask] = mapDict;
}
var mapChunkEnumerator = new ChunkIndicesEnumerator(viewPos, range, ChunkSize);
while (mapChunkEnumerator.MoveNext(out var chunkIndices))
{
var chunkLocation = new MapChunkLocation(mapId, chunkIndices.Value);
var entry = (visMask, chunkLocation);
if (mapDict.TryGetValue(chunkLocation, out var indexOf))
{
playerChunks[i].Add(indexOf);
}
else
{
playerChunks[i].Add(chunkList.Count);
mapDict.Add(chunkLocation, chunkList.Count);
chunkList.Add(entry);
}
}
// Get the nyoom dictionary for index lookups.
if (!gridIndices.TryGetValue(visMask, out var gridDict))
{
gridDict = new Dictionary<GridChunkLocation, int>(32);
gridIndices[visMask] = gridDict;
}
_mapManager.FindGridsIntersectingEnumerator(mapId, new Box2(viewPos - range, viewPos + range), out var gridEnumerator, true);
while (gridEnumerator.MoveNext(out var mapGrid))
{
var localPos = transformQuery.GetComponent(mapGrid.GridEntityId).InvWorldMatrix.Transform(viewPos);
var gridChunkEnumerator =
new ChunkIndicesEnumerator(localPos, range, ChunkSize);
while (gridChunkEnumerator.MoveNext(out var gridChunkIndices))
{
var chunkLocation = new GridChunkLocation(mapGrid.Index, gridChunkIndices.Value);
var entry = (visMask, chunkLocation);
if (gridDict.TryGetValue(chunkLocation, out var indexOf))
{
playerChunks[i].Add(indexOf);
}
else
{
playerChunks[i].Add(chunkList.Count);
gridDict.Add(chunkLocation, chunkList.Count);
chunkList.Add(entry);
}
}
}
}
_uidSetPool.Return(viewers);
}
return (chunkList, playerChunks, viewerEntities);
}
public Dictionary<EntityUid, MetaDataComponent>? CalculateChunk(IChunkIndexLocation chunkLocation, uint visMask, EntityQuery<TransformComponent> transform, EntityQuery<MetaDataComponent> metadata)
{
var chunk = chunkLocation switch
{
GridChunkLocation gridChunkLocation => _entityPvsCollection.TryGetChunk(gridChunkLocation.GridId,
gridChunkLocation.ChunkIndices, out var gridChunk)
? gridChunk
: null,
MapChunkLocation mapChunkLocation => _entityPvsCollection.TryGetChunk(mapChunkLocation.MapId,
mapChunkLocation.ChunkIndices, out var mapChunk)
? mapChunk
: null
};
if (chunk == null) return null;
var chunkSet = _chunkCachePool.Get();
foreach (var uid in chunk)
{
AddToChunkSetRecursively(in uid, visMask, chunkSet, transform, metadata);
}
return chunkSet;
}
public void ReturnToPool(Dictionary<EntityUid, MetaDataComponent>?[] chunkCache, HashSet<int>[] playerChunks)
{
foreach (var chunk in chunkCache)
{
if(chunk == null) continue;
_chunkCachePool.Return(chunk);
}
foreach (var playerChunk in playerChunks)
{
_playerChunkPool.Return(playerChunk);
}
}
private bool AddToChunkSetRecursively(in EntityUid uid, uint visMask, Dictionary<EntityUid, MetaDataComponent> set, EntityQuery<TransformComponent> transform,
EntityQuery<MetaDataComponent> metadata)
{
//are we valid?
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
if (!uid.IsValid()) return false;
if (set.ContainsKey(uid)) return false;
var mComp = metadata.GetComponent(uid);
// TODO: Don't need to know about parents so no longer need to use bool for this method.
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
if ((visMask & mComp.VisibilityMask) != mComp.VisibilityMask)
return false;
var parent = transform.GetComponent(uid).ParentUid;
if (parent.IsValid() && //is it not a worldentity?
!set.ContainsKey(parent) && //was the parent not yet added to toSend?
!AddToChunkSetRecursively(in parent, visMask, set, transform, metadata)) //did we just fail to add the parent?
return false; //we failed? suppose we dont get added either
//todo paul i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles, make this a simpl assignment at some point maybe idk
set.Add(uid, mComp);
return true;
}
public (List<EntityState>? updates, List<EntityUid>? deletions) CalculateEntityStates(IPlayerSession session,
GameTick fromTick, GameTick toTick, Dictionary<EntityUid, MetaDataComponent>?[] chunkCache, HashSet<int> chunkIndices, EntityQuery<MetaDataComponent> mQuery, EntityUid[] viewerEntities)
{
DebugTools.Assert(session.Status == SessionStatus.InGame);
var newEntitiesSent = 0;
var entitiesSent = 0;
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
if (!_cullingEnabled)
{
var allStates = GetAllEntityStates(session, fromTick, toTick);
return (allStates, deletions);
}
var playerVisibleSet = _playerVisibleSets[session];
var visibleEnts = _visSetPool.Get();
var seenSet = _playerSeenSets[session];
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
visibleEnts.Clear();
var eyeQuery = EntityManager.GetEntityQuery<EyeComponent>();
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
var globalOverridesEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
while(globalOverridesEnumerator.MoveNext())
var globalEnumerator = _entityPvsCollection.GlobalOverridesEnumerator;
while (globalEnumerator.MoveNext())
{
var uid = globalOverridesEnumerator.Current;
//todo paul reenable budgetcheck here once you fix mapmanager
TryAddToVisibleEnts(
in uid,
seenSet,
playerVisibleSet,
visibleEnts,
fromTick,
ref newEntitiesSent,
ref entitiesSent,
metadataQuery,
transformQuery,
dontSkip: true);
var uid = globalEnumerator.Current;
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, dontSkip: true);
}
globalOverridesEnumerator.Dispose();
globalEnumerator.Dispose();
var localOverridesEnumerator = _entityPvsCollection.GetElementsForSession(session);
while (localOverridesEnumerator.MoveNext())
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
while (localEnumerator.MoveNext())
{
var uid = localOverridesEnumerator.Current;
//todo paul reenable budgetcheck here once you fix mapmanager
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, dontSkip: true);
var uid = localEnumerator.Current;
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, dontSkip: true);
}
localOverridesEnumerator.Dispose();
localEnumerator.Dispose();
var expandEvent = new ExpandPvsEvent((IPlayerSession) session, new List<EntityUid>());
RaiseLocalEvent(ref expandEvent);
foreach (var entityUid in expandEvent.Entities)
foreach (var viewerEntity in viewerEntities)
{
TryAddToVisibleEnts(in entityUid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery);
TryAddToVisibleEnts(in viewerEntity, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, dontSkip: true);
}
var viewers = GetSessionViewers(session);
foreach (var eyeEuid in viewers)
foreach (var i in chunkIndices)
{
var (viewBox, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
uint visMask = EyeComponent.DefaultVisibilityMask;
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
visMask = eyeComp.VisibilityMask;
//todo at some point just register the viewerentities as localoverrides
TryAddToVisibleEnts(in eyeEuid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask, dontSkip: true);
var mapChunkEnumerator = new ChunkIndicesEnumerator(viewBox, ChunkSize);
while (mapChunkEnumerator.MoveNext(out var chunkIndices))
var chunk = chunkCache[i];
if(chunk == null) continue;
foreach (var (uid, metadata) in chunk)
{
if(_entityPvsCollection.TryGetChunk(mapId, chunkIndices.Value, out var chunk))
{
foreach (var index in chunk)
{
TryAddToVisibleEnts(in index, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask);
}
}
}
TryAddToVisibleEnts(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, metadata);
_mapManager.FindGridsIntersectingEnumerator(mapId, viewBox, out var gridEnumerator, true);
while (gridEnumerator.MoveNext(out var mapGrid))
{
var gridXform = transformQuery.GetComponent(mapGrid.GridEntityId);
var gridChunkEnumerator =
new ChunkIndicesEnumerator(gridXform.InvWorldMatrix.TransformBox(viewBox), ChunkSize);
while (gridChunkEnumerator.MoveNext(out var gridChunkIndices))
{
if (_entityPvsCollection.TryGetChunk(mapGrid.Index, gridChunkIndices.Value, out var chunk))
{
foreach (var index in chunk)
{
TryAddToVisibleEnts(in index, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent, ref entitiesSent, metadataQuery, transformQuery, visMask);
}
}
}
}
}
viewers.Clear();
_viewerEntsPool.Return(viewers);
var entityStates = new List<EntityState>();
foreach (var (entityUid, visiblity) in visibleEnts)
@@ -411,7 +520,7 @@ internal sealed partial class PVSSystem : EntitySystem
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool TryAddToVisibleEnts(
private void TryAddToVisibleEnts(
in EntityUid uid,
HashSet<EntityUid> seenSet,
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
@@ -420,40 +529,16 @@ internal sealed partial class PVSSystem : EntitySystem
ref int newEntitiesSent,
ref int totalEnteredEntities,
EntityQuery<MetaDataComponent> metadataQuery,
EntityQuery<TransformComponent> transformQuery,
uint? visMask = null,
bool dontSkip = false,
bool trustParent = false)
MetaDataComponent? metaDataComponent = null,
bool dontSkip = false)
{
//are we valid yet?
//are we valid?
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
if (!uid.IsValid()) return false;
if (!uid.IsValid()) return;
//did we already get added?
if (toSend.ContainsKey(uid)) return true;
var metadata = metadataQuery.GetComponent(uid);
// if we are invisible, we are not going into the visSet, so don't worry about parents, and children are not going in
if (visMask != null)
{
// TODO: Don't need to know about parents so no longer need to use bool for this method.
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
if ((visMask & metadata.VisibilityMask) != metadata.VisibilityMask)
return false;
}
var parent = transformQuery.GetComponent(uid).ParentUid;
if (!trustParent && //do we have it on good authority the parent exists?
parent.IsValid() && //is it not a worldentity?
!toSend.ContainsKey(parent) && //was the parent not yet added to toSend?
!TryAddToVisibleEnts(in parent, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent, ref totalEnteredEntities, metadataQuery, transformQuery, visMask)) //did we just fail to add the parent?
return false; //we failed? suppose we dont get added either
//did we already get added through the parent call?
if (toSend.ContainsKey(uid)) return true;
//todo paul can this happen?
if (toSend.ContainsKey(uid)) return;
//are we new?
var @new = !seenSet.Contains(uid);
@@ -462,7 +547,7 @@ internal sealed partial class PVSSystem : EntitySystem
if (entered)
{
if (!dontSkip && totalEnteredEntities >= _entityBudget)
return false;
return;
totalEnteredEntities++;
}
@@ -471,7 +556,7 @@ internal sealed partial class PVSSystem : EntitySystem
{
//we just entered pvs, do we still have enough budget to send us?
if(!dontSkip && newEntitiesSent >= _newEntityBudget)
return false;
return;
newEntitiesSent++;
seenSet.Add(uid);
@@ -480,29 +565,33 @@ internal sealed partial class PVSSystem : EntitySystem
if (entered)
{
toSend.Add(uid, PVSEntityVisiblity.Entered);
return true;
return;
}
if (metadata.EntityLastModifiedTick < fromTick)
metaDataComponent ??= metadataQuery.GetComponent(uid);
if (metaDataComponent.EntityLastModifiedTick < fromTick)
{
//entity has been sent before and hasnt been updated since
toSend.Add(uid, PVSEntityVisiblity.StayedUnchanged);
return true;
return;
}
//add us
toSend.Add(uid, PVSEntityVisiblity.StayedChanged);
return true;
return;
}
/// <summary>
/// Gets all entity states that have been modified after and including the provided tick.
/// </summary>
private List<EntityState>? GetAllEntityStates(ICommonSession player, GameTick fromTick, GameTick toTick)
public (List<EntityState>? updates, List<EntityUid>? deletions) GetAllEntityStates(ICommonSession player, GameTick fromTick, GameTick toTick)
{
List<EntityState> stateEntities;
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
// no point sending an empty collection
if (deletions.Count == 0) deletions = default;
stateEntities = new List<EntityState>();
var stateEntities = new List<EntityState>();
var seenEnts = new HashSet<EntityUid>();
var slowPath = false;
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
@@ -544,7 +633,9 @@ internal sealed partial class PVSSystem : EntitySystem
if (!slowPath)
{
return stateEntities.Count == 0 ? default : stateEntities;
if (stateEntities.Count == 0) stateEntities = default;
return (stateEntities, deletions);
}
stateEntities = new List<EntityState>(EntityManager.EntityCount);
@@ -559,7 +650,9 @@ internal sealed partial class PVSSystem : EntitySystem
}
// no point sending an empty collection
return stateEntities.Count == 0 ? default : stateEntities;
if (stateEntities.Count == 0) stateEntities = default;
return (stateEntities, deletions);
}
/// <summary>
@@ -618,7 +711,7 @@ internal sealed partial class PVSSystem : EntitySystem
private HashSet<EntityUid> GetSessionViewers(ICommonSession session)
{
var viewers = _viewerEntsPool.Get();
var viewers = _uidSetPool.Get();
if (session.Status != SessionStatus.InGame)
return viewers;
@@ -638,14 +731,38 @@ internal sealed partial class PVSSystem : EntitySystem
}
// Read Safe
private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid, EntityQuery<TransformComponent> transformQuery)
private (Vector2 worldPos, float range, MapId mapId) CalcViewBounds(in EntityUid euid, EntityQuery<TransformComponent> transformQuery)
{
var xform = transformQuery.GetComponent(euid);
return (xform.WorldPosition, _viewSize / 2f, xform.MapID);
}
var view = Box2.UnitCentered.Scale(_viewSize).Translated(xform.WorldPosition);
var map = xform.MapID;
public sealed class SetPolicy<T> : PooledObjectPolicy<HashSet<T>>
{
public override HashSet<T> Create()
{
return new HashSet<T>();
}
return (view, map);
public override bool Return(HashSet<T> obj)
{
obj.Clear();
return true;
}
}
public sealed class DictPolicy<T1, T2> : PooledObjectPolicy<Dictionary<T1, T2>> where T1 : notnull
{
public override Dictionary<T1, T2> Create()
{
return new Dictionary<T1, T2>();
}
public override bool Return(Dictionary<T1, T2> obj)
{
obj.Clear();
return true;
}
}
}

View File

@@ -1,9 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Enums;
@@ -125,6 +128,35 @@ namespace Robust.Server.GameStates
// people not in the game don't get states
var players = _playerManager.ServerSessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
//todo paul oh my god make this less shit
EntityQuery<MetaDataComponent> metadataQuery = default!;
HashSet<int>[] playerChunks = default!;
EntityUid[][] viewerEntities = default!;
Dictionary<EntityUid, MetaDataComponent>?[] chunkCache = default!;
if (_pvs.CullingEnabled)
{
List<(uint, IChunkIndexLocation)> chunks;
(chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
const int ChunkBatchSize = 2;
var chunksCount = chunks.Count;
var chunkBatches = (int) MathF.Ceiling((float) chunksCount / ChunkBatchSize);
chunkCache = new Dictionary<EntityUid, MetaDataComponent>?[chunks.Count];
var transformQuery = _entityManager.GetEntityQuery<TransformComponent>();
metadataQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
Parallel.For(0, chunkBatches, i =>
{
var start = i * ChunkBatchSize;
var end = Math.Min(start + ChunkBatchSize, chunksCount);
for (var j = start; j < end; ++j)
{
var (visMask, chunkIndexLocation) = chunks[j];
chunkCache[j] = _pvs.CalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery);
}
});
}
const int BatchSize = 2;
var batches = (int) MathF.Ceiling((float) players.Length / BatchSize);
@@ -135,11 +167,9 @@ namespace Robust.Server.GameStates
for (var j = start; j < end; ++j)
{
var session = players[j];
try
{
SendStateUpdate(session);
SendStateUpdate(j);
}
catch (Exception e) // Catch EVERY exception
{
@@ -148,8 +178,10 @@ namespace Robust.Server.GameStates
}
});
void SendStateUpdate(IPlayerSession session)
void SendStateUpdate(int sessionIndex)
{
var session = players[sessionIndex];
// KILL IT WITH FIRE
if(mainThread != Thread.CurrentThread)
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
@@ -161,7 +193,10 @@ namespace Robust.Server.GameStates
DebugTools.Assert("Why does this channel not have an entry?");
}
var (entStates, deletions) = _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
var (entStates, deletions) = _pvs.CullingEnabled
? _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick, chunkCache,
playerChunks[sessionIndex], metadataQuery, viewerEntities[sessionIndex])
: _pvs.GetAllEntityStates(session, lastAck, _gameTiming.CurTick);
var playerStates = _playerManager.GetPlayerStates(lastAck);
var mapData = _mapManager.GetStateData(lastAck);
@@ -194,6 +229,8 @@ namespace Robust.Server.GameStates
_networkManager.ServerSendMessage(stateUpdateMessage, channel);
}
if(_pvs.CullingEnabled)
_pvs.ReturnToPool(chunkCache, playerChunks);
_pvs.Cleanup(_playerManager.ServerSessions);
var oldestAck = new GameTick(oldestAckValue);

View File

@@ -24,7 +24,6 @@ using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
@@ -44,7 +43,6 @@ namespace Robust.Server.Maps
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IServerEntityManagerInternal _serverEntityManager = default!;
[Dependency] private readonly IPauseManager _pauseManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public event Action<YamlStream, string>? LoadedMapData;
@@ -54,7 +52,7 @@ namespace Robust.Server.Maps
{
var grid = _mapManager.GetGrid(gridId);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
context.RegisterGrid(grid);
var root = context.Serialize();
var document = new YamlDocument(root);
@@ -100,7 +98,7 @@ namespace Robust.Server.Maps
throw new InvalidDataException("Cannot instance map with multiple grids as blueprint.");
}
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
context.Deserialize();
grid = context.Grids[0];
@@ -120,7 +118,7 @@ namespace Robust.Server.Maps
_serverEntityManager.GetComponent<MetaDataComponent>(entity).EntityLifeStage = EntityLifeStage.MapInitialized;
}
}
else if (_pauseManager.IsMapInitialized(mapId))
else if (_mapManager.IsMapInitialized(mapId))
{
foreach (var entity in context.Entities)
{
@@ -128,7 +126,7 @@ namespace Robust.Server.Maps
}
}
if (_pauseManager.IsMapPaused(mapId))
if (_mapManager.IsMapPaused(mapId))
{
foreach (var entity in context.Entities)
{
@@ -141,7 +139,7 @@ namespace Robust.Server.Maps
public void SaveMap(MapId mapId, string yamlPath)
{
Logger.InfoS("map", $"Saving map {mapId} to {yamlPath}");
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
{
context.RegisterGrid(grid);
@@ -207,7 +205,7 @@ namespace Robust.Server.Maps
LoadedMapData?.Invoke(data.Stream, resPath.ToString());
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
context.Deserialize();
@@ -226,7 +224,6 @@ namespace Robust.Server.Maps
private readonly IMapManagerInternal _mapManager;
private readonly ITileDefinitionManager _tileDefinitionManager;
private readonly IServerEntityManagerInternal _serverEntityManager;
private readonly IPauseManager _pauseManager;
private readonly IPrototypeManager _prototypeManager;
private readonly MapLoadOptions? _loadOptions;
@@ -260,12 +257,11 @@ namespace Robust.Server.Maps
public bool MapIsPostInit { get; private set; }
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities, IPauseManager pauseManager, IPrototypeManager prototypeManager)
IServerEntityManagerInternal entities, IPrototypeManager prototypeManager)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
_serverEntityManager = entities;
_pauseManager = pauseManager;
_prototypeManager = prototypeManager;
RootNode = new YamlMappingNode();
@@ -283,13 +279,12 @@ namespace Robust.Server.Maps
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities,
IPauseManager pauseManager, IPrototypeManager prototypeManager,
IPrototypeManager prototypeManager,
YamlMappingNode node, MapId targetMapId, MapLoadOptions options)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
_serverEntityManager = entities;
_pauseManager = pauseManager;
_loadOptions = options;
RootNode = node;
@@ -612,7 +607,7 @@ namespace Robust.Server.Maps
if (!MapIsPostInit)
{
_pauseManager.AddUninitializedMap(TargetMap);
_mapManager.AddUninitializedMap(TargetMap);
}
}
}
@@ -719,7 +714,7 @@ namespace Robust.Server.Maps
var isPostInit = false;
foreach (var grid in Grids)
{
if (_pauseManager.IsMapInitialized(grid.ParentMapId))
if (_mapManager.IsMapInitialized(grid.ParentMapId))
{
isPostInit = true;
break;

View File

@@ -130,6 +130,12 @@ namespace Robust.Shared.Maths
return new((int) MathF.Floor(X), (int) MathF.Floor(Y));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Vector2i Ceiled()
{
return new((int) MathF.Ceiling(X), (int) MathF.Ceiling(Y));
}
/// <summary>
/// Subtracts a vector from another, returning a new vector.
/// </summary>

View File

@@ -1,25 +1,24 @@
using Robust.Shared.IoC;
using Robust.Shared.Timing;
using Robust.Shared.Map;
namespace Robust.Shared.GameObjects
{
[RegisterComponent]
public sealed class IgnorePauseComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
protected override void OnAdd()
{
base.OnAdd();
_entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = false;
IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(Owner).EntityPaused = false;
}
protected override void OnRemove()
{
base.OnRemove();
if (IoCManager.Resolve<IPauseManager>().IsMapPaused(_entMan.GetComponent<TransformComponent>(Owner).MapID))
var entMan = IoCManager.Resolve<IEntityManager>();
if (IoCManager.Resolve<IMapManager>().IsMapPaused(entMan.GetComponent<TransformComponent>(Owner).MapID))
{
_entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = true;
entMan.GetComponent<MetaDataComponent>(Owner).EntityPaused = true;
}
}
}

View File

@@ -147,9 +147,11 @@ namespace Robust.Shared.GameObjects
get => _entityPaused;
set
{
if (_entityPaused == value)
return;
var entMan = IoCManager.Resolve<IEntityManager>();
if (_entityPaused == value || value && entMan.HasComponent<IgnorePauseComponent>(Owner))
if (value && entMan.HasComponent<IgnorePauseComponent>(Owner))
return;
_entityPaused = value;

View File

@@ -155,12 +155,18 @@ namespace Robust.Shared.GameObjects
{
get
{
if (_parent.IsValid())
var parent = _parent;
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
var rotation = _localRotation;
while (parent.IsValid())
{
return Parent!.WorldRotation + _localRotation;
var parentXform = xformQuery.GetComponent(parent);
rotation += parentXform._localRotation;
parent = parentXform.ParentUid;
}
return _localRotation;
return rotation;
}
set
{
@@ -211,15 +217,21 @@ namespace Robust.Shared.GameObjects
{
get
{
if (_parent.IsValid())
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
var parent = _parent;
var myMatrix = _localMatrix;
while (parent.IsValid())
{
var parentMatrix = Parent!.WorldMatrix;
var myMatrix = GetLocalMatrix();
var parentXform = xformQuery.GetComponent(parent);
var parentMatrix = parentXform._localMatrix;
parent = parentXform.ParentUid;
Matrix3.Multiply(ref myMatrix, ref parentMatrix, out var result);
return result;
myMatrix = result;
}
return GetLocalMatrix();
return myMatrix;
}
}
@@ -230,15 +242,21 @@ namespace Robust.Shared.GameObjects
{
get
{
if (_parent.IsValid())
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
var parent = _parent;
var myMatrix = _invLocalMatrix;
while (parent.IsValid())
{
var matP = Parent!.InvWorldMatrix;
var myMatrix = GetLocalMatrixInv();
Matrix3.Multiply(ref matP, ref myMatrix, out var result);
return result;
var parentXform = xformQuery.GetComponent(parent);
var parentMatrix = parentXform._invLocalMatrix;
parent = parentXform.ParentUid;
Matrix3.Multiply(ref parentMatrix, ref myMatrix, out var result);
myMatrix = result;
}
return GetLocalMatrixInv();
return myMatrix;
}
}
@@ -314,15 +332,16 @@ namespace Robust.Shared.GameObjects
if (!sameParent)
{
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
changedParent = true;
var newParent = _entMan.GetComponent<TransformComponent>(value.EntityId);
var newParent = xformQuery.GetComponent(value.EntityId);
DebugTools.Assert(newParent != this,
$"Can't parent a {nameof(TransformComponent)} to itself.");
// That's already our parent, don't bother attaching again.
var oldParent = Parent;
var oldParent = _parent.IsValid() ? xformQuery.GetComponent(_parent) : null;
var uid = Owner;
oldParent?._children.Remove(uid);
newParent._children.Add(uid);
@@ -343,7 +362,8 @@ namespace Robust.Shared.GameObjects
// This may not in fact be the right thing.
if (changedParent || !DeferUpdates)
RebuildMatrices();
Dirty();
Dirty(_entMan);
if (!DeferUpdates)
{
@@ -507,7 +527,7 @@ namespace Robust.Shared.GameObjects
// Children MAY be initialized here before their parents are.
// We do this whole dance to handle this recursively,
// setting _mapIdInitialized along the way to avoid going to the IMapComponent every iteration.
static MapId FindMapIdAndSet(TransformComponent p, IEntityManager entMan)
static MapId FindMapIdAndSet(TransformComponent p, IEntityManager entMan, EntityQuery<TransformComponent> xformQuery)
{
if (p._mapIdInitialized)
{
@@ -517,7 +537,7 @@ namespace Robust.Shared.GameObjects
MapId value;
if (p._parent.IsValid())
{
value = FindMapIdAndSet((TransformComponent) p.Parent!, entMan);
value = FindMapIdAndSet(xformQuery.GetComponent(p._parent), entMan, xformQuery);
}
else
{
@@ -537,9 +557,11 @@ namespace Robust.Shared.GameObjects
return value;
}
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
if (!_mapIdInitialized)
{
FindMapIdAndSet(this, _entMan);
FindMapIdAndSet(this, _entMan, xformQuery);
_mapIdInitialized = true;
}
@@ -728,17 +750,27 @@ namespace Robust.Shared.GameObjects
var oldMapId = MapID;
//Set Paused state
var mapPaused = _mapManager.IsMapPaused(newMapId);
var metaData = _entMan.GetComponent<MetaDataComponent>(Owner);
metaData.EntityPaused = mapPaused;
MapID = newMapId;
MapIdChanged(oldMapId);
UpdateChildMapIdsRecursive(MapID, _entMan);
UpdateChildMapIdsRecursive(MapID, _entMan, mapPaused);
}
private void UpdateChildMapIdsRecursive(MapId newMapId, IEntityManager entMan)
private void UpdateChildMapIdsRecursive(MapId newMapId, IEntityManager entMan, bool mapPaused)
{
var xforms = _entMan.GetEntityQuery<TransformComponent>();
var metaEnts = _entMan.GetEntityQuery<MetaDataComponent>();
foreach (var child in _children)
{
//Set Paused state
var metaData = metaEnts.GetComponent(child);
metaData.EntityPaused = mapPaused;
var concrete = xforms.GetComponent(child);
var old = concrete.MapID;
@@ -747,7 +779,7 @@ namespace Robust.Shared.GameObjects
if (concrete.ChildCount != 0)
{
concrete.UpdateChildMapIdsRecursive(newMapId, entMan);
concrete.UpdateChildMapIdsRecursive(newMapId, entMan, mapPaused);
}
}
}
@@ -800,14 +832,14 @@ namespace Robust.Shared.GameObjects
{
var parent = _parent;
var worldRot = _localRotation;
var worldMatrix = GetLocalMatrix();
var worldMatrix = _localMatrix;
// By doing these all at once we can elide multiple IsValid + GetComponent calls
while (parent.IsValid())
{
var xform = xforms.GetComponent(parent);
worldRot += xform.LocalRotation;
var parentMatrix = xform.GetLocalMatrix();
var parentMatrix = xform._localMatrix;
Matrix3.Multiply(ref worldMatrix, ref parentMatrix, out var result);
worldMatrix = result;
parent = xform.ParentUid;
@@ -861,8 +893,8 @@ namespace Robust.Shared.GameObjects
{
var parent = _parent;
var worldRot = _localRotation;
var invMatrix = GetLocalMatrixInv();
var worldMatrix = GetLocalMatrix();
var invMatrix = _invLocalMatrix;
var worldMatrix = _localMatrix;
// By doing these all at once we can elide multiple IsValid + GetComponent calls
while (parent.IsValid())
@@ -870,11 +902,11 @@ namespace Robust.Shared.GameObjects
var xform = xformQuery.GetComponent(parent);
worldRot += xform.LocalRotation;
var parentMatrix = xform.GetLocalMatrix();
var parentMatrix = xform._localMatrix;
Matrix3.Multiply(ref worldMatrix, ref parentMatrix, out var result);
worldMatrix = result;
var parentInvMatrix = xform.GetLocalMatrixInv();
var parentInvMatrix = xform._invLocalMatrix;
Matrix3.Multiply(ref parentInvMatrix, ref invMatrix, out var invResult);
invMatrix = invResult;
@@ -1005,16 +1037,6 @@ namespace Robust.Shared.GameObjects
}
}
public Matrix3 GetLocalMatrix()
{
return _localMatrix;
}
public Matrix3 GetLocalMatrixInv()
{
return _invLocalMatrix;
}
private void RebuildMatrices()
{
var pos = _localPosition;

View File

@@ -20,7 +20,6 @@ namespace Robust.Shared.GameObjects
[IoC.Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
[IoC.Dependency] private readonly IPauseManager _pauseManager = default!;
#endregion Dependencies
@@ -438,7 +437,7 @@ namespace Robust.Shared.GameObjects
StartEntity(entity);
// If the map we're initializing the entity on is initialized, run map init on it.
if (_pauseManager.IsMapInitialized(mapId))
if (_mapManager.IsMapInitialized(mapId))
entity.RunMapInit();
}
catch (Exception e)

View File

@@ -3,7 +3,6 @@ using JetBrains.Annotations;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
@@ -172,7 +171,7 @@ namespace Robust.Shared.GameObjects
set
{
if (MetaData is {} metaData)
metaData.EntityName = value;
metaData.EntityDescription = value;
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects;
internal abstract class SharedAppearanceSystem : EntitySystem
public abstract class SharedAppearanceSystem : EntitySystem
{
public virtual void MarkDirty(AppearanceComponent component) {}
}

View File

@@ -70,7 +70,7 @@ namespace Robust.Shared.GameObjects
IoCManager.Resolve<IIslandManager>().Initialize();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange, true);
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange);
}
private void HandlePhysicsMapInit(EntityUid uid, SharedPhysicsMapComponent component, ComponentInit args)
@@ -81,6 +81,7 @@ namespace Robust.Shared.GameObjects
component.ContactManager = new();
component.ContactManager.Initialize();
component.ContactManager.MapId = component.MapId;
component.AutoClearForces = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.AutoClearForces);
component.ContactManager.KinematicControllerCollision += KinematicControllerCollision;
}

View File

@@ -1,30 +1,69 @@
using System;
using System.Collections.Generic;
using System.Text;
using Linguini.Bundle.Errors;
using Linguini.Syntax.Parser.Error;
namespace Robust.Shared.Localization
namespace Robust.Shared.Localization;
internal static class LocHelper
{
internal static class LocHelper
public static string FormatCompileErrors(this ParseError self, ReadOnlyMemory<char> resource,
string? newLine = null)
{
public static string FormatCompileErrors(this ParseError self, ReadOnlyMemory<char> resource)
ErrorSpan span = new(self.Row, self.Slice!.Value.Start.Value, self.Slice.Value.End.Value,
self.Position.Start.Value, self.Position.End.Value);
return FormatErrors(self.Message, span, resource, newLine);
}
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource, string? newLine)
{
var sb = new StringBuilder();
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
var lines = new List<ReadOnlyMemory<char>>(5);
var currLineOffset = 0;
var lastStart = 0;
for (var i = 0; i < span.StartMark - span.StartSpan; i++)
{
ErrorSpan span = new(self.Row, self.Slice!.Value.Start.Value, self.Slice.Value.End.Value,
self.Position.Start.Value, self.Position.End.Value);
return FormatErrors(self.Message, span, resource);
switch (errContext[i])
{
// Reset current line so that mark aligns with the reported error
// We cheat here a bit, since we both `\r\n` and `\n` end with '\n'
case '\n':
if (i > 0 && errContext[i - 1] == '\r')
{
lines.Add(resource.Slice(lastStart, currLineOffset - 1));
}
else
{
lines.Add(resource.Slice(lastStart, currLineOffset));
}
lastStart = currLineOffset + 1;
currLineOffset = 0;
break;
default:
currLineOffset++;
break;
}
}
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource)
lines.Add(resource.Slice(lastStart, resource.Length - lastStart));
var lastLine = $"{span.Row + lines.Count - 1}".Length;
for (var index = 0; index < lines.Count; index++)
{
var sb = new StringBuilder();
var row = $" {span.Row} ";
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
sb.Append(row).Append('|')
.AppendLine(errContext);
sb.Append(' ', row.Length).Append('|')
.Append(' ', span.StartMark - span.StartSpan - 1).Append('^', span.EndMark - span.StartMark)
.AppendLine($" {message}");
return sb.ToString();
var line = lines[index];
sb.Append(newLine ?? Environment.NewLine).Append(' ').Append($"{span.Row + index}".PadLeft(lastLine))
.Append(" |").Append(line);
}
sb.Append(newLine ?? Environment.NewLine)
.Append(' ', currLineOffset + lastLine + 3)
.Append('^', span.EndMark - span.StartMark)
.Append($" {message}");
return sb.ToString();
}
}

View File

@@ -47,7 +47,7 @@ namespace Robust.Shared.Localization
if (!TryGetString(messageId, out var msg))
{
_logSawmill.Warning("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
_logSawmill.Debug("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
msg = messageId;
}
@@ -62,7 +62,7 @@ namespace Robust.Shared.Localization
if (!TryGetString(messageId, out var msg, args0))
{
_logSawmill.Warning("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
_logSawmill.Debug("Unknown messageId ({culture}): {messageId}", _defaultCulture.Name, messageId);
msg = messageId;
}

View File

@@ -1,4 +1,6 @@
using System;
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Robust.Shared.Map
@@ -13,6 +15,14 @@ namespace Robust.Shared.Map
internal readonly int Value;
/// <summary>
/// Constructs a new instance of <see cref="GridId"/>.
/// </summary>
/// <remarks>
/// This should NOT be used in regular code, and is only public for special/legacy
/// cases. Generally you should only use this for parsing a GridId in console commands
/// and immediately check if the grid actually exists in the <see cref="IMapManager"/>.
/// </remarks>
public GridId(int value)
{
Value = value;
@@ -57,6 +67,30 @@ namespace Robust.Shared.Map
return self.Value;
}
/// <summary>
/// <see cref="GridId"/> is an alias of the <see cref="EntityUid"/> that
/// holds the <see cref="IMapGridComponent"/>, so it can be implicitly converted.
/// </summary>
public static implicit operator EntityUid(GridId self)
{
// If this throws, you are either using an unallocated gridId,
// or using it after the grid was freed. Both of these are bugs.
return IoCManager.Resolve<IMapManager>().GetGridEuid(self);
}
/// <summary>
/// <see cref="GridId"/> is an alias of the <see cref="EntityUid"/> that
/// holds the <see cref="IMapGridComponent"/>.
/// </summary>
public static implicit operator GridId(EntityUid euid)
{
// If this throws, you are using an EntityUid that isn't a grid.
// This would raise the question, "Why does your code think this entity is a grid?".
// Grid-ness is defined by the entity having an IMapGridComponent,
// was the component removed without you knowing?
return IoCManager.Resolve<IMapManager>().GetGridComp(euid).GridIndex;
}
public override string ToString()
{
return Value.ToString();

View File

@@ -3,13 +3,14 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
/// <summary>
/// This manages all of the grids in the world.
/// </summary>
public interface IMapManager
public interface IMapManager : IPauseManager
{
/// <summary>
/// The default <see cref="MapId" /> that is always available. Equivalent to SS13 Null space.

View File

@@ -2,101 +2,121 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Timing
namespace Robust.Shared.Map
{
internal sealed class PauseManager : IPauseManager, IPostInjectInit
internal partial class MapManager
{
[Dependency] private readonly IConsoleHost _conhost = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] private readonly HashSet<MapId> _pausedMaps = new();
[ViewVariables] private readonly HashSet<MapId> _unInitializedMaps = new();
/// <inheritdoc />
public void SetMapPaused(MapId mapId, bool paused)
{
if(!MapExists(mapId))
throw new ArgumentException("That map does not exist.");
if (paused)
{
_pausedMaps.Add(mapId);
foreach (var entity in _entityLookup.GetEntitiesInMap(mapId))
{
_entityManager.GetComponent<MetaDataComponent>(entity).EntityPaused = true;
}
}
else
{
_pausedMaps.Remove(mapId);
}
foreach (var entity in _entityLookup.GetEntitiesInMap(mapId))
{
_entityManager.GetComponent<MetaDataComponent>(entity).EntityPaused = false;
}
var mapEnt = GetMapEntityId(mapId);
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
RecursiveSetPaused(mapEnt, paused, in xformQuery, in metaQuery);
}
private static void RecursiveSetPaused(EntityUid entity, bool paused,
in EntityQuery<TransformComponent> xformQuery,
in EntityQuery<MetaDataComponent> metaQuery)
{
metaQuery.GetComponent(entity).EntityPaused = paused;
foreach (var child in xformQuery.GetComponent(entity)._children)
{
RecursiveSetPaused(child, paused, in xformQuery, in metaQuery);
}
}
/// <inheritdoc />
public void DoMapInitialize(MapId mapId)
{
if(!MapExists(mapId))
throw new ArgumentException("That map does not exist.");
if (IsMapInitialized(mapId))
throw new ArgumentException("That map is already initialized.");
_unInitializedMaps.Remove(mapId);
foreach (var entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesInMap(mapId).ToArray())
{
entity.RunMapInit();
var mapEnt = GetMapEntityId(mapId);
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
// MapInit could have deleted this entity.
if(_entityManager.TryGetComponent(entity, out MetaDataComponent? meta))
meta.EntityPaused = false;
RecursiveDoMapInit(mapEnt, in xformQuery, in metaQuery);
}
private static void RecursiveDoMapInit(EntityUid entity,
in EntityQuery<TransformComponent> xformQuery,
in EntityQuery<MetaDataComponent> metaQuery)
{
// RunMapInit can modify the TransformTree
// ToArray caches deleted euids, we check here if they still exist.
if(!metaQuery.TryGetComponent(entity, out var meta))
return;
entity.RunMapInit();
meta.EntityPaused = false;
foreach (var child in xformQuery.GetComponent(entity)._children.ToArray())
{
RecursiveDoMapInit(child, in xformQuery, in metaQuery);
}
}
/// <inheritdoc />
public void DoGridMapInitialize(IMapGrid grid)
{
DoGridMapInitialize(grid.Index);
// NOP
}
/// <inheritdoc />
public void DoGridMapInitialize(GridId gridId)
{
var mapId = _mapManager.GetGrid(gridId).ParentMapId;
foreach (var entity in _entityLookup.GetEntitiesInMap(mapId))
{
if (_entityManager.GetComponent<TransformComponent>(entity).GridID != gridId)
continue;
entity.RunMapInit();
_entityManager.GetComponent<MetaDataComponent>(entity).EntityPaused = false;
}
// NOP
}
/// <inheritdoc />
public void AddUninitializedMap(MapId mapId)
{
_unInitializedMaps.Add(mapId);
}
/// <inheritdoc />
public bool IsMapPaused(MapId mapId)
{
return _pausedMaps.Contains(mapId) || _unInitializedMaps.Contains(mapId);
}
/// <inheritdoc />
public bool IsGridPaused(IMapGrid grid)
{
return IsMapPaused(grid.ParentMapId);
}
/// <inheritdoc />
public bool IsGridPaused(GridId gridId)
{
if (_mapManager.TryGetGrid(gridId, out var grid))
if (TryGetGrid(gridId, out var grid))
{
return IsGridPaused(grid);
}
@@ -105,15 +125,18 @@ namespace Robust.Shared.Timing
return true;
}
/// <inheritdoc />
public bool IsMapInitialized(MapId mapId)
{
return !_unInitializedMaps.Contains(mapId);
}
/// <inheritdoc />
public void PostInject()
/// <summary>
/// Initializes the map pausing system.
/// </summary>
private void InitializeMapPausing()
{
_mapManager.MapDestroyed += (_, args) =>
MapDestroyed += (_, args) =>
{
_pausedMaps.Remove(args.Map);
_unInitializedMaps.Add(args.Map);
@@ -130,10 +153,9 @@ namespace Robust.Shared.Timing
return;
}
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var mapId = new MapId(int.Parse(args[0], CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
if (!MapExists(mapId))
{
shell.WriteError("That map does not exist.");
return;
@@ -147,10 +169,9 @@ namespace Robust.Shared.Timing
"querymappaused <map ID>",
(shell, _, args) =>
{
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var mapId = new MapId(int.Parse(args[0], CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
if (!MapExists(mapId))
{
shell.WriteError("That map does not exist.");
return;
@@ -170,10 +191,9 @@ namespace Robust.Shared.Timing
return;
}
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var mapId = new MapId(int.Parse(args[0], CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
if (!MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -7,20 +8,25 @@ using Robust.Shared.Utility;
namespace Robust.Shared.Map;
/// <inheritdoc cref="IMapManager" />
[Virtual]
internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
{
[field: Dependency] public IGameTiming GameTiming { get; } = default!;
[field: Dependency] public IEntityManager EntityManager { get; } = default!;
[Dependency] private readonly IConsoleHost _conhost = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
/// <inheritdoc />
public void Initialize()
{
InitializeGridTrees();
#if DEBUG
DebugTools.Assert(!_dbgGuardInit);
DebugTools.Assert(!_dbgGuardRunning);
_dbgGuardInit = true;
#endif
InitializeGridTrees();
InitializeMapPausing();
}
/// <inheritdoc />

View File

@@ -19,7 +19,7 @@
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="YamlDotNet" Version="9.1.4" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Linguini.Bundle" Version="0.1.2" />
<PackageReference Include="Linguini.Bundle" Version="0.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Asynchronous;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
@@ -32,7 +32,7 @@ namespace Robust.Shared
IoCManager.Register<ILocalizationManager, LocalizationManager>();
IoCManager.Register<ILocalizationManagerInternal, LocalizationManager>();
IoCManager.Register<ILogManager, LogManager>();
IoCManager.Register<IPauseManager, PauseManager>();
IoCManager.Register<IPauseManager, NetworkedMapManager>();
IoCManager.Register<IModLoader, ModLoader>();
IoCManager.Register<IModLoaderInternal, ModLoader>();
IoCManager.Register<INetManager, NetManager>();

View File

@@ -1,15 +1,20 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.Map;
namespace Robust.Shared.Timing
{
[Obsolete("Use the same functions on IMapManager.")]
public interface IPauseManager
{
void SetMapPaused(MapId mapId, bool paused);
void DoMapInitialize(MapId mapId);
[Obsolete("This function does nothing, per-grid pausing isn't a thing anymore.")]
void DoGridMapInitialize(GridId gridId);
[Obsolete("This function does nothing, per-grid pausing isn't a thing anymore.")]
void DoGridMapInitialize(IMapGrid grid);
void AddUninitializedMap(MapId mapId);

View File

@@ -207,7 +207,7 @@ namespace Robust.UnitTesting.Server
container.Register<IIslandManager, IslandManager>();
container.Register<IManifoldManager, CollisionManager>();
container.Register<IMapManagerInternal, MapManager>();
container.RegisterInstance<IPauseManager>(new Mock<IPauseManager>().Object); // TODO: get timing working similar to RobustIntegrationTest
container.Register<IPauseManager, MapManager>();
container.Register<IPhysicsManager, PhysicsManager>();
_diFactory?.Invoke(container);

View File

@@ -0,0 +1,98 @@
using System;
using Linguini.Syntax.Parser.Error;
using NUnit.Framework;
using Robust.Shared.Localization;
namespace Robust.UnitTesting.Shared.Localization;
[TestFixture]
[Parallelizable]
public sealed class TestFormatErrors
{
private const string Res1 = "err1 = $user)";
private const string Res2 = "a = b {{\r\n err = x";
private const string Res2Lf = "a = b {{\n err = x";
#region ExpectedCrlf
private const string Expect1Wide1Crlf = "\r\n 99 |err1 = $user)\r\n ^ Unbalanced closing brace";
private const string Expect2Wide1Crlf = "\r\n 99 |err1 = $user)\r\n ^ Unbalanced closing brace";
private const string Expect1Wide4Crlf =
"\r\n 99 |err1 = $user)\r\n ^^^^ Expected a message field for \"x\"";
private const string Expect2Wide4Crlf = "\r\n 99 |err1 = $user)\r\n ^^^^ Expected a message field for \"x\"";
private const string ExpectMulti1Wide1Crlf =
"\r\n 99 |a = b {{\r\n 100 | err = x\r\n ^ Unbalanced closing brace";
private const string ExpectMulti1Wide3Crlf =
"\r\n 99 |a = b {{\r\n 100 | err = x\r\n ^^^ Expected a message field for \"x\"";
#endregion
[Test]
[TestCase(Expect1Wide1Crlf, Res1, 7)]
[TestCase(Expect2Wide1Crlf, Res1, 0)]
[TestCase(Expect1Wide4Crlf, Res1, 8, 12)]
[TestCase(Expect2Wide4Crlf, Res1, 0, 4)]
[TestCase(ExpectMulti1Wide1Crlf, Res2, 12)]
[TestCase(ExpectMulti1Wide3Crlf, Res2, 12, 15)]
[TestCase(ExpectMulti1Wide1Crlf, Res2Lf, 11)]
[TestCase(ExpectMulti1Wide3Crlf, Res2Lf, 11, 14)]
public void TestSingleLineTestCrlf(string expected, string resource, int start, int? end = null)
{
var err = ParseError.UnbalancedClosingBrace(start, 99);
if (end != null)
{
err = ParseError.ExpectedMessageField("x".AsMemory(), start, end.Value, 99);
}
err.Slice = new Range(0, resource.Length);
var actual = err.FormatCompileErrors(resource.AsMemory(), "\r\n");
Assert.That(actual, Is.EqualTo(expected));
}
#region ExpectedLf
private const string Expect1Wide1Lf = "\n 99 |err1 = $user)\n ^ Unbalanced closing brace";
private const string Expect2Wide1Lf = "\n 99 |err1 = $user)\n ^ Unbalanced closing brace";
private const string Expect1Wide4Lf =
"\n 99 |err1 = $user)\n ^^^^ Expected a message field for \"x\"";
private const string Expect2Wide4Lf = "\n 99 |err1 = $user)\n ^^^^ Expected a message field for \"x\"";
private const string ExpectMulti1Wide1Lf = "\n 99 |a = b {{\n 100 | err = x\n ^ Unbalanced closing brace";
private const string ExpectMulti1Wide3Lf =
"\n 99 |a = b {{\n 100 | err = x\n ^^^ Expected a message field for \"x\"";
#endregion
[Test]
[TestCase(Expect1Wide1Lf, Res1, 7)]
[TestCase(Expect2Wide1Lf, Res1, 0)]
[TestCase(Expect1Wide4Lf, Res1, 8, 12)]
[TestCase(Expect2Wide4Lf, Res1, 0, 4)]
[TestCase(ExpectMulti1Wide1Lf, Res2, 12)]
[TestCase(ExpectMulti1Wide3Lf, Res2, 12, 15)]
[TestCase(ExpectMulti1Wide1Lf, Res2Lf, 11)]
[TestCase(ExpectMulti1Wide3Lf, Res2Lf, 11, 14)]
public void TestSingleLineTestLf(string expected, string resource, int start, int? end = null)
{
var err = ParseError.UnbalancedClosingBrace(start, 99);
if (end != null)
{
err = ParseError.ExpectedMessageField("x".AsMemory(), start, end.Value, 99);
}
err.Slice = new Range(0, resource.Length);
var actual = err.FormatCompileErrors(resource.AsMemory(), "\n");
Assert.That(actual, Is.EqualTo(expected));
}
}

View File

@@ -8,44 +8,37 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using MapGrid = Robust.Shared.Map.MapGrid;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Shared.Map
{
[TestFixture, TestOf(typeof(MapGrid))]
sealed class MapGrid_Tests : RobustUnitTest
sealed class MapGrid_Tests
{
protected override void OverrideIoC()
private static ISimulation SimulationFactory()
{
base.OverrideIoC();
var sim = RobustServerSimulation
.NewSimulation()
.InitializeInstance();
var mock = new Mock<IEntitySystemManager>();
var broady = new BroadPhaseSystem();
var physics = new PhysicsSystem();
mock.Setup(m => m.GetEntitySystem<SharedBroadphaseSystem>()).Returns(broady);
mock.Setup(m => m.GetEntitySystem<SharedPhysicsSystem>()).Returns(physics);
IoCManager.RegisterInstance<IEntitySystemManager>(mock.Object, true);
}
[OneTimeSetUp]
public void Setup()
{
IoCManager.Resolve<IComponentFactory>().GenerateNetIds();
return sim;
}
[Test]
public void GetTileRefCoords()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
var result = grid.GetTileRef(new Vector2i(-9, -1));
Assert.That(grid.ChunkCount, Is.EqualTo(1));
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
Assert.That(result, Is.EqualTo(new TileRef(new MapId(5), new GridId(1), new Vector2i(-9,-1), new Tile(1, 2))));
Assert.That(result, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9,-1), new Tile(1, 2))));
}
/// <summary>
@@ -54,7 +47,11 @@ namespace Robust.UnitTesting.Shared.Map
[Test]
public void BoundsExpansion()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.WorldPosition = new Vector2(3, 5);
grid.SetTile(new Vector2i(-1, -2), new Tile(1));
grid.SetTile(new Vector2i(1, 2), new Tile(1));
@@ -74,7 +71,11 @@ namespace Robust.UnitTesting.Shared.Map
[Test]
public void BoundsContract()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.WorldPosition = new Vector2(3, 5);
grid.SetTile(new Vector2i(-1, -2), new Tile(1));
grid.SetTile(new Vector2i(1, 2), new Tile(1));
@@ -93,7 +94,10 @@ namespace Robust.UnitTesting.Shared.Map
[Test]
public void GridTileToChunkIndices()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
var result = grid.GridTileToChunkIndices(new Vector2i(-9, -1));
@@ -106,7 +110,10 @@ namespace Robust.UnitTesting.Shared.Map
[Test]
public void ToLocalCentered()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
var result = grid.GridTileToLocal(new Vector2i(0, 0)).Position;
@@ -117,7 +124,10 @@ namespace Robust.UnitTesting.Shared.Map
[Test]
public void TryGetTileRefNoTile()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
var foundTile = grid.TryGetTileRef(new Vector2i(-9, -1), out var tileRef);
@@ -129,7 +139,11 @@ namespace Robust.UnitTesting.Shared.Map
[Test]
public void TryGetTileRefTileExists()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.SetTile(new Vector2i(-9, -1), new Tile(1, 2));
var foundTile = grid.TryGetTileRef(new Vector2i(-9, -1), out var tileRef);
@@ -137,13 +151,17 @@ namespace Robust.UnitTesting.Shared.Map
Assert.That(foundTile, Is.True);
Assert.That(grid.ChunkCount, Is.EqualTo(1));
Assert.That(grid.GetMapChunks().Keys.ToList()[0], Is.EqualTo(new Vector2i(-2, -1)));
Assert.That(tileRef, Is.EqualTo(new TileRef(new MapId(5), new GridId(1), new Vector2i(-9, -1), new Tile(1, 2))));
Assert.That(tileRef, Is.EqualTo(new TileRef(mapId, grid.Index, new Vector2i(-9, -1), new Tile(1, 2))));
}
[Test]
public void PointCollidesWithGrid()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.SetTile(new Vector2i(19, 23), new Tile(1));
var result = grid.CollidesWithGrid(new Vector2i(19, 23));
@@ -154,31 +172,16 @@ namespace Robust.UnitTesting.Shared.Map
[Test]
public void PointNotCollideWithGrid()
{
var grid = MapGridFactory(new GridId(1));
var sim = SimulationFactory();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
var grid = (IMapGridInternal)mapMan.CreateGrid(mapId, null, 8);
grid.SetTile(new Vector2i(19, 23), new Tile(1));
var result = grid.CollidesWithGrid(new Vector2i(19, 24));
Assert.That(result, Is.False);
}
private static IMapGridInternal MapGridFactory(GridId id)
{
var mapId = new MapId(5);
var mapMan = IoCManager.Resolve<IMapManager>();
if(mapMan.MapExists(mapId))
mapMan.DeleteMap(mapId);
mapMan.CreateMap(mapId);
if(mapMan.GridExists(id))
mapMan.DeleteGrid(id);
var newGrid = mapMan.CreateGrid(mapId, id, 8);
newGrid.WorldPosition = new Vector2(3, 5);
return (IMapGridInternal)newGrid;
}
}
}

View File

@@ -0,0 +1,272 @@
using System;
using System.Linq;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.Map;
[TestFixture]
internal sealed class MapPauseTests
{
private static ISimulation SimulationFactory()
{
var sim = RobustServerSimulation
.NewSimulation()
.RegisterComponents(factory => factory.RegisterClass<IgnorePauseComponent>())
.InitializeInstance();
return sim;
}
/// <summary>
/// When an entity is on a paused map, it does not get returned by an EntityQuery.
/// </summary>
[Test]
public void Paused_NotIncluded_NotInQuery()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, true);
entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var query = entMan.EntityQuery<TransformComponent>(false).ToList();
// 0 ents, map and the spawned one are not returned
Assert.That(query.Count, Is.EqualTo(0));
}
/// <summary>
/// When an entity is on an unpaused map, it is returned by an EntityQuery.
/// </summary>
[Test]
public void UnPaused_NotIncluded_InQuery()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, false);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var query = entMan.EntityQuery<TransformComponent>(false).ToList();
// 2 ents, map and the spawned one
Assert.That(query.Count, Is.EqualTo(2));
}
/// <summary>
/// When an entity is on a paused map, it is get returned by an EntityQuery when included.
/// </summary>
[Test]
public void Paused_Included_InQuery()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, true);
entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var query = entMan.EntityQuery<TransformComponent>(true).ToList();
// 2 ents, map and the spawned one are returned because includePaused
Assert.That(query.Count, Is.EqualTo(2));
}
/// <summary>
/// A new child entity added to a paused map will be created paused.
/// </summary>
[Test]
public void Paused_AddEntity_IsPaused()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, true);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.True);
}
/// <summary>
/// A new child entity added to an unpaused map will be created unpaused.
/// </summary>
[Test]
public void UnPaused_AddEntity_IsNotPaused()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, false);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.False);
}
/// <summary>
/// When a new grid is added to a paused map, the grid becomes paused.
/// </summary>
[Test]
public void Paused_AddGrid_GridPaused()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, true);
// act
var newGrid = mapMan.CreateGrid(mapId);
// assert
Assert.That(mapMan.IsMapPaused(mapId), Is.True);
Assert.That(mapMan.IsGridPaused(newGrid.GridEntityId), Is.True);
var metaData = entMan.GetComponent<MetaDataComponent>(newGrid.GridEntityId);
Assert.That(metaData.EntityPaused, Is.True);
}
/// <summary>
/// When a tree of entities are teleported from a paused map
/// to an unpaused map, all of the entities in the tree are unpaused.
/// </summary>
[Test]
public void Paused_TeleportBetweenMaps_Unpaused()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var map1 = mapMan.CreateMap();
mapMan.SetMapPaused(map1, true);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map1));
var xform = entMan.GetComponent<TransformComponent>(newEnt);
var map2 = mapMan.CreateMap();
mapMan.SetMapPaused(map2, false);
// Act
xform.ParentUid = mapMan.GetMapEntityId(map2);
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.False);
}
/// <summary>
/// When a tree of entities are teleported from an unpaused map
/// to a paused map, all of the entitites in the tree are paused.
/// </summary>
[Test]
public void Unpaused_TeleportBetweenMaps_IsPaused()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
// arrange
var map1 = mapMan.CreateMap();
mapMan.SetMapPaused(map1, false);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map1));
var xform = entMan.GetComponent<TransformComponent>(newEnt);
var map2 = mapMan.CreateMap();
mapMan.SetMapPaused(map2, true);
// Act
xform.ParentUid = mapMan.GetMapEntityId(map2);
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.True);
}
/// <summary>
/// When a paused map is unpaused, all of the entities on the map are unpaused.
/// </summary>
[Test]
public void Paused_UnpauseMap_UnpausedEntities()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, true);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
mapMan.SetMapPaused(mapId, false);
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.False);
}
/// <summary>
/// When an unpaused map is paused, all of the entities on the map are paused.
/// </summary>
[Test]
public void Unpaused_PauseMap_PausedEntities()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, false);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
mapMan.SetMapPaused(mapId, true);
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.True);
}
/// <summary>
/// An entity that has set IgnorePause will not be paused when the map is paused.
/// </summary>
[Test]
public void IgnorePause_PauseMap_NotPaused()
{
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var mapMan = sim.Resolve<IMapManager>();
var mapId = mapMan.CreateMap();
mapMan.SetMapPaused(mapId, false);
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
entMan.AddComponent<IgnorePauseComponent>(newEnt);
mapMan.SetMapPaused(mapId, true);
var metaData = entMan.GetComponent<MetaDataComponent>(newEnt);
Assert.That(metaData.EntityPaused, Is.False);
}
}