CatStation/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs
Naxel ca95da6daa
Add parallelization to RadiationSystem.GridCast (#39173)
* add parallelization to RadiationSystem.GridCast

* change the preparation of sources receivers data

* failed integrationtest fix

* Update Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs

Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>

* Change namespace to file-scoped

* Method signature changed

* null check changed

* null check changed. exactly

* Use TryComp instead of Resolve in UpdateSource

* replace TryComp with xform in UpdateSource

* replace DynamicTree with B2DynamicTree

* return comment

* Remove magic number

* Optimize radiation memory usage

* replace ThreadLocal to ArrayPool

* fix "false sharing" issue

* Revert "fix "false sharing" issue"

This reverts commit c0729f024f8e2df9d15f1cc9cc9bc1be4d4be312.

* cleanup

* inject entquery

---------

Co-authored-by: xNaxelx <46362288+xNaxelx@users.noreply.github.com>
Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
2026-04-05 04:39:00 +00:00

278 lines
9.5 KiB
C#

using System.Buffers;
using System.Collections.Concurrent;
using System.Linq;
using System.Numerics;
using Content.Server.Radiation.Components;
using Content.Server.Radiation.Events;
using Content.Shared.Radiation.Components;
using Content.Shared.Radiation.Systems;
using JetBrains.Annotations;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Threading;
using Robust.Shared.Utility;
namespace Content.Server.Radiation.Systems;
// main algorithm that fire radiation rays to target
public partial class RadiationSystem
{
private readonly record struct SourceData(
float Intensity,
float Slope,
float MaxRange,
Entity<RadiationSourceComponent, TransformComponent> Entity,
Vector2 WorldPosition)
{
public EntityUid Uid => Entity.Owner;
public TransformComponent Transform => Entity.Comp2;
}
private void UpdateGridcast()
{
var debug = _debugSessions.Count > 0;
var stopwatch = new Robust.Shared.Timing.Stopwatch();
stopwatch.Start();
var sourcesCount = _sourceDataMap.Count;
if (_activeReceivers.Count == 0 || sourcesCount == 0)
{
RaiseLocalEvent(new RadiationSystemUpdatedEvent());
return;
}
var results = new float[_activeReceivers.Count];
var debugRays = debug ? new ConcurrentBag<DebugRadiationRay>() : null;
var job = new RadiationJob
{
System = this,
SourceTree = _sourceTree,
SourceDataMap = _sourceDataMap,
Destinations = _activeReceivers,
Results = results,
DebugRays = debugRays,
Debug = debug
};
_parallel.ProcessNow(job, _activeReceivers.Count);
for (var i = 0; i < _activeReceivers.Count; i++)
{
var uid = _activeReceivers[i];
var rads = results[i];
if (_receiverQuery.TryComp(uid, out var receiver))
{
receiver.CurrentRadiation = rads;
if (rads > 0)
IrradiateEntity(uid, rads, GridcastUpdateRate);
}
}
if (debugRays is not null)
UpdateGridcastDebugOverlay(stopwatch.Elapsed.TotalMilliseconds, sourcesCount, _activeReceivers.Count, debugRays.ToList());
RaiseLocalEvent(new RadiationSystemUpdatedEvent());
}
private RadiationRay? Irradiate(SourceData source,
EntityUid destUid,
TransformComponent destTrs,
Vector2 destWorld,
bool saveVisitedTiles,
List<Entity<MapGridComponent>> gridList)
{
var mapId = destTrs.MapID;
var dist = (destWorld - source.WorldPosition).Length();
var rads = source.Intensity - source.Slope * dist;
if (rads < MinIntensity)
return null;
var ray = new RadiationRay(mapId, source.Entity, source.WorldPosition, destUid, destWorld, rads);
var box = Box2.FromTwoPoints(source.WorldPosition, destWorld);
gridList.Clear();
_mapManager.FindGridsIntersecting(mapId, box, ref gridList, true);
foreach (var grid in gridList)
{
ray = Gridcast((grid.Owner, grid.Comp, Transform(grid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
if (ray.Rads <= 0)
return ray;
}
return ray;
}
private RadiationRay Gridcast(
Entity<MapGridComponent, TransformComponent> grid,
ref RadiationRay ray,
bool saveVisitedTiles,
TransformComponent sourceTrs,
TransformComponent destTrs)
{
var blockers = saveVisitedTiles ? new List<(Vector2i, float)>() : null;
var gridUid = grid.Owner;
if (!_resistanceQuery.TryGetComponent(gridUid, out var resistance))
return ray;
var resistanceMap = resistance.ResistancePerTile;
Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner
? sourceTrs.LocalPosition
: Vector2.Transform(ray.Source, grid.Comp2.InvLocalMatrix);
Vector2 dstLocal = destTrs.ParentUid == grid.Owner
? destTrs.LocalPosition
: Vector2.Transform(ray.Destination, grid.Comp2.InvLocalMatrix);
Vector2i sourceGrid = new((int)Math.Floor(srcLocal.X / grid.Comp1.TileSize), (int)Math.Floor(srcLocal.Y / grid.Comp1.TileSize));
Vector2i destGrid = new((int)Math.Floor(dstLocal.X / grid.Comp1.TileSize), (int)Math.Floor(dstLocal.Y / grid.Comp1.TileSize));
var line = new GridLineEnumerator(sourceGrid, destGrid);
while (line.MoveNext())
{
var point = line.Current;
if (!resistanceMap.TryGetValue(point, out var resData))
continue;
ray.Rads -= resData;
if (saveVisitedTiles && blockers is not null)
blockers.Add((point, ray.Rads));
if (ray.Rads <= MinIntensity)
{
ray.Rads = 0;
break;
}
}
if (blockers is null || blockers.Count == 0)
return ray;
ray.Blockers ??= new();
ray.Blockers.Add(GetNetEntity(gridUid), blockers);
return ray;
}
private float GetAdjustedRadiationIntensity(EntityUid uid, float rads)
{
var child = uid;
var xform = Transform(uid);
var parent = xform.ParentUid;
while (parent.IsValid())
{
var parentXform = Transform(parent);
var childMeta = MetaData(child);
if ((childMeta.Flags & MetaDataFlags.InContainer) != MetaDataFlags.InContainer)
{
child = parent;
parent = parentXform.ParentUid;
continue;
}
if (_blockerQuery.TryComp(xform.ParentUid, out var blocker))
{
rads -= blocker.RadResistance;
if (rads < 0)
return 0;
}
child = parent;
parent = parentXform.ParentUid;
}
return rads;
}
[UsedImplicitly]
private readonly record struct RadiationJob : IParallelRobustJob
{
public int BatchSize => 5;
public required RadiationSystem System { get; init; }
public required B2DynamicTree<EntityUid> SourceTree { get; init; }
public required Dictionary<EntityUid, SourceData> SourceDataMap { get; init; }
public required List<EntityUid> Destinations { get; init; }
public required float[] Results { get; init; }
public required ConcurrentBag<DebugRadiationRay>? DebugRays { get; init; }
public required bool Debug { get; init; }
public void Execute(int index)
{
var destUid = Destinations[index];
if (System.Deleted(destUid) || !System.TryComp(destUid, out TransformComponent? destTrs))
{
Results[index] = 0;
return;
}
var nearbySourcesArray = ArrayPool<EntityUid>.Shared.Rent(256);
var gridList = new List<Entity<MapGridComponent>>(8);
try
{
var destWorld = System._transform.GetWorldPosition(destTrs);
var rads = 0f;
var destMapId = destTrs.MapID;
var queryAabb = new Box2(destWorld, destWorld);
var state = (nearbySourcesArray, 0, SourceTree);
SourceTree.Query(ref state,
static (ref (EntityUid[] arr, int count, B2DynamicTree<EntityUid> tree) tuple,
DynamicTree.Proxy proxy) =>
{
if (tuple.count >= tuple.arr.Length)
return true;
var uid = tuple.tree.GetUserData(proxy);
tuple.arr[tuple.count++] = uid;
return true;
},
in queryAabb);
var nearbySourcesSpan = nearbySourcesArray.AsSpan(0, state.Item2);
foreach (var sourceUid in nearbySourcesSpan)
{
if (!SourceDataMap.TryGetValue(sourceUid, out var source)
|| source.Transform.MapID != destMapId)
continue;
var delta = source.WorldPosition - destWorld;
if (delta.LengthSquared() > source.MaxRange * source.MaxRange)
continue;
var dist = delta.Length();
var radsAfterDist = source.Intensity - source.Slope * dist;
if (radsAfterDist < System.MinIntensity)
continue;
if (System.Irradiate(source, destUid, destTrs, destWorld, Debug, gridList) is not { } ray)
continue;
if (ray.ReachedDestination)
rads += ray.Rads;
DebugRays?.Add(new DebugRadiationRay(
ray.MapId,
System.GetNetEntity(ray.SourceUid),
ray.Source,
System.GetNetEntity(ray.DestinationUid),
ray.Destination,
ray.Rads,
ray.Blockers ?? new Dictionary<NetEntity, List<(Vector2i, float)>>())
);
}
rads = System.GetAdjustedRadiationIntensity(destUid, rads);
Results[index] = rads;
}
finally
{
ArrayPool<EntityUid>.Shared.Return(nearbySourcesArray);
}
}
}
}