Optimize pipe net appearance updating. (#6469)

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2022-02-09 16:10:55 +13:00
committed by GitHub
parent 544a892348
commit 21e0cd4256
9 changed files with 193 additions and 278 deletions

View File

@@ -0,0 +1,81 @@
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using System.Collections.Generic;
namespace Content.Server.Atmos.Piping.EntitySystems;
public sealed class AtmosPipeNodeAppearanceSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
public override void Initialize()
{
base.Initialize();
// This should probably just be a directed event, but that would require a weird component that exists only to
// receive directed events (*cough* *cough* CableVisComponent *cough*).
//
// Really I want to target any entity with a PipeConnectorVisualizer or a NodeContainerComponent that contains a
// pipe-node. But I don't know of nice way of doing that.
SubscribeLocalEvent<NodeGroupsRebuilt>(OnNodeUpdate);
}
private void OnNodeUpdate(ref NodeGroupsRebuilt ev)
{
UpdateAppearance(ev.NodeOwner);
}
private void UpdateAppearance(EntityUid uid, AppearanceComponent? appearance = null, NodeContainerComponent? container = null,
TransformComponent? xform = null)
{
if (!Resolve(uid, ref appearance, ref container, ref xform, false))
return;
if (!_mapManager.TryGetGrid(xform.GridID, out var grid))
return;
// get connected entities
var anyPipeNodes = false;
HashSet<EntityUid> connected = new();
foreach (var node in container.Nodes.Values)
{
if (node is not PipeNode)
continue;
anyPipeNodes = true;
foreach (var connectedNode in node.ReachableNodes)
{
if (connectedNode is PipeNode)
connected.Add(connectedNode.Owner);
}
}
if (!anyPipeNodes)
return;
// find the cardinal directions of any connected entities
var netConnectedDirections = PipeDirection.None;
var tile = grid.TileIndicesFor(xform.Coordinates);
foreach (var neighbour in connected)
{
var otherTile = grid.TileIndicesFor(Transform(neighbour).Coordinates);
netConnectedDirections |= (otherTile - tile) switch
{
(0, 1) => PipeDirection.North,
(0, -1) => PipeDirection.South,
(1, 0) => PipeDirection.East,
(-1, 0) => PipeDirection.West,
_ => PipeDirection.None
};
}
appearance.SetData(PipeVisuals.VisualState, netConnectedDirections);
}
}

View File

@@ -1,6 +1,7 @@
using Content.Server.NodeContainer.Nodes;
using Content.Server.NodeContainer.Nodes;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.NodeContainer.EntitySystems
{
@@ -9,8 +10,10 @@ namespace Content.Server.NodeContainer.EntitySystems
/// </summary>
/// <seealso cref="NodeGroupSystem"/>
[UsedImplicitly]
public class NodeContainerSystem : EntitySystem
public sealed class NodeContainerSystem : EntitySystem
{
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -22,44 +25,50 @@ namespace Content.Server.NodeContainer.EntitySystems
SubscribeLocalEvent<NodeContainerComponent, RotateEvent>(OnRotateEvent);
}
private static void OnInitEvent(EntityUid uid, NodeContainerComponent component, ComponentInit args)
private void OnInitEvent(EntityUid uid, NodeContainerComponent component, ComponentInit args)
{
foreach (var (key, node) in component.Nodes)
{
node.Name = key;
node.Initialize(component.Owner);
node.Initialize(component.Owner, EntityManager);
}
}
private static void OnStartupEvent(EntityUid uid, NodeContainerComponent component, ComponentStartup args)
private void OnStartupEvent(EntityUid uid, NodeContainerComponent component, ComponentStartup args)
{
foreach (var node in component.Nodes.Values)
{
node.OnContainerStartup();
_nodeGroupSystem.QueueReflood(node);
}
}
private static void OnShutdownEvent(EntityUid uid, NodeContainerComponent component, ComponentShutdown args)
private void OnShutdownEvent(EntityUid uid, NodeContainerComponent component, ComponentShutdown args)
{
foreach (var node in component.Nodes.Values)
{
node.OnContainerShutdown();
_nodeGroupSystem.QueueNodeRemove(node);
node.Deleting = true;
}
}
private static void OnAnchorStateChanged(
private void OnAnchorStateChanged(
EntityUid uid,
NodeContainerComponent component,
ref AnchorStateChangedEvent args)
{
foreach (var node in component.Nodes.Values)
{
node.AnchorUpdate();
node.AnchorStateChanged();
if (!node.NeedAnchored)
continue;
if (args.Anchored)
_nodeGroupSystem.QueueReflood(node);
else
_nodeGroupSystem.QueueNodeRemove(node);
}
}
private static void OnRotateEvent(EntityUid uid, NodeContainerComponent container, ref RotateEvent ev)
private void OnRotateEvent(EntityUid uid, NodeContainerComponent container, ref RotateEvent ev)
{
if (ev.NewRotation == ev.OldRotation)
{
@@ -68,8 +77,11 @@ namespace Content.Server.NodeContainer.EntitySystems
foreach (var node in container.Nodes.Values)
{
if (node is not IRotatableNode rotatableNode) continue;
rotatableNode.RotateEvent(ref ev);
if (node is not IRotatableNode rotatableNode)
continue;
if (rotatableNode.RotateEvent(ref ev))
_nodeGroupSystem.QueueReflood(node);
}
}
}

View File

@@ -245,14 +245,22 @@ namespace Content.Server.NodeContainer.EntitySystems
_toRemake.Clear();
_toRemove.Clear();
// notify entities that node groups have been updated, so they can do things like update their visuals.
HashSet<EntityUid> entities = new();
foreach (var group in newGroups)
{
foreach (var node in group.Nodes)
{
node.OnPostRebuild();
entities.Add(node.Owner);
}
}
foreach (var uid in entities)
{
var ev = new NodeGroupsRebuilt(uid);
RaiseLocalEvent(uid, ref ev, true);
}
_sawmill.Debug($"Updated node groups in {sw.Elapsed.TotalMilliseconds}ms. {newGroups.Count} new groups, {refloodCount} nodes processed.");
}
@@ -402,4 +410,19 @@ namespace Content.Server.NodeContainer.EntitySystems
};
}
}
/// <summary>
/// Event raised after node groups have been updated. Directed at any entity with a <see
/// cref="NodeContainerComponent"/> that had a relevant node.
/// </summary>
[ByRefEvent]
public readonly struct NodeGroupsRebuilt
{
public readonly EntityUid NodeOwner;
public NodeGroupsRebuilt(EntityUid nodeOwner)
{
NodeOwner = nodeOwner;
}
}
}

