Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize & clean up RadiationSystem #34459

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Content.Client/Radiation/Systems/RadiationSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public sealed class RadiationSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;

public List<RadiationRay>? Rays;
public List<DebugRadiationRay>? Rays;
public Dictionary<NetEntity, Dictionary<Vector2i, float>>? ResistanceGrids;

public override void Initialize()
Expand Down
16 changes: 10 additions & 6 deletions Content.Server/Radiation/Systems/RadiationSystem.Debug.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Content.Shared.Radiation.Events;
using Content.Shared.Radiation.Systems;
using Robust.Shared.Console;
using Robust.Shared.Debugging;
using Robust.Shared.Enums;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
Expand Down Expand Up @@ -42,12 +43,12 @@ public void ToggleDebugView(ICommonSession session)
/// </summary>
private void UpdateDebugOverlay(EntityEventArgs ev)
{
var sessions = _debugSessions.ToArray();
foreach (var session in sessions)
foreach (var session in _debugSessions)
{
if (session.Status != SessionStatus.InGame)
_debugSessions.Remove(session);
RaiseNetworkEvent(ev, session.Channel);
else
RaiseNetworkEvent(ev, session);
}
}

Expand All @@ -70,13 +71,16 @@ private void UpdateResistanceDebugOverlay()
UpdateDebugOverlay(ev);
}

