using System.Linq;
using Content.Shared.Decals;
using Microsoft.Extensions.ObjectPool;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Shared.Chunking;
///
/// This system just exists to provide some utility functions for other systems that chunk data that needs to be
/// sent to players. In particular, see .
///
public sealed class ChunkingSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery _xformQuery;
private Box2 _baseViewBounds;
public override void Initialize()
{
base.Initialize();
_xformQuery = GetEntityQuery();
_configurationManager.OnValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged, true);
}
public override void Shutdown()
{
base.Shutdown();
_configurationManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged);
}
private void OnPvsRangeChanged(float value)
{
_baseViewBounds = Box2.UnitCentered.Scale(value);
}
public Dictionary> GetChunksForSession(
ICommonSession session,
int chunkSize,
ObjectPool> indexPool,
ObjectPool>> viewerPool,
float? viewEnlargement = null)
{
var chunks = viewerPool.Get();
DebugTools.Assert(chunks.Count == 0);
if (session.Status != SessionStatus.InGame || session.AttachedEntity is not {} player)
return chunks;
var enlargement = viewEnlargement ?? chunkSize;
AddViewerChunks(player, chunks, indexPool, chunkSize, enlargement);
foreach (var uid in session.ViewSubscriptions)
{
AddViewerChunks(uid, chunks, indexPool, chunkSize, enlargement);
}
return chunks;
}
private void AddViewerChunks(EntityUid viewer,
Dictionary> chunks,
ObjectPool> indexPool,
int chunkSize,
float viewEnlargement)
{
if (!_xformQuery.TryGetComponent(viewer, out var xform))
return;
var pos = _transform.GetWorldPosition(xform);
var bounds = _baseViewBounds.Translated(pos).Enlarged(viewEnlargement);
var state = new QueryState(chunks, indexPool, chunkSize, bounds, _transform, EntityManager);
_mapManager.FindGridsIntersecting(xform.MapID, bounds, ref state, AddGridChunks, true);
}
private static bool AddGridChunks(
EntityUid uid,
MapGridComponent grid,
ref QueryState state)
{
var netGrid = state.EntityManager.GetNetEntity(uid);
if (!state.Chunks.TryGetValue(netGrid, out var set))
{
state.Chunks[netGrid] = set = state.Pool.Get();
DebugTools.Assert(set.Count == 0);
}
var aabb = state.Transform.GetInvWorldMatrix(uid).TransformBox(state.Bounds);
var enumerator = new ChunkIndicesEnumerator(aabb, state.ChunkSize);
while (enumerator.MoveNext(out var indices))
{
set.Add(indices.Value);
}
return true;
}
private readonly struct QueryState
{
public readonly Dictionary> Chunks;
public readonly ObjectPool> Pool;
public readonly int ChunkSize;
public readonly Box2 Bounds;
public readonly SharedTransformSystem Transform;
public readonly EntityManager EntityManager;
public QueryState(
Dictionary> chunks,
ObjectPool> pool,
int chunkSize,
Box2 bounds,
SharedTransformSystem transform,
EntityManager entityManager)
{
Chunks = chunks;
Pool = pool;
ChunkSize = chunkSize;
Bounds = bounds;
Transform = transform;
EntityManager = entityManager;
}
}
}