View File

@@ -54,7 +54,6 @@ namespace Content.Server.NodeContainer.NodeGroups
{
var pipeNode = (PipeNode) node;
_pipes.Add(pipeNode);
pipeNode.JoinPipeNet(this);
Air.Volume += pipeNode.Volume;
}
}

View File

@@ -9,8 +9,8 @@ namespace Content.Server.NodeContainer.Nodes
public interface IRotatableNode
{
/// <summary>
/// Rotates this <see cref="Node"/>.
/// Rotates this <see cref="Node"/>. Returns true if the node's connections need to be updated.
/// </summary>
void RotateEvent(ref RotateEvent ev);
bool RotateEvent(ref RotateEvent ev);
}
}

View File

@@ -56,7 +56,7 @@ namespace Content.Server.NodeContainer.Nodes
[ViewVariables(VVAccess.ReadWrite)]
[DataField("needAnchored")]
private bool NeedAnchored { get; } = true;
public bool NeedAnchored { get; } = true;
/// <summary>
/// Prevents a node from being used by other nodes while midway through removal.
@@ -83,70 +83,11 @@ namespace Content.Server.NodeContainer.Nodes
/// Invoked when the owning <see cref="NodeContainerComponent"/> is initialized.
/// </summary>
/// <param name="owner">The owning entity.</param>
public virtual void Initialize(EntityUid owner)
public virtual void Initialize(EntityUid owner, IEntityManager entMan)
{
Owner = owner;
}
/// <summary>
/// Invoked when the owning <see cref="NodeContainerComponent"/> is started.
/// </summary>
public virtual void OnContainerStartup()
{
EntitySystem.Get<NodeGroupSystem>().QueueReflood(this);
}
/// <summary>
/// Immediately create a single-node node group for this node if it does not have one yet.
/// </summary>
/// <remarks>
/// This can be useful for nodes like pipes
/// that need immediate access to their node group to set parameters like node volume.
/// The node group created by this function (if necessary) will still update and form new,
/// merged groups later if necessary.
/// Set parameters like pipe net volume should then be transferred/merged there.
/// </remarks>
public void CreateSingleNetImmediate()
{
EntitySystem.Get<NodeGroupSystem>().CreateSingleNetImmediate(this);
}
public void AnchorUpdate()
{
if (Anchored)
{
EntitySystem.Get<NodeGroupSystem>().QueueReflood(this);
}
else
{
EntitySystem.Get<NodeGroupSystem>().QueueNodeRemove(this);
}
}
/// <summary>
/// Called when the anchored state of the owning entity changes.
/// </summary>
public virtual void AnchorStateChanged()
{
}
/// <summary>
/// Called after the parent node group has been rebuilt.
/// </summary>
public virtual void OnPostRebuild()
{
}
/// <summary>
/// Called when the owning <see cref="NodeContainerComponent"/> is shut down.
/// </summary>
public virtual void OnContainerShutdown()
{
Deleting = true;
EntitySystem.Get<NodeGroupSystem>().QueueNodeRemove(this);
}
/// <summary>
/// How this node will attempt to find other reachable <see cref="Node"/>s to group with.
/// Returns a set of <see cref="Node"/>s to consider grouping with. Should not return this current <see cref="Node"/>.