private void UpdateGridcastDebugOverlay(double elapsedTime, int totalSources,
int totalReceivers, List<RadiationRay> rays)
private void UpdateGridcastDebugOverlay(
double elapsedTime,
int totalSources,
int totalReceivers,
List<DebugRadiationRay>? rays)
{
if (_debugSessions.Count == 0)
return;

var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays);
var ev = new OnRadiationOverlayUpdateEvent(elapsedTime, totalSources, totalReceivers, rays ?? new());
UpdateDebugOverlay(ev);
}
}
Expand Down
198 changes: 121 additions & 77 deletions Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
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 Content.Shared.Stacks;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
Expand All @@ -16,68 +13,86 @@ namespace Content.Server.Radiation.Systems;
// main algorithm that fire radiation rays to target
public partial class RadiationSystem
{
[Dependency] private readonly SharedStackSystem _stack = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
private List<Entity<MapGridComponent>> _grids = new();

private EntityQuery<RadiationBlockingContainerComponent> _radiationBlockingContainers;
private readonly record struct SourceData(
float Intensity,
Entity<RadiationSourceComponent, TransformComponent> Entity,
Vector2 WorldPosition)
{
public EntityUid? GridUid => Entity.Comp2.GridUid;
public float Slope => Entity.Comp1.Slope;
public TransformComponent Transform => Entity.Comp2;
}

private void UpdateGridcast()
{
// should we save debug information into rays?
// if there is no debug sessions connected - just ignore it
var saveVisitedTiles = _debugSessions.Count > 0;
var debug = _debugSessions.Count > 0;

var stopwatch = new Stopwatch();
stopwatch.Start();

_sources.Clear();
_sources.EnsureCapacity(EntityManager.Count<RadiationSourceComponent>());

var sources = EntityQueryEnumerator<RadiationSourceComponent, TransformComponent>();
var destinations = EntityQueryEnumerator<RadiationReceiverComponent, TransformComponent>();
var resistanceQuery = GetEntityQuery<RadiationGridResistanceComponent>();
var transformQuery = GetEntityQuery<TransformComponent>();
var gridQuery = GetEntityQuery<MapGridComponent>();
var stackQuery = GetEntityQuery<StackComponent>();

_radiationBlockingContainers = GetEntityQuery<RadiationBlockingContainerComponent>();

// precalculate world positions for each source
// so we won't need to calc this in cycle over and over again
var sourcesData = new ValueList<(EntityUid, RadiationSourceComponent, TransformComponent, Vector2)>();
while (sources.MoveNext(out var uid, out var source, out var sourceTrs))
while (sources.MoveNext(out var uid, out var source, out var xform))
{
if (!source.Enabled)
continue;

var worldPos = _transform.GetWorldPosition(sourceTrs, transformQuery);
var data = (uid, source, sourceTrs, worldPos);
sourcesData.Add(data);
var worldPos = _transform.GetWorldPosition(xform);

// Intensity is scaled by stack size.
var intensity = source.Intensity * _stack.GetCount(uid);

// Apply rad modifier if the source is enclosed within a radiation blocking container
// Note that this also applies to receivers, and it doesn't bother to check if the container sits between them.
// I.e., a source & receiver in the same blocking container will get double-blocked, when no blocking should be applied.
intensity = GetAdjustedRadiationIntensity(uid, intensity);

_sources.Add(new(intensity, (uid, source, xform), worldPos));
}

// trace all rays from rad source to rad receivers
var rays = new List<RadiationRay>();
var debugRays = debug ? new List<DebugRadiationRay>() : null;
var receiversTotalRads = new ValueList<(Entity<RadiationReceiverComponent>, float)>();

// TODO RADIATION Parallelize
// Would need to give receiversTotalRads a fixed size.
// Also the _grids list needs to be local to a job. (or better yet cached in SourceData)
// And I guess disable parallelization when debugging to make populating the debug List<RadiationRay> easier.
// Or just make it threadsafe?
while (destinations.MoveNext(out var destUid, out var dest, out var destTrs))
{
var destWorld = _transform.GetWorldPosition(destTrs, transformQuery);
var destWorld = _transform.GetWorldPosition(destTrs);

var rads = 0f;
foreach (var (uid, source, sourceTrs, sourceWorld) in sourcesData)
foreach (var source in _sources)
{
stackQuery.TryGetComponent(uid, out var stack);
var intensity = source.Intensity * _stack.GetCount(uid, stack);

// send ray towards destination entity
var ray = Irradiate(uid, sourceTrs, sourceWorld,
destUid, destTrs, destWorld,
intensity, source.Slope, saveVisitedTiles, resistanceQuery, transformQuery, gridQuery);
if (ray == null)
if (Irradiate(source, destUid, destTrs, destWorld, debug) is not {} ray)
continue;

// save ray for debug
rays.Add(ray);

// add rads to total rad exposure
if (ray.ReachedDestination)
rads += ray.Rads;

if (!debug)
continue;

debugRays!.Add(new DebugRadiationRay(
ray.MapId,
GetNetEntity(ray.SourceUid),
ray.Source,
GetNetEntity(ray.DestinationUid),
ray.Destination,
ray.Rads,
ray.Blockers ?? new())
);
}

// Apply modifier if the destination entity is hidden within a radiation blocking container
Expand All @@ -88,9 +103,9 @@ private void UpdateGridcast()

// update information for debug overlay
var elapsedTime = stopwatch.Elapsed.TotalMilliseconds;
var totalSources = sourcesData.Count;
var totalSources = _sources.Count;
var totalReceivers = receiversTotalRads.Count;
UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, rays);
UpdateGridcastDebugOverlay(elapsedTime, totalSources, totalReceivers, debugRays);

// send rads to each entity
foreach (var (receiver, rads) in receiversTotalRads)
Expand All @@ -108,82 +123,87 @@ private void UpdateGridcast()
RaiseLocalEvent(new RadiationSystemUpdatedEvent());
}

private RadiationRay? Irradiate(EntityUid sourceUid, TransformComponent sourceTrs, Vector2 sourceWorld,
EntityUid destUid, TransformComponent destTrs, Vector2 destWorld,
float incomingRads, float slope, bool saveVisitedTiles,
EntityQuery<RadiationGridResistanceComponent> resistanceQuery,
EntityQuery<TransformComponent> transformQuery, EntityQuery<MapGridComponent> gridQuery)
private RadiationRay? Irradiate(SourceData source,
EntityUid destUid,
TransformComponent destTrs,
Vector2 destWorld,
bool saveVisitedTiles)
{
// lets first check that source and destination on the same map
if (sourceTrs.MapID != destTrs.MapID)
if (source.Transform.MapID != destTrs.MapID)
return null;
var mapId = sourceTrs.MapID;

var mapId = destTrs.MapID;

// get direction from rad source to destination and its distance
var dir = destWorld - sourceWorld;
var dir = destWorld - source.WorldPosition;
var dist = dir.Length();

// check if receiver is too far away
if (dist > GridcastMaxDistance)
return null;

// will it even reach destination considering distance penalty
var rads = incomingRads - slope * dist;

// Apply rad modifier if the source is enclosed within a radiation blocking container
rads = GetAdjustedRadiationIntensity(sourceUid, rads);

var rads = source.Intensity - source.Slope * dist;
if (rads <= MinIntensity)
return null;
ElectroJr marked this conversation as resolved.
Show resolved Hide resolved

// create a new radiation ray from source to destination
// at first we assume that it doesn't hit any radiation blockers
// and has only distance penalty
var ray = new RadiationRay(mapId, GetNetEntity(sourceUid), sourceWorld, GetNetEntity(destUid), destWorld, rads);
var ray = new RadiationRay(mapId, source.Entity, source.WorldPosition, destUid, destWorld, rads);

// if source and destination on the same grid it's possible that
// between them can be another grid (ie. shuttle in center of donut station)
// however we can do simplification and ignore that case
if (GridcastSimplifiedSameGrid && sourceTrs.GridUid != null && sourceTrs.GridUid == destTrs.GridUid)
if (GridcastSimplifiedSameGrid && destTrs.GridUid is {} gridUid && source.GridUid == gridUid)
{
if (!gridQuery.TryGetComponent(sourceTrs.GridUid.Value, out var gridComponent))
if (!_gridQuery.TryGetComponent(gridUid, out var gridComponent))
return ray;
return Gridcast((sourceTrs.GridUid.Value, gridComponent), ray, saveVisitedTiles, resistanceQuery, sourceTrs, destTrs, transformQuery.GetComponent(sourceTrs.GridUid.Value));
return Gridcast((gridUid, gridComponent, Transform(gridUid)), ref ray, saveVisitedTiles, source.Transform, destTrs);
}

// lets check how many grids are between source and destination
// do a box intersection test between target and destination
// it's not very precise, but really cheap
var box = Box2.FromTwoPoints(sourceWorld, destWorld);
var grids = new List<Entity<MapGridComponent>>();
_mapManager.FindGridsIntersecting(mapId, box, ref grids, true);

// TODO RADIATION
// Consider caching this in SourceData?
// I.e., make the lookup for grids as large as the sources's max distance and store the result in SourceData.
// Avoids having to do a lookup per source*receiver.
var box = Box2.FromTwoPoints(source.WorldPosition, destWorld);
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, box, ref _grids, true);

// gridcast through each grid and try to hit some radiation blockers
// the ray will be updated with each grid that has some blockers
foreach (var grid in grids)
foreach (var grid in _grids)
{
ray = Gridcast(grid, ray, saveVisitedTiles, resistanceQuery, sourceTrs, destTrs, transformQuery.GetComponent(grid));
ray = Gridcast((grid.Owner, grid.Comp, Transform(grid)), ref ray, saveVisitedTiles, source.Transform, destTrs);

// looks like last grid blocked all radiation
// we can return right now
if (ray.Rads <= 0)
return ray;
}

_grids.Clear();

return ray;
}

