From 60a563fe758787b048669feac97074526c49a10d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 30 Jul 2018 13:35:26 +0300 Subject: [PATCH] EntityEvent fixes: - Clients wait for midround syncing to finish before applying the remote state to connection panels and inventories (because the wires connected to the connection panel or items in the inventory may not exist before the EntitySpawner events have been received). - Server writes 0 as the projectile ID if the projectile doesn't exist anymore when a Turret event is sent. - More info in networkevent error messages. --- .../Source/Items/Components/ItemComponent.cs | 14 ++-- .../Components/Signal/ConnectionPanel.cs | 65 ++++++++++++++++++ .../Source/Items/Components/Turret.cs | 4 +- .../Source/Items/Inventory.cs | 66 +++++++++++++++++++ .../Source/Networking/GameClient.cs | 5 ++ .../ClientEntityEventManager.cs | 15 ++++- .../Components/Signal/ConnectionPanel.cs | 54 +-------------- .../Source/Items/Components/Turret.cs | 4 +- .../Source/Items/Inventory.cs | 58 ---------------- 9 files changed, 167 insertions(+), 118 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index ee2740938..764f758da 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -224,21 +224,27 @@ namespace Barotrauma.Items.Components } //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero. - protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime) + protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync = false) { if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); - delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime)); + delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync)); } - private IEnumerable DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime) + private IEnumerable DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync) { - while (correctionTimer > 0.0f) + while (GameMain.Client != null && + (correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing))) { correctionTimer -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } + if (item.Removed || GameMain.Client == null) + { + yield return CoroutineStatus.Success; + } + ((IServerSerializable)this).ClientRead(type, buffer, sendingTime); correctionTimer = 0.0f; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs index b2f10ae90..c25397641 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/ConnectionPanel.cs @@ -1,5 +1,9 @@ using Barotrauma.Networking; +using Lidgren.Network; using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; namespace Barotrauma.Items.Components { @@ -24,5 +28,66 @@ namespace Barotrauma.Items.Components HighlightedWire = null; Connection.DrawConnections(spriteBatch, this, character); } + + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + if (GameMain.Client.MidRoundSyncing) + { + //delay reading the state until midround syncing is done + //because some of the wires connected to the panel may not exist yet + int bitsToRead = Connections.Count * Connection.MaxLinked * 16; + StartDelayedCorrection(type, msg.ExtractBits(bitsToRead), sendingTime, waitForMidRoundSync: true); + } + else + { + ApplyRemoteState(msg); + } + } + + private void ApplyRemoteState(NetBuffer msg) + { + List prevWires = Connections.SelectMany(c => Array.FindAll(c.Wires, w => w != null)).ToList(); + List newWires = new List(); + + foreach (Connection connection in Connections) + { + connection.ClearConnections(); + } + + foreach (Connection connection in Connections) + { + for (int i = 0; i < Connection.MaxLinked; i++) + { + ushort wireId = msg.ReadUInt16(); + + Item wireItem = Entity.FindEntityByID(wireId) as Item; + if (wireItem == null) continue; + + Wire wireComponent = wireItem.GetComponent(); + if (wireComponent == null) continue; + + newWires.Add(wireComponent); + + connection.Wires[i] = wireComponent; + wireComponent.Connect(connection, false); + } + } + + foreach (Wire wire in prevWires) + { + if (wire.Connections[0] == null && wire.Connections[1] == null) + { + wire.Item.Drop(null); + } + //wires that are not in anyone's inventory (i.e. not currently being rewired) can never be connected to only one connection + // -> someone must have dropped the wire from the connection panel + else if (wire.Item.ParentInventory == null && + (wire.Connections[0] != null ^ wire.Connections[1] != null)) + { + wire.Item.Drop(null); + } + } + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs index 3141ee613..fdd20abf1 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Turret.cs @@ -148,8 +148,10 @@ namespace Barotrauma.Items.Components public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { UInt16 projectileID = msg.ReadUInt16(); - Item projectile = Entity.FindEntityByID(projectileID) as Item; + //projectile removed, do nothing + if (projectileID == 0) return; + Item projectile = Entity.FindEntityByID(projectileID) as Item; if (projectile == null) { DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found"); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 7b1028a69..06a11e43c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -1,4 +1,6 @@ using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -464,5 +466,69 @@ namespace Barotrauma item.Sprite.Draw(spriteBatch, new Vector2(rect.X + rect.Width / 2, rect.Y + rect.Height / 2), item.GetSpriteColor()); } + + public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) + { + receivedItemIDs = new ushort[capacity]; + + for (int i = 0; i < capacity; i++) + { + receivedItemIDs[i] = msg.ReadUInt16(); + } + + //delay applying the new state if less than 1 second has passed since this client last sent a state to the server + //prevents the inventory from briefly reverting to an old state if items are moved around in quick succession + + //also delay if we're still midround syncing, some of the items in the inventory may not exist yet + if (syncItemsDelay > 0.0f || GameMain.Client.MidRoundSyncing) + { + if (syncItemsCoroutine != null) CoroutineManager.StopCoroutines(syncItemsCoroutine); + syncItemsCoroutine = CoroutineManager.StartCoroutine(SyncItemsAfterDelay()); + } + else + { + ApplyReceivedState(); + } + } + + private IEnumerable SyncItemsAfterDelay() + { + while (syncItemsDelay > 0.0f && GameMain.Client != null && GameMain.Client.MidRoundSyncing) + { + syncItemsDelay -= CoroutineManager.DeltaTime; + yield return CoroutineStatus.Running; + } + + if (Owner.Removed || GameMain.Client == null) + { + yield return CoroutineStatus.Success; + } + + ApplyReceivedState(); + + yield return CoroutineStatus.Success; + } + + private void ApplyReceivedState() + { + if (receivedItemIDs == null) return; + + for (int i = 0; i < capacity; i++) + { + if (receivedItemIDs[i] == 0) + { + if (Items[i] != null) Items[i].Drop(); + } + else + { + var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; + if (item == null) continue; + + TryPutItem(item, i, true, true, null, false); + } + } + + receivedItemIDs = null; + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 07829eb16..55f6d0feb 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -64,6 +64,11 @@ namespace Barotrauma.Networking { get { return fileReceiver; } } + + public bool MidRoundSyncing + { + get { return entityEventManager.MidRoundSyncing; } + } public GameClient(string newName) { diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 90e02538e..79cfc308b 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -23,6 +23,11 @@ namespace Barotrauma.Networking private UInt16 lastReceivedID; + public bool MidRoundSyncing + { + get { return firstNewID.HasValue; } + } + public ClientEntityEventManager(GameClient client) { events = new List(); @@ -195,13 +200,19 @@ namespace Barotrauma.Networking catch (Exception e) { + string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\"! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace; + errorMsg += "\nPrevious entities:"; + for (int j = entities.Count - 2; j >= 0; j--) + { + errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); + } + if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); } GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), - GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Failed to read event for entity \"" + entity.ToString() + "\"!\n" + e.StackTrace); + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); msg.Position = msgPosition + msgLength * 8; } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs index 7cffc8836..159372397 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs @@ -150,11 +150,9 @@ namespace Barotrauma.Items.Components { foreach (Connection connection in Connections) { - Wire[] wires = Array.FindAll(connection.Wires, w => w != null); - msg.WriteRangedInteger(0, Connection.MaxLinked, wires.Length); - for (int i = 0; i < wires.Length; i++) + for (int i = 0; i < Connection.MaxLinked; i++) { - msg.Write(wires[i].Item.ID); + msg.Write(connection.Wires[i]?.Item == null ? (ushort)0 : connection.Wires[i].Item.ID); } } } @@ -295,52 +293,6 @@ namespace Barotrauma.Items.Components public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { ClientWrite(msg, extraData); - } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - List prevWires = Connections.SelectMany(c => Array.FindAll(c.Wires, w => w != null)).ToList(); - List newWires = new List(); - - foreach (Connection connection in Connections) - { - connection.ClearConnections(); - } - - foreach (Connection connection in Connections) - { - int wireCount = msg.ReadRangedInteger(0, Connection.MaxLinked); - for (int i = 0; i < wireCount; i++) - { - ushort wireId = msg.ReadUInt16(); - - Item wireItem = Entity.FindEntityByID(wireId) as Item; - if (wireItem == null) continue; - - Wire wireComponent = wireItem.GetComponent(); - if (wireComponent == null) continue; - - newWires.Add(wireComponent); - - connection.Wires[i] = wireComponent; - wireComponent.Connect(connection, false); - } - } - - foreach (Wire wire in prevWires) - { - if (wire.Connections[0] == null && wire.Connections[1] == null) - { - wire.Item.Drop(null); - } - //wires that are not in anyone's inventory (i.e. not currently being rewired) can never be connected to only one connection - // -> someone must have dropped the wire from the connection panel - else if (wire.Item.ParentInventory == null && - (wire.Connections[0] != null ^ wire.Connections[1] != null)) - { - wire.Item.Drop(null); - } - } - } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 7ad0320a4..0c622bbc4 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -419,8 +419,8 @@ namespace Barotrauma.Items.Components public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { - //ID of the launched projectile - msg.Write(((Item)extraData[2]).ID); + Item item = (Item)extraData[2]; + msg.Write(item.Removed ? (ushort)0 : item.ID); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs index 43a9a8efa..c315aa67a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs @@ -269,63 +269,5 @@ namespace Barotrauma msg.Write((ushort)(Items[i] == null ? 0 : Items[i].ID)); } } - - public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) - { - receivedItemIDs = new ushort[capacity]; - - for (int i = 0; i < capacity; i++) - { - receivedItemIDs[i] = msg.ReadUInt16(); - } - - if (syncItemsDelay > 0.0f) - { - //delay applying the new state if less than 1 second has passed since this client last sent a state to the server - //prevents the inventory from briefly reverting to an old state if items are moved around in quick succession - if (syncItemsCoroutine != null) CoroutineManager.StopCoroutines(syncItemsCoroutine); - - syncItemsCoroutine = CoroutineManager.StartCoroutine(SyncItemsAfterDelay()); - } - else - { - ApplyReceivedState(); - } - } - - private IEnumerable SyncItemsAfterDelay() - { - while (syncItemsDelay > 0.0f) - { - syncItemsDelay -= CoroutineManager.DeltaTime; - yield return CoroutineStatus.Running; - } - - ApplyReceivedState(); - - yield return CoroutineStatus.Success; - } - - private void ApplyReceivedState() - { - if (receivedItemIDs == null) return; - - for (int i = 0; i < capacity; i++) - { - if (receivedItemIDs[i] == 0) - { - if (Items[i] != null) Items[i].Drop(); - } - else - { - var item = Entity.FindEntityByID(receivedItemIDs[i]) as Item; - if (item == null) continue; - - TryPutItem(item, i, true, true, null, false); - } - } - - receivedItemIDs = null; - } } }