View File

@@ -21,8 +21,6 @@ namespace Content.Server.NodeContainer.Nodes
[DataDefinition]
public class PipeNode : Node, IGasMixtureHolder, IRotatableNode
{
private PipeDirection _connectedDirections;
/// <summary>
/// The directions in which this pipe can connect to other pipes around it.
/// </summary>
@@ -56,21 +54,6 @@ namespace Content.Server.NodeContainer.Nodes
EntitySystem.Get<NodeGroupSystem>().QueueRemakeGroup((BaseNodeGroup) NodeGroup);
}
/// <summary>
/// The directions in which this node is connected to other nodes.
/// Used by <see cref="PipeVisualState"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public PipeDirection ConnectedDirections
{
get => _connectedDirections;
private set
{
_connectedDirections = value;
UpdateAppearance();
}
}
/// <summary>
/// Whether this node can connect to others or not.
/// </summary>
@@ -124,30 +107,37 @@ namespace Content.Server.NodeContainer.Nodes
private const float DefaultVolume = 200f;
public override void OnContainerStartup()
public override void Initialize(EntityUid owner, IEntityManager entMan)
{
base.OnContainerStartup();
OnConnectedDirectionsNeedsUpdating();
base.Initialize(owner, entMan);
if (!RotationsEnabled)
return;
var xform = entMan.GetComponent<TransformComponent>(owner);
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation);
}
public override void OnContainerShutdown()
bool IRotatableNode.RotateEvent(ref RotateEvent ev)
{
base.OnContainerShutdown();
UpdateAdjacentConnectedDirections();
if (_originalPipeDirection == PipeDirection.Fourway)
return false;
// update valid pipe direction
if (!RotationsEnabled)
{
if (CurrentPipeDirection == _originalPipeDirection)
return false;
CurrentPipeDirection = _originalPipeDirection;
}
else
{
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(ev.NewRotation);
}
public void JoinPipeNet(IPipeNet pipeNet)
{
OnConnectedDirectionsNeedsUpdating();
}
/// <summary>
/// Rotates the <see cref="PipeDirection"/> when the entity is rotated, and re-calculates the <see cref="IPipeNet"/>.
/// </summary>
void IRotatableNode.RotateEvent(ref RotateEvent ev)
{
OnConnectedDirectionsNeedsUpdating();
UpdateAppearance();
// node connections need to be updated
return true;
}
public override IEnumerable<Node> GetReachableNodes(TransformComponent xform,
@@ -229,104 +219,5 @@ namespace Content.Server.NodeContainer.Nodes
}
}
}
/// <summary>
/// Updates the <see cref="ConnectedDirections"/> of this and all sorrounding pipes.
/// Also updates CurrentPipeDirection.
/// </summary>
private void OnConnectedDirectionsNeedsUpdating()
{
if (RotationsEnabled)
{
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(Owner).LocalRotation);
}
else
{
CurrentPipeDirection = _originalPipeDirection;
}
UpdateConnectedDirections();
UpdateAdjacentConnectedDirections();
UpdateAppearance();
}
/// <summary>
/// Checks what directions there are connectable pipes in, to update <see cref="ConnectedDirections"/>.
/// </summary>
private void UpdateConnectedDirections()
{
ConnectedDirections = PipeDirection.None;
var entMan = IoCManager.Resolve<IEntityManager>();
var xform = entMan.GetComponent<TransformComponent>(Owner);
if (!IoCManager.Resolve<IMapManager>().TryGetGrid(xform.GridID, out var grid))
return;
var pos = grid.WorldToTile(xform.WorldPosition);
var query = entMan.GetEntityQuery<NodeContainerComponent>();
for (var i = 0; i < PipeDirectionHelpers.AllPipeDirections; i++)
{
var pipeDir = (PipeDirection) (1 << i);
if (!CurrentPipeDirection.HasDirection(pipeDir))
continue;
foreach (var pipe in LinkableNodesInDirection(pos, pipeDir, grid, query))
{
if (pipe.Connectable(entMan) && pipe.NodeGroupID == NodeGroupID)
{
ConnectedDirections |= pipeDir;
break;
}
}
}
}
/// <summary>
/// Calls <see cref="UpdateConnectedDirections"/> on all adjacent pipes,
/// to update their <see cref="ConnectedDirections"/> when this pipe is changed.
/// </summary>
private void UpdateAdjacentConnectedDirections()
{
var entMan = IoCManager.Resolve<IEntityManager>();
var xform = entMan.GetComponent<TransformComponent>(Owner);
if (!IoCManager.Resolve<IMapManager>().TryGetGrid(xform.GridID, out var grid))
return;
var pos = grid.WorldToTile(xform.WorldPosition);
var query = entMan.GetEntityQuery<NodeContainerComponent>();
for (var i = 0; i < PipeDirectionHelpers.PipeDirections; i++)
{
var pipeDir = (PipeDirection) (1 << i);
foreach (var pipe in LinkableNodesInDirection(pos, pipeDir, grid, query))
{
pipe.UpdateConnectedDirections();
pipe.UpdateAppearance();
}
}
}
/// <summary>
/// Updates the <see cref="AppearanceComponent"/>.
/// Gets the combined <see cref="ConnectedDirections"/> of every pipe on this entity, so the visualizer on this entity can draw the pipe connections.
/// </summary>
private void UpdateAppearance()
{
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out AppearanceComponent? appearance)
|| !IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out NodeContainerComponent? container))
return;
var netConnectedDirections = PipeDirection.None;
foreach (var node in container.Nodes.Values)
{
if (node is PipeNode pipe)
{
netConnectedDirections |= pipe.ConnectedDirections;
}
}
appearance.SetData(PipeVisuals.VisualState, netConnectedDirections);
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.Power.Components;
@@ -16,45 +17,26 @@ namespace Content.Server.Power.EntitySystems
{
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly HashSet<EntityUid> _toUpdate = new();
public void QueueUpdate(EntityUid uid)
{
_toUpdate.Add(uid);
}
public override void Initialize()
{
base.Initialize();
UpdatesAfter.Add(typeof(NodeGroupSystem));
SubscribeLocalEvent<CableVisComponent, NodeGroupsRebuilt>(UpdateAppearance);
}
public override void Update(float frameTime)
private void UpdateAppearance(EntityUid uid, CableVisComponent cableVis, ref NodeGroupsRebuilt args)
{
base.Update(frameTime);
foreach (var uid in _toUpdate)
{
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)
|| !EntityManager.TryGetComponent(uid, out CableVisComponent? cableVis)
|| !EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance))
{
continue;
}
if (cableVis.Node == null)
continue;
return;
if (!TryComp(uid, out NodeContainerComponent? nodeContainer) || !TryComp(uid, out AppearanceComponent? appearance))
return;
var transform = Transform(uid);
if (!_mapManager.TryGetGrid(transform.GridID, out var grid))
return;
var mask = WireVisDirFlags.None;
var transform = EntityManager.GetComponent<TransformComponent>(uid);
// Only valid grids allowed.
if(!transform.GridID.IsValid())
continue;
var grid = _mapManager.GetGrid(transform.GridID);
var tile = grid.TileIndicesFor(transform.Coordinates);
var node = nodeContainer.GetNode<CableNode>(cableVis.Node);
@@ -63,10 +45,7 @@ namespace Content.Server.Power.EntitySystems
if (reachable is not CableNode)
continue;
var otherTransform = EntityManager.GetComponent<TransformComponent>(reachable.Owner);
if (otherTransform.GridID != grid.Index)
continue;
var otherTransform = Transform(reachable.Owner);
var otherTile = grid.TileIndicesFor(otherTransform.Coordinates);
var diff = otherTile - tile;
@@ -82,8 +61,5 @@ namespace Content.Server.Power.EntitySystems
appearance.SetData(WireVisVisuals.ConnectedMask, mask);
}
_toUpdate.Clear();
}
}
}

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -70,12 +69,5 @@ namespace Content.Server.Power.Nodes
yield return node;
}
}
public override void OnPostRebuild()
{
base.OnPostRebuild();
EntitySystem.Get<CableVisSystem>().QueueUpdate(Owner);
}
}
}