private RadiationRay Gridcast(Entity<MapGridComponent> grid, RadiationRay ray, bool saveVisitedTiles,
EntityQuery<RadiationGridResistanceComponent> resistanceQuery,
private RadiationRay Gridcast(
Entity<MapGridComponent, TransformComponent> grid,
ref RadiationRay ray,
bool saveVisitedTiles,
TransformComponent sourceTrs,
TransformComponent destTrs,
TransformComponent gridTrs)
TransformComponent destTrs)
{
var blockers = new List<(Vector2i, float)>();
var blockers = saveVisitedTiles ? new List<(Vector2i, float)>() : null;

// if grid doesn't have resistance map just apply distance penalty
var gridUid = grid.Owner;
if (!resistanceQuery.TryGetComponent(gridUid, out var resistance))
if (!_resistanceQuery.TryGetComponent(gridUid, out var resistance))
return ray;
var resistanceMap = resistance.ResistancePerTile;

Expand All @@ -195,19 +215,19 @@ private RadiationRay Gridcast(Entity<MapGridComponent> grid, RadiationRay ray, b

Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner
? sourceTrs.LocalPosition
: Vector2.Transform(ray.Source, gridTrs.InvLocalMatrix);
: Vector2.Transform(ray.Source, grid.Comp2.InvLocalMatrix);

Vector2 dstLocal = destTrs.ParentUid == grid.Owner
? destTrs.LocalPosition
: Vector2.Transform(ray.Destination, gridTrs.InvLocalMatrix);
: Vector2.Transform(ray.Destination, grid.Comp2.InvLocalMatrix);

Vector2i sourceGrid = new(
(int) Math.Floor(srcLocal.X / grid.Comp.TileSize),
(int) Math.Floor(srcLocal.Y / grid.Comp.TileSize));
(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.Comp.TileSize),
(int) Math.Floor(dstLocal.Y / grid.Comp.TileSize));
(int) Math.Floor(dstLocal.X / grid.Comp1.TileSize),
(int) Math.Floor(dstLocal.Y / grid.Comp1.TileSize));

// iterate tiles in grid line from source to destination
var line = new GridLineEnumerator(sourceGrid, destGrid);
Expand All @@ -220,7 +240,7 @@ private RadiationRay Gridcast(Entity<MapGridComponent> grid, RadiationRay ray, b

// save data for debug
if (saveVisitedTiles)
blockers.Add((point, ray.Rads));
blockers!.Add((point, ray.Rads));

// no intensity left after blocker
if (ray.Rads <= MinIntensity)
Expand All @@ -230,21 +250,45 @@ private RadiationRay Gridcast(Entity<MapGridComponent> grid, RadiationRay ray, b
}
}

if (!saveVisitedTiles || blockers!.Count <= 0)
return ray;

// save data for debug if needed
if (saveVisitedTiles && blockers.Count > 0)
ray.Blockers.Add(GetNetEntity(gridUid), blockers);
ray.Blockers ??= new();
ray.Blockers.Add(GetNetEntity(gridUid), blockers);

return ray;
}

private float GetAdjustedRadiationIntensity(EntityUid uid, float rads)
{
var radblockingComps = new List<RadiationBlockingContainerComponent>();
if (_container.TryFindComponentsOnEntityContainerOrParent(uid, _radiationBlockingContainers, radblockingComps))
var child = uid;
var xform = Transform(uid);
var parent = xform.ParentUid;

while (parent.IsValid())
{
rads -= radblockingComps.Sum(x => x.RadResistance);
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 float.Max(rads, 0);
return rads;
}
}
Loading
Loading