EntitySpawner sends spawn/removal messages to clients using EntityEvents.

EntityEvents and EntitySpawner used to work independently of each other, with separate IDs, and there was no guarantee that spawning and events would happen in the correct order. For example, a client could fail to read events during midround syncing because the entity has been removed, or read an event for an incorrect entity because the entity has been removed and the ID taken by some other entity.
This commit is contained in:
Regalis
2017-03-27 20:44:20 +03:00
parent 6a6486e270
commit 1a3c18c727
13 changed files with 121 additions and 196 deletions
+2 -5
View File
@@ -38,9 +38,7 @@ namespace Barotrauma.Networking
public UInt16 lastSentEntityEventID = 0;
public UInt16 lastRecvEntityEventID = 0;
public UInt16 lastRecvEntitySpawnID = 0;
public List<ChatMessage> chatMsgQueue = new List<ChatMessage>();
public UInt16 lastChatMsgQueueID;
public float ChatSpamSpeed;
@@ -76,8 +74,7 @@ namespace Barotrauma.Networking
lastRecvChatMsgID = ChatMessage.LastID;
lastRecvGeneralUpdate = 0;
lastRecvEntitySpawnID = 0;
lastRecvEntityEventID = 0;
UnreceivedEntityEventCount = 0;
+55 -100
View File
@@ -6,18 +6,12 @@ using System.Linq;
namespace Barotrauma
{
class EntitySpawner : IServerSerializable
class EntitySpawner : Entity, IServerSerializable
{
const int MaxEntitiesPerWrite = 10;
private enum SpawnableType { Item, Character };
public UInt16 NetStateID
{
get;
private set;
}
interface IEntitySpawnInfo
{
Entity Spawn();
@@ -84,9 +78,8 @@ namespace Barotrauma
}
}
private List<SpawnOrRemove> spawnHistory = new List<SpawnOrRemove>();
public EntitySpawner()
: base(null)
{
spawnQueue = new Queue<IEntitySpawnInfo>();
removeQueue = new Queue<Entity>();
@@ -132,6 +125,13 @@ namespace Barotrauma
}
}
public void CreateNetworkEvent(Entity entity, bool remove)
{
if (GameMain.Server != null && entity != null)
{
GameMain.Server.CreateEntityEvent(this, new object[] { new SpawnOrRemove(entity, remove) });
}
}
public void Update()
{
@@ -142,38 +142,22 @@ namespace Barotrauma
var entitySpawnInfo = spawnQueue.Dequeue();
var spawnedEntity = entitySpawnInfo.Spawn();
if (spawnedEntity != null) AddToSpawnedList(spawnedEntity);
if (spawnedEntity != null)
{
CreateNetworkEvent(spawnedEntity, false);
}
}
while (removeQueue.Count > 0)
{
var entity = removeQueue.Dequeue();
spawnHistory.Add(new SpawnOrRemove(entity, true));
var removedEntity = removeQueue.Dequeue();
entity.Remove();
NetStateID = (UInt16)spawnHistory.Count;
}
}
if (GameMain.Server != null)
{
CreateNetworkEvent(removedEntity, true);
}
public void AddToSpawnedList(Entity entity)
{
if (GameMain.Server == null) return;
if (entity == null) return;
spawnHistory.Add(new SpawnOrRemove(entity, false));
NetStateID = (UInt16)spawnHistory.Count;
}
public void AddToSpawnedList(IEnumerable<Entity> entities)
{
if (GameMain.Server == null) return;
if (entities == null) return;
foreach (Entity entity in entities)
{
spawnHistory.Add(new SpawnOrRemove(entity, false));
NetStateID = (UInt16)spawnHistory.Count;
removedEntity.Remove();
}
}
@@ -181,89 +165,60 @@ namespace Barotrauma
{
if (GameMain.Server == null) return;
//skip items that the client already knows about
List<SpawnOrRemove> entities = spawnHistory.Skip((int)client.lastRecvEntitySpawnID).ToList();
SpawnOrRemove entities = (SpawnOrRemove)extraData[0];
message.Write(entities.Remove);
if (entities.Count > MaxEntitiesPerWrite)
if (entities.Remove)
{
entities = entities.GetRange(0, MaxEntitiesPerWrite);
message.Write(entities.Entity.ID);
}
message.Write((UInt16)(spawnHistory.IndexOf(entities[0])+1));
message.WriteRangedInteger(0, MaxEntitiesPerWrite, entities.Count);
for (int i = 0; i < entities.Count; i++)
else
{
message.Write(entities[i].Remove);
if (entities[i].Remove)
if (entities.Entity is Item)
{
message.Write(entities[i].Entity.ID);
message.Write((byte)SpawnableType.Item);
((Item)entities.Entity).WriteSpawnData(message);
}
else
else if (entities.Entity is Character)
{
if (entities[i].Entity is Item)
{
message.Write((byte)SpawnableType.Item);
((Item)entities[i].Entity).WriteSpawnData(message);
}
else if (entities[i].Entity is Character)
{
message.Write((byte)SpawnableType.Character);
((Character)entities[i].Entity).WriteSpawnData(message);
}
message.Write((byte)SpawnableType.Character);
((Character)entities.Entity).WriteSpawnData(message);
}
}
}
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer message, float sendingTime)
{
if (GameMain.Server != null) return;
UInt16 ID = message.ReadUInt16();
var entityCount = message.ReadRangedInteger(0, MaxEntitiesPerWrite);
for (int i = 0; i < entityCount; i++)
bool remove = message.ReadBoolean();
if (remove)
{
bool remove = message.ReadBoolean();
ushort entityId = message.ReadUInt16();
if (remove)
var entity = FindEntityByID(entityId);
if (entity != null)
{
ushort entityId = message.ReadUInt16();
var entity = Entity.FindEntityByID(entityId);
if (entity != null && NetIdUtils.IdMoreRecent((UInt16)(ID + i), NetStateID))
{
entity.Remove();
}
}
else
{
switch (message.ReadByte())
{
case (byte)SpawnableType.Item:
Item.ReadSpawnData(message, NetIdUtils.IdMoreRecent((UInt16)(ID + i), NetStateID));
break;
case (byte)SpawnableType.Character:
Character.ReadSpawnData(message, NetIdUtils.IdMoreRecent((UInt16)(ID + i), NetStateID));
break;
default:
DebugConsole.ThrowError("Received invalid entity spawn message (unknown spawnable type)");
break;
}
entity.Remove();
}
}
else
{
switch (message.ReadByte())
{
case (byte)SpawnableType.Item:
Item.ReadSpawnData(message, true);
break;
case (byte)SpawnableType.Character:
Character.ReadSpawnData(message, true);
break;
default:
DebugConsole.ThrowError("Received invalid entity spawn message (unknown spawnable type)");
break;
}
}
NetStateID = Math.Max((UInt16)(ID + entityCount - 1), NetStateID);
}
public void Clear()
{
NetStateID = 0;
spawnQueue.Clear();
removeQueue.Clear();
spawnHistory.Clear();
}
}
}
+1 -10
View File
@@ -2,11 +2,8 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using FarseerPhysics;
using System.IO;
using System.Linq;
using System.Text;
using Barotrauma.Items.Components;
using System.ComponentModel;
namespace Barotrauma.Networking
@@ -635,8 +632,7 @@ namespace Barotrauma.Networking
//enable spectate button in case we fail to start the round now
//(for example, due to a missing sub file or an error)
GameMain.NetLobbyScreen.ShowSpectateButton();
Entity.Spawner.Clear();
entityEventManager.Clear();
LastSentEntityEventID = 0;
@@ -921,10 +917,6 @@ namespace Barotrauma.Networking
case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc);
break;
case ServerNetObject.ENTITY_SPAWN:
Item.Spawner.ClientRead(objHeader, inc, sendingTime);
inc.ReadPadBits();
break;
default:
DebugConsole.ThrowError("Error while reading update from server (unknown object header \""+objHeader+"\"!)");
break;
@@ -959,7 +951,6 @@ namespace Barotrauma.Networking
outmsg.Write((byte)ClientNetObject.SYNC_IDS);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.Write(ChatMessage.LastID);
outmsg.Write(Entity.Spawner.NetStateID);
outmsg.Write(entityEventManager.LastReceivedID);
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
+7 -32
View File
@@ -680,11 +680,9 @@ namespace Barotrauma.Networking
//TODO: might want to use a clever class for this
UInt16 lastRecvChatMsgID = inc.ReadUInt16();
UInt16 lastRecvEntitySpawnID = inc.ReadUInt16();
UInt16 lastRecvEntityEventID = inc.ReadUInt16();
//last msgs we've created/sent, the client IDs should never be higher than these
UInt16 lastEntitySpawnID = Entity.Spawner.NetStateID;
UInt16 lastEntityEventID = entityEventManager.Events.Count == 0 ? (UInt16)0 : entityEventManager.Events.Last().ID;
if (c.NeedsMidRoundSync)
@@ -705,20 +703,14 @@ namespace Barotrauma.Networking
//client thinks they've received a msg we haven't sent yet (corrupted packet, msg read/written incorrectly?)
if (NetIdUtils.IdMoreRecent(lastRecvChatMsgID, c.lastChatMsgQueueID))
DebugConsole.ThrowError("client.lastRecvChatMsgID > lastChatMsgQueueID");
if (lastRecvEntitySpawnID > lastEntitySpawnID)
DebugConsole.ThrowError("client.lastRecvEntitySpawnID > lastEntitySpawnID");
if (lastRecvEntityEventID > lastEntityEventID)
DebugConsole.ThrowError("client.lastRecvEntityEventID > lastEntityEventID");
#endif
if (NetIdUtils.IdMoreRecent(lastRecvChatMsgID, c.lastRecvChatMsgID)) c.lastRecvChatMsgID = lastRecvChatMsgID;
if (NetIdUtils.IdMoreRecent(c.lastRecvChatMsgID, c.lastChatMsgQueueID)) c.lastRecvChatMsgID = c.lastChatMsgQueueID;
if (NetIdUtils.IdMoreRecent(lastRecvEntitySpawnID, c.lastRecvEntitySpawnID)) c.lastRecvEntitySpawnID = lastRecvEntitySpawnID;
if (NetIdUtils.IdMoreRecent(c.lastRecvEntitySpawnID, lastEntitySpawnID)) c.lastRecvEntitySpawnID = lastEntitySpawnID;
if (NetIdUtils.IdMoreRecent(lastRecvEntityEventID, c.lastRecvEntityEventID)) c.lastRecvEntityEventID = lastRecvEntityEventID;
if (NetIdUtils.IdMoreRecent(c.lastRecvEntityEventID, lastEntityEventID)) c.lastRecvEntityEventID = lastEntityEventID;
@@ -839,14 +831,7 @@ namespace Barotrauma.Networking
{
cMsg.ServerWrite(outmsg, c);
}
if (Item.Spawner.NetStateID > c.lastRecvEntitySpawnID)
{
outmsg.Write((byte)ServerNetObject.ENTITY_SPAWN);
Item.Spawner.ServerWrite(outmsg, c);
outmsg.WritePadBits();
}
foreach (Character character in Character.CharacterList)
{
if (!character.Enabled) continue;
@@ -1054,8 +1039,7 @@ namespace Barotrauma.Networking
private IEnumerable<object> StartGame(Submarine selectedSub, Submarine selectedShuttle, GameModePreset selectedMode)
{
initiatedStartGame = true;
Item.Spawner.Clear();
entityEventManager.Clear();
GameMain.NetLobbyScreen.StartButton.Enabled = false;
@@ -1103,9 +1087,7 @@ namespace Barotrauma.Networking
foreach (Client client in teamClients)
{
client.NeedsMidRoundSync = false;
client.lastRecvEntitySpawnID = 0;
client.entityEventLastSent.Clear();
client.lastSentEntityEventID = 0;
client.lastRecvEntityEventID = 0;
@@ -1149,13 +1131,7 @@ namespace Barotrauma.Networking
GameMain.GameSession.CrewManager.characters.Add(myCharacter);
}
}
foreach (Character c in GameMain.GameSession.CrewManager.characters)
{
Entity.Spawner.AddToSpawnedList(c);
Entity.Spawner.AddToSpawnedList(c.SpawnItems);
}
TraitorManager = null;
if (TraitorsEnabled == YesNoMaybe.Yes ||
(TraitorsEnabled == YesNoMaybe.Maybe && Rand.Range(0.0f, 1.0f) < 0.5f))
@@ -1268,8 +1244,7 @@ namespace Barotrauma.Networking
myCharacter = null;
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false;
Item.Spawner.Clear();
entityEventManager.Clear();
foreach (Client c in connectedClients)
{
@@ -54,9 +54,7 @@ namespace Barotrauma.Networking
VOTE,
ENTITY_POSITION,
ENTITY_EVENT,
ENTITY_EVENT_INITIAL,
ENTITY_SPAWN
ENTITY_EVENT_INITIAL
}
enum VoteType
+8 -13
View File
@@ -428,7 +428,6 @@ namespace Barotrauma.Networking
bool myCharacter = i >= clients.Count;
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, !myCharacter, false);
Entity.Spawner.AddToSpawnedList(character);
character.TeamID = 1;
@@ -448,29 +447,25 @@ namespace Barotrauma.Networking
if (divingSuitPrefab != null && oxyPrefab != null)
{
var divingSuit = new Item(divingSuitPrefab, pos, respawnShuttle);
Entity.Spawner.CreateNetworkEvent(divingSuit, false);
var oxyTank = new Item(oxyPrefab, pos, respawnShuttle);
divingSuit.Combine(oxyTank);
spawnedItems.Add(divingSuit);
spawnedItems.Add(oxyTank);
Entity.Spawner.CreateNetworkEvent(oxyTank, false);
divingSuit.Combine(oxyTank);
}
if (scooterPrefab != null && batteryPrefab != null)
{
var scooter = new Item(scooterPrefab, pos, respawnShuttle);
Entity.Spawner.CreateNetworkEvent(scooter, false);
var battery = new Item(batteryPrefab, pos, respawnShuttle);
Entity.Spawner.CreateNetworkEvent(battery, false);
scooter.Combine(battery);
spawnedItems.Add(scooter);
spawnedItems.Add(battery);
}
character.GiveJobItems(mainSubSpawnPoints[i]);
Entity.Spawner.AddToSpawnedList(character.SpawnItems);
Entity.Spawner.AddToSpawnedList(spawnedItems);
GameMain.GameSession.CrewManager.characters.Add(character);
}