diff --git a/Subsurface/Source/Characters/CharacterNetworking.cs b/Subsurface/Source/Characters/CharacterNetworking.cs index 778d0c2e0..f0e1ac9f3 100644 --- a/Subsurface/Source/Characters/CharacterNetworking.cs +++ b/Subsurface/Source/Characters/CharacterNetworking.cs @@ -480,7 +480,7 @@ namespace Barotrauma memPos.Insert(index, posInfo); break; - case ServerNetObject.ENTITY_STATE: + case ServerNetObject.ENTITY_EVENT: bool isInventoryUpdate = msg.ReadBoolean(); if (isInventoryUpdate) diff --git a/Subsurface/Source/Networking/Client.cs b/Subsurface/Source/Networking/Client.cs index 7725d8b9f..575cd448a 100644 --- a/Subsurface/Source/Networking/Client.cs +++ b/Subsurface/Source/Networking/Client.cs @@ -47,6 +47,10 @@ namespace Barotrauma.Networking public float ChatSpamTimer; public int ChatSpamCount; + public bool NeedsMidRoundSync; + //how many unique events the client missed before joining the server + public UInt32 UnreceivedEntityEventCount; + private List kickVoters; //when was a specific entity event last sent to the client @@ -73,6 +77,9 @@ namespace Barotrauma.Networking lastRecvEntitySpawnID = 0; lastRecvEntityEventID = 0; + + UnreceivedEntityEventCount = 0; + NeedsMidRoundSync = false; } public int KickVoteCount diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 652d17c7a..f69744e7b 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -646,7 +646,7 @@ namespace Barotrauma.Networking bool isTraitor = inc.ReadBoolean(); string traitorTargetName = isTraitor ? inc.ReadString() : null; - + GameModePreset gameMode = GameModePreset.list.Find(gm => gm.Name == modeName); if (gameMode == null) @@ -900,8 +900,9 @@ namespace Barotrauma.Networking inc.ReadPadBits(); break; - case ServerNetObject.ENTITY_STATE: - entityEventManager.Read(inc, sendingTime); + case ServerNetObject.ENTITY_EVENT: + case ServerNetObject.ENTITY_EVENT_INITIAL: + entityEventManager.Read(objHeader, inc, sendingTime); break; case ServerNetObject.CHAT_MESSAGE: ChatMessage.ClientRead(inc); diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index 52a9eca05..aba979f74 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -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) { name = name.Replace(":", ""); @@ -527,6 +533,7 @@ namespace Barotrauma.Networking //game already started -> send start message immediately if (gameStarted) { + connectedClient.NeedsMidRoundSync = true; SendStartMessage(roundStartSeed, Submarine.MainSub, GameMain.GameSession.gameMode.Preset, connectedClient); } } @@ -606,7 +613,16 @@ namespace Barotrauma.Networking 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; 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 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 //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"); #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.lastRecvEntityEventID = Math.Min(Math.Max(c.lastRecvEntityEventID, lastRecvEntityEventID), lastEntityEventID); - break; case ClientNetObject.CHAT_MESSAGE: ChatMessage.ServerRead(inc, c); diff --git a/Subsurface/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Subsurface/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 7a830017b..f3866e6ca 100644 --- a/Subsurface/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Subsurface/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -97,12 +97,21 @@ namespace Barotrauma.Networking msg.Write((byte)ClientNetObject.ENTITY_STATE); Write(msg, eventsToSync); } - + /// /// Read the events from the message, ignoring ones we've already received /// - 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(); int eventCount = msg.ReadByte(); @@ -148,6 +157,15 @@ namespace Barotrauma.Networking } 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) @@ -160,7 +178,7 @@ namespace Barotrauma.Networking 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() diff --git a/Subsurface/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Subsurface/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs index 05c5b824a..c370dc2f1 100644 --- a/Subsurface/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs +++ b/Subsurface/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs @@ -11,6 +11,10 @@ namespace Barotrauma.Networking { private List events; + //list of unique events (i.e. !IsDuplicate) created during the round + //used for syncing clients who join mid-round + private List uniqueEvents; + private UInt32 lastSentToAll; public List Events @@ -56,6 +60,8 @@ namespace Barotrauma.Networking this.server = server; bufferedEvents = new List(); + + uniqueEvents = new List(); } public void CreateEvent(IServerSerializable entity, object[] extraData = null) @@ -69,7 +75,7 @@ namespace Barotrauma.Networking var newEvent = new ServerEntityEvent(entity, ID + 1); 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--) { //we already have an identical event that's waiting to be sent @@ -80,6 +86,15 @@ namespace Barotrauma.Networking ID++; 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 clients) @@ -140,30 +155,16 @@ namespace Barotrauma.Networking { if (events.Count == 0) return; - List eventsToSync = new List(); - - //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) + List eventsToSync = null; + if (client.NeedsMidRoundSync) { - 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; //too many events for one packet @@ -178,8 +179,73 @@ namespace Barotrauma.Networking client.entityEventLastSent[entityEvent.ID] = (float)NetTime.Now; } - msg.Write((byte)ServerNetObject.ENTITY_STATE); - Write(msg, eventsToSync, client); + if (client.NeedsMidRoundSync) + { + 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); + } + } + + /// + /// Returns a list of events that should be sent to the client from the eventList + /// + /// + /// + /// + private List GetEventsToSync(Client client, List eventList) + { + List eventsToSync = new List(); + + //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; + } } /// @@ -248,6 +314,8 @@ namespace Barotrauma.Networking bufferedEvents.Clear(); + uniqueEvents.Clear(); + server.ConnectedClients.ForEach(c => c.entityEventLastSent.Clear()); } } diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index 7a93d58e0..899fc873a 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -49,7 +49,8 @@ namespace Barotrauma.Networking CHAT_MESSAGE, VOTE, ENTITY_POSITION, - ENTITY_STATE, + ENTITY_EVENT, + ENTITY_EVENT_INITIAL, ENTITY_SPAWN } @@ -127,7 +128,6 @@ namespace Barotrauma.Networking get { return inGameHUD; } } - public virtual List ConnectedClients { get { return null; }