您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

1457 行
64 KiB

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Networking.Transport.Protocols;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Mathematics;
using Unity.Networking.Transport.Error;
using Unity.Networking.Transport.Utilities;
namespace Unity.Networking.Transport
{
public unsafe struct QueuedSendMessage
{
public fixed byte Data[NetworkParameterConstants.MTU];
public NetworkInterfaceEndPoint Dest;
public int DataLength;
}
/// <summary>
/// The NetworkDriver is an implementation of Virtual Connections over any transport.
///
/// Basic usage:
/// <code>
/// var driver = new NetworkDriver.Create();
/// </code>
/// </summary>
public struct NetworkDriver : IDisposable
{
/// <summary>
/// Create a Concurrent Copy of the NetworkDriver.
/// </summary>
public Concurrent ToConcurrent()
{
return new Concurrent
{
m_NetworkSendInterface = m_NetworkSendInterface,
m_NetworkProtocolInterface = m_NetworkProtocolInterface,
m_EventQueue = m_EventQueue.ToConcurrent(),
m_ConnectionList = m_ConnectionList,
m_DataStream = m_DataStream,
m_DisconnectReasons = m_DisconnectReasons,
m_PipelineProcessor = m_PipelineProcessor.ToConcurrent(),
m_DefaultHeaderFlags = m_DefaultHeaderFlags,
m_ConcurrentParallelSendQueue = m_ParallelSendQueue.AsParallelWriter(),
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_ThreadIndex = 0,
m_PendingBeginSend = m_PendingBeginSend
#endif
};
}
private Concurrent ToConcurrentSendOnly()
{
return new Concurrent
{
m_NetworkSendInterface = m_NetworkSendInterface,
m_NetworkProtocolInterface = m_NetworkProtocolInterface,
m_EventQueue = default,
m_ConnectionList = m_ConnectionList,
m_DataStream = m_DataStream,
m_DisconnectReasons = m_DisconnectReasons,
m_PipelineProcessor = m_PipelineProcessor.ToConcurrent(),
m_DefaultHeaderFlags = m_DefaultHeaderFlags,
m_ConcurrentParallelSendQueue = m_ParallelSendQueue.AsParallelWriter(),
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_ThreadIndex = 0,
m_PendingBeginSend = m_PendingBeginSend
#endif
};
}
/// <summary>
/// The Concurrent struct is used to create an Concurrent instance of the GenericNetworkDriver.
/// </summary>
public struct Concurrent
{
public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader)
{
return PopEventForConnection(connectionId, out reader, out var _);
}
public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader, out NetworkPipeline pipeline)
{
pipeline = default(NetworkPipeline);
reader = default(DataStreamReader);
if (connectionId.m_NetworkId < 0 || connectionId.m_NetworkId >= m_ConnectionList.Length ||
m_ConnectionList[connectionId.m_NetworkId].Version != connectionId.m_NetworkVersion)
return (int) NetworkEvent.Type.Empty;
var type = m_EventQueue.PopEventForConnection(connectionId.m_NetworkId, out var offset, out var size, out var pipelineId);
pipeline = new NetworkPipeline { Id = pipelineId };
if (type == NetworkEvent.Type.Disconnect && offset < 0)
reader = new DataStreamReader(((NativeArray<byte>)m_DisconnectReasons).GetSubArray(math.abs(offset), 1));
else if (size > 0)
reader = new DataStreamReader(((NativeArray<byte>)m_DataStream).GetSubArray(offset, size));
return type;
}
public int MaxHeaderSize(NetworkPipeline pipe)
{
var headerSize = m_NetworkProtocolInterface.PaddingSize;
if (pipe.Id > 0)
{
// All headers plus one byte for pipeline id
headerSize += m_PipelineProcessor.SendHeaderCapacity(pipe) + 1;
}
return headerSize;
}
struct PendingSend
{
public NetworkPipeline Pipeline;
public NetworkConnection Connection;
public NetworkInterfaceSendHandle SendHandle;
public int headerSize;
}
/// <summary>
/// Acquires a DataStreamWriter for starting a asynchronous send.
/// </summary>
/// <param name="id">The NetworkConnection id to write through</param>
/// <param name="writer">A DataStreamWriter to write to</param>
/// <param name="requiredPayloadSize">If you require the payload to be of certain size</param>
/// <value>Returns <see cref="StatusCode.Success"/> on a successful acquire. Otherwise returns an <see cref="StatusCode"/> indicating the error.</value>
/// <remarks> Will throw a <exception cref="InvalidOperationException"></exception> if the connection is in a Connecting state.</remarks>
public unsafe int BeginSend(NetworkConnection id, out DataStreamWriter writer, int requiredPayloadSize = 0)
{
return BeginSend(NetworkPipeline.Null, id, out writer, requiredPayloadSize);
}
/// <summary>
/// Acquires a DataStreamWriter for starting a asynchronous send.
/// </summary>
/// <param name="pipe">The NetworkPipeline to write through</param>
/// <param name="id">The NetworkConnection id to write through</param>
/// <param name="writer">A DataStreamWriter to write to</param>
/// <param name="requiredPayloadSize">If you require the payload to be of certain size</param>
/// <value>Returns <see cref="StatusCode.Success"/> on a successful acquire. Otherwise returns an <see cref="StatusCode"/> indicating the error.</value>
/// <remarks> Will throw a <exception cref="InvalidOperationException"></exception> if the connection is in a Connecting state.</remarks>
public unsafe int BeginSend(NetworkPipeline pipe, NetworkConnection id,
out DataStreamWriter writer, int requiredPayloadSize = 0)
{
writer = default;
if (id.m_NetworkId < 0 || id.m_NetworkId >= m_ConnectionList.Length)
return (int)Error.StatusCode.NetworkIdMismatch;
var connection = m_ConnectionList[id.m_NetworkId];
if (connection.Version != id.m_NetworkVersion)
return (int)Error.StatusCode.NetworkVersionMismatch;
if (connection.State == NetworkConnection.State.Connecting)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
UnityEngine.Debug.LogError("Cannot send data while connecting");
#endif
return (int)Error.StatusCode.NetworkStateMismatch;
}
var pipelineHeader = (pipe.Id > 0) ? m_PipelineProcessor.SendHeaderCapacity(pipe) + 1 : 0;
var payloadCapacity = requiredPayloadSize > 0 ? requiredPayloadSize + pipelineHeader : 0;
// This will set the payloadCapacity value to the actual capacity provided by the protocol
var packetAllocationSize = m_NetworkProtocolInterface.ComputePacketAllocationSize.Ptr.Invoke(ref connection, ref payloadCapacity, out var payloadOffset);
payloadCapacity -= pipelineHeader;
if (packetAllocationSize > m_PipelineProcessor.PayloadCapacity(pipe) || payloadCapacity < requiredPayloadSize)
return (int) Error.StatusCode.NetworkPacketOverflow;
var result = 0;
if ((result = m_NetworkSendInterface.BeginSendMessage.Ptr.Invoke(out var sendHandle, m_NetworkSendInterface.UserData, packetAllocationSize)) != 0)
{
sendHandle.data = (IntPtr)UnsafeUtility.Malloc(packetAllocationSize, 8, Allocator.Temp);
sendHandle.capacity = packetAllocationSize;
sendHandle.id = 0;
sendHandle.size = 0;
sendHandle.flags = SendHandleFlags.AllocatedByDriver;
}
if (sendHandle.capacity < packetAllocationSize)
return (int) Error.StatusCode.NetworkPacketOverflow;
var slice = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<byte>((byte*) sendHandle.data + payloadOffset + pipelineHeader, payloadCapacity, Allocator.Invalid);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
var safety = AtomicSafetyHandle.GetTempMemoryHandle();
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref slice, safety);
#endif
writer = new DataStreamWriter(slice);
writer.m_SendHandleData = (IntPtr)UnsafeUtility.Malloc(UnsafeUtility.SizeOf<PendingSend>(), UnsafeUtility.AlignOf<PendingSend>(), Allocator.Temp);
*(PendingSend*)writer.m_SendHandleData = new PendingSend
{
Pipeline = pipe,
Connection = id,
SendHandle = sendHandle,
headerSize = payloadOffset,
};
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize/4] = m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize/4] + 1;
#endif
return (int)Error.StatusCode.Success;
}
/// <summary>
/// Ends a asynchronous send.
/// </summary>
/// <param name="writer">If you require the payload to be of certain size.</param>
/// <value>The length of the buffer sent if nothing went wrong.</value>
/// <exception cref="InvalidOperationException">If endsend is called with a matching BeginSend call.</exception>
/// <exception cref="InvalidOperationException">If the connection got closed between the call of being and end send.</exception>
public unsafe int EndSend(DataStreamWriter writer)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
// Just here to trigger a safety check on the writer
if (writer.Capacity == 0)
throw new InvalidOperationException("EndSend without matching BeginSend");
#endif
PendingSend* pendingSendPtr = (PendingSend*)writer.m_SendHandleData;
if (pendingSendPtr == null || pendingSendPtr->Connection == default)
throw new InvalidOperationException("EndSend without matching BeginSend");
if (m_ConnectionList[pendingSendPtr->Connection.m_NetworkId].Version != pendingSendPtr->Connection.m_NetworkVersion)
throw new InvalidOperationException("Connection closed between begin and end send");
PendingSend pendingSend = *(PendingSend*)writer.m_SendHandleData;
pendingSendPtr->Connection = default;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize/4] = m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize/4] - 1;
#endif
pendingSend.SendHandle.size = pendingSend.headerSize + writer.Length;
int retval = 0;
if (pendingSend.Pipeline.Id > 0)
{
pendingSend.SendHandle.size += m_PipelineProcessor.SendHeaderCapacity(pendingSend.Pipeline) + 1;
var oldHeaderFlags = m_DefaultHeaderFlags;
m_DefaultHeaderFlags = UdpCHeader.HeaderFlags.HasPipeline;
retval = m_PipelineProcessor.Send(this, pendingSend.Pipeline, pendingSend.Connection, pendingSend.SendHandle, pendingSend.headerSize);
m_DefaultHeaderFlags = oldHeaderFlags;
}
else
// TODO: Is there a better way we could set the hasPipeline value correctly?
// this case is when the message is sent from the pipeline directly, "without a pipeline" so the hasPipeline flag is set in m_DefaultHeaderFlags
// allowing us to capture it here
retval = CompleteSend(pendingSend.Connection, pendingSend.SendHandle, (m_DefaultHeaderFlags & UdpCHeader.HeaderFlags.HasPipeline) != 0);
if (retval <= 0)
return retval;
return writer.Length;
}
/// <summary>
/// Aborts a asynchronous send.
/// </summary>
/// <param name="writer">If you require the payload to be of certain size.</param>
/// <value>The length of the buffer sent if nothing went wrong.</value>
/// <exception cref="InvalidOperationException">If endsend is called with a matching BeginSend call.</exception>
/// <exception cref="InvalidOperationException">If the connection got closed between the call of being and end send.</exception>
public unsafe void AbortSend(DataStreamWriter writer)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
// Just here to trigger a safety check on the writer
if (writer.Capacity == 0)
throw new InvalidOperationException("EndSend without matching BeginSend");
#endif
PendingSend* pendingSendPtr = (PendingSend*)writer.m_SendHandleData;
if (pendingSendPtr == null || pendingSendPtr->Connection == default)
throw new InvalidOperationException("EndSend without matching BeginSend");
PendingSend pendingSend = *(PendingSend*)writer.m_SendHandleData;
pendingSendPtr->Connection = default;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize/4] = m_PendingBeginSend[m_ThreadIndex * JobsUtility.CacheLineSize/4] - 1;
#endif
AbortSend(pendingSend.SendHandle);
}
internal unsafe int CompleteSend(NetworkConnection sendConnection, NetworkInterfaceSendHandle sendHandle, bool hasPipeline)
{
if (0 != (sendHandle.flags & SendHandleFlags.AllocatedByDriver))
{
var ret = 0;
NetworkInterfaceSendHandle originalHandle = sendHandle;
if ((ret = m_NetworkSendInterface.BeginSendMessage.Ptr.Invoke(out sendHandle, m_NetworkSendInterface.UserData, originalHandle.size)) != 0)
{
return ret;
}
UnsafeUtility.MemCpy((void*) sendHandle.data, (void*) originalHandle.data, originalHandle.size);
sendHandle.size = originalHandle.size;
}
var connection = m_ConnectionList[sendConnection.m_NetworkId];
var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ConcurrentParallelSendQueue);
return m_NetworkProtocolInterface.ProcessSend.Ptr.Invoke(ref connection, hasPipeline, ref m_NetworkSendInterface, ref sendHandle, ref queueHandle, m_NetworkProtocolInterface.UserData);
}
internal void AbortSend(NetworkInterfaceSendHandle sendHandle)
{
if(0 == (sendHandle.flags & SendHandleFlags.AllocatedByDriver))
{
m_NetworkSendInterface.AbortSendMessage.Ptr.Invoke(ref sendHandle, m_NetworkSendInterface.UserData);
}
}
public NetworkConnection.State GetConnectionState(NetworkConnection id)
{
if (id.m_NetworkId < 0 || id.m_NetworkId >= m_ConnectionList.Length)
return NetworkConnection.State.Disconnected;
var connection = m_ConnectionList[id.m_NetworkId];
if (connection.Version != id.m_NetworkVersion)
return NetworkConnection.State.Disconnected;
return connection.State;
}
internal NetworkSendInterface m_NetworkSendInterface;
internal NetworkProtocol m_NetworkProtocolInterface;
internal NetworkEventQueue.Concurrent m_EventQueue;
internal NativeArray<byte> m_DisconnectReasons;
[ReadOnly] internal NativeList<Connection> m_ConnectionList;
[ReadOnly] internal NativeList<byte> m_DataStream;
internal NetworkPipelineProcessor.Concurrent m_PipelineProcessor;
internal UdpCHeader.HeaderFlags m_DefaultHeaderFlags;
internal NativeQueue<QueuedSendMessage>.ParallelWriter m_ConcurrentParallelSendQueue;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
[NativeSetThreadIndex] internal int m_ThreadIndex;
[NativeDisableParallelForRestriction] internal NativeArray<int> m_PendingBeginSend;
#endif
}
internal struct Connection
{
public NetworkInterfaceEndPoint Address;
public long LastAttempt;
public int Id;
public int Version;
public int Attempts;
public NetworkConnection.State State;
public ushort ReceiveToken;
public ushort SendToken;
public byte DidReceiveData;
public static bool operator ==(Connection lhs, Connection rhs)
{
return lhs.Id == rhs.Id && lhs.Version == rhs.Version && lhs.Address == rhs.Address;
}
public static bool operator !=(Connection lhs, Connection rhs)
{
return lhs.Id != rhs.Id || lhs.Version != rhs.Version || lhs.Address != rhs.Address;
}
public override bool Equals(object compare)
{
return this == (Connection) compare;
}
public static Connection Null => new Connection() {Id = 0, Version = 0};
public override int GetHashCode()
{
return Id;
}
public bool Equals(Connection connection)
{
return connection.Id == Id && connection.Version == Version && connection.Address == Address;
}
}
// internal variables :::::::::::::::::::::::::::::::::::::::::::::::::
static List<INetworkInterface> s_NetworkInterfaces = new List<INetworkInterface>();
static List<INetworkProtocol> s_NetworkProtocols = new List<INetworkProtocol>();
int m_NetworkInterfaceIndex;
NetworkSendInterface m_NetworkSendInterface;
int m_NetworkProtocolIndex;
NetworkProtocol m_NetworkProtocolInterface;
NativeQueue<QueuedSendMessage> m_ParallelSendQueue;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArray<int> m_PendingBeginSend;
#endif
NetworkEventQueue m_EventQueue;
private NativeArray<byte> m_DisconnectReasons;
NativeQueue<int> m_FreeList;
NativeQueue<int> m_NetworkAcceptQueue;
NativeList<Connection> m_ConnectionList;
NativeArray<int> m_InternalState;
NativeQueue<int> m_PendingFree;
NativeArray<ushort> m_SessionIdCounter;
NativeArray<int> m_ErrorCodes;
enum ErrorCodeType
{
ReceiveError = 0,
SendError = 1,
NumErrorCodes
}
#pragma warning disable 649
struct Parameters
{
public NetworkDataStreamParameter dataStream;
public NetworkConfigParameter config;
public Parameters(params INetworkParameter[] param)
{
config = new NetworkConfigParameter {
maxConnectAttempts = NetworkParameterConstants.MaxConnectAttempts,
connectTimeoutMS = NetworkParameterConstants.ConnectTimeoutMS,
disconnectTimeoutMS = NetworkParameterConstants.DisconnectTimeoutMS,
maxFrameTimeMS = 0
};
dataStream = default(NetworkDataStreamParameter);
for (int i = 0; i < param.Length; ++i)
{
if (param[i] is NetworkConfigParameter)
config = (NetworkConfigParameter)param[i];
else if (param[i] is NetworkDataStreamParameter)
dataStream = (NetworkDataStreamParameter)param[i];
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
public static void ValidateParameters(Parameters param)
{
if (param.dataStream.size < 0)
throw new ArgumentException($"Value for NetworkDataStreamParameter.size must be larger then zero.");
}
}
#pragma warning restore 649
private Parameters m_NetworkParams;
private NativeList<byte> m_DataStream;
private NativeArray<int> m_DataStreamSize;
private NetworkPipelineProcessor m_PipelineProcessor;
private UdpCHeader.HeaderFlags m_DefaultHeaderFlags;
private long m_updateTime;
private long m_updateTimeAdjustment;
// properties :::::::::::::::::::::::::::::::::::::::::::::::::::::::::
private const int InternalStateListening = 0;
private const int InternalStateBound = 1;
public bool Listening
{
get { return (m_InternalState[InternalStateListening] != 0); }
internal set { m_InternalState[InternalStateListening] = value ? 1 : 0; }
}
// 0 = Unbound
// 1 = Binding
// 2 = Bound
public bool Bound => m_InternalState[InternalStateBound] == 2;
/// <summary>
/// Helper function for creating a NetworkDriver.
/// </summary>
/// <param name="param">
/// An optional array of INetworkParameter. There are currently only two <see cref="INetworkParameter"/>,
/// the <see cref="NetworkDataStreamParameter"/> and the <see cref="NetworkConfigParameter"/>.
/// </param>
/// <exception cref="InvalidOperationException"></exception>
public static NetworkDriver Create(params INetworkParameter[] param)
{
#if UNITY_WEBGL
return new NetworkDriver(new IPCNetworkInterface(), param);
#else
return new NetworkDriver(new BaselibNetworkInterface(), param);
#endif
}
private static int InsertInAvailableIndex<T>(List<T> list, T element)
{
var index = -1;
for (int i = 0; i < list.Count; ++i)
{
if (list[i] == null)
{
index = i;
list[i] = element;
break;
}
}
if (index < 0)
{
index = list.Count;
list.Add(element);
}
return index;
}
private static INetworkProtocol GetProtocolForParameters(INetworkParameter[] parameters)
{
foreach (var parameter in parameters)
{
if (parameter is Unity.Networking.Transport.Relay.RelayNetworkParameter)
return new Unity.Networking.Transport.Relay.RelayNetworkProtocol();
}
return new UnityTransportProtocol();
}
public NetworkDriver(INetworkInterface netIf, params INetworkParameter[] param) : this(netIf, GetProtocolForParameters(param), param)
{
}
/// <summary>
/// Constructor for NetworkDriver.
/// </summary>
/// <param name="param">
/// An array of INetworkParameter. There are currently only two <see cref="INetworkParameter"/>,
/// the <see cref="NetworkDataStreamParameter"/> and the <see cref="NetworkConfigParameter"/>.
/// </param>
/// <exception cref="ArgumentException">Thrown if the value for NetworkDataStreamParameter.size is smaller then zero.</exception>
internal NetworkDriver(INetworkInterface netIf, INetworkProtocol netProtocol, params INetworkParameter[] param)
{
m_NetworkParams = new Parameters(param);
Parameters.ValidateParameters(m_NetworkParams);
NetworkPipelineParams.ValidateParameters(param);
netProtocol.Initialize(param);
m_NetworkProtocolIndex = InsertInAvailableIndex(s_NetworkProtocols, netProtocol);
m_NetworkProtocolInterface = netProtocol.CreateProtocolInterface();
m_NetworkInterfaceIndex = InsertInAvailableIndex(s_NetworkInterfaces, netIf);
var result = netIf.Initialize(param);
if (0 != result)
{
throw new InvalidOperationException($"Failed to initialize the NetworkInterface. Error Code: {result}.");
}
m_NetworkSendInterface = netIf.CreateSendInterface();
m_PipelineProcessor = new NetworkPipelineProcessor(param);
m_ParallelSendQueue = new NativeQueue<QueuedSendMessage>(Allocator.Persistent);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_PendingBeginSend = new NativeArray<int>(JobsUtility.MaxJobThreadCount * JobsUtility.CacheLineSize/4, Allocator.Persistent);
#endif
var time = Stopwatch.GetTimestamp() / (Stopwatch.Frequency/1000);
m_updateTime = m_NetworkParams.config.fixedFrameTimeMS > 0 ? 1 : time;
m_updateTimeAdjustment = 0;
int initialStreamSize = m_NetworkParams.dataStream.size;
if (initialStreamSize == 0)
initialStreamSize = NetworkParameterConstants.DriverDataStreamSize;
m_DataStream = new NativeList<byte>(initialStreamSize, Allocator.Persistent);
m_DataStream.ResizeUninitialized(initialStreamSize);
m_DataStreamSize = new NativeArray<int>(1, Allocator.Persistent);
m_DefaultHeaderFlags = 0;
m_NetworkAcceptQueue = new NativeQueue<int>(Allocator.Persistent);
m_ConnectionList = new NativeList<Connection>(1, Allocator.Persistent);
m_FreeList = new NativeQueue<int>(Allocator.Persistent);
m_EventQueue = new NetworkEventQueue(NetworkParameterConstants.InitialEventQueueSize);
const int reasons = (int)DisconnectReason.Count;
m_DisconnectReasons = new NativeArray<byte>(reasons, Allocator.Persistent);
for (var idx = 0; idx < reasons; ++idx)
m_DisconnectReasons[idx] = (byte)idx;
m_InternalState = new NativeArray<int>(2, Allocator.Persistent);
m_PendingFree = new NativeQueue<int>(Allocator.Persistent);
m_ReceiveCount = new NativeArray<int>(1, Allocator.Persistent);
m_SessionIdCounter = new NativeArray<ushort>(1, Allocator.Persistent) {[0] = RandomHelpers.GetRandomUShort()};
m_ErrorCodes = new NativeArray<int>((int)ErrorCodeType.NumErrorCodes, Allocator.Persistent);
ReceiveCount = 0;
Listening = false;
}
// interface implementation :::::::::::::::::::::::::::::::::::::::::::
public void Dispose()
{
s_NetworkProtocols[m_NetworkProtocolIndex].Dispose();
s_NetworkProtocols[m_NetworkProtocolIndex] = null;
s_NetworkInterfaces[m_NetworkInterfaceIndex].Dispose();
s_NetworkInterfaces[m_NetworkInterfaceIndex] = null;
m_NetworkProtocolIndex = -1;
m_NetworkInterfaceIndex = -1;
m_DataStream.Dispose();
m_DataStreamSize.Dispose();
m_PipelineProcessor.Dispose();
m_EventQueue.Dispose();
m_DisconnectReasons.Dispose();
m_NetworkAcceptQueue.Dispose();
m_ConnectionList.Dispose();
m_FreeList.Dispose();
m_InternalState.Dispose();
m_PendingFree.Dispose();
m_ReceiveCount.Dispose();
m_SessionIdCounter.Dispose();
m_ErrorCodes.Dispose();
m_ParallelSendQueue.Dispose();
#if ENABLE_UNITY_COLLECTIONS_CHECKS
m_PendingBeginSend.Dispose();
#endif
}
public bool IsCreated => m_InternalState.IsCreated;
[BurstCompile]
struct UpdateJob : IJob
{
public NetworkDriver driver;
public void Execute()
{
driver.InternalUpdate();
}
}
struct ClearEventQueue : IJob
{
public NativeList<byte> dataStream;
public NativeArray<int> dataStreamSize;
public NetworkEventQueue eventQueue;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
public NativeArray<int> pendingSend;
[ReadOnly] public NativeList<Connection> connectionList;
[ReadOnly] public NativeArray<int> internalState;
#endif
public void Execute()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
for (int i = 0; i < connectionList.Length; ++i)
{
int conCount = eventQueue.GetCountForConnection(i);
if (conCount != 0 && connectionList[i].State != NetworkConnection.State.Disconnected)
{
UnityEngine.Debug.LogError($"Resetting event queue with pending events (Count={conCount}, ConnectionID={i}) Listening: {internalState[InternalStateListening]}");
}
}
bool didPrint = false;
for (int i = 0; i < JobsUtility.MaxJobThreadCount; ++i)
{
if (pendingSend[i * JobsUtility.CacheLineSize / 4] > 0)
{
pendingSend[i * JobsUtility.CacheLineSize / 4] = 0;
if (!didPrint)
{
UnityEngine.Debug.LogError(
"Missing EndSend, calling BeginSend without calling EndSend will result in a memory leak");
didPrint = true;
}
}
}
#endif
eventQueue.Clear();
dataStream.ResizeUninitialized(dataStream.Length);
dataStreamSize[0] = 0;
}
}
public long LastUpdateTime => m_updateTime;
public JobHandle ScheduleUpdate(JobHandle dep = default(JobHandle))
{
long timeNow = m_NetworkParams.config.fixedFrameTimeMS > 0 ? m_updateTime + m_NetworkParams.config.fixedFrameTimeMS :
Stopwatch.GetTimestamp() / (Stopwatch.Frequency/1000) - m_updateTimeAdjustment;
if (m_NetworkParams.config.maxFrameTimeMS > 0 && (timeNow - m_updateTime) > m_NetworkParams.config.maxFrameTimeMS)
{
m_updateTimeAdjustment += (timeNow - m_updateTime) - m_NetworkParams.config.maxFrameTimeMS;
timeNow = m_updateTime + m_NetworkParams.config.maxFrameTimeMS;
}
m_updateTime = timeNow;
var job = new UpdateJob {driver = this};
JobHandle handle;
var clearJob = new ClearEventQueue
{
dataStream = m_DataStream,
dataStreamSize = m_DataStreamSize,
eventQueue = m_EventQueue,
#if ENABLE_UNITY_COLLECTIONS_CHECKS
pendingSend = m_PendingBeginSend,
connectionList = m_ConnectionList,
internalState = m_InternalState
#endif
};
handle = clearJob.Schedule(dep);
handle = job.Schedule(handle);
handle = s_NetworkInterfaces[m_NetworkInterfaceIndex].ScheduleReceive(new NetworkPacketReceiver{m_Driver = this}, handle);
handle = s_NetworkInterfaces[m_NetworkInterfaceIndex].ScheduleSend(m_ParallelSendQueue, handle);
return handle;
}
public JobHandle ScheduleFlushSend(JobHandle dep)
{
return s_NetworkInterfaces[m_NetworkInterfaceIndex].ScheduleSend(m_ParallelSendQueue, dep);
}
void InternalUpdate()
{
m_PipelineProcessor.Timestamp = m_updateTime;
while (m_PendingFree.TryDequeue(out var free))
{
int ver = m_ConnectionList[free].Version + 1;
if (ver == 0)
ver = 1;
m_ConnectionList[free] = new Connection {Id = free, Version = ver};
m_FreeList.Enqueue(free);
}
CheckTimeouts();
if (m_NetworkProtocolInterface.NeedsUpdate)
{
var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter());
m_NetworkProtocolInterface.Update.Ptr.Invoke(m_updateTime, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData);
}
m_PipelineProcessor.UpdateReceive(this, out var updateCount);
// TODO: Find a good way to establish a good limit (connections*pipelines/2?)
if (updateCount > (m_ConnectionList.Length - m_FreeList.Count) * 64)
{
UnityEngine.Debug.LogWarning(
FixedString.Format("A lot of pipeline updates have been queued, possibly too many being scheduled in pipeline logic, queue count: {0}", updateCount));
}
m_DefaultHeaderFlags = UdpCHeader.HeaderFlags.HasPipeline;
m_PipelineProcessor.UpdateSend(ToConcurrentSendOnly(), out updateCount);
if (updateCount > (m_ConnectionList.Length - m_FreeList.Count) * 64)
{
UnityEngine.Debug.LogWarning(
FixedString.Format("A lot of pipeline updates have been queued, possibly too many being scheduled in pipeline logic, queue count: {0}", updateCount));
}
m_DefaultHeaderFlags = 0;
}
/// <summary>
/// Create a new pipeline.
/// </summary>
/// <param name="stages">
/// An array of stages the pipeline should contain.
/// </param>
/// <exception cref="InvalidOperationException">If the driver is not created properly</exception>
/// <exception cref="InvalidOperationException">A connection has already been established</exception>
public NetworkPipeline CreatePipeline(params Type[] stages)
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if (!m_InternalState.IsCreated)
throw new InvalidOperationException(
"Driver must be constructed with a populated or empty INetworkParameter params list");
if (m_ConnectionList.Length > 0)
throw new InvalidOperationException(
"Pipelines cannot be created after establishing connections");
#endif
return m_PipelineProcessor.CreatePipeline(stages);
}
/// <summary>
/// Bind the driver to a endpoint.
/// </summary>
/// <param name="endpoint">The endpoint to bind to.</param>
/// <value>Returns 0 on success. And a negative value if a error occured.</value>
/// <exception cref="InvalidOperationException">If the driver is not created properly</exception>
/// <exception cref="InvalidOperationException">If bind is called more then once on the driver</exception>
/// <exception cref="InvalidOperationException">If bind is called after a connection has already been established</exception>
public int Bind(NetworkEndPoint endpoint)
{
var ifEndPoint = new NetworkInterfaceEndPoint();
if (s_NetworkInterfaces[m_NetworkInterfaceIndex].CreateInterfaceEndPoint(endpoint, out ifEndPoint) != 0)
{
return -1;
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if (!m_InternalState.IsCreated)
throw new InvalidOperationException(
"Driver must be constructed with a populated or empty INetworkParameter params list");
// question: should this really be an error?
if (m_InternalState[InternalStateBound] != 0)
throw new InvalidOperationException(
"Bind can only be called once per NetworkDriver");
if (m_ConnectionList.Length > 0)
throw new InvalidOperationException(
"Bind cannot be called after establishing connections");
#endif
var protocolBind = s_NetworkProtocols[m_NetworkProtocolIndex].Bind(s_NetworkInterfaces[m_NetworkInterfaceIndex], ref ifEndPoint);
if (protocolBind < 0)
return protocolBind;
m_InternalState[InternalStateBound] = protocolBind;
return 0;
}
/// <summary>
/// Set the driver to Listen for incomming connections
/// </summary>
/// <value>Returns 0 on success.</value>
/// <exception cref="InvalidOperationException">If the driver is not created properly</exception>
/// <exception cref="InvalidOperationException">If listen is called more then once on the driver</exception>
/// <exception cref="InvalidOperationException">If bind has not been called before calling Listen.</exception>
public int Listen()
{
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if (!m_InternalState.IsCreated)
throw new InvalidOperationException(
"Driver must be constructed with a populated or empty INetworkParameter params list");
if (Listening)
throw new InvalidOperationException(
"Listen can only be called once per NetworkDriver");
if (!Bound)
throw new InvalidOperationException(
"Listen can only be called after a successful call to Bind");
#endif
if (!Bound)
return -1;
var ret = s_NetworkInterfaces[m_NetworkInterfaceIndex].Listen();
if (ret == 0)
Listening = true;
return ret;
}
/// <summary>
/// Checks to see if there are any new connections to Accept.
/// </summary>
/// <value>If accept fails it returnes a default NetworkConnection.</value>
public NetworkConnection Accept()
{
if (!Listening)
return default(NetworkConnection);
if (!m_NetworkAcceptQueue.TryDequeue(out var id))
return default(NetworkConnection);
return new NetworkConnection {m_NetworkId = id, m_NetworkVersion = m_ConnectionList[id].Version};
}
/// <summary>
/// Connects the driver to a endpoint
/// </summary>
/// <value>If connect fails it returns a default NetworkConnection.</value>
/// <exception cref="InvalidOperationException">If the driver is not created properly</exception>
public NetworkConnection Connect(NetworkEndPoint endpoint)
{
var address = new NetworkInterfaceEndPoint();
if (s_NetworkProtocols[m_NetworkProtocolIndex].Connect(s_NetworkInterfaces[m_NetworkInterfaceIndex], endpoint, out address) != 0)
{
return default;
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if (!m_InternalState.IsCreated)
throw new InvalidOperationException(
"Driver must be constructed with a populated or empty INetworkParameter params list");
#endif
if (!m_FreeList.TryDequeue(out var id))
{
id = m_ConnectionList.Length;
m_ConnectionList.Add(new Connection{Id = id, Version = 1});
}
int ver = m_ConnectionList[id].Version;
var c = new Connection
{
Id = id,
Version = ver,
State = NetworkConnection.State.Connecting,
Address = address,
Attempts = 1,
LastAttempt = m_updateTime,
SendToken = 0,
ReceiveToken = m_SessionIdCounter[0]
};
m_SessionIdCounter[0] = (ushort)(m_SessionIdCounter[0] + 1);
m_ConnectionList[id] = c;
var netcon = new NetworkConnection {m_NetworkId = id, m_NetworkVersion = ver};
SendConnectionRequest(c);
m_PipelineProcessor.initializeConnection(netcon);
return netcon;
}
void SendConnectionRequest(Connection c)
{
var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter());
m_NetworkProtocolInterface.ProcessSendConnectionRequest.Ptr.Invoke(ref c, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData);
}
/// <summary>
/// Disconnects a NetworkConnection
/// </summary>
/// <param name="id">The NetworkConnection we want to Disconnect.</param>
/// <value>Return 0 on success.</value>
public int Disconnect(NetworkConnection id)
{
Connection connection;
if ((connection = GetConnection(id)) == Connection.Null)
return 0;
if (connection.State == NetworkConnection.State.Connected)
{
var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter());
m_NetworkProtocolInterface.ProcessSendDisconnect.Ptr.Invoke(ref connection, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData);
}
RemoveConnection(connection);
return 0;
}
/// <summary>
/// Returns the PipelineBuffers for a specific pipeline and stage.
/// </summary>
/// <param name="pipeline"></param>
/// <param name="stageId"></param>
/// <param name="connection"></param>
/// <param name="readProcessingBuffer"></param>
/// <param name="writeProcessingBuffer"></param>
/// <param name="sharedProcessingBuffer"></param>
/// <exception cref="InvalidOperationException">If the the connection is invalid.</exception>
public void GetPipelineBuffers(NetworkPipeline pipeline, NetworkPipelineStageId stageId, NetworkConnection connection, out NativeArray<byte> readProcessingBuffer, out NativeArray<byte> writeProcessingBuffer, out NativeArray<byte> sharedBuffer)
{
if (connection.m_NetworkId < 0 || connection.m_NetworkId >= m_ConnectionList.Length ||
m_ConnectionList[connection.m_NetworkId].Version != connection.m_NetworkVersion)
throw new InvalidOperationException("Invalid connection");
m_PipelineProcessor.GetPipelineBuffers(pipeline, stageId, connection, out readProcessingBuffer, out writeProcessingBuffer, out sharedBuffer);
}
public NetworkConnection.State GetConnectionState(NetworkConnection con)
{
Connection connection;
if ((connection = GetConnection(con)) == Connection.Null)
return NetworkConnection.State.Disconnected;
return connection.State;
}
public NetworkEndPoint RemoteEndPoint(NetworkConnection id)
{
if (id == default(NetworkConnection))
return default(NetworkEndPoint);
Connection connection;
if ((connection = GetConnection(id)) == Connection.Null)
return default(NetworkEndPoint);
return s_NetworkProtocols[m_NetworkProtocolIndex].GetRemoteEndPoint(s_NetworkInterfaces[m_NetworkInterfaceIndex], connection.Address);
}
public NetworkEndPoint LocalEndPoint()
{
var ep = s_NetworkInterfaces[m_NetworkInterfaceIndex].LocalEndPoint;
return s_NetworkInterfaces[m_NetworkInterfaceIndex].GetGenericEndPoint(ep);
}
public int MaxHeaderSize(NetworkPipeline pipe)
{
return ToConcurrentSendOnly().MaxHeaderSize(pipe);
}
public int BeginSend(NetworkPipeline pipe, NetworkConnection id, out DataStreamWriter writer, int requiredPayloadSize = 0)
{
return ToConcurrentSendOnly().BeginSend(pipe, id, out writer, requiredPayloadSize);
}
public int BeginSend(NetworkConnection id, out DataStreamWriter writer, int requiredPayloadSize = 0)
{
return ToConcurrentSendOnly().BeginSend(NetworkPipeline.Null, id, out writer, requiredPayloadSize);
}
public int EndSend(DataStreamWriter writer)
{
return ToConcurrentSendOnly().EndSend(writer);
}
public void AbortSend(DataStreamWriter writer)
{
ToConcurrentSendOnly().AbortSend(writer);
}
/// <summary>
/// Pops an event
/// </summary>
/// <param name="con"></param>
/// <param name="reader"></param>
/// <value>Returns the type of event received, if the value is a <see cref="NetworkEvent.Type.Disconnect"/> event
/// then the DataStreamReader will contain the disconnect reason.<value/>
public NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReader reader)
{
return PopEvent(out con, out reader, out var _);
}
public NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReader reader, out NetworkPipeline pipeline)
{
reader = default(DataStreamReader);
var type = m_EventQueue.PopEvent(out var id, out var offset, out var size, out var pipelineId);
pipeline = new NetworkPipeline { Id = pipelineId };
if (type == NetworkEvent.Type.Disconnect && offset < 0)
reader = new DataStreamReader(((NativeArray<byte>)m_DisconnectReasons).GetSubArray(math.abs(offset), 1));
else if (size > 0)
reader = new DataStreamReader(((NativeArray<byte>)m_DataStream).GetSubArray(offset, size));
con = id < 0
? default(NetworkConnection)
: new NetworkConnection {m_NetworkId = id, m_NetworkVersion = m_ConnectionList[id].Version};
return type;
}
/// <summary>
/// Pops an event for a specific connection
/// </summary>
/// <param name="connectionId"></param>
/// <param name="reader"></param>
/// <value>Returns the type of event received, if the value is a <see cref="NetworkEvent.Type.Disconnect"/> event
/// then the DataStreamReader will contain the disconnect reason.<value/>
public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader)
{
return PopEventForConnection(connectionId, out reader, out var _);
}
public NetworkEvent.Type PopEventForConnection(NetworkConnection connectionId, out DataStreamReader reader, out NetworkPipeline pipeline)
{
reader = default(DataStreamReader);
pipeline = default(NetworkPipeline);
if (connectionId.m_NetworkId < 0 || connectionId.m_NetworkId >= m_ConnectionList.Length ||
m_ConnectionList[connectionId.m_NetworkId].Version != connectionId.m_NetworkVersion)
return (int) NetworkEvent.Type.Empty;
var type = m_EventQueue.PopEventForConnection(connectionId.m_NetworkId, out var offset, out var size, out var pipelineId);
pipeline = new NetworkPipeline { Id = pipelineId };
if (type == NetworkEvent.Type.Disconnect && offset < 0)
reader = new DataStreamReader(((NativeArray<byte>)m_DisconnectReasons).GetSubArray(math.abs(offset), 1));
else if (size > 0)
reader = new DataStreamReader(((NativeArray<byte>)m_DataStream).GetSubArray(offset, size));
return type;
}
/// <summary>
/// Returns the size of the eventqueue for a specific connection
/// </summary>
/// <param name="connectionId"></param>
/// <value>If the connection is valid it returns the size of the event queue otherwise it returns 0.</value>
public int GetEventQueueSizeForConnection(NetworkConnection connectionId)
{
if (connectionId.m_NetworkId < 0 || connectionId.m_NetworkId >= m_ConnectionList.Length ||
m_ConnectionList[connectionId.m_NetworkId].Version != connectionId.m_NetworkVersion)
return 0;
return m_EventQueue.GetCountForConnection(connectionId.m_NetworkId);
}
// internal helper functions ::::::::::::::::::::::::::::::::::::::::::
void AddConnection(int id)
{
m_EventQueue.PushEvent(new NetworkEvent {connectionId = id, type = NetworkEvent.Type.Connect});
}
void AddDisconnection(int id, Error.DisconnectReason reason = DisconnectReason.Default)
{
m_EventQueue.PushEvent(new NetworkEvent { connectionId = id, type = NetworkEvent.Type.Disconnect, status = (int)reason });
}
Connection GetConnection(NetworkConnection id)
{
if (id.m_NetworkId < 0 || id.m_NetworkId >= m_ConnectionList.Length)
return Connection.Null;
var con = m_ConnectionList[id.m_NetworkId];
if (con.Version != id.m_NetworkVersion)
return Connection.Null;
return con;
}
Connection GetConnection(NetworkInterfaceEndPoint address, ushort sessionId)
{
for (int i = 0; i < m_ConnectionList.Length; i++)
{
if (address == m_ConnectionList[i].Address && m_ConnectionList[i].ReceiveToken == sessionId )
return m_ConnectionList[i];
}
return Connection.Null;
}
Connection GetNewConnection(NetworkInterfaceEndPoint address, ushort sessionId)
{
for (int i = 0; i < m_ConnectionList.Length; i++)
{
if (address == m_ConnectionList[i].Address && m_ConnectionList[i].SendToken == sessionId )
return m_ConnectionList[i];
}
return Connection.Null;
}
void SetConnection(Connection connection)
{
m_ConnectionList[connection.Id] = connection;
}
bool RemoveConnection(Connection connection)
{
if (connection.State != NetworkConnection.State.Disconnected && connection == m_ConnectionList[connection.Id])
{
connection.State = NetworkConnection.State.Disconnected;
m_ConnectionList[connection.Id] = connection;
m_PendingFree.Enqueue(connection.Id);
return true;
}
return false;
}
bool UpdateConnection(Connection connection)
{
if (connection == m_ConnectionList[connection.Id])
{
SetConnection(connection);
return true;
}
return false;
}
void CheckTimeouts()
{
for (int i = 0; i < m_ConnectionList.Length; ++i)
{
var connection = m_ConnectionList[i];
if (connection == Connection.Null)
continue;
long now = m_updateTime;
var netcon = new NetworkConnection {m_NetworkId = connection.Id, m_NetworkVersion = connection.Version};
if ((connection.State == NetworkConnection.State.Connecting ||
connection.State == NetworkConnection.State.AwaitingResponse) &&
now - connection.LastAttempt > m_NetworkParams.config.connectTimeoutMS)
{
if (connection.Attempts >= m_NetworkParams.config.maxConnectAttempts)
{
RemoveConnection(connection);
AddDisconnection(connection.Id, DisconnectReason.MaxConnectionAttempts);
continue;
}
connection.Attempts = ++connection.Attempts;
connection.LastAttempt = now;
SetConnection(connection);
if (connection.State == NetworkConnection.State.Connecting)
SendConnectionRequest(connection);
else
{
var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter());
m_NetworkProtocolInterface.ProcessSendConnectionAccept.Ptr.Invoke(ref connection, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData);
}
}
if (connection.State == NetworkConnection.State.Connected &&
now - connection.LastAttempt > m_NetworkParams.config.disconnectTimeoutMS)
{
Disconnect(netcon);
AddDisconnection(connection.Id, DisconnectReason.Timeout);
}
}
}
public int ReceiveErrorCode
{
get { return m_ErrorCodes[(int)ErrorCodeType.ReceiveError]; }
internal set
{
if (value != 0)
{
UnityEngine.Debug.LogError(FixedString.Format("Error on receive, errorCode = {0}", value));
}
m_ErrorCodes[(int)ErrorCodeType.ReceiveError] = value;
}
}
// Interface for receiving packages from a NetworkInterface
internal NativeList<byte> GetDataStream()
{
return m_DataStream;
}
internal int GetDataStreamSize()
{
return m_DataStreamSize[0];
}
private NativeArray<int> m_ReceiveCount;
internal int ReceiveCount {
get { return m_ReceiveCount[0]; }
set { m_ReceiveCount[0] = value; }
}
internal bool DynamicDataStreamSize()
{
return m_NetworkParams.dataStream.size == 0;
}
internal bool IsAddressUsed(NetworkInterfaceEndPoint address)
{
for (int i = 0; i < m_ConnectionList.Length; i++)
{
if (address == m_ConnectionList[i].Address)
return true;
}
return false;
}
internal unsafe int AppendPacket(NetworkInterfaceEndPoint endpoint, int dataLen)
{
var count = 0;
var command = default(ProcessPacketCommand);
byte* dataStream = (byte*)m_DataStream.GetUnsafePtr() + m_DataStreamSize[0];
var queueHandle = NetworkSendQueueHandle.ToTempHandle(m_ParallelSendQueue.AsParallelWriter());
m_NetworkProtocolInterface.ProcessReceive.Ptr.Invoke((IntPtr)dataStream, ref endpoint, dataLen, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData, ref command);
switch (command.Type)
{
case ProcessPacketCommandType.AddressUpdate:
{
for (int i = 0; i < m_ConnectionList.Length; i++)
{
if (command.AsAddressUpdate.Address == m_ConnectionList[i].Address && command.AsAddressUpdate.SessionToken == m_ConnectionList[i].ReceiveToken)
m_ConnectionList.ElementAt(i).Address = command.AsAddressUpdate.NewAddress;
}
} break;
case ProcessPacketCommandType.BindAccept:
{
m_InternalState[InternalStateBound] = 2;
} break;
case ProcessPacketCommandType.ConnectionAccept:
{
Connection c = GetConnection(command.AsConnectionAccept.Address, command.AsConnectionAccept.SessionId);
if (c != Connection.Null)
{
c.DidReceiveData = 1;
if (c.State == NetworkConnection.State.Connected)
{
//DebugLog("Dropping connect request for an already connected endpoint [" + address + "]");
return 0;
}
if (c.State == NetworkConnection.State.Connecting)
{
c.SendToken = command.AsConnectionAccept.ConnectionToken;
c.State = NetworkConnection.State.Connected;
UpdateConnection(c);
AddConnection(c.Id);
count++;
}
}
} break;
case ProcessPacketCommandType.ConnectionReject:
break;
case ProcessPacketCommandType.ConnectionRequest:
{
if (!Listening)
return 0;
Connection c = GetNewConnection(command.AsConnectionRequest.Address, command.AsConnectionRequest.SessionId);
if (c == Connection.Null || c.State == NetworkConnection.State.Disconnected)
{
var sessionId = m_SessionIdCounter[0];
m_SessionIdCounter[0] = (ushort) (m_SessionIdCounter[0] + 1);
if (!m_FreeList.TryDequeue(out var id))
{
id = m_ConnectionList.Length;
m_ConnectionList.Add(new Connection{Id = id, Version = 1});
}
int ver = m_ConnectionList[id].Version;
c = new Connection
{
Id = id,
Version = ver,
ReceiveToken = sessionId,
SendToken = command.AsConnectionRequest.SessionId,
State = NetworkConnection.State.Connected,
Address = command.AsConnectionRequest.Address,
Attempts = 1,
LastAttempt = m_updateTime
};
SetConnection(c);
m_PipelineProcessor.initializeConnection(new NetworkConnection{m_NetworkId = id, m_NetworkVersion = c.Version});
m_NetworkAcceptQueue.Enqueue(id);
count++;
}
else
{
c.Attempts++;
c.LastAttempt = m_updateTime;
SetConnection(c);
}
m_NetworkProtocolInterface.ProcessSendConnectionAccept.Ptr.Invoke(ref c, ref m_NetworkSendInterface, ref queueHandle, m_NetworkProtocolInterface.UserData);
} break;
case ProcessPacketCommandType.Disconnect:
{
Connection c = GetConnection(command.AsDisconnect.Address, command.AsDisconnect.SessionId);
if (c != Connection.Null)
{
if (RemoveConnection(c))
AddDisconnection(c.Id, DisconnectReason.ClosedByRemote);
count++;
}
} break;
case ProcessPacketCommandType.DataWithImplicitConnectionAccept:
{
Connection c = GetConnection(command.AsDataWithImplicitConnectionAccept.Address, command.AsDataWithImplicitConnectionAccept.SessionId);
if (c == Connection.Null)
return 0;
c.DidReceiveData = 1;
c.LastAttempt = m_updateTime;
UpdateConnection(c);
if (c.State == NetworkConnection.State.Connecting)
{
c.SendToken = command.AsDataWithImplicitConnectionAccept.ConnectionToken;
c.State = NetworkConnection.State.Connected;
UpdateConnection(c);
UnityEngine.Assertions.Assert.IsTrue(!Listening);
AddConnection(c.Id);
count++;
}
if (c.State == NetworkConnection.State.Connected)
{
var sliceOffset = m_DataStreamSize[0] + command.AsDataWithImplicitConnectionAccept.Offset;
m_DataStreamSize[0] = sliceOffset + command.AsDataWithImplicitConnectionAccept.Length;
if (command.AsDataWithImplicitConnectionAccept.HasPipeline)
{
var netCon = new NetworkConnection {m_NetworkId = c.Id, m_NetworkVersion = c.Version};
m_PipelineProcessor.Receive(this, netCon, ((NativeArray<byte>)m_DataStream).GetSubArray(sliceOffset, command.AsDataWithImplicitConnectionAccept.Length));
// TODO: is this return 0 intended? shouldn't this be return count + 1
return 0;
}
m_EventQueue.PushEvent(new NetworkEvent
{
connectionId = c.Id,
type = NetworkEvent.Type.Data,
offset = sliceOffset,
size = command.AsDataWithImplicitConnectionAccept.Length
});
count++;
}
} break;
case ProcessPacketCommandType.Data:
{
Connection c = GetConnection(command.AsData.Address, command.AsData.SessionId);
if (c == Connection.Null)
return 0;
c.DidReceiveData = 1;
c.LastAttempt = m_updateTime;
UpdateConnection(c);
if (c.State == NetworkConnection.State.Connected)
{
var sliceOffset = m_DataStreamSize[0] + command.AsData.Offset;
m_DataStreamSize[0] = sliceOffset + command.AsData.Length;
if (command.AsData.HasPipeline)
{
var netCon = new NetworkConnection {m_NetworkId = c.Id, m_NetworkVersion = c.Version};
m_PipelineProcessor.Receive(this, netCon, ((NativeArray<byte>)m_DataStream).GetSubArray(sliceOffset, command.AsData.Length));
// TODO: is this return 0 intended? shouldn't this be return count + 1
return 0;
}
m_EventQueue.PushEvent(new NetworkEvent
{
connectionId = c.Id,
type = NetworkEvent.Type.Data,
offset = sliceOffset,
size = command.AsData.Length
});
count++;
}
} break;
case ProcessPacketCommandType.Drop:
break;
}
return count;
}
// Interface for receiving data from a pipeline
internal unsafe void PushDataEvent(NetworkConnection con, int pipelineId, byte* dataPtr, int dataLength)
{
byte* streamBasePtr = (byte*)m_DataStream.GetUnsafePtr();
int sliceOffset = 0;
if (dataPtr >= streamBasePtr && dataPtr + dataLength <= streamBasePtr + m_DataStreamSize[0])
{
// Pointer is a subset of our receive buffer, no need to copy
sliceOffset = (int)(dataPtr - streamBasePtr);
}
else
{
if (DynamicDataStreamSize())
{
while (m_DataStreamSize[0] + dataLength >= m_DataStream.Length)
m_DataStream.ResizeUninitialized(m_DataStream.Length * 2);
}
else if (m_DataStreamSize[0] + dataLength >= m_DataStream.Length)
return; // FIXME: how do we signal this error?
sliceOffset = m_DataStreamSize[0];
streamBasePtr = (byte*)m_DataStream.GetUnsafePtr();
UnsafeUtility.MemCpy(streamBasePtr + sliceOffset, dataPtr, dataLength);
m_DataStreamSize[0] = sliceOffset + dataLength;
}
m_EventQueue.PushEvent(new NetworkEvent
{
pipelineId = (short)pipelineId,
connectionId = con.m_NetworkId,
type = NetworkEvent.Type.Data,
offset = sliceOffset,
size = dataLength
});
}
}
}