Client LuaCsForBarotrauma
ClientEntityEventManager.cs
1 using System;
2 using System.Collections.Generic;
3 using Microsoft.Xna.Framework;
4 
5 namespace Barotrauma.Networking
6 {
8  {
9  private List<ClientEntityEvent> events;
10 
11  private UInt16 ID;
12 
13  private GameClient thisClient;
14 
15  //when was a specific entity event last sent to the client
16  // key = event id, value = NetTime.Now when sending
17  public Dictionary<UInt16, float> eventLastSent;
18 
19  public UInt16 LastReceivedID
20  {
21  get { return lastReceivedID; }
22  }
23 
24  private UInt16 lastReceivedID;
25 
26  public bool MidRoundSyncing
27  {
28  get { return firstNewID.HasValue; }
29  }
30 
31  public bool MidRoundSyncingDone
32  {
33  get;
34  private set;
35  }
36 
38  {
39  events = new List<ClientEntityEvent>();
40  eventLastSent = new Dictionary<UInt16, float>();
41 
42  thisClient = client;
43  }
44 
45  public void CreateEvent(IClientSerializable entity, NetEntityEvent.IData extraData = null, bool requireControlledCharacter = true)
46  {
47  if (GameMain.Client == null) { return; }
48  if (requireControlledCharacter && GameMain.Client.Character == null) { return; }
49 
50  if (!ValidateEntity(entity)) { return; }
51 
52  var newEvent = new ClientEntityEvent(
53  entity,
54  eventId: (UInt16)(ID + 1),
56  if (extraData != null) { newEvent.SetData(extraData); }
57 
58  for (int i = events.Count - 1; i >= 0; i--)
59  {
60  //we already have an identical event that's waiting to be sent
61  // -> no need to add a new one
62  if (!events[i].Sent && events[i].IsDuplicate(newEvent)) return;
63  }
64 
65  ID++;
66 
67  events.Add(newEvent);
68  }
69 
70  public void Write(in SegmentTableWriter<ClientNetSegment> segmentTable, IWriteMessage msg, NetworkConnection serverConnection)
71  {
72  if (events.Count == 0 || serverConnection == null) return;
73 
74  List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>();
75 
76  //find the index of the first event the server hasn't received
77  int startIndex = events.Count;
78  while (startIndex > 0 &&
79  NetIdUtils.IdMoreRecent(events[startIndex - 1].ID, thisClient.LastSentEntityEventID))
80  {
81  startIndex--;
82  }
83 
84  //remove events the server has already received
85  events.RemoveRange(0, startIndex);
86 
87  for (int i = 0; i < events.Count; i++)
88  {
89  //find the first event that hasn't been sent in roundtriptime or at all
90  eventLastSent.TryGetValue(events[i].ID, out float lastSent);
91 
92  if (lastSent > Lidgren.Network.NetTime.Now - 0.2) //TODO: reimplement serverConnection.AverageRoundtripTime
93  {
94  continue;
95  }
96 
97  eventsToSync.AddRange(events.GetRange(i, events.Count - i));
98  break;
99  }
100  if (eventsToSync.Count == 0) { return; }
101 
102  foreach (NetEntityEvent entityEvent in eventsToSync)
103  {
104  eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
105  }
106 
107  segmentTable.StartNewSegment(ClientNetSegment.EntityState);
108  Write(msg, eventsToSync, out _);
109  }
110 
111  private UInt16? firstNewID;
112 
113  private readonly List<IServerSerializable> tempEntityList = new List<IServerSerializable>();
117  public bool Read(ServerNetSegment type, IReadMessage msg, float sendingTime)
118  {
119  if (type == ServerNetSegment.EntityEventInitial)
120  {
121  UInt16 unreceivedEntityEventCount = msg.ReadUInt16();
122  firstNewID = msg.ReadUInt16();
123 
124  if (GameSettings.CurrentConfig.VerboseLogging)
125  {
126  DebugConsole.NewMessage(
127  "received midround syncing msg, unreceived: " + unreceivedEntityEventCount +
128  ", first new ID: " + firstNewID, Microsoft.Xna.Framework.Color.Yellow);
129  }
130  }
131  else
132  {
133  MidRoundSyncingDone = true;
134  if (firstNewID != null)
135  {
136  if (GameSettings.CurrentConfig.VerboseLogging)
137  {
138  DebugConsole.NewMessage("midround syncing complete, switching to ID " + (UInt16) (firstNewID - 1),
139  Microsoft.Xna.Framework.Color.Yellow);
140  }
141  lastReceivedID = (UInt16)(firstNewID - 1);
142  firstNewID = null;
143  }
144  }
145 
146  tempEntityList.Clear();
147 
148  msg.ReadPadBits();
149  UInt16 firstEventID = msg.ReadUInt16();
150  int eventCount = msg.ReadByte();
151 
152  for (int i = 0; i < eventCount; i++)
153  {
154  //16 = entity ID, 8 = msg length
155  if (msg.BitPosition + 16 + 8 > msg.LengthBits)
156  {
157  string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
158  errorMsg += "\nPrevious entities:";
159  for (int j = tempEntityList.Count - 1; j >= 0; j--)
160  {
161  errorMsg += "\n" + (tempEntityList[j] == null ? "NULL" : tempEntityList[j].ToString());
162  }
163  DebugConsole.ThrowError(errorMsg);
164  return false;
165  }
166 
167  UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
168  UInt16 entityID = msg.ReadUInt16();
169 
170  if (entityID == Entity.NullEntityID)
171  {
172  if (GameSettings.CurrentConfig.VerboseLogging)
173  {
174  DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
175  Microsoft.Xna.Framework.Color.Orange);
176  }
177  tempEntityList.Add(null);
178  if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
179  continue;
180  }
181 
182  int msgLength = (int)msg.ReadVariableUInt32();
183 
185  tempEntityList.Add(entity);
186 
187  //skip the event if we've already received it or if the entity isn't found
188  if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
189  {
190  if (thisEventID != (UInt16) (lastReceivedID + 1))
191  {
192  if (GameSettings.CurrentConfig.VerboseLogging)
193  {
194  DebugConsole.NewMessage(
195  "Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")",
196  NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1))
197  ? GUIStyle.Red
198  : Microsoft.Xna.Framework.Color.Yellow);
199  }
200  }
201  else if (entity == null)
202  {
203  DebugConsole.NewMessage(
204  "Received msg " + thisEventID + ", entity " + entityID + " not found",
205  GUIStyle.Red);
206  GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventId: thisEventID, entityId: entityID);
207  return false;
208  }
209 
210  msg.BitPosition += msgLength * 8;
211  }
212  else
213  {
214  int msgPosition = msg.BitPosition;
215  if (GameSettings.CurrentConfig.VerboseLogging)
216  {
217  DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")",
218  Microsoft.Xna.Framework.Color.Green);
219  }
220  lastReceivedID++;
221  ReadEvent(msg, entity, sendingTime);
222  msg.ReadPadBits();
223 
224  if (msg.BitPosition != msgPosition + msgLength * 8)
225  {
226  var prevEntity = tempEntityList.Count >= 2 ? tempEntityList[tempEntityList.Count - 2] : null;
227  ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
228  string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
229  +$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
230  +$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
231 
232  GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
233 
234  throw new Exception(errorMsg);
235  }
236  }
237  }
238  return true;
239  }
240 
241  protected override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null)
242  {
243  var clientEvent = entityEvent as ClientEntityEvent;
244  if (clientEvent == null) return;
245 
246  clientEvent.Write(buffer);
247  clientEvent.Sent = true;
248  }
249 
250  protected void ReadEvent(IReadMessage buffer, IServerSerializable entity, float sendingTime)
251  {
252  entity.ClientEventRead(buffer, sendingTime);
253  }
254 
255  public void Clear()
256  {
257  lastReceivedID = 0;
258  firstNewID = null;
259  eventLastSent.Clear();
260  MidRoundSyncingDone = false;
261 
262  ClearSelf();
263  }
264 
269  public void ClearSelf()
270  {
271  ID = 0;
272  events.Clear();
273  if (thisClient != null)
274  {
275  thisClient.LastSentEntityEventID = 0;
276  }
277  }
278  }
279 }
const ushort NullEntityID
Definition: Entity.cs:14
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
static GameClient Client
Definition: GameMain.cs:188
void ClearSelf()
Clears events generated by the current client, used when resynchronizing with the server after a time...
bool Read(ServerNetSegment type, IReadMessage msg, float sendingTime)
Read the events from the message, ignoring ones we've already received. Returns false if reading the ...
void Write(in SegmentTableWriter< ClientNetSegment > segmentTable, IWriteMessage msg, NetworkConnection serverConnection)
void ReadEvent(IReadMessage buffer, IServerSerializable entity, float sendingTime)
void CreateEvent(IClientSerializable entity, NetEntityEvent.IData extraData=null, bool requireControlledCharacter=true)
override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient=null)
void ReportError(ClientNetError error, UInt16 expectedId=0, UInt16 eventId=0, UInt16 entityId=0)
Definition: GameClient.cs:3642
static bool ValidateEntity(INetSerializable entity)
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
void ClientEventRead(IReadMessage msg, float sendingTime)