From bb987676cae1d482eec1b5c6fbe7ff62407a2413 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 1 Jan 2018 14:11:01 +0200 Subject: [PATCH] Improved PowerTransfer logic. The PowerTransfer components cache all the connections that are connected to the item (directly or via another item) and only recalculate them when changes are done to the wiring. Signals received by the PowerTransfer component are relayed directly to the recipients instead of stepping through all the wires and junction boxes in between. Fixes stack overflow exceptions caused by signals looping between junction boxes and improves performance because the layout of the power grid doesn't have to be calculated every frame and the wire connections don't have to be rechecked when sending a signal. Closes #222 --- .../Items/Components/Signal/Connection.cs | 4 +- .../Items/Components/Power/PowerTransfer.cs | 205 +++++++++++++++--- .../Items/Components/Signal/Connection.cs | 4 +- .../Items/Components/Signal/RelayComponent.cs | 11 +- .../Source/Items/Components/Signal/Wire.cs | 31 ++- 5 files changed, 199 insertions(+), 56 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs index 93b37d1f6..7efc9cf88 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/Connection.cs @@ -182,8 +182,8 @@ namespace Barotrauma.Items.Components GameServer.Log(Character.Controlled.LogName + " connected a wire from " + Item.Name + " (" + Name + ") to " + otherConnection.item.Name + " (" + otherConnection.Name + ")", ServerLog.MessageType.ItemInteraction); } - - Wires[index] = draggingConnected; + + AddLink(index, draggingConnected); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index 575b0b4c8..4e0680e11 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -9,9 +9,7 @@ namespace Barotrauma.Items.Components { static float fullPower; static float fullLoad; - - //private bool updated; - + private int updateTimer; const float FireProbability = 0.15f; @@ -19,35 +17,82 @@ namespace Barotrauma.Items.Components //affects how fast changes in power/load are carried over the grid static float inertia = 5.0f; - static HashSet connectedList = new HashSet(); - + private HashSet connectedPoweredList = new HashSet(); private List powerConnections; + + private Dictionary connectionDirty = new Dictionary(); + + //a list of connections a given connection is connected to, either directly or via other power transfer components + private Dictionary> connectedRecipients = new Dictionary>(); private float powerLoad; + private bool isBroken; + public float PowerLoad { get { return powerLoad; } } //can the component transfer power - public virtual bool CanTransfer + private bool canTransfer; + public bool CanTransfer { - get { return IsActive; } + get { return canTransfer; } + set + { + if (canTransfer == value) return; + canTransfer = value; + SetAllConnectionsDirty(); + } + } + + public override bool IsActive + { + get + { + return base.IsActive; + } + + set + { + if (base.IsActive != value) SetAllConnectionsDirty(); + base.IsActive = value; + } } public PowerTransfer(Item item, XElement element) : base(item, element) { IsActive = true; + canTransfer = true; powerConnections = new List(); } + public override void UpdateBroken(float deltaTime, Camera cam) + { + base.UpdateBroken(deltaTime, cam); + + if (!isBroken) + { + SetAllConnectionsDirty(); + isBroken = true; + } + } + public override void Update(float deltaTime, Camera cam) { if (!CanTransfer) return; + if (isBroken) + { + SetAllConnectionsDirty(); + isBroken = false; + } + + RefreshConnections(); + if (updateTimer > 0) { //this junction box has already been updated this frame @@ -60,16 +105,13 @@ namespace Barotrauma.Items.Components fullPower = 0.0f; fullLoad = 0.0f; - connectedList.Clear(); + connectedPoweredList.Clear(); - CheckJunctions(deltaTime); + CheckPower(deltaTime); updateTimer = 0; - foreach (Powered p in connectedList) - { - PowerTransfer pt = p as PowerTransfer; - if (pt == null) continue; - + foreach (PowerTransfer pt in connectedPoweredList) + { pt.powerLoad += (fullLoad - pt.powerLoad) / inertia; pt.currPowerConsumption += (-fullPower - pt.currPowerConsumption) / inertia; pt.Item.SendSignal(0, "", "power", null, fullPower / Math.Max(fullLoad, 1.0f)); @@ -112,39 +154,99 @@ namespace Barotrauma.Items.Components return picker != null; } + private void RefreshConnections() + { + var connections = item.Connections; + foreach (Connection c in connections) + { + if (!connectionDirty[c]) continue; + + HashSet connected = new HashSet(); + if (!connectedRecipients.ContainsKey(c)) + { + connectedRecipients.Add(c, connected); + } + else + { + //mark all previous recipients as dirty + foreach (Connection recipient in connectedRecipients[c]) + { + var pt = recipient.Item.GetComponent(); + if (pt != null) pt.connectionDirty[recipient] = true; + } + } + + //find all connections that are connected to this one (directly or via another PowerTransfer) + connected.Add(c); + GetConnected(c, connected); + connectedRecipients[c] = connected; + + //go through all the PowerTransfers and we're connected to and set their connections to match the ones we just calculated + //(no need to go through the recursive GetConnected method again) + foreach (Connection recipient in connected) + { + var recipientPowerTransfer = recipient.Item.GetComponent(); + if (recipientPowerTransfer == null) continue; + + if (!connectedRecipients.ContainsKey(recipient)) + { + connectedRecipients.Add(recipient, connected); + } + + recipientPowerTransfer.connectedRecipients[recipient] = connected; + recipientPowerTransfer.connectionDirty[recipient] = false; + } + } + } + + //Finds all the connections that can receive a signal sent into the given connection and stores them in the hashset. + private void GetConnected(Connection c, HashSet connected) + { + var recipients = c.Recipients; + + foreach (Connection recipient in recipients) + { + if (recipient == null || connected.Contains(recipient)) continue; + + Item it = recipient.Item; + if (it == null || it.Condition <= 0.0f) continue; + + connected.Add(recipient); + + var powerTransfer = it.GetComponent(); + if (powerTransfer != null && powerTransfer.CanTransfer && powerTransfer.IsActive) + { + GetConnected(recipient, connected); + } + } + } + //a recursive function that goes through all the junctions and adds up //all the generated/consumed power of the constructions connected to the grid - private void CheckJunctions(float deltaTime) + private void CheckPower(float deltaTime) { updateTimer = 1; - connectedList.Add(this); - ApplyStatusEffects(ActionType.OnActive, deltaTime, null); - + connectedPoweredList.Clear(); + foreach (Connection c in powerConnections) { - var recipients = c.Recipients; - + HashSet recipients = connectedRecipients[c]; foreach (Connection recipient in recipients) { if (recipient == null) continue; Item it = recipient.Item; - if (it == null) continue; - - if (it.Condition <= 0.0f) continue; + if (it == null || it.Condition <= 0.0f) continue; foreach (Powered powered in it.GetComponents()) { if (powered == null || !powered.IsActive) continue; - - if (connectedList.Contains(powered)) continue; - PowerTransfer powerTransfer = powered as PowerTransfer; if (powerTransfer != null) { - if (!powerTransfer.CanTransfer) continue; - powerTransfer.CheckJunctions(deltaTime); + connectedPoweredList.Add(powerTransfer); + powerTransfer.updateTimer = 1; continue; } @@ -162,25 +264,38 @@ namespace Barotrauma.Items.Components } else { - connectedList.Add(powered); //positive power consumption = the construction requires power -> increase load if (powered.CurrPowerConsumption > 0.0f) { fullLoad += powered.CurrPowerConsumption; } else if (powered.CurrPowerConsumption < 0.0f) - //negative power consumption = the construction is a - //generator/battery or another junction box + //negative power consumption = the construction is a /generator/battery { fullPower -= powered.CurrPowerConsumption; } } } - } + } + } + + public void SetAllConnectionsDirty() + { + if (item.Connections == null) return; + foreach (Connection c in item.Connections) + { + connectionDirty[c] = true; } } - + + public void SetConnectionDirty(Connection connection) + { + var connections = item.Connections; + if (connections == null || !connections.Contains(connection)) return; + connectionDirty[connection] = true; + } + public override void OnMapLoaded() { var connections = item.Connections; @@ -189,18 +304,38 @@ namespace Barotrauma.Items.Components IsActive = false; return; } - + powerConnections = connections.FindAll(c => c.IsPower); if (powerConnections.Count == 0) IsActive = false; + + SetAllConnectionsDirty(); } public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power) { base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power); + if (!connectedRecipients.ContainsKey(connection)) return; + if (connection.Name.Length > 5 && connection.Name.Substring(0, 6).ToLowerInvariant() == "signal") { - connection.SendSignal(stepsTaken, signal, source, sender, 0.0f); + foreach (Connection recipient in connectedRecipients[connection]) + { + if (recipient.Item == item || recipient.Item == source) continue; + + foreach (ItemComponent ic in recipient.Item.components) + { + //powertransfer components don't need to receive the signal because we relay it straight + //to the connected items without going through the whole chain of junction boxes + if (ic is PowerTransfer) continue; + ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, 0.0f); + } + + foreach (StatusEffect effect in recipient.effects) + { + recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f); + } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs index 4beb77dca..de7641d6d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs @@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components private static Wire draggingConnected; - private List effects; + public readonly List effects; public readonly ushort[] wireId; @@ -167,8 +167,6 @@ namespace Barotrauma.Items.Components foreach (StatusEffect effect in recipient.effects) { - - //effect.Apply(ActionType.OnUse, 1.0f, recipient.item, recipient.item); recipient.item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs index 594fb71b7..e236086cb 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs @@ -31,21 +31,14 @@ namespace Barotrauma.Items.Components set { isOn = value; + CanTransfer = value; if (!isOn) { currPowerConsumption = 0.0f; } } } - - public override bool CanTransfer - { - get - { - return isOn; - } - } - + public RelayComponent(Item item, XElement element) : base (item, element) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs index f32fa02d9..cc44dd0b8 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs @@ -86,14 +86,15 @@ namespace Barotrauma.Items.Components public void RemoveConnection(Item item) { - for (int i = 0; i<2; i++) + for (int i = 0; i < 2; i++) { - if (connections[i]==null || connections[i].Item!=item) continue; - - for (int n = 0; n< connections[i].Wires.Length; n++) + if (connections[i] == null || connections[i].Item != item) continue; + + for (int n = 0; n < connections[i].Wires.Length; n++) { if (connections[i].Wires[n] != this) continue; + SetConnectedDirty(); connections[i].Wires[n] = null; } connections[i] = null; @@ -104,6 +105,8 @@ namespace Barotrauma.Items.Components { if (connection == connections[0]) connections[0] = null; if (connection == connections[1]) connections[1] = null; + + SetConnectedDirty(); } public bool Connect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false) @@ -137,8 +140,7 @@ namespace Barotrauma.Items.Components if (newConnection.Item.Submarine == null) continue; if (nodes.Count > 0 && nodes[0] == newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition) break; - if (nodes.Count > 1 && nodes[nodes.Count-1] == newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition) break; - + if (nodes.Count > 1 && nodes[nodes.Count - 1] == newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition) break; if (i == 0) { @@ -148,11 +150,12 @@ namespace Barotrauma.Items.Components { nodes.Add(newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition); } - break; } + SetConnectedDirty(); + if (connections[0] != null && connections[1] != null) { foreach (ItemComponent ic in item.components) @@ -323,6 +326,8 @@ namespace Barotrauma.Items.Components connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction); } } + + SetConnectedDirty(); for (int i = 0; i < 2; i++) { @@ -363,6 +368,18 @@ namespace Barotrauma.Items.Components return position; } + public void SetConnectedDirty() + { + for (int i = 0; i < 2; i++) + { + if (connections[i]?.Item != null) + { + var pt = connections[i].Item.GetComponent(); + if (pt != null) pt.SetConnectionDirty(connections[i]); + } + } + } + private void CleanNodes() { for (int i = nodes.Count - 2; i > 0; i--)