Files
LuaCsForBarotraumaEP/Subsurface/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs

323 lines
11 KiB
C#

using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
class ServerEntityEventManager : NetEntityEventManager
{
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;
public List<ServerEntityEvent> Events
{
get { return events; }
}
private class BufferedEvent
{
public readonly Client Sender;
public readonly UInt16 CharacterStateID;
public readonly NetBuffer Data;
public readonly Character Character;
public readonly IClientSerializable TargetEntity;
public bool IsProcessed;
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, NetBuffer data)
{
this.Sender = sender;
this.Character = senderCharacter;
this.CharacterStateID = characterStateID;
this.TargetEntity = targetEntity;
this.Data = data;
}
}
private List<BufferedEvent> bufferedEvents;
private UInt32 ID;
private GameServer server;
public ServerEntityEventManager(GameServer server)
{
events = new List<ServerEntityEvent>();
this.server = server;
bufferedEvents = new List<BufferedEvent>();
uniqueEvents = new List<ServerEntityEvent>();
}
public void CreateEvent(IServerSerializable entity, object[] extraData = null)
{
if (entity == null || !(entity is Entity))
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + "!");
return;
}
var newEvent = new ServerEntityEvent(entity, ID + 1);
if (extraData != null) newEvent.SetData(extraData);
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
// -> no need to add a new one
if (events[i].IsDuplicate(newEvent)) return;
}
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<Client> clients)
{
foreach (BufferedEvent bufferedEvent in bufferedEvents)
{
if (bufferedEvent.Character == null)
{
bufferedEvent.IsProcessed = true;
continue;
}
if (NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID)) continue;
try
{
ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender);
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Failed to read event for entity \"" + bufferedEvent.TargetEntity.ToString() + "\"!", e);
#endif
}
bufferedEvent.IsProcessed = true;
}
if (clients.Count > 0)
{
lastSentToAll = clients[0].lastRecvEntityEventID;
clients.ForEach(c => { if (c.inGame) lastSentToAll = Math.Min(lastSentToAll, c.lastRecvEntityEventID); });
}
bufferedEvents.RemoveAll(b => b.IsProcessed);
}
private void BufferEvent(BufferedEvent bufferedEvent)
{
if (bufferedEvents.Count > 512)
{
//should normally never happen
//a client could potentially spam events with a much higher character state ID
//than the state of their character and/or stop sending character inputs,
//so we'll drop some events to make sure no-one blows up our buffer
bufferedEvents.RemoveRange(0, 256);
}
bufferedEvents.Add(bufferedEvent);
}
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, NetOutgoingMessage msg)
{
if (events.Count == 0) return;
List<NetEntityEvent> eventsToSync = null;
if (client.NeedsMidRoundSync)
{
eventsToSync = GetEventsToSync(client, uniqueEvents);
}
else
{
eventsToSync = GetEventsToSync(client, events);
}
if (eventsToSync.Count == 0) return;
//too many events for one packet
if (eventsToSync.Count > MaxEventsPerWrite)
{
eventsToSync.RemoveRange(MaxEventsPerWrite, eventsToSync.Count - MaxEventsPerWrite);
}
foreach (NetEntityEvent entityEvent in eventsToSync)
{
(entityEvent as ServerEntityEvent).Sent = true;
client.entityEventLastSent[entityEvent.ID] = (float)NetTime.Now;
}
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);
}
}
/// <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>
/// Read the events from the message, ignoring ones we've already received
/// </summary>
public void Read(NetIncomingMessage msg, Client sender = null)
{
UInt32 firstEventID = msg.ReadUInt32();
int eventCount = msg.ReadByte();
for (int i = 0; i < eventCount; i++)
{
UInt32 thisEventID = firstEventID + (UInt32)i;
UInt16 entityID = msg.ReadUInt16();
byte msgLength = msg.ReadByte();
IClientSerializable entity = Entity.FindEntityByID(entityID) as IClientSerializable;
//skip the event if we've already received it or if the entity isn't found
if (thisEventID != sender.lastSentEntityEventID + 1 || entity == null)
{
if (thisEventID != sender.lastSentEntityEventID + 1)
{
DebugConsole.NewMessage("received msg " + thisEventID, Microsoft.Xna.Framework.Color.Red);
}
else if (entity == null)
{
DebugConsole.NewMessage("received msg " + thisEventID + ", entity " + entityID + " not found", Microsoft.Xna.Framework.Color.Red);
}
msg.Position += msgLength * 8;
}
else
{
UInt16 characterStateID = msg.ReadUInt16();
NetBuffer buffer = new NetBuffer();
buffer.Write(msg.ReadBytes(msgLength-2));
BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer));
sender.lastSentEntityEventID++;
}
msg.ReadPadBits();
}
}
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
{
var serverEvent = entityEvent as ServerEntityEvent;
if (serverEvent == null) return;
serverEvent.Write(buffer, recipient);
}
protected void ReadEvent(NetBuffer buffer, INetSerializable entity, Client sender = null)
{
var clientEntity = entity as IClientSerializable;
if (clientEntity == null) return;
clientEntity.ServerRead(ClientNetObject.ENTITY_STATE, buffer, sender);
}
public void Clear()
{
ID = 0;
events.Clear();
bufferedEvents.Clear();
uniqueEvents.Clear();
server.ConnectedClients.ForEach(c => c.entityEventLastSent.Clear());
}
}
}