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

View File

@@ -245,14 +245,22 @@ namespace Content.Server.NodeContainer.EntitySystems
_toRemake.Clear(); _toRemake.Clear();
_toRemove.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 group in newGroups)
{ {
foreach (var node in group.Nodes) 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."); _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; var pipeNode = (PipeNode) node;
_pipes.Add(pipeNode); _pipes.Add(pipeNode);
pipeNode.JoinPipeNet(this);
Air.Volume += pipeNode.Volume; Air.Volume += pipeNode.Volume;
} }
} }

View File

@@ -9,8 +9,8 @@ namespace Content.Server.NodeContainer.Nodes
public interface IRotatableNode public interface IRotatableNode
{ {
/// <summary> /// <summary>
/// Rotates this <see cref="Node"/>. /// Rotates this <see cref="Node"/>. Returns true if the node's connections need to be updated.
/// </summary> /// </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)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("needAnchored")] [DataField("needAnchored")]
private bool NeedAnchored { get; } = true; public bool NeedAnchored { get; } = true;
/// <summary> /// <summary>
/// Prevents a node from being used by other nodes while midway through removal. /// 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. /// Invoked when the owning <see cref="NodeContainerComponent"/> is initialized.
/// </summary> /// </summary>
/// <param name="owner">The owning entity.</param> /// <param name="owner">The owning entity.</param>
public virtual void Initialize(EntityUid owner) public virtual void Initialize(EntityUid owner, IEntityManager entMan)
{ {
Owner = owner; 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> /// <summary>
/// How this node will attempt to find other reachable <see cref="Node"/>s to group with. /// 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"/>. /// 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] [DataDefinition]
public class PipeNode : Node, IGasMixtureHolder, IRotatableNode public class PipeNode : Node, IGasMixtureHolder, IRotatableNode
{ {
private PipeDirection _connectedDirections;
/// <summary> /// <summary>
/// The directions in which this pipe can connect to other pipes around it. /// The directions in which this pipe can connect to other pipes around it.
/// </summary> /// </summary>
@@ -56,21 +54,6 @@ namespace Content.Server.NodeContainer.Nodes
EntitySystem.Get<NodeGroupSystem>().QueueRemakeGroup((BaseNodeGroup) NodeGroup); 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> /// <summary>
/// Whether this node can connect to others or not. /// Whether this node can connect to others or not.
/// </summary> /// </summary>
@@ -124,30 +107,37 @@ namespace Content.Server.NodeContainer.Nodes
private const float DefaultVolume = 200f; private const float DefaultVolume = 200f;
public override void OnContainerStartup() public override void Initialize(EntityUid owner, IEntityManager entMan)
{ {
base.OnContainerStartup(); base.Initialize(owner, entMan);
OnConnectedDirectionsNeedsUpdating();
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(); if (_originalPipeDirection == PipeDirection.Fourway)
UpdateAdjacentConnectedDirections(); 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) // node connections need to be updated
{ return true;
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();
} }
public override IEnumerable<Node> GetReachableNodes(TransformComponent xform, 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;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.Power.Components; using Content.Server.Power.Components;
@@ -16,45 +17,26 @@ namespace Content.Server.Power.EntitySystems
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
private readonly HashSet<EntityUid> _toUpdate = new();
public void QueueUpdate(EntityUid uid)
{
_toUpdate.Add(uid);
}
public override void Initialize() public override void Initialize()
{ {
base.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) 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 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 tile = grid.TileIndicesFor(transform.Coordinates);
var node = nodeContainer.GetNode<CableNode>(cableVis.Node); var node = nodeContainer.GetNode<CableNode>(cableVis.Node);
@@ -63,10 +45,7 @@ namespace Content.Server.Power.EntitySystems
if (reachable is not CableNode) if (reachable is not CableNode)
continue; continue;
var otherTransform = EntityManager.GetComponent<TransformComponent>(reachable.Owner); var otherTransform = Transform(reachable.Owner);
if (otherTransform.GridID != grid.Index)
continue;
var otherTile = grid.TileIndicesFor(otherTransform.Coordinates); var otherTile = grid.TileIndicesFor(otherTransform.Coordinates);
var diff = otherTile - tile; var diff = otherTile - tile;
@@ -82,8 +61,5 @@ namespace Content.Server.Power.EntitySystems
appearance.SetData(WireVisVisuals.ConnectedMask, mask); appearance.SetData(WireVisVisuals.ConnectedMask, mask);
} }
_toUpdate.Clear();
}
} }
} }

View File

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