2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
14 public string StackTrace;
17 private double createTime;
20 get {
return createTime; }
25 createTime = Timing.TotalTime;
29 : base(serializableEntity, id)
31 serializable = serializableEntity;
32 createTime = Timing.TotalTime;
35 StackTrace = Environment.StackTrace.CleanupStackTrace();
47 private readonly List<ServerEntityEvent> events;
51 private readonly List<ServerEntityEvent> uniqueEvents;
53 private UInt16 lastSentToAll;
54 private UInt16 lastSentToAnyone;
55 private double lastSentToAnyoneTime;
56 private double lastWarningTime;
58 public List<ServerEntityEvent>
Events
60 get {
return events; }
65 get {
return uniqueEvents; }
68 private class BufferedEvent
70 public readonly
Client Sender;
72 public readonly UInt16 CharacterStateID;
73 public readonly ReadWriteMessage
Data;
79 public bool IsProcessed;
84 public bool RequireCharacter =
true;
89 this.Character = senderCharacter;
90 this.CharacterStateID = characterStateID;
92 this.TargetEntity = targetEntity;
98 private readonly List<BufferedEvent> bufferedEvents;
102 private readonly GameServer server;
104 private double lastEventCountHighWarning;
108 events =
new List<ServerEntityEvent>();
110 this.server = server;
112 bufferedEvents =
new List<BufferedEvent>();
114 uniqueEvents =
new List<ServerEntityEvent>();
116 lastWarningTime = -10.0;
124 if (extraData !=
null) newEvent.SetData(extraData);
126 bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
133 events.RemoveAll(e =>
134 (NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
135 e.CreateTime < Timing.TotalTime - NetConfig.EventRemovalTime);
138 for (
int i = events.Count - 1; i >= 0; i--)
142 if (events[i].IsDuplicate(newEvent) && !events[i].Sent)
return;
147 events.Add(newEvent);
149 if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
152 var uniqueEvent =
new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
153 uniqueEvent.SetData(extraData);
155 uniqueEvents.Add(uniqueEvent);
161 foreach (BufferedEvent bufferedEvent
in bufferedEvents)
163 if (bufferedEvent.Character ==
null || bufferedEvent.Character.IsDead)
165 if (bufferedEvent.RequireCharacter)
167 bufferedEvent.IsProcessed =
true;
176 if (bufferedEvent.Character !=
null &&
177 !bufferedEvent.Character.IsIncapacitated &&
178 NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID))
180 DebugConsole.Log($
"Delaying reading entity event sent by a client until the character state has been processed. Event's character state: {bufferedEvent.CharacterStateID}, last processed character state: {bufferedEvent.Character.LastProcessedID}");
186 ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender);
191 string entityName = bufferedEvent.TargetEntity ==
null ?
"null" : bufferedEvent.TargetEntity.ToString();
192 if (GameSettings.CurrentConfig.VerboseLogging)
194 string errorMsg =
"Failed to read server event for entity \"" + entityName +
"\"!";
196 DebugConsole.ThrowError(errorMsg, e);
198 GameAnalyticsManager.AddErrorEventOnce(
"ServerEntityEventManager.Read:ReadFailed" + entityName,
199 GameAnalyticsManager.ErrorSeverity.Error,
200 "Failed to read server event for entity \"" + entityName +
"\"!\n" + e.StackTrace.CleanupStackTrace());
203 bufferedEvent.IsProcessed =
true;
206 var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync);
207 if (inGameClients.Count > 0)
209 lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
210 lastSentToAll = inGameClients[0].LastRecvEntityEventID;
212 if (server.OwnerConnection !=
null)
214 var owner = clients.Find(c => c.Connection == server.OwnerConnection);
217 lastSentToAll = owner.LastRecvEntityEventID;
220 inGameClients.ForEach(c =>
222 if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; }
223 if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; }
225 lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
227 if (Timing.TotalTime - lastWarningTime > 5.0 &&
228 Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
231 lastWarningTime = Timing.TotalTime;
232 GameServer.
Log(
"WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() +
", latest create id: " + ID.ToString(),
ServerLog.
MessageType.ServerMessage);
233 events.ForEach(e => e.ResetCreateTime());
237 clients.Where(c => c.NeedsMidRoundSync).ForEach(c => {
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
239 ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
240 if (firstEventToResend !=
null &&
242 ((lastSentToAnyoneTime - firstEventToResend.
CreateTime) > NetConfig.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.
CreateTime) > NetConfig.OldEventKickTime))
248 List<Client> toKick = inGameClients.FindAll(c =>
249 NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
250 (firstEventToResend.
CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
253 DebugConsole.NewMessage(c.Name +
" was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() +
")", Color.Red);
254 GameServer.
Log(
GameServer.ClientLogName(c) +
" was kicked because they were expecting a very old network event ("
255 + (c.LastRecvEntityEventID + 1).ToString() +
256 " (created " + (Timing.TotalTime - firstEventToResend.
CreateTime).ToString(
"0.##") +
" s ago, " +
257 (lastSentToAnyoneTime - firstEventToResend.
CreateTime).ToString(
"0.##") +
" s older than last event sent to anyone)" +
258 " Events queued: " + events.Count +
", last sent to all: " + lastSentToAll,
ServerLog.
MessageType.Error);
259 server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
264 if (events.Count > 0)
268 List<Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.LastRecvEntityEventID + 1)));
271 DebugConsole.NewMessage(c.Name +
" was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() +
", last available is " + events[0].ID.ToString() +
")", Color.Red);
272 GameServer.
Log(
GameServer.ClientLogName(c) +
" was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() +
", last available is " + events[0].ID.ToString() +
")",
ServerLog.
MessageType.Error);
273 server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncRemovedEvent));
278 var timedOutClients = clients.FindAll(c => c.Connection !=
GameMain.
Server.
OwnerConnection && c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
279 foreach (
Client timedOutClient
in timedOutClients)
285 bufferedEvents.RemoveAll(b => b.IsProcessed);
288 private void BufferEvent(BufferedEvent bufferedEvent)
290 if (bufferedEvents.Count > 512)
297 DebugConsole.Log(
"Excessive amount of events in a client's event buffer. The client may be spamming events or their event IDs might be out of sync. Dropping events...");
298 bufferedEvents.RemoveRange(0, 256);
301 bufferedEvents.Add(bufferedEvent);
309 Write(segmentTable, client, msg, out _);
317 List<NetEntityEvent> eventsToSync = GetEventsToSync(client);
319 if (eventsToSync.Count == 0)
321 sentEvents = eventsToSync;
329 if (eventsToSync.Count > 200 && !client.
NeedsMidRoundSync && Timing.TotalTime > lastEventCountHighWarning + 2.0)
331 Color color = eventsToSync.Count > 500 ? Color.Red : Color.Orange;
332 if (eventsToSync.Count < 300) { color = Color.Yellow; }
333 string warningMsg =
"WARNING: event count very high: " + eventsToSync.Count;
335 var sortedEvents = eventsToSync.GroupBy(e => e.Entity.ToString())
336 .Select(e =>
new { Value = e.Key, Count = e.Count() })
337 .OrderByDescending(e => e.Count);
340 foreach (var sortedEvent
in sortedEvents)
342 warningMsg +=
"\n" + count +
". " + (sortedEvent.Value?.ToString() ??
"null") +
" x" + sortedEvent.Count;
344 if (count > 3) {
break; }
346 if (GameSettings.CurrentConfig.VerboseLogging)
350 DebugConsole.NewMessage(warningMsg, color);
351 lastEventCountHighWarning = Timing.TotalTime;
361 Write(msg, eventsToSync, out sentEvents, client);
366 Write(msg, eventsToSync, out sentEvents, client);
372 client.EntityEventLastSent[entityEvent.
ID] = Lidgren.Network.NetTime.Now;
379 private List<NetEntityEvent> GetEventsToSync(
Client client)
381 List<NetEntityEvent> eventsToSync =
new List<NetEntityEvent>();
385 if (eventList.Count == 0) {
return eventsToSync; }
388 int startIndex = eventList.Count;
389 while (startIndex > 0 &&
395 for (
int i = startIndex; i < eventList.Count; i++)
400 float avgRoundtripTime = 0.01f;
401 float minInterval = Math.Max(avgRoundtripTime, (
float)server.UpdateInterval.TotalSeconds * 2);
403 if (lastSent > Lidgren.Network.NetTime.Now - Math.Min(minInterval, 0.5f))
417 eventsToSync.AddRange(eventList.GetRange(i, eventList.Count - i));
430 if (uniqueEvents.Count == 0 || (events.Count > 0 && events[0].ID == uniqueEvents[0].ID))
439 double midRoundSyncTimeOut = uniqueEvents.Count / 10 * server.UpdateInterval.TotalSeconds;
440 midRoundSyncTimeOut = Math.Max(midRoundSyncTimeOut, server.ServerSettings.MinimumMidRoundSyncTimeout);
451 client.
FirstNewEventID = events.Count == 0 ? (UInt16)0 : events[events.Count - 1].ID;
464 for (
int i = 0; i < eventCount; i++)
466 UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
471 if (thisEventID == (UInt16)(sender.LastSentEntityEventID + 1)) sender.LastSentEntityEventID++;
480 if (thisEventID != (UInt16)(sender.LastSentEntityEventID + 1))
482 if (GameSettings.CurrentConfig.VerboseLogging)
484 DebugConsole.NewMessage(
"Received msg " + thisEventID +
", expecting " + sender.LastSentEntityEventID, Color.Red);
488 else if (entity ==
null)
493 if (GameSettings.CurrentConfig.VerboseLogging)
495 DebugConsole.NewMessage(
496 "Received msg " + thisEventID +
", entity " + entityID +
" not found",
497 Microsoft.Xna.Framework.Color.Orange);
499 sender.LastSentEntityEventID++;
504 if (GameSettings.CurrentConfig.VerboseLogging)
506 DebugConsole.NewMessage(
"Received msg " + thisEventID, Microsoft.Xna.Framework.Color.Green);
511 ReadWriteMessage buffer =
new ReadWriteMessage();
512 byte[] temp = msg.
ReadBytes(msgLength - 2);
513 buffer.WriteBytes(temp, 0, msgLength - 2);
514 buffer.BitPosition = 0;
516 new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer)
519 RequireCharacter = entity is not Hull
522 sender.LastSentEntityEventID++;
530 if (serverEvent ==
null)
return;
532 serverEvent.
Write(buffer, recipient);
538 if (clientEntity ==
null)
return;
548 bufferedEvents.Clear();
552 uniqueEvents.Clear();
554 foreach (
Client c
in server.ConnectedClients)
const ushort NullEntityID
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
static GameSession GameSession
UInt16 UnreceivedEntityEventCount
UInt16 LastRecvEntityEventID
readonly Dictionary< UInt16, double > EntityEventLastSent
UInt16 LastSentEntityEventID
double MidRoundSyncTimeOut
NetworkConnection OwnerConnection
void DisconnectClient(NetworkConnection senderConnection, PeerDisconnectPacket peerDisconnectPacket)
static void Log(string line, ServerLog.MessageType messageType)
static bool ValidateEntity(INetSerializable entity)
ServerEntityEvent(IServerSerializable serializableEntity, UInt16 id)
void Write(IWriteMessage msg, Client recipient)
List< ServerEntityEvent > UniqueEvents
void Write(in SegmentTableWriter< ServerNetSegment > segmentTable, Client client, IWriteMessage msg)
Writes all the events that the client hasn't received yet into the outgoing message
void InitClientMidRoundSync(Client client)
override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient=null)
void Update(List< Client > clients)
void Read(IReadMessage msg, Client sender=null)
Read the events from the message, ignoring ones we've already received
void Write(in SegmentTableWriter< ServerNetSegment > segmentTable, Client client, IWriteMessage msg, out List< NetEntityEvent > sentEvents)
Writes all the events that the client hasn't received yet into the outgoing message
ServerEntityEventManager(GameServer server)
List< ServerEntityEvent > Events
void ReadEvent(IReadMessage buffer, INetSerializable entity, Client sender=null)
void CreateEvent(IServerSerializable entity, NetEntityEvent.IData extraData=null)
Interface for entities that the clients can send events to the server
void ServerEventRead(IReadMessage msg, Client c)
UInt32 ReadVariableUInt32()
byte[] ReadBytes(int numberOfBytes)
Interface for entities that the server can send events to the clients
void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData=null)
void WriteUInt16(UInt16 val)