The server maintains a list of unique EntityEvents created during the round and sends them to clients who join mid-round

This commit is contained in:
Regalis
2017-02-18 15:09:13 +02:00
parent 0e66f2a69c
commit 6c8f5b8999
7 changed files with 160 additions and 37 deletions
@@ -480,7 +480,7 @@ namespace Barotrauma
memPos.Insert(index, posInfo); memPos.Insert(index, posInfo);
break; break;
case ServerNetObject.ENTITY_STATE: case ServerNetObject.ENTITY_EVENT:
bool isInventoryUpdate = msg.ReadBoolean(); bool isInventoryUpdate = msg.ReadBoolean();
if (isInventoryUpdate) if (isInventoryUpdate)
+7
View File
@@ -47,6 +47,10 @@ namespace Barotrauma.Networking
public float ChatSpamTimer; public float ChatSpamTimer;
public int ChatSpamCount; public int ChatSpamCount;
public bool NeedsMidRoundSync;
//how many unique events the client missed before joining the server
public UInt32 UnreceivedEntityEventCount;
private List<Client> kickVoters; private List<Client> kickVoters;
//when was a specific entity event last sent to the client //when was a specific entity event last sent to the client
@@ -73,6 +77,9 @@ namespace Barotrauma.Networking
lastRecvEntitySpawnID = 0; lastRecvEntitySpawnID = 0;
lastRecvEntityEventID = 0; lastRecvEntityEventID = 0;
UnreceivedEntityEventCount = 0;
NeedsMidRoundSync = false;
} }
public int KickVoteCount public int KickVoteCount
+3 -2
View File
@@ -900,8 +900,9 @@ namespace Barotrauma.Networking
inc.ReadPadBits(); inc.ReadPadBits();
break; break;
case ServerNetObject.ENTITY_STATE: case ServerNetObject.ENTITY_EVENT:
entityEventManager.Read(inc, sendingTime); case ServerNetObject.ENTITY_EVENT_INITIAL:
entityEventManager.Read(objHeader, inc, sendingTime);
break; break;
case ServerNetObject.CHAT_MESSAGE: case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc); ChatMessage.ClientRead(inc);
+33 -4
View File
@@ -53,6 +53,12 @@ namespace Barotrauma.Networking
} }
} }
public ServerEntityEventManager EntityEventManager
{
get { return entityEventManager; }
}
public GameServer(string name, int port, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10) public GameServer(string name, int port, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10)
{ {
name = name.Replace(":", ""); name = name.Replace(":", "");
@@ -527,6 +533,7 @@ namespace Barotrauma.Networking
//game already started -> send start message immediately //game already started -> send start message immediately
if (gameStarted) if (gameStarted)
{ {
connectedClient.NeedsMidRoundSync = true;
SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset, connectedClient); SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset, connectedClient);
} }
} }
@@ -606,7 +613,16 @@ namespace Barotrauma.Networking
return; return;
} }
if (gameStarted) c.inGame = true; if (gameStarted)
{
if (!c.inGame && c.NeedsMidRoundSync)
{
//client joined mid-round and has just started up the game
//check which unique messages they've missed
entityEventManager.InitClientMidRoundSync(c);
}
c.inGame = true;
}
ClientNetObject objHeader; ClientNetObject objHeader;
while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE) while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE)
@@ -622,7 +638,21 @@ namespace Barotrauma.Networking
//last msgs we've created/sent, the client IDs should never be higher than these //last msgs we've created/sent, the client IDs should never be higher than these
UInt32 lastEntitySpawnID = Entity.Spawner.NetStateID; UInt32 lastEntitySpawnID = Entity.Spawner.NetStateID;
UInt32 lastEntityEventID = entityEventManager.Events.Count() == 0 ? 0 : entityEventManager.Events.Last().ID; UInt32 lastEntityEventID = entityEventManager.Events.Count == 0 ? 0 : entityEventManager.Events.Last().ID;
if (c.NeedsMidRoundSync)
{
//received all the old events -> client in sync, we can switch to normal behavior
if (lastRecvEntityEventID >= c.UnreceivedEntityEventCount - 1 ||
c.UnreceivedEntityEventCount == 0)
{
c.NeedsMidRoundSync = false;
}
else
{
lastEntityEventID = (uint)c.UnreceivedEntityEventCount - 1;
}
}
#if DEBUG #if DEBUG
//client thinks they've received a msg we haven't sent yet (corrupted packet, msg read/written incorrectly?) //client thinks they've received a msg we haven't sent yet (corrupted packet, msg read/written incorrectly?)
@@ -636,11 +666,10 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("client.lastRecvEntityEventID > lastEntityEventID"); DebugConsole.ThrowError("client.lastRecvEntityEventID > lastEntityEventID");
#endif #endif
c.lastRecvChatMsgID = Math.Min(Math.Max(c.lastRecvChatMsgID, lastRecvChatMsgID), c.lastChatMsgQueueID); c.lastRecvChatMsgID = NetIdUtils.Clamp(c.lastRecvChatMsgID, lastRecvChatMsgID, c.lastChatMsgQueueID);
c.lastRecvEntitySpawnID = Math.Min(Math.Max(c.lastRecvEntitySpawnID, lastRecvEntitySpawnID), lastEntitySpawnID); c.lastRecvEntitySpawnID = Math.Min(Math.Max(c.lastRecvEntitySpawnID, lastRecvEntitySpawnID), lastEntitySpawnID);
c.lastRecvEntityEventID = Math.Min(Math.Max(c.lastRecvEntityEventID, lastRecvEntityEventID), lastEntityEventID); c.lastRecvEntityEventID = Math.Min(Math.Max(c.lastRecvEntityEventID, lastRecvEntityEventID), lastEntityEventID);
break; break;
case ClientNetObject.CHAT_MESSAGE: case ClientNetObject.CHAT_MESSAGE:
ChatMessage.ServerRead(inc, c); ChatMessage.ServerRead(inc, c);
@@ -101,8 +101,17 @@ namespace Barotrauma.Networking
/// <summary> /// <summary>
/// Read the events from the message, ignoring ones we've already received /// Read the events from the message, ignoring ones we've already received
/// </summary> /// </summary>
public void Read(NetIncomingMessage msg, float sendingTime) public void Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime)
{ {
UInt32 unreceivedEntityEventCount = 0;
UInt32 firstNewID = 0;
if (type == ServerNetObject.ENTITY_EVENT_INITIAL)
{
unreceivedEntityEventCount = msg.ReadUInt32();
firstNewID = msg.ReadUInt32();
}
UInt32 firstEventID = msg.ReadUInt32(); UInt32 firstEventID = msg.ReadUInt32();
int eventCount = msg.ReadByte(); int eventCount = msg.ReadByte();
@@ -148,6 +157,15 @@ namespace Barotrauma.Networking
} }
msg.ReadPadBits(); msg.ReadPadBits();
} }
if (type == ServerNetObject.ENTITY_EVENT_INITIAL)
{
if (lastReceivedID == unreceivedEntityEventCount - 1 ||
unreceivedEntityEventCount == 0)
{
lastReceivedID = firstNewID - 1;
}
}
} }
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null) protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
@@ -160,7 +178,7 @@ namespace Barotrauma.Networking
protected void ReadEvent(NetIncomingMessage buffer, IServerSerializable entity, float sendingTime) protected void ReadEvent(NetIncomingMessage buffer, IServerSerializable entity, float sendingTime)
{ {
entity.ClientRead(ServerNetObject.ENTITY_STATE, buffer, sendingTime); entity.ClientRead(ServerNetObject.ENTITY_EVENT, buffer, sendingTime);
} }
public void Clear() public void Clear()
@@ -11,6 +11,10 @@ namespace Barotrauma.Networking
{ {
private List<ServerEntityEvent> events; private List<ServerEntityEvent> events;
//list of unique events (i.e. !IsDuplicate) created during the round
//used for syncing clients who join mid-round
private List<ServerEntityEvent> uniqueEvents;
private UInt32 lastSentToAll; private UInt32 lastSentToAll;
public List<ServerEntityEvent> Events public List<ServerEntityEvent> Events
@@ -56,6 +60,8 @@ namespace Barotrauma.Networking
this.server = server; this.server = server;
bufferedEvents = new List<BufferedEvent>(); bufferedEvents = new List<BufferedEvent>();
uniqueEvents = new List<ServerEntityEvent>();
} }
public void CreateEvent(IServerSerializable entity, object[] extraData = null) public void CreateEvent(IServerSerializable entity, object[] extraData = null)
@@ -69,7 +75,7 @@ namespace Barotrauma.Networking
var newEvent = new ServerEntityEvent(entity, ID + 1); var newEvent = new ServerEntityEvent(entity, ID + 1);
if (extraData != null) newEvent.SetData(extraData); if (extraData != null) newEvent.SetData(extraData);
events.RemoveAll(e => e.ID <= lastSentToAll && e.IsDuplicate(newEvent)); //remove outdated events, they are redundant now events.RemoveAll(e => e.ID <= lastSentToAll); //remove events that , they are redundant now
for (int i = events.Count - 1; i >= 0; i--) for (int i = events.Count - 1; i >= 0; i--)
{ {
//we already have an identical event that's waiting to be sent //we already have an identical event that's waiting to be sent
@@ -80,6 +86,15 @@ namespace Barotrauma.Networking
ID++; ID++;
events.Add(newEvent); events.Add(newEvent);
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
{
//create a copy of the event and give it a new ID
var uniqueEvent = new ServerEntityEvent(entity, (uint)uniqueEvents.Count);
uniqueEvent.SetData(extraData);
uniqueEvents.Add(uniqueEvent);
}
} }
public void Update(List<Client> clients) public void Update(List<Client> clients)
@@ -140,30 +155,16 @@ namespace Barotrauma.Networking
{ {
if (events.Count == 0) return; if (events.Count == 0) return;
List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>(); List<NetEntityEvent> eventsToSync = null;
if (client.NeedsMidRoundSync)
//find the index of the first event the client hasn't received
int startIndex = events.Count;
while (startIndex > 0 &&
events[startIndex-1].ID > client.lastRecvEntityEventID)
{ {
startIndex--; eventsToSync = GetEventsToSync(client, uniqueEvents);
}
else
{
eventsToSync = GetEventsToSync(client, events);
} }
for (int i = startIndex; i < events.Count; i++)
{
//find the first event that hasn't been sent in 1.5 * roundtriptime or at all
float lastSent = 0;
client.entityEventLastSent.TryGetValue(events[i].ID, out lastSent);
if (lastSent > NetTime.Now - client.Connection.AverageRoundtripTime * 1.5f)
{
continue;
}
eventsToSync.AddRange(events.GetRange(i, events.Count - i));
break;
}
if (eventsToSync.Count == 0) return; if (eventsToSync.Count == 0) return;
//too many events for one packet //too many events for one packet
@@ -178,8 +179,73 @@ namespace Barotrauma.Networking
client.entityEventLastSent[entityEvent.ID] = (float)NetTime.Now; client.entityEventLastSent[entityEvent.ID] = (float)NetTime.Now;
} }
msg.Write((byte)ServerNetObject.ENTITY_STATE); if (client.NeedsMidRoundSync)
Write(msg, eventsToSync, client); {
msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
//how many (unique) events the clients had missed before joining
msg.Write(client.UnreceivedEntityEventCount);
//ID of the first event sent after the client joined
//(after the client has been synced they'll switch their lastReceivedID
//to the one before this, and the eventmanagers will start to function "normally")
msg.Write(events.Count == 0 ? 0 : events[events.Count - 1].ID);
Write(msg, eventsToSync, client);
}
else
{
msg.Write((byte)ServerNetObject.ENTITY_EVENT);
Write(msg, eventsToSync, client);
}
}
/// <summary>
/// Returns a list of events that should be sent to the client from the eventList
/// </summary>
/// <param name="client"></param>
/// <param name="eventList"></param>
/// <returns></returns>
private List<NetEntityEvent> GetEventsToSync(Client client, List<ServerEntityEvent> eventList)
{
List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>();
//find the index of the first event the client hasn't received
int startIndex = eventList.Count;
while (startIndex > 0 &&
eventList[startIndex - 1].ID > client.lastRecvEntityEventID)
{
startIndex--;
}
for (int i = startIndex; i < eventList.Count; i++)
{
//find the first event that hasn't been sent in 1.5 * roundtriptime or at all
float lastSent = 0;
client.entityEventLastSent.TryGetValue(eventList[i].ID, out lastSent);
if (lastSent > NetTime.Now - client.Connection.AverageRoundtripTime * 1.5f)
{
continue;
}
eventsToSync.AddRange(eventList.GetRange(i, eventList.Count - i));
break;
}
return eventsToSync;
}
public void InitClientMidRoundSync(Client client)
{
if (uniqueEvents.Count > 0)
{
client.UnreceivedEntityEventCount = (UInt32)uniqueEvents.Count;
client.NeedsMidRoundSync = true;
}
else
{
client.UnreceivedEntityEventCount = 0;
client.NeedsMidRoundSync = false;
}
} }
/// <summary> /// <summary>
@@ -248,6 +314,8 @@ namespace Barotrauma.Networking
bufferedEvents.Clear(); bufferedEvents.Clear();
uniqueEvents.Clear();
server.ConnectedClients.ForEach(c => c.entityEventLastSent.Clear()); server.ConnectedClients.ForEach(c => c.entityEventLastSent.Clear());
} }
} }
@@ -49,7 +49,8 @@ namespace Barotrauma.Networking
CHAT_MESSAGE, CHAT_MESSAGE,
VOTE, VOTE,
ENTITY_POSITION, ENTITY_POSITION,
ENTITY_STATE, ENTITY_EVENT,
ENTITY_EVENT_INITIAL,
ENTITY_SPAWN ENTITY_SPAWN
} }
@@ -127,7 +128,6 @@ namespace Barotrauma.Networking
get { return inGameHUD; } get { return inGameHUD; }
} }
public virtual List<Client> ConnectedClients public virtual List<Client> ConnectedClients
{ {
get { return null; } get { return null; }