Move PVS to a system (#2189)

This commit is contained in:
metalgearsloth
2021-11-02 16:19:11 +11:00
committed by GitHub
parent c321400347
commit 3e344d00a8
2 changed files with 91 additions and 112 deletions

View File

@@ -1,10 +1,13 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
@@ -15,7 +18,7 @@ using Robust.Shared.Utility;
namespace Robust.Server.GameStates
{
internal sealed class EntityViewCulling
internal sealed class PVSSystem : EntitySystem
{
private const int ViewSetCapacity = 256; // starting number of entities that are in view
private const int PlayerSetSize = 64; // Starting number of players
@@ -23,12 +26,15 @@ namespace Robust.Server.GameStates
private static readonly Vector2 Vector2NaN = new(float.NaN, float.NaN);
private readonly IServerEntityManager _entMan;
private readonly IMapManager _mapManager;
private readonly IEntityLookup _lookup;
[Shared.IoC.Dependency] private readonly IServerEntityManager _entMan = default!;
[Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
[Shared.IoC.Dependency] private readonly IEntityLookup _lookup = default!;
[Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
[Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
[Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
private readonly Dictionary<ICommonSession, HashSet<EntityUid>> _playerVisibleSets = new(PlayerSetSize);
internal readonly Dictionary<ICommonSession, Dictionary<IMapChunkInternal, GameTick>> PlayerChunks = new(PlayerSetSize);
private readonly Dictionary<ICommonSession, Dictionary<IMapChunkInternal, GameTick>> _playerChunks = new(PlayerSetSize);
private readonly Dictionary<ICommonSession, ChunkStreamingData>
_streamingChunks = new();
@@ -81,25 +87,11 @@ namespace Robust.Server.GameStates
}
}
public EntityViewCulling(IServerEntityManager entMan, IMapManager mapManager, IEntityLookup lookup)
{
_entMan = entMan;
_mapManager = mapManager;
_lookup = lookup;
}
public void SetTransformNetId(ushort value)
{
_transformNetId = value;
}
// Not thread safe
public void EntityDeleted(EntityUid e)
{
// Not aware of prediction
_deletionHistory.Add((_entMan.CurrentTick, e));
}
// Not thread safe
public void CullDeletionHistory(GameTick oldestAck)
{
@@ -117,54 +109,86 @@ namespace Robust.Server.GameStates
return list;
}
// Not thread safe
public override void Initialize()
{
base.Initialize();
EntityManager.EntityDeleted += OnEntityDelete;
_playerManager.PlayerStatusChanged += OnPlayerStatusChange;
_mapManager.OnGridRemoved += OnGridRemoved;
// If you want to make this modifiable at runtime you need to subscribe to tickrate updates and streaming updates
// plus invalidate any chunks currently being streamed as well.
StreamingTilesPerTick = (int) (_configManager.GetCVar(CVars.StreamedTilesPerSecond) / _gameTiming.TickRate);
_configManager.OnValueChanged(CVars.StreamedTileRange, SetStreamRange, true);
}
private void SetStreamRange(float value)
{
StreamRange = value;
}
public override void Shutdown()
{
base.Shutdown();
EntityManager.EntityDeleted -= OnEntityDelete;
_playerManager.PlayerStatusChanged -= OnPlayerStatusChange;
_mapManager.OnGridRemoved -= OnGridRemoved;
_configManager.UnsubValueChanged(CVars.StreamedTileRange, SetStreamRange);
}
private void OnGridRemoved(MapId mapid, GridId gridid)
{
// Remove any sort of tracking for when a chunk was sent.
foreach (var (_, chunks) in _playerChunks)
{
foreach (var (chunk, _) in chunks.ToArray())
{
if (chunk is not MapChunk mapChunk ||
mapChunk.GridId == gridid)
{
chunks.Remove(chunk);
}
}
}
}
#region Player Status
private void OnPlayerStatusChange(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus == SessionStatus.InGame)
{
AddPlayer(e.Session);
}
else if (e.OldStatus == SessionStatus.InGame)
{
RemovePlayer(e.Session);
}
}
public void AddPlayer(ICommonSession session)
{
var visSet = _visSetPool.Get();
_playerVisibleSets.Add(session, visSet);
PlayerChunks.Add(session, new Dictionary<IMapChunkInternal, GameTick>(32));
_playerChunks.Add(session, new Dictionary<IMapChunkInternal, GameTick>(32));
_streamingChunks.Add(session, new ChunkStreamingData());
}
// Not thread safe
public void RemovePlayer(ICommonSession session)
{
_playerVisibleSets.Remove(session);
PlayerChunks.Remove(session);
_playerChunks.Remove(session);
_playerLastFullMap.Remove(session, out _);
_streamingChunks.Remove(session);
}
// thread safe
public bool IsPointVisible(ICommonSession session, in MapCoordinates position)
#endregion
private void OnEntityDelete(object? sender, EntityUid e)
{
var viewables = GetSessionViewers(session);
bool CheckInView(MapCoordinates mapCoordinates, HashSet<EntityUid> entityUids)
{
foreach (var euid in entityUids)
{
var (viewBox, mapId) = CalcViewBounds(in euid);
if (mapId != mapCoordinates.MapId)
continue;
if (!CullingEnabled)
return true;
if (viewBox.Contains(mapCoordinates.Position))
return true;
}
return false;
}
bool result = CheckInView(position, viewables);
viewables.Clear();
_viewerEntsPool.Return(viewables);
return result;
// Not aware of prediction
_deletionHistory.Add((EntityManager.CurrentTick, e));
}
private HashSet<EntityUid> GetSessionViewers(ICommonSession session)
@@ -214,7 +238,7 @@ namespace Robust.Server.GameStates
if (session.AttachedEntityUid is not null)
{
var viewers = GetSessionViewers(session);
var chunksSeen = PlayerChunks[session];
var chunksSeen = _playerChunks[session];
foreach (var eyeEuid in viewers)
{
@@ -449,10 +473,10 @@ namespace Robust.Server.GameStates
//TODO: HACK: somehow an entity left the view, transform does not exist (deleted?), but was not in the
// deleted list. This seems to happen with the map entity on round restart.
if (!_entMan.EntityExists(entityUid))
if (!EntityManager.EntityExists(entityUid))
continue;
var xform = _entMan.GetComponent<ITransformComponent>(entityUid);
var xform = EntityManager.GetComponent<TransformComponent>(entityUid);
// Anchored entities don't ever leave
if (xform.Anchored) continue;
@@ -477,14 +501,14 @@ namespace Robust.Server.GameStates
{
// skip sending anchored entities (walls)
DebugTools.Assert(!_entMan.GetComponent<ITransformComponent>(entityUid).Anchored);
DebugTools.Assert(!EntityManager.GetComponent<TransformComponent>(entityUid).Anchored);
if (previousSet.Contains(entityUid))
{
//Still Visible
// Nothing new to send
if(_entMan.GetEntity(entityUid).LastModifiedTick < fromTick)
if (EntityManager.GetEntity(entityUid).LastModifiedTick < fromTick)
continue;
// only send new changes
@@ -537,13 +561,13 @@ namespace Robust.Server.GameStates
return true;
// if we are invisible, we are not going into the visSet, so don't worry about parents, and children are not going in
if (_entMan.TryGetComponent<VisibilityComponent>(uid, out var visComp))
if (EntityManager.TryGetComponent<VisibilityComponent>(uid, out var visComp))
{
if ((visMask & visComp.Layer) == 0)
return false;
}
var xform = _entMan.GetComponent<TransformComponent>(uid);
var xform = EntityManager.GetComponent<TransformComponent>(uid);
var parentUid = xform.ParentUid;
@@ -606,7 +630,7 @@ namespace Robust.Server.GameStates
// Read Safe
private (Box2 view, MapId mapId) CalcViewBounds(in EntityUid euid)
{
var xform = _entMan.GetComponent<ITransformComponent>(euid);
var xform = _entMan.GetComponent<TransformComponent>(euid);
var view = Box2.UnitCentered.Scale(ViewSize).Translated(xform.WorldPosition);
var map = xform.MapID;

View File

@@ -13,7 +13,6 @@ 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.Players;
@@ -30,10 +29,9 @@ namespace Robust.Server.GameStates
private readonly Dictionary<long, GameTick> _ackedStates = new();
private GameTick _lastOldestAck = GameTick.Zero;
private EntityViewCulling _entityView = null!;
private PVSSystem _pvs = default!;
[Dependency] private readonly IServerEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IServerNetManager _networkManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -58,13 +56,12 @@ namespace Robust.Server.GameStates
public void SetTransformNetId(ushort netId)
{
_entityView.SetTransformNetId(netId);
_pvs.SetTransformNetId(netId);
}
public void PostInject()
{
_logger = Logger.GetSawmill("PVS");
_entityView = new EntityViewCulling(_entityManager, _mapManager, _lookup);
}
/// <inheritdoc />
@@ -76,49 +73,7 @@ namespace Robust.Server.GameStates
_networkManager.Connected += HandleClientConnected;
_networkManager.Disconnect += HandleClientDisconnect;
_playerManager.PlayerStatusChanged += HandlePlayerStatusChanged;
_entityManager.EntityDeleted += HandleEntityDeleted;
_mapManager.OnGridRemoved += HandleGridRemove;
// If you want to make this modifiable at runtime you need to subscribe to tickrate updates and streaming updates
// plus invalidate any chunks currently being streamed as well.
_entityView.StreamingTilesPerTick = (int) (_configurationManager.GetCVar(CVars.StreamedTilesPerSecond) / _gameTiming.TickRate);
_configurationManager.OnValueChanged(CVars.StreamedTileRange, value => _entityView.StreamRange = value, true);
}
private void HandleGridRemove(MapId mapid, GridId gridid)
{
// Remove any sort of tracking for when a chunk was sent.
foreach (var (_, chunks) in _entityView.PlayerChunks)
{
foreach (var (chunk, _) in chunks.ToArray())
{
if (chunk is not MapChunk mapChunk ||
mapChunk.GridId == gridid)
{
chunks.Remove(chunk);
}
}
}
}
private void HandleEntityDeleted(object? sender, EntityUid e)
{
_entityView.EntityDeleted(e);
}
private void HandlePlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus == SessionStatus.InGame)
{
_entityView.AddPlayer(e.Session);
}
else if(e.OldStatus == SessionStatus.InGame)
{
_entityView.RemovePlayer(e.Session);
}
_pvs = EntitySystem.Get<PVSSystem>();
}
private void HandleClientConnected(object? sender, NetChannelArgs e)
@@ -166,14 +121,14 @@ namespace Robust.Server.GameStates
{
DebugTools.Assert(_networkManager.IsServer);
_entityView.ViewSize = PvsRange * 2;
_entityView.CullingEnabled = PvsEnabled;
_pvs.ViewSize = PvsRange * 2;
_pvs.CullingEnabled = PvsEnabled;
if (!_networkManager.IsConnected)
{
// Prevent deletions piling up if we have no clients.
_entityManager.CullDeletionHistory(GameTick.MaxValue);
_entityView.CullDeletionHistory(GameTick.MaxValue);
_pvs.CullDeletionHistory(GameTick.MaxValue);
_mapManager.CullDeletionHistory(GameTick.MaxValue);
return;
}
@@ -202,7 +157,7 @@ namespace Robust.Server.GameStates
DebugTools.Assert("Why does this channel not have an entry?");
}
var (entStates, deletions) = _entityView.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
var (entStates, deletions) = _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
var playerStates = _playerManager.GetPlayerStates(lastAck);
var mapData = _mapManager.GetStateData(lastAck);
@@ -254,7 +209,7 @@ namespace Robust.Server.GameStates
{
_lastOldestAck = oldestAck;
_entityManager.CullDeletionHistory(oldestAck);
_entityView.CullDeletionHistory(oldestAck);
_pvs.CullDeletionHistory(oldestAck);
_mapManager.CullDeletionHistory(oldestAck);
}
}