Gonna move to server authority

This commit is contained in:
2022-01-31 17:27:38 +05:30
parent f3d21f4ec6
commit 7368968176
1354 changed files with 107808 additions and 80043 deletions

View File

@@ -1,178 +0,0 @@
// uses the first available transport for server and client.
// example: to use Apathy if on Windows/Mac/Linux and fall back to Telepathy
// otherwise.
using System;
using UnityEngine;
namespace Mirror
{
// Deprecated 2021-05-13
[HelpURL("https://mirror-networking.gitbook.io/docs/transports/fallback-transport")]
[DisallowMultipleComponent]
[Obsolete("Fallback Transport will be retired. It was only needed for Apathy/Libuv. Use kcp or Telepathy instead, those run everywhere.")]
public class FallbackTransport : Transport
{
public Transport[] transports;
// the first transport that is available on this platform
Transport available;
public void Awake()
{
if (transports == null || transports.Length == 0)
{
throw new Exception("FallbackTransport requires at least 1 underlying transport");
}
available = GetAvailableTransport();
Debug.Log("FallbackTransport available: " + available.GetType());
}
void OnEnable()
{
available.enabled = true;
}
void OnDisable()
{
available.enabled = false;
}
// The client just uses the first transport available
Transport GetAvailableTransport()
{
foreach (Transport transport in transports)
{
if (transport.Available())
{
return transport;
}
}
throw new Exception("No transport suitable for this platform");
}
public override bool Available()
{
return available.Available();
}
public override void ClientConnect(string address)
{
available.OnClientConnected = OnClientConnected;
available.OnClientDataReceived = OnClientDataReceived;
available.OnClientError = OnClientError;
available.OnClientDisconnected = OnClientDisconnected;
available.ClientConnect(address);
}
public override void ClientConnect(Uri uri)
{
foreach (Transport transport in transports)
{
if (transport.Available())
{
try
{
transport.ClientConnect(uri);
available = transport;
}
catch (ArgumentException)
{
// transport does not support the schema, just move on to the next one
}
}
}
throw new Exception("No transport suitable for this platform");
}
public override bool ClientConnected()
{
return available.ClientConnected();
}
public override void ClientDisconnect()
{
available.ClientDisconnect();
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
available.ClientSend(segment, channelId);
}
// right now this just returns the first available uri,
// should we return the list of all available uri?
public override Uri ServerUri() => available.ServerUri();
public override bool ServerActive()
{
return available.ServerActive();
}
public override string ServerGetClientAddress(int connectionId)
{
return available.ServerGetClientAddress(connectionId);
}
public override void ServerDisconnect(int connectionId)
{
available.ServerDisconnect(connectionId);
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
available.ServerSend(connectionId, segment, channelId);
}
public override void ServerStart()
{
available.OnServerConnected = OnServerConnected;
available.OnServerDataReceived = OnServerDataReceived;
available.OnServerError = OnServerError;
available.OnServerDisconnected = OnServerDisconnected;
available.ServerStart();
}
public override void ServerStop()
{
available.ServerStop();
}
public override void ClientEarlyUpdate() => available.ClientEarlyUpdate();
public override void ServerEarlyUpdate() => available.ServerEarlyUpdate();
public override void ClientLateUpdate() => available.ClientLateUpdate();
public override void ServerLateUpdate() => available.ServerLateUpdate();
public override void Shutdown()
{
available.Shutdown();
}
public override int GetMaxPacketSize(int channelId = 0)
{
// finding the max packet size in a fallback environment has to be
// done very carefully:
// * servers and clients might run different transports depending on
// which platform they are on.
// * there should only ever be ONE true max packet size for everyone,
// otherwise a spawn message might be sent to all tcp sockets, but
// be too big for some udp sockets. that would be a debugging
// nightmare and allow for possible exploits and players on
// different platforms seeing a different game state.
// => the safest solution is to use the smallest max size for all
// transports. that will never fail.
int mininumAllowedSize = int.MaxValue;
foreach (Transport transport in transports)
{
int size = transport.GetMaxPacketSize(channelId);
mininumAllowedSize = Mathf.Min(size, mininumAllowedSize);
}
return mininumAllowedSize;
}
public override string ToString()
{
return available.ToString();
}
}
}

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: cc98cd95e3fb22b4eb88082706967357
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: cc98cd95e3fb22b4eb88082706967357
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: e03829cb9e647274db0f6a7b8b1b757b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: e03829cb9e647274db0f6a7b8b1b757b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,296 +1,351 @@
// Ignorance 1.4.x
// Ignorance. It really kicks the Unity LLAPIs ass.
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance Transport is licensed under the MIT license. Refer
// to the LICENSE file for more information.
using ENet;
// using NetStack.Buffers;
using System;
using System.Collections.Concurrent;
using System.Threading;
using UnityEngine;
using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine
namespace IgnoranceTransport
{
public class IgnoranceClient
{
// Client connection address and port
public string ConnectAddress = "127.0.0.1";
public int ConnectPort = 7777;
// How many channels are expected
public int ExpectedChannels = 2;
// Native poll waiting time
public int PollTime = 1;
// Maximum Packet Size
public int MaximumPacketSize = 33554432;
// General Verbosity by default.
public int Verbosity = 1;
// Queues
public ConcurrentQueue<IgnoranceIncomingPacket> Incoming = new ConcurrentQueue<IgnoranceIncomingPacket>();
public ConcurrentQueue<IgnoranceOutgoingPacket> Outgoing = new ConcurrentQueue<IgnoranceOutgoingPacket>();
public ConcurrentQueue<IgnoranceCommandPacket> Commands = new ConcurrentQueue<IgnoranceCommandPacket>();
public ConcurrentQueue<IgnoranceConnectionEvent> ConnectionEvents = new ConcurrentQueue<IgnoranceConnectionEvent>();
public ConcurrentQueue<IgnoranceClientStats> StatusUpdates = new ConcurrentQueue<IgnoranceClientStats>();
public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive;
private volatile bool CeaseOperation = false;
private Thread WorkerThread;
public void Start()
{
Debug.Log("IgnoranceClient.Start()");
if (WorkerThread != null && WorkerThread.IsAlive)
{
// Cannot do that.
Debug.LogError("A worker thread is already running. Cannot start another.");
return;
}
CeaseOperation = false;
ThreadParamInfo threadParams = new ThreadParamInfo()
{
Address = ConnectAddress,
Port = ConnectPort,
Channels = ExpectedChannels,
PollTime = PollTime,
PacketSizeLimit = MaximumPacketSize,
Verbosity = Verbosity
};
// Drain queues.
if (Incoming != null) while (Incoming.TryDequeue(out _)) ;
if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ;
if (Commands != null) while (Commands.TryDequeue(out _)) ;
if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ;
if (StatusUpdates != null) while (StatusUpdates.TryDequeue(out _)) ;
WorkerThread = new Thread(ThreadWorker);
WorkerThread.Start(threadParams);
Debug.Log("Client has dispatched worker thread.");
}
public void Stop()
{
Debug.Log("Telling client thread to stop, this may take a while depending on network load");
CeaseOperation = true;
}
// This runs in a seperate thread, be careful accessing anything outside of it's thread
// or you may get an AccessViolation/crash.
private void ThreadWorker(Object parameters)
{
if (Verbosity > 0)
Debug.Log("Ignorance Client: Initializing. Please stand by...");
ThreadParamInfo setupInfo;
Address clientAddress = new Address();
Peer clientPeer;
Host clientENetHost;
Event clientENetEvent;
IgnoranceClientStats icsu = default;
// Grab the setup information.
if (parameters.GetType() == typeof(ThreadParamInfo))
{
setupInfo = (ThreadParamInfo)parameters;
}
else
{
Debug.LogError("Ignorance Client: Startup failure: Invalid thread parameters. Aborting.");
return;
}
// Attempt to initialize ENet inside the thread.
if (Library.Initialize())
{
Debug.Log("Ignorance Client: ENet initialized.");
}
else
{
Debug.LogError("Ignorance Client: Failed to initialize ENet. This threads' fucked.");
return;
}
// Attempt to connect to our target.
clientAddress.SetHost(setupInfo.Address);
clientAddress.Port = (ushort)setupInfo.Port;
using (clientENetHost = new Host())
{
// TODO: Maybe try catch this
clientENetHost.Create();
clientPeer = clientENetHost.Connect(clientAddress, setupInfo.Channels);
while (!CeaseOperation)
{
bool pollComplete = false;
// Step 0: Handle commands.
while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket))
{
switch (commandPacket.Type)
{
default:
break;
case IgnoranceCommandType.ClientWantsToStop:
CeaseOperation = true;
break;
case IgnoranceCommandType.ClientRequestsStatusUpdate:
// Respond with statistics so far.
if (!clientPeer.IsSet)
break;
icsu.RTT = clientPeer.RoundTripTime;
icsu.BytesReceived = clientPeer.BytesReceived;
icsu.BytesSent = clientPeer.BytesSent;
icsu.PacketsReceived = clientENetHost.PacketsReceived;
icsu.PacketsSent = clientPeer.PacketsSent;
icsu.PacketsLost = clientPeer.PacketsLost;
StatusUpdates.Enqueue(icsu);
break;
}
}
// Step 1: Send out data.
// ---> Sending to Server
while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
{
// TODO: Revise this, could we tell the Peer to disconnect right here?
// Stop early if we get a client stop packet.
// if (outgoingPacket.Type == IgnorancePacketType.ClientWantsToStop) break;
int ret = clientPeer.Send(outgoingPacket.Channel, ref outgoingPacket.Payload);
if (ret < 0 && setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Client: ENet error code {ret} while sending packet to Peer {outgoingPacket.NativePeerId}.");
}
// Step 2:
// <----- Receive Data packets
// This loops until polling is completed. It may take a while, if it's
// a slow networking day.
while (!pollComplete)
{
Packet incomingPacket;
Peer incomingPeer;
int incomingPacketLength;
// Any events worth checking out?
if (clientENetHost.CheckEvents(out clientENetEvent) <= 0)
{
// If service time is met, break out of it.
if (clientENetHost.Service(setupInfo.PollTime, out clientENetEvent) <= 0) break;
// Poll is done.
pollComplete = true;
}
// Setup the packet references.
incomingPeer = clientENetEvent.Peer;
// Now, let's handle those events.
switch (clientENetEvent.Type)
{
case EventType.None:
default:
break;
case EventType.Connect:
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent()
{
NativePeerId = incomingPeer.ID,
IP = incomingPeer.IP,
Port = incomingPeer.Port
});
break;
case EventType.Disconnect:
case EventType.Timeout:
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent()
{
WasDisconnect = true
});
break;
case EventType.Receive:
// Receive event type usually includes a packet; so cache its reference.
incomingPacket = clientENetEvent.Packet;
if (!incomingPacket.IsSet)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Client: A receive event did not supply us with a packet to work with. This should never happen.");
break;
}
incomingPacketLength = incomingPacket.Length;
// Never consume more than we can have capacity for.
if (incomingPacketLength > setupInfo.PacketSizeLimit)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Client: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes.");
incomingPacket.Dispose();
break;
}
IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
{
Channel = clientENetEvent.ChannelID,
NativePeerId = incomingPeer.ID,
Payload = incomingPacket
};
Incoming.Enqueue(incomingQueuePacket);
break;
}
}
}
Debug.Log("Ignorance Server: Shutdown commencing, disconnecting and flushing connection.");
// Flush the client and disconnect.
clientPeer.Disconnect(0);
clientENetHost.Flush();
// Fix for client stuck in limbo, since the disconnection event may not be fired until next loop.
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent()
{
WasDisconnect = true
});
}
// Deinitialize
Library.Deinitialize();
if (setupInfo.Verbosity > 0)
Debug.Log("Ignorance Client: Shutdown complete.");
}
private struct ThreadParamInfo
{
public int Channels;
public int PollTime;
public int Port;
public int PacketSizeLimit;
public int Verbosity;
public string Address;
}
}
}
// Ignorance 1.4.x LTS (Long Term Support)
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance is licensed under the MIT license. Refer
// to the LICENSE file for more information.
using System;
using System.Collections.Concurrent;
using System.Threading;
using ENet;
using IgnoranceThirdparty;
using UnityEngine;
using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine
namespace IgnoranceCore
{
public class IgnoranceClient
{
// Client connection address and port
public string ConnectAddress = "127.0.0.1";
public int ConnectPort = 7777;
// How many channels are expected
public int ExpectedChannels = 2;
// Native poll waiting time
public int PollTime = 1;
// Maximum Packet Size
public int MaximumPacketSize = 33554432;
// General Verbosity by default.
public int Verbosity = 1;
// Maximum ring buffer capacity.
public int IncomingOutgoingBufferSize = 5000;
public int ConnectionEventBufferSize = 100;
// Queues
public RingBuffer<IgnoranceIncomingPacket> Incoming;
public RingBuffer<IgnoranceOutgoingPacket> Outgoing;
public RingBuffer<IgnoranceCommandPacket> Commands;
public RingBuffer<IgnoranceConnectionEvent> ConnectionEvents;
public RingBuffer<IgnoranceClientStats> StatusUpdates;
public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive;
private volatile bool CeaseOperation = false;
private Thread WorkerThread;
public void Start()
{
if (WorkerThread != null && WorkerThread.IsAlive)
{
// Cannot do that.
Debug.LogError("Ignorance Client: A worker thread is already running. Cannot start another.");
return;
}
// Setup the ring buffers.
SetupRingBuffersIfNull();
CeaseOperation = false;
ThreadParamInfo threadParams = new ThreadParamInfo()
{
Address = ConnectAddress,
Port = ConnectPort,
Channels = ExpectedChannels,
PollTime = PollTime,
PacketSizeLimit = MaximumPacketSize,
Verbosity = Verbosity
};
// Drain queues.
if (Incoming != null) while (Incoming.TryDequeue(out _)) ;
if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ;
if (Commands != null) while (Commands.TryDequeue(out _)) ;
if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ;
if (StatusUpdates != null) while (StatusUpdates.TryDequeue(out _)) ;
WorkerThread = new Thread(ThreadWorker);
WorkerThread.Start(threadParams);
Debug.Log("Ignorance Client: Dispatched worker thread.");
}
public void Stop()
{
if (WorkerThread != null && !CeaseOperation)
{
Debug.Log("Ignorance Client: Stop acknowledged. This may take a while depending on network load...");
CeaseOperation = true;
}
}
#region The meat and potatoes.
// This runs in a seperate thread, be careful accessing anything outside of it's thread
// or you may get an AccessViolation/crash.
private void ThreadWorker(Object parameters)
{
if (Verbosity > 0)
Debug.Log("Ignorance Client: Initializing. Please stand by...");
ThreadParamInfo setupInfo;
Address clientAddress = new Address();
Peer clientPeer; // The peer object that represents the client's connection.
Host clientHost; // NOT related to Mirror "Client Host". This is the client's ENet Host Object.
Event clientEvent; // Used when clients get events on the network.
IgnoranceClientStats icsu = default;
bool alreadyNotifiedAboutDisconnect = false;
// Grab the setup information.
if (parameters.GetType() == typeof(ThreadParamInfo))
setupInfo = (ThreadParamInfo)parameters;
else
{
Debug.LogError("Ignorance Client: Startup failure; Invalid thread parameters. Aborting.");
return;
}
// Attempt to initialize ENet inside the thread.
if (Library.Initialize())
Debug.Log("Ignorance Client: ENet Native successfully initialized.");
else
{
Debug.LogError("Ignorance Client: Failed to initialize ENet Native. Aborting.");
return;
}
// Attempt to connect to our target.
clientAddress.SetHost(setupInfo.Address);
clientAddress.Port = (ushort)setupInfo.Port;
using (clientHost = new Host())
{
try
{
clientHost.Create();
clientPeer = clientHost.Connect(clientAddress, setupInfo.Channels);
}
catch (Exception ex)
{
// Oops, something failed.
Debug.LogError($"Ignorance Client: Looks like something went wrong. While attempting to create client object, we caught an exception:\n{ex.Message}");
Debug.LogError($"You could try the debug-enabled version of the native ENet library which creates a logfile, or alternatively you could try restart " +
$"your device to ensure jank is cleared out of memory. If problems persist, please file a support ticket explaining what happened.");
Library.Deinitialize();
return;
}
// Process network events as long as we're not ceasing operation.
while (!CeaseOperation)
{
bool pollComplete = false;
while (Commands.TryDequeue(out IgnoranceCommandPacket ignoranceCommandPacket))
{
switch (ignoranceCommandPacket.Type)
{
case IgnoranceCommandType.ClientStatusRequest:
// Respond with statistics so far.
if (!clientPeer.IsSet)
break;
icsu.RTT = clientPeer.RoundTripTime;
icsu.BytesReceived = clientPeer.BytesReceived;
icsu.BytesSent = clientPeer.BytesSent;
icsu.PacketsReceived = clientHost.PacketsReceived;
icsu.PacketsSent = clientPeer.PacketsSent;
icsu.PacketsLost = clientPeer.PacketsLost;
StatusUpdates.Enqueue(icsu);
break;
case IgnoranceCommandType.ClientWantsToStop:
CeaseOperation = true;
break;
}
}
// If something outside the thread has told us to stop execution, then we need to break out of this while loop.
if (CeaseOperation)
break;
// Step 1: Sending to Server
while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
{
// TODO: Revise this, could we tell the Peer to disconnect right here?
// Stop early if we get a client stop packet.
// if (outgoingPacket.Type == IgnorancePacketType.ClientWantsToStop) break;
int ret = clientPeer.Send(outgoingPacket.Channel, ref outgoingPacket.Payload);
if (ret < 0 && setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Client: ENet error {ret} while sending packet to Server via Peer {outgoingPacket.NativePeerId}.");
}
// If something outside the thread has told us to stop execution, then we need to break out of this while loop.
// while loop to break out of is while(!CeaseOperation).
if (CeaseOperation)
break;
// Step 2: Receive Data packets
// This loops until polling is completed. It may take a while, if it's
// a slow networking day.
while (!pollComplete)
{
Packet incomingPacket;
Peer incomingPeer;
int incomingPacketLength;
// Any events worth checking out?
if (clientHost.CheckEvents(out clientEvent) <= 0)
{
// If service time is met, break out of it.
if (clientHost.Service(setupInfo.PollTime, out clientEvent) <= 0) break;
// Poll is done.
pollComplete = true;
}
// Setup the packet references.
incomingPeer = clientEvent.Peer;
// Now, let's handle those events.
switch (clientEvent.Type)
{
case EventType.None:
default:
break;
case EventType.Connect:
if (setupInfo.Verbosity > 0)
Debug.Log("Ignorance Client: ENet has connected to the server.");
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent
{
EventType = 0x00,
NativePeerId = incomingPeer.ID,
IP = incomingPeer.IP,
Port = incomingPeer.Port
});
break;
case EventType.Disconnect:
case EventType.Timeout:
if (setupInfo.Verbosity > 0)
Debug.Log("Ignorance Client: ENet has been disconnected from the server.");
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent { EventType = 0x01 });
CeaseOperation = true;
alreadyNotifiedAboutDisconnect = true;
break;
case EventType.Receive:
// Receive event type usually includes a packet; so cache its reference.
incomingPacket = clientEvent.Packet;
if (!incomingPacket.IsSet)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Client: A receive event did not supply us with a packet to work with. This should never happen.");
break;
}
incomingPacketLength = incomingPacket.Length;
// Never consume more than we can have capacity for.
if (incomingPacketLength > setupInfo.PacketSizeLimit)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Client: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes.");
incomingPacket.Dispose();
break;
}
IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
{
Channel = clientEvent.ChannelID,
NativePeerId = incomingPeer.ID,
Payload = incomingPacket
};
Incoming.Enqueue(incomingQueuePacket);
break;
}
}
// If something outside the thread has told us to stop execution, then we need to break out of this while loop.
// while loop to break out of is while(!CeaseOperation).
if (CeaseOperation)
break;
}
Debug.Log("Ignorance Client: Thread shutdown commencing. Disconnecting and flushing connection.");
// Flush the client and disconnect.
clientPeer.Disconnect(0);
clientHost.Flush();
// Fix for client stuck in limbo, since the disconnection event may not be fired until next loop.
if (!alreadyNotifiedAboutDisconnect)
{
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent { EventType = 0x01 });
alreadyNotifiedAboutDisconnect = true;
}
}
// Fix for client stuck in limbo, since the disconnection event may not be fired until next loop, again.
if (!alreadyNotifiedAboutDisconnect)
ConnectionEvents.Enqueue(new IgnoranceConnectionEvent { EventType = 0x01 });
// Deinitialize
Library.Deinitialize();
if (setupInfo.Verbosity > 0)
Debug.Log("Ignorance Client: Shutdown complete.");
}
#endregion
private void SetupRingBuffersIfNull()
{
Debug.Log($"Ignorance: Setting up ring buffers if they're not already created. " +
$"If they are already, this step will be skipped.");
if (Incoming == null)
Incoming = new RingBuffer<IgnoranceIncomingPacket>(IncomingOutgoingBufferSize);
if (Outgoing == null)
Outgoing = new RingBuffer<IgnoranceOutgoingPacket>(IncomingOutgoingBufferSize);
if (Commands == null)
Commands = new RingBuffer<IgnoranceCommandPacket>(100);
if (ConnectionEvents == null)
ConnectionEvents = new RingBuffer<IgnoranceConnectionEvent>(ConnectionEventBufferSize);
if (StatusUpdates == null)
StatusUpdates = new RingBuffer<IgnoranceClientStats>(10);
}
private struct ThreadParamInfo
{
public int Channels;
public int PollTime;
public int Port;
public int PacketSizeLimit;
public int Verbosity;
public string Address;
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: b7b9e2c091c3d42439840a02fe700252
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: b7b9e2c091c3d42439840a02fe700252
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,328 +1,411 @@
// Ignorance 1.4.x
// Ignorance. It really kicks the Unity LLAPIs ass.
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance Transport is licensed under the MIT license. Refer
// to the LICENSE file for more information.
using ENet;
// using NetStack.Buffers;
using System.Collections.Concurrent;
using System.Threading;
using UnityEngine;
using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine
namespace IgnoranceTransport
{
public class IgnoranceServer
{
// Server Properties
// - Bind Settings
public string BindAddress = "127.0.0.1";
public int BindPort = 7777;
// - Maximum allowed channels, peers, etc.
public int MaximumChannels = 2;
public int MaximumPeers = 100;
public int MaximumPacketSize = 33554432; // ENet.cs: uint maxPacketSize = 32 * 1024 * 1024 = 33554432
// - Native poll waiting time
public int PollTime = 1;
public int Verbosity = 1;
public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive;
private volatile bool CeaseOperation = false;
// Queues
public ConcurrentQueue<IgnoranceIncomingPacket> Incoming = new ConcurrentQueue<IgnoranceIncomingPacket>();
public ConcurrentQueue<IgnoranceOutgoingPacket> Outgoing = new ConcurrentQueue<IgnoranceOutgoingPacket>();
public ConcurrentQueue<IgnoranceCommandPacket> Commands = new ConcurrentQueue<IgnoranceCommandPacket>();
public ConcurrentQueue<IgnoranceConnectionEvent> ConnectionEvents = new ConcurrentQueue<IgnoranceConnectionEvent>();
public ConcurrentQueue<IgnoranceConnectionEvent> DisconnectionEvents = new ConcurrentQueue<IgnoranceConnectionEvent>();
// Thread
private Thread WorkerThread;
public void Start()
{
if (WorkerThread != null && WorkerThread.IsAlive)
{
// Cannot do that.
Debug.LogError("A worker thread is already running. Cannot start another.");
return;
}
CeaseOperation = false;
ThreadParamInfo threadParams = new ThreadParamInfo()
{
Address = BindAddress,
Port = BindPort,
Peers = MaximumPeers,
Channels = MaximumChannels,
PollTime = PollTime,
PacketSizeLimit = MaximumPacketSize,
Verbosity = Verbosity
};
// Drain queues.
if (Incoming != null) while (Incoming.TryDequeue(out _)) ;
if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ;
if (Commands != null) while (Commands.TryDequeue(out _)) ;
if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ;
if (DisconnectionEvents != null) while (DisconnectionEvents.TryDequeue(out _)) ;
WorkerThread = new Thread(ThreadWorker);
WorkerThread.Start(threadParams);
// Announce
if (Verbosity > 0)
Debug.Log("Server has dispatched worker thread.");
}
public void Stop()
{
if (Verbosity > 0)
Debug.Log("Telling server thread to stop, this may take a while depending on network load");
CeaseOperation = true;
}
private void ThreadWorker(Object parameters)
{
if (Verbosity > 0)
Debug.Log("Ignorance Server: Initializing. Please stand by...");
// Thread cache items
ThreadParamInfo setupInfo;
Address serverAddress = new Address();
Host serverENetHost;
Event serverENetEvent;
Peer[] serverPeerArray;
// Grab the setup information.
if (parameters.GetType() == typeof(ThreadParamInfo))
{
setupInfo = (ThreadParamInfo)parameters;
}
else
{
Debug.LogError("Ignorance Server: Startup failure: Invalid thread parameters. Aborting.");
return;
}
// Attempt to initialize ENet inside the thread.
if (Library.Initialize())
{
Debug.Log("Ignorance Server: ENet initialized.");
}
else
{
Debug.LogError("Ignorance Server: Failed to initialize ENet. This threads' fucked.");
return;
}
// Configure the server address.
serverAddress.SetHost(setupInfo.Address);
serverAddress.Port = (ushort)setupInfo.Port;
serverPeerArray = new Peer[setupInfo.Peers];
using (serverENetHost = new Host())
{
// Create the server object.
serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels);
// Loop until we're told to cease operations.
while (!CeaseOperation)
{
// Intermission: Command Handling
while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket))
{
switch (commandPacket.Type)
{
default:
break;
// Boot a Peer off the Server.
case IgnoranceCommandType.ServerKickPeer:
uint targetPeer = commandPacket.PeerId;
if (!serverPeerArray[targetPeer].IsSet) continue;
if (setupInfo.Verbosity > 0)
Debug.Log($"Ignorance Server: Booting Peer {targetPeer} off this server instance.");
IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent()
{
WasDisconnect = true,
NativePeerId = targetPeer
};
DisconnectionEvents.Enqueue(iced);
// Disconnect and reset the peer array's entry for that peer.
serverPeerArray[targetPeer].DisconnectNow(0);
serverPeerArray[targetPeer] = default;
break;
}
}
// Step One:
// ---> Sending to peers
while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
{
// Only create a packet if the server knows the peer.
if (serverPeerArray[outgoingPacket.NativePeerId].IsSet)
{
int ret = serverPeerArray[outgoingPacket.NativePeerId].Send(outgoingPacket.Channel, ref outgoingPacket.Payload);
if (ret < 0 && setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Server: ENet error code {ret} while sending packet to Peer {outgoingPacket.NativePeerId}.");
}
else
{
// A peer might have disconnected, this is OK - just log the packet if set to paranoid.
if (setupInfo.Verbosity > 1)
Debug.LogWarning("Ignorance Server: Can't send packet, a native peer object is not set. This may be normal if the Peer has disconnected before this send cycle.");
}
}
// Step 2
// <--- Receiving from peers
bool pollComplete = false;
while (!pollComplete)
{
Packet incomingPacket;
Peer incomingPeer;
int incomingPacketLength;
// Any events happening?
if (serverENetHost.CheckEvents(out serverENetEvent) <= 0)
{
// If service time is met, break out of it.
if (serverENetHost.Service(setupInfo.PollTime, out serverENetEvent) <= 0) break;
pollComplete = true;
}
// Setup the packet references.
incomingPeer = serverENetEvent.Peer;
switch (serverENetEvent.Type)
{
// Idle.
case EventType.None:
default:
break;
// Connection Event.
case EventType.Connect:
if (setupInfo.Verbosity > 1)
Debug.Log("Ignorance Server: Here comes a new Peer connection.");
IgnoranceConnectionEvent ice = new IgnoranceConnectionEvent()
{
NativePeerId = incomingPeer.ID,
IP = incomingPeer.IP,
Port = incomingPeer.Port
};
ConnectionEvents.Enqueue(ice);
// Assign a reference to the Peer.
serverPeerArray[incomingPeer.ID] = incomingPeer;
break;
// Disconnect/Timeout. Mirror doesn't care if it's either, so we lump them together.
case EventType.Disconnect:
case EventType.Timeout:
if (!serverPeerArray[incomingPeer.ID].IsSet) break;
if (setupInfo.Verbosity > 1)
Debug.Log("Ignorance Server: Peer disconnection.");
IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent()
{
WasDisconnect = true,
NativePeerId = incomingPeer.ID
};
DisconnectionEvents.Enqueue(iced);
// Reset the peer array's entry for that peer.
serverPeerArray[incomingPeer.ID] = default;
break;
case EventType.Receive:
// Receive event type usually includes a packet; so cache its reference.
incomingPacket = serverENetEvent.Packet;
if (!incomingPacket.IsSet)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Server: A receive event did not supply us with a packet to work with. This should never happen.");
break;
}
incomingPacketLength = incomingPacket.Length;
// Firstly check if the packet is too big. If it is, do not process it - drop it.
if (incomingPacketLength > setupInfo.PacketSizeLimit)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Server: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes.");
incomingPacket.Dispose();
break;
}
IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
{
Channel = serverENetEvent.ChannelID,
NativePeerId = incomingPeer.ID,
Payload = incomingPacket,
};
// Enqueue.
Incoming.Enqueue(incomingQueuePacket);
break;
}
}
}
if (Verbosity > 0)
Debug.Log("Ignorance Server: Shutdown commencing, flushing connections.");
// Cleanup and flush everything.
serverENetHost.Flush();
// Kick everyone.
for (int i = 0; i < serverPeerArray.Length; i++)
{
if (!serverPeerArray[i].IsSet) continue;
serverPeerArray[i].DisconnectNow(0);
}
}
// Flush again to ensure ENet gets those Disconnection stuff out.
// May not be needed; better to err on side of caution
if (setupInfo.Verbosity > 0)
Debug.Log("Ignorance Server: Shutdown complete.");
Library.Deinitialize();
}
private struct ThreadParamInfo
{
public int Channels;
public int Peers;
public int PollTime;
public int Port;
public int PacketSizeLimit;
public int Verbosity;
public string Address;
}
}
}
// Ignorance 1.4.x LTS (Long Term Support)
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance is licensed under the MIT license. Refer
// to the LICENSE file for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using ENet;
using IgnoranceThirdparty;
using UnityEngine;
using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine
using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine
namespace IgnoranceCore
{
public class IgnoranceServer
{
// Server Properties
// - Bind Settings
public string BindAddress = "127.0.0.1";
public int BindPort = 7777;
// - Maximum allowed channels, peers, etc.
public int MaximumChannels = 2;
public int MaximumPeers = 100;
public int MaximumPacketSize = 33554432; // ENet.cs: uint maxPacketSize = 32 * 1024 * 1024 = 33554432
// - Native poll waiting time
public int PollTime = 1;
// - Verbosity.
public int Verbosity = 1;
// - Queue Sizing
public int IncomingOutgoingBufferSize = 5000;
public int ConnectionEventBufferSize = 100;
public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive;
private volatile bool CeaseOperation = false;
// Queues
// v1.4.0b9: Replace the queues with RingBuffers.
public RingBuffer<IgnoranceIncomingPacket> Incoming;
public RingBuffer<IgnoranceOutgoingPacket> Outgoing;
public RingBuffer<IgnoranceCommandPacket> Commands;
public RingBuffer<IgnoranceConnectionEvent> ConnectionEvents;
public RingBuffer<IgnoranceConnectionEvent> DisconnectionEvents;
public RingBuffer<IgnoranceServerStats> StatusUpdates;
public RingBuffer<IgnoranceServerStats> RecycledServerStatBlocks = new RingBuffer<IgnoranceServerStats>(100);
// Thread
private Thread WorkerThread;
public void Start()
{
if (WorkerThread != null && WorkerThread.IsAlive)
{
// Cannot do that.
Debug.LogError("Ignorance Server: A worker thread is already running. Cannot start another.");
return;
}
// Setup the ring buffers.
SetupRingBuffersIfNull();
CeaseOperation = false;
ThreadParamInfo threadParams = new ThreadParamInfo()
{
Address = BindAddress,
Port = BindPort,
Peers = MaximumPeers,
Channels = MaximumChannels,
PollTime = PollTime,
PacketSizeLimit = MaximumPacketSize,
Verbosity = Verbosity
};
// Drain queues.
if (Incoming != null) while (Incoming.TryDequeue(out _)) ;
if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ;
if (Commands != null) while (Commands.TryDequeue(out _)) ;
if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ;
if (DisconnectionEvents != null) while (DisconnectionEvents.TryDequeue(out _)) ;
if (StatusUpdates != null) while (StatusUpdates.TryDequeue(out _)) ;
WorkerThread = new Thread(ThreadWorker);
WorkerThread.Start(threadParams);
// Announce
if (Verbosity > 0)
Debug.Log("Ignorance Server: Dispatched worker thread.");
}
public void Stop()
{
// v1.4.0b7: Mirror may call this; if the worker thread isn't alive then don't announce it.
if (WorkerThread != null && WorkerThread.IsAlive)
{
if (Verbosity > 0)
Debug.Log("Ignorance Server: Server stop acknowledged. Depending on network load, this may take a moment or two...");
CeaseOperation = true;
}
}
#region The meat and potatoes.
private void ThreadWorker(Object parameters)
{
if (Verbosity > 0)
Debug.Log("Ignorance Server: Initializing. Please stand by...");
// Thread cache items
ThreadParamInfo setupInfo;
Address serverAddress = new Address();
Host serverENetHost;
Event serverENetEvent;
Peer[] serverPeerArray;
IgnoranceClientStats peerStats = default;
// Grab the setup information.
if (parameters.GetType() == typeof(ThreadParamInfo))
{
setupInfo = (ThreadParamInfo)parameters;
}
else
{
Debug.LogError("Ignorance Server: Startup failure; Invalid thread parameters. Aborting.");
return;
}
// Attempt to initialize ENet inside the thread.
if (Library.Initialize())
{
Debug.Log("Ignorance Server: ENet Native successfully initialized.");
}
else
{
Debug.LogError("Ignorance Server: Failed to initialize ENet Native. Aborting.");
return;
}
// Configure the server address.
serverAddress.SetHost(setupInfo.Address);
serverAddress.Port = (ushort)setupInfo.Port;
serverPeerArray = new Peer[setupInfo.Peers];
using (serverENetHost = new Host())
{
// Create the server object.
try
{
serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels);
}
catch (Exception ex)
{
Debug.LogError($"Ignorance Server: While attempting to create server host object, we caught an exception:\n{ex.Message}");
Debug.LogError($"If you are getting a \"Host creation call failed\" exception, please ensure you don't have a server already running on the same IP and Port.\n" +
$"Multiple server instances running on the same port are not supported. Also check to see if ports are not in-use by another application. In the worse case scenario, " +
$"restart your device to ensure no random background ENet threads are active that haven't been cleaned up correctly. If problems persist, please file a support ticket.");
Library.Deinitialize();
return;
}
// Loop until we're told to cease operations.
while (!CeaseOperation)
{
// Intermission: Command Handling
while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket))
{
switch (commandPacket.Type)
{
default:
break;
// Boot a Peer off the Server.
case IgnoranceCommandType.ServerKickPeer:
uint targetPeer = commandPacket.PeerId;
if (!serverPeerArray[targetPeer].IsSet) continue;
if (setupInfo.Verbosity > 0)
Debug.Log($"Ignorance Server: Booting Peer {targetPeer} off");
IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent
{
EventType = 0x01,
NativePeerId = targetPeer
};
DisconnectionEvents.Enqueue(iced);
// Disconnect and reset the peer array's entry for that peer.
serverPeerArray[targetPeer].DisconnectNow(0);
serverPeerArray[targetPeer] = default;
break;
case IgnoranceCommandType.ServerStatusRequest:
IgnoranceServerStats serverStats;
if (!RecycledServerStatBlocks.TryDequeue(out serverStats))
serverStats.PeerStats = new Dictionary<int, IgnoranceClientStats>(setupInfo.Peers);
serverStats.PeerStats.Clear();
serverStats.BytesReceived = serverENetHost.BytesReceived;
serverStats.BytesSent = serverENetHost.BytesSent;
serverStats.PacketsReceived = serverENetHost.PacketsReceived;
serverStats.PacketsSent = serverENetHost.PacketsSent;
serverStats.PeersCount = serverENetHost.PeersCount;
for (int i = 0; i < serverPeerArray.Length; i++)
{
if (!serverPeerArray[i].IsSet) continue;
peerStats.RTT = serverPeerArray[i].RoundTripTime;
peerStats.BytesReceived = serverPeerArray[i].BytesReceived;
peerStats.BytesSent = serverPeerArray[i].BytesSent;
peerStats.PacketsSent = serverPeerArray[i].PacketsSent;
peerStats.PacketsLost = serverPeerArray[i].PacketsLost;
serverStats.PeerStats.Add(i, peerStats);
}
StatusUpdates.Enqueue(serverStats);
break;
}
}
// Step One:
// ---> Sending to peers
while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket))
{
// Only create a packet if the server knows the peer.
if (serverPeerArray[outgoingPacket.NativePeerId].IsSet)
{
int ret = serverPeerArray[outgoingPacket.NativePeerId].Send(outgoingPacket.Channel, ref outgoingPacket.Payload);
if (ret < 0 && setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Server: ENet error {ret} while sending packet to Peer {outgoingPacket.NativePeerId}.");
}
else
{
// A peer might have disconnected, this is OK - just log the packet if set to paranoid.
if (setupInfo.Verbosity > 1)
Debug.LogWarning("Ignorance Server: Can't send packet, a native peer object is not set. This may be normal if the Peer has disconnected before this send cycle.");
}
}
// Step 2
// <--- Receiving from peers
bool pollComplete = false;
while (!pollComplete)
{
Packet incomingPacket;
Peer incomingPeer;
int incomingPacketLength;
// Any events happening?
if (serverENetHost.CheckEvents(out serverENetEvent) <= 0)
{
// If service time is met, break out of it.
if (serverENetHost.Service(setupInfo.PollTime, out serverENetEvent) <= 0) break;
pollComplete = true;
}
// Setup the packet references.
incomingPeer = serverENetEvent.Peer;
// What type are you?
switch (serverENetEvent.Type)
{
// Idle.
case EventType.None:
default:
break;
// Connection Event.
case EventType.Connect:
if (setupInfo.Verbosity > 1)
Debug.Log($"Ignorance Server: Peer ID {incomingPeer.ID} says Hi.");
IgnoranceConnectionEvent ice = new IgnoranceConnectionEvent()
{
NativePeerId = incomingPeer.ID,
IP = incomingPeer.IP,
Port = incomingPeer.Port
};
ConnectionEvents.Enqueue(ice);
// Assign a reference to the Peer.
serverPeerArray[incomingPeer.ID] = incomingPeer;
break;
// Disconnect/Timeout. Mirror doesn't care if it's either, so we lump them together.
case EventType.Disconnect:
case EventType.Timeout:
if (!serverPeerArray[incomingPeer.ID].IsSet) break;
if (setupInfo.Verbosity > 1)
Debug.Log($"Ignorance Server: Peer {incomingPeer.ID} has disconnected.");
IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent
{
EventType = 0x01,
NativePeerId = incomingPeer.ID
};
DisconnectionEvents.Enqueue(iced);
// Reset the peer array's entry for that peer.
serverPeerArray[incomingPeer.ID] = default;
break;
case EventType.Receive:
// Receive event type usually includes a packet; so cache its reference.
incomingPacket = serverENetEvent.Packet;
if (!incomingPacket.IsSet)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Server: A receive event did not supply us with a packet to work with. This should never happen.");
break;
}
incomingPacketLength = incomingPacket.Length;
// Firstly check if the packet is too big. If it is, do not process it - drop it.
if (incomingPacketLength > setupInfo.PacketSizeLimit)
{
if (setupInfo.Verbosity > 0)
Debug.LogWarning($"Ignorance Server: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes.");
incomingPacket.Dispose();
break;
}
IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket
{
Channel = serverENetEvent.ChannelID,
NativePeerId = incomingPeer.ID,
Payload = incomingPacket,
};
// Enqueue.
Incoming.Enqueue(incomingQueuePacket);
break;
}
}
}
if (Verbosity > 0)
Debug.Log("Ignorance Server: Thread shutdown commencing. Flushing connections.");
// Cleanup and flush everything.
serverENetHost.Flush();
// Kick everyone.
for (int i = 0; i < serverPeerArray.Length; i++)
{
if (!serverPeerArray[i].IsSet) continue;
serverPeerArray[i].DisconnectNow(0);
}
}
if (setupInfo.Verbosity > 0)
Debug.Log("Ignorance Server: Shutdown complete.");
Library.Deinitialize();
}
#endregion
private void SetupRingBuffersIfNull()
{
Debug.Log($"Ignorance: Setting up ring buffers if they're not already created. " +
$"If they are already, this step will be skipped.");
if (Incoming == null)
Incoming = new RingBuffer<IgnoranceIncomingPacket>(IncomingOutgoingBufferSize);
if (Outgoing == null)
Outgoing = new RingBuffer<IgnoranceOutgoingPacket>(IncomingOutgoingBufferSize);
if (Commands == null)
Commands = new RingBuffer<IgnoranceCommandPacket>(100);
if (ConnectionEvents == null)
ConnectionEvents = new RingBuffer<IgnoranceConnectionEvent>(ConnectionEventBufferSize);
if (DisconnectionEvents == null)
DisconnectionEvents = new RingBuffer<IgnoranceConnectionEvent>(ConnectionEventBufferSize);
if (StatusUpdates == null)
StatusUpdates = new RingBuffer<IgnoranceServerStats>(10);
}
private struct ThreadParamInfo
{
public int Channels;
public int Peers;
public int PollTime;
public int Port;
public int PacketSizeLimit;
public int Verbosity;
public string Address;
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 1069f42b88a4adb4ab1990cec4949343
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 1069f42b88a4adb4ab1990cec4949343
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 12f903db684732e45b130ad56f7c86c1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 12f903db684732e45b130ad56f7c86c1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 12a7875e95f5ebb4a9b58390441dd933
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 12a7875e95f5ebb4a9b58390441dd933
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,279 @@
// The following dependency was taken from https://github.com/dave-hillier/disruptor-unity3d
// The Apache License 2.0 this dependency follows is located at https://github.com/dave-hillier/disruptor-unity3d/blob/master/LICENSE.
// Modifications were made by SoftwareGuy (Coburn).
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace IgnoranceThirdparty
{
/// <summary>
/// Implementation of the Disruptor pattern
/// </summary>
/// <typeparam name="T">the type of item to be stored</typeparam>
public class RingBuffer<T>
{
private readonly T[] _entries;
private readonly int _modMask;
private Volatile.PaddedLong _consumerCursor = new Volatile.PaddedLong();
private Volatile.PaddedLong _producerCursor = new Volatile.PaddedLong();
/// <summary>
/// Creates a new RingBuffer with the given capacity
/// </summary>
/// <param name="capacity">The capacity of the buffer</param>
/// <remarks>Only a single thread may attempt to consume at any one time</remarks>
public RingBuffer(int capacity)
{
capacity = NextPowerOfTwo(capacity);
_modMask = capacity - 1;
_entries = new T[capacity];
}
/// <summary>
/// The maximum number of items that can be stored
/// </summary>
public int Capacity
{
get { return _entries.Length; }
}
public T this[long index]
{
get { unchecked { return _entries[index & _modMask]; } }
set { unchecked { _entries[index & _modMask] = value; } }
}
/// <summary>
/// Removes an item from the buffer.
/// </summary>
/// <returns>The next available item</returns>
public T Dequeue()
{
var next = _consumerCursor.ReadAcquireFence() + 1;
while (_producerCursor.ReadAcquireFence() < next) // makes sure we read the data from _entries after we have read the producer cursor
{
Thread.SpinWait(1);
}
var result = this[next];
_consumerCursor.WriteReleaseFence(next); // makes sure we read the data from _entries before we update the consumer cursor
return result;
}
/// <summary>
/// Attempts to remove an items from the queue
/// </summary>
/// <param name="obj">the items</param>
/// <returns>True if successful</returns>
public bool TryDequeue(out T obj)
{
var next = _consumerCursor.ReadAcquireFence() + 1;
if (_producerCursor.ReadAcquireFence() < next)
{
obj = default(T);
return false;
}
obj = Dequeue();
return true;
}
/// <summary>
/// Add an item to the buffer
/// </summary>
/// <param name="item"></param>
public void Enqueue(T item)
{
var next = _producerCursor.ReadAcquireFence() + 1;
long wrapPoint = next - _entries.Length;
long min = _consumerCursor.ReadAcquireFence();
while (wrapPoint > min)
{
min = _consumerCursor.ReadAcquireFence();
Thread.SpinWait(1);
}
this[next] = item;
_producerCursor.WriteReleaseFence(next); // makes sure we write the data in _entries before we update the producer cursor
}
/// <summary>
/// The number of items in the buffer
/// </summary>
/// <remarks>for indicative purposes only, may contain stale data</remarks>
public int Count { get { return (int)(_producerCursor.ReadFullFence() - _consumerCursor.ReadFullFence()); } }
private static int NextPowerOfTwo(int x)
{
var result = 2;
while (result < x)
{
result <<= 1;
}
return result;
}
}
public static class Volatile
{
private const int CacheLineSize = 64;
[StructLayout(LayoutKind.Explicit, Size = CacheLineSize * 2)]
public struct PaddedLong
{
[FieldOffset(CacheLineSize)]
private long _value;
/// <summary>
/// Create a new <see cref="PaddedLong"/> with the given initial value.
/// </summary>
/// <param name="value">Initial value</param>
public PaddedLong(long value)
{
_value = value;
}
/// <summary>
/// Read the value without applying any fence
/// </summary>
/// <returns>The current value</returns>
public long ReadUnfenced()
{
return _value;
}
/// <summary>
/// Read the value applying acquire fence semantic
/// </summary>
/// <returns>The current value</returns>
public long ReadAcquireFence()
{
var value = _value;
Thread.MemoryBarrier();
return value;
}
/// <summary>
/// Read the value applying full fence semantic
/// </summary>
/// <returns>The current value</returns>
public long ReadFullFence()
{
Thread.MemoryBarrier();
return _value;
}
/// <summary>
/// Read the value applying a compiler only fence, no CPU fence is applied
/// </summary>
/// <returns>The current value</returns>
[MethodImpl(MethodImplOptions.NoOptimization)]
public long ReadCompilerOnlyFence()
{
return _value;
}
/// <summary>
/// Write the value applying release fence semantic
/// </summary>
/// <param name="newValue">The new value</param>
public void WriteReleaseFence(long newValue)
{
Thread.MemoryBarrier();
_value = newValue;
}
/// <summary>
/// Write the value applying full fence semantic
/// </summary>
/// <param name="newValue">The new value</param>
public void WriteFullFence(long newValue)
{
Thread.MemoryBarrier();
_value = newValue;
}
/// <summary>
/// Write the value applying a compiler fence only, no CPU fence is applied
/// </summary>
/// <param name="newValue">The new value</param>
[MethodImpl(MethodImplOptions.NoOptimization)]
public void WriteCompilerOnlyFence(long newValue)
{
_value = newValue;
}
/// <summary>
/// Write without applying any fence
/// </summary>
/// <param name="newValue">The new value</param>
public void WriteUnfenced(long newValue)
{
_value = newValue;
}
/// <summary>
/// Atomically set the value to the given updated value if the current value equals the comparand
/// </summary>
/// <param name="newValue">The new value</param>
/// <param name="comparand">The comparand (expected value)</param>
/// <returns></returns>
public bool AtomicCompareExchange(long newValue, long comparand)
{
return Interlocked.CompareExchange(ref _value, newValue, comparand) == comparand;
}
/// <summary>
/// Atomically set the value to the given updated value
/// </summary>
/// <param name="newValue">The new value</param>
/// <returns>The original value</returns>
public long AtomicExchange(long newValue)
{
return Interlocked.Exchange(ref _value, newValue);
}
/// <summary>
/// Atomically add the given value to the current value and return the sum
/// </summary>
/// <param name="delta">The value to be added</param>
/// <returns>The sum of the current value and the given value</returns>
public long AtomicAddAndGet(long delta)
{
return Interlocked.Add(ref _value, delta);
}
/// <summary>
/// Atomically increment the current value and return the new value
/// </summary>
/// <returns>The incremented value.</returns>
public long AtomicIncrementAndGet()
{
return Interlocked.Increment(ref _value);
}
/// <summary>
/// Atomically increment the current value and return the new value
/// </summary>
/// <returns>The decremented value.</returns>
public long AtomicDecrementAndGet()
{
return Interlocked.Decrement(ref _value);
}
/// <summary>
/// Returns the string representation of the current value.
/// </summary>
/// <returns>the string representation of the current value.</returns>
public override string ToString()
{
var value = ReadFullFence();
return value.ToString();
}
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 330c9aab13d2d42069c6ebbe582b73ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 7f0c413335c890f4a87e1a9dbe32bd68
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 0995e08af14888348b42ecaa6eb21544
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 0995e08af14888348b42ecaa6eb21544
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,83 +1,102 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
namespace IgnoranceTransport
{
/// <summary>
/// Adds the given define symbols to PlayerSettings define symbols.
/// Just add your own define symbols to the Symbols property at the below.
/// </summary>
[InitializeOnLoad]
public class AddIgnoranceDefine : Editor
{
private static string existingDefines = string.Empty;
/// <summary>
/// Symbols that will be added to the editor
/// </summary>
public static readonly string[] Symbols = new string[] {
"IGNORANCE", // Ignorance exists
"IGNORANCE_1", // Major version
"IGNORANCE_1_4" // Major and minor version
};
/// <summary>
/// Do not remove these symbols
/// </summary>
public static readonly string[] DoNotRemoveTheseSymbols = new string[]
{
"IGNORANCE_NO_UPNP",
"IGNORANCE_MIRROR_POLLING"
};
/// <summary>
/// Add define symbols as soon as Unity gets done compiling.
/// </summary>
static AddIgnoranceDefine()
{
// Get the current scripting defines
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
if (existingDefines == definesString)
{
// 1.2.6: There is no need to apply the changes, return.
return;
}
// Convert the string to a list
List<string> allDefines = definesString.Split(';').ToList();
// Remove any old version defines from previous installs
allDefines.RemoveAll(IsSafeToRemove);
// x => x.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(x));
// Add any symbols that weren't already in the list
allDefines.AddRange(Symbols.Except(allDefines));
string newDefines = string.Join(";", allDefines.ToArray());
PlayerSettings.SetScriptingDefineSymbolsForGroup(
EditorUserBuildSettings.selectedBuildTargetGroup,
newDefines
);
existingDefines = newDefines;
}
// 1.2.4: Workaround to stop things from eating custom IGNORANCE_ symbols
static bool DoesSymbolExistInBlacklist(string symbol)
{
foreach(string s in DoNotRemoveTheseSymbols)
{
if (s == symbol.Trim()) return true;
}
return false;
}
static bool IsSafeToRemove (string input)
{
if (input.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(input)) return true;
return false;
}
}
}
#endif
// Ignorance 1.4.x LTS (Long Term Support)
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance is licensed under the MIT license. Refer
// to the LICENSE file for more information.
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
namespace IgnoranceTransport
{
/// <summary>
/// Adds the given define symbols to PlayerSettings define symbols.
/// Just add your own define symbols to the Symbols property at the below.
/// </summary>
[InitializeOnLoad]
public class AddIgnoranceDefine : Editor
{
private static bool debugMode = false;
private static string existingDefines = string.Empty;
/// <summary>
/// Symbols that will be added to the editor
/// </summary>
public static readonly string[] Symbols = new string[] {
"IGNORANCE", // Ignorance exists
"IGNORANCE_1", // Major version
"IGNORANCE_1_4" // Major and minor version
};
/// <summary>
/// Do not remove these symbols
/// </summary>
public static readonly string[] DoNotRemoveTheseSymbols = new string[]
{
"IGNORANCE",
"IGNORANCE_1",
"IGNORANCE_1_4",
"IGNORANCE_MIRROR_POLLING"
};
/// <summary>
/// Add define symbols as soon as Unity gets done compiling.
/// </summary>
static AddIgnoranceDefine()
{
// Get the current scripting defines
string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
if (existingDefines == definesString)
{
// 1.2.6: There is no need to apply the changes, return.
return;
}
// Convert the string to a list
List<string> allDefines = definesString.Split(';').ToList();
// Remove any old version defines from previous installs
allDefines.RemoveAll(IsSafeToRemove);
// x => x.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(x));
// Add any symbols that weren't already in the list
allDefines.AddRange(Symbols.Except(allDefines));
string newDefines = string.Join(";", allDefines.ToArray());
PlayerSettings.SetScriptingDefineSymbolsForGroup(
EditorUserBuildSettings.selectedBuildTargetGroup,
newDefines
);
existingDefines = newDefines;
}
// 1.2.4: Workaround to stop things from eating custom IGNORANCE_ symbols
static bool DoesSymbolExistInBlacklist(string symbol)
{
foreach(string s in DoNotRemoveTheseSymbols)
{
if(debugMode)
UnityEngine.Debug.Log($"{s.Trim()} matches blacklist");
if (s == symbol.Trim()) return true;
}
return false;
}
static bool IsSafeToRemove (string input)
{
if (input.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(input))
{
if (debugMode)
UnityEngine.Debug.Log($"{input.Trim()} is safe to remove");
return true;
}
return false;
}
}
}
#endif

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: ed8acbde141f2d8469baf2142712de9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ed8acbde141f2d8469baf2142712de9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,44 +0,0 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
namespace IgnoranceTransport
{
public class IgnoranceToolbox
{
#pragma warning disable IDE0051
[MenuItem("Ignorance/Mirror/Switch Update Method")]
public static void SwitchIgnoranceUpdateMethod ()
{
}
[MenuItem("Ignorance/Debug/Reveal ENet Native Library Name")]
public static void RevealEnetLibraryName()
{
EditorUtility.DisplayDialog("Enet Library Name", $"Use this for debugging.\nYour platform expects the native Enet library to be called: {ENet.Native.nativeLibraryName}", "Got it");
}
[MenuItem("Ignorance/RTFM/Github Repository")]
private static void LaunchGithubRepo()
{
UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance");
}
[MenuItem("Ignorance/RTFM/Github Issue Tracker")]
private static void LaunchGithubIssueTracker()
{
UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance/issues");
}
[MenuItem("Ignorance/RTFM/ENet-CSharp Fork")]
private static void LaunchENetCSharpForkRepo()
{
UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/ENet-CSharp");
}
#pragma warning restore
}
}
#endif

View File

@@ -0,0 +1,43 @@
// Ignorance 1.4.x LTS (Long Term Support)
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance is licensed under the MIT license. Refer
// to the LICENSE file for more information.
#if UNITY_EDITOR
using UnityEditor;
namespace IgnoranceTransport
{
public class Toolbox
{
#pragma warning disable IDE0051
[MenuItem("Ignorance/Debug/Native Library Name")]
public static void RevealEnetLibraryName()
{
EditorUtility.DisplayDialog("ENet Library Name", $"Your platform expects the native library to be called: {ENet.Native.nativeLibraryName}.\n\n" +
$"This info is very useful when trying to diagnose issues with DLL loading.", "Got it");
}
[MenuItem("Ignorance/RTFM/Github Repo")]
private static void LaunchGithubRepo()
{
UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance");
}
[MenuItem("Ignorance/RTFM/Issue Tracker")]
private static void LaunchGithubIssueTracker()
{
UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance/issues");
}
[MenuItem("Ignorance/RTFM/ENet-C# Repo")]
private static void LaunchENetCSharpForkRepo()
{
UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/ENet-CSharp");
}
#pragma warning restore
}
}
#endif

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 1fdecc996313d614ca16214d4e2b9162
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 1fdecc996313d614ca16214d4e2b9162
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 872fa23ef6e77334ca452ce16f6cd091
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -32000
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 872fa23ef6e77334ca452ce16f6cd091
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -32000
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,94 +1,109 @@
using System;
using ENet;
namespace IgnoranceTransport
{
// Snipped from the transport files, as this will help
// me keep things up to date.
[Serializable]
public enum IgnoranceChannelTypes
{
Reliable = PacketFlags.Reliable, // TCP Emulation.
ReliableUnsequenced = PacketFlags.Reliable | PacketFlags.Unsequenced, // TCP Emulation, but no sequencing.
Unreliable = PacketFlags.Unsequenced, // Pure UDP.
UnreliableFragmented = PacketFlags.UnreliableFragmented, // Pure UDP, but fragmented.
UnreliableSequenced = PacketFlags.None, // Pure UDP, but sequenced.
Unthrottled = PacketFlags.Unthrottled, // Apparently ENet's version of Taco Bell.
}
public class IgnoranceInternals
{
public const string Version = "1.4.0b6";
public const string Scheme = "enet";
public const string BindAllIPv4 = "0.0.0.0";
public const string BindAllMacs = "::0";
}
public enum IgnoranceLogType
{
Nothing,
Standard,
Verbose
}
// Struct optimized for cache efficiency. (Thanks Vincenzo!)
public struct IgnoranceIncomingPacket
{
public byte Channel;
public uint NativePeerId;
public Packet Payload;
}
// Struct optimized for cache efficiency. (Thanks Vincenzo!)
public struct IgnoranceOutgoingPacket
{
public byte Channel;
public uint NativePeerId;
public Packet Payload;
}
// Struct optimized for cache efficiency. (Thanks Vincenzo!)
public struct IgnoranceConnectionEvent
{
public bool WasDisconnect;
public ushort Port;
public uint NativePeerId;
public string IP;
}
public struct IgnoranceCommandPacket
{
public IgnoranceCommandType Type;
public uint PeerId;
}
public struct IgnoranceClientStats
{
// Stats only - may not always be used!
public uint RTT;
public ulong BytesReceived;
public ulong BytesSent;
public ulong PacketsReceived;
public ulong PacketsSent;
public ulong PacketsLost;
}
public enum IgnoranceCommandType
{
// Client
ClientWantsToStop,
ClientRequestsStatusUpdate,
// ENet internal
ResponseToClientStatusRequest,
// Server
ServerKickPeer
}
// TODO: Optimize struct for Cache performance.
public struct PeerConnectionData
{
public ushort Port;
public uint NativePeerId;
public string IP;
}
}
// Ignorance 1.4.x LTS (Long Term Support)
// https://github.com/SoftwareGuy/Ignorance
// -----------------
// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64)
// Ignorance is licensed under the MIT license. Refer
// to the LICENSE file for more information.
using System;
using System.Collections.Generic;
using ENet;
namespace IgnoranceCore
{
// Snipped from the transport files, as this will help
// me keep things up to date.
[Serializable]
public enum IgnoranceChannelTypes
{
Reliable = PacketFlags.Reliable, // Reliable UDP (TCP-like emulation)
ReliableUnsequenced = PacketFlags.Reliable | PacketFlags.Unsequenced, // Reliable UDP (TCP-like emulation w/o sequencing)
Unreliable = PacketFlags.Unsequenced, // Pure UDP, high velocity packet action.
UnreliableFragmented = PacketFlags.UnreliableFragmented, // Pure UDP, but fragmented.
UnreliableSequenced = PacketFlags.None, // Pure UDP, but sequenced.
Unthrottled = PacketFlags.Unthrottled, // Pure UDP. Literally turbo mode.
}
public class IgnoranceInternals
{
public const string Version = "1.4.0r0 (LTS)";
public const string Scheme = "enet";
public const string BindAnyAddress = "::0";
}
public enum IgnoranceLogType
{
Nothing,
Standard,
Verbose
}
public struct IgnoranceIncomingPacket
{
public byte Channel;
public uint NativePeerId;
public Packet Payload;
}
public struct IgnoranceOutgoingPacket
{
public byte Channel;
public uint NativePeerId;
public Packet Payload;
}
public struct IgnoranceConnectionEvent
{
public byte EventType;
public ushort Port;
public uint NativePeerId;
public string IP;
}
public struct IgnoranceCommandPacket
{
public IgnoranceCommandType Type;
public uint PeerId;
}
// Stats only - may not always be used!
public struct IgnoranceClientStats
{
public uint RTT;
public ulong BytesReceived;
public ulong BytesSent;
public ulong PacketsReceived;
public ulong PacketsSent;
public ulong PacketsLost;
}
public enum IgnoranceCommandType
{
// Client
ClientWantsToStop,
ClientStatusRequest,
// Server
ServerKickPeer,
ServerStatusRequest
}
// Stats only - may not always be used!
public struct IgnoranceServerStats
{
public ulong BytesReceived;
public ulong BytesSent;
public ulong PacketsReceived;
public ulong PacketsSent;
public ulong PeersCount;
public Dictionary<int, IgnoranceClientStats> PeerStats;
}
public struct PeerConnectionData
{
public ushort Port;
public uint NativePeerId;
public string IP;
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 3b2d1f7f7d9d3d64297755419f6ab925
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 3b2d1f7f7d9d3d64297755419f6ab925
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: e8bd04b9965420e40ac911fbf4294e1c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: e8bd04b9965420e40ac911fbf4294e1c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: c90c88052305a054d87177362f63dea8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: c90c88052305a054d87177362f63dea8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: e4a42b5855436864693138f74335c094
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: e4a42b5855436864693138f74335c094
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,111 +1,111 @@
fileFormatVersion: 2
guid: 4b3abd2268dea254da11190b00d16051
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARM64
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
WebGL: WebGL
second:
enabled: 0
settings: {}
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 4b3abd2268dea254da11190b00d16051
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARM64
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
WebGL: WebGL
second:
enabled: 0
settings: {}
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: bfbaf4fbcf8987e4585ca666e28f4871
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: bfbaf4fbcf8987e4585ca666e28f4871
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,106 +1,106 @@
fileFormatVersion: 2
guid: e3ce4b8600e09d74ead46137ba184d01
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: x86
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: e3ce4b8600e09d74ead46137ba184d01
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: x86
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: b3486c84aaebff4489e2e11f5bd3030f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: b3486c84aaebff4489e2e11f5bd3030f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,80 +1,80 @@
fileFormatVersion: 2
guid: ba78b07719f786c4cae4c52b9528903f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: x86
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ba78b07719f786c4cae4c52b9528903f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 0
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 1
settings:
CPU: x86
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 8e3ca9b41c9f4064581a6b56ae549a9c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 8e3ca9b41c9f4064581a6b56ae549a9c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
Rename this debug library without the "_debug" suffix and replace (maybe backup the original first) the library.
For example, to replace the x64 ENet Library with the Debug version, you'd first backup/move "libenet.so", then extract and rename "libenet_debug.so" to "libenet.so".
If you have trouble, file a GitHub support ticket at https://github.com/SoftwareGuy/Ignorance.
- Coburn

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 765c88bc8183ed44cb51a5529d8f743e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,97 +1,97 @@
fileFormatVersion: 2
guid: f2bd341c8082ddd47b4b5cc9bfdf2332
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
'': Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 0
Exclude LinuxUniversal: 0
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 0
Exclude Win64: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: x86_64
DefaultValueInitialized: true
OS: Linux
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: LinuxUniversal
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: f2bd341c8082ddd47b4b5cc9bfdf2332
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 0
Exclude LinuxUniversal: 0
Exclude OSXUniversal: 1
Exclude WebGL: 1
Exclude Win: 0
Exclude Win64: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: x86_64
DefaultValueInitialized: true
OS: Linux
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: LinuxUniversal
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c7482955b4ae93c4081db9ddd04cae47
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 8837cb1a7320b8b4f824a02e23f4a545
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 8837cb1a7320b8b4f824a02e23f4a545
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,3 +1,3 @@
This is a Windows x64 build of ENet-CSharp.
This is a Windows x64 build of ENet-CSharp.
If you require a version of ENet for 32 Bit computer systems (ie. Windows 7/8/10 32Bit) then please get in touch, or you can install the requirements yourself and compile it using ENet-CSharp's MSBuild-based build system. Get in touch if you want me to compile them for you, but keep in mind that I do not support them when reporting bugs.

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 09ee288deb259474c819a5e3daf54b80
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 09ee288deb259474c819a5e3daf54b80
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,111 +1,111 @@
fileFormatVersion: 2
guid: cc98033c6bc234a419ccd053e7173781
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
'': Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux: 0
Exclude Linux64: 0
Exclude LinuxUniversal: 0
Exclude OSXUniversal: 0
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 0
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: None
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 1
settings:
CPU: x86
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: LinuxUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
WebGL: WebGL
second:
enabled: 0
settings: {}
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: cc98033c6bc234a419ccd053e7173781
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
'': Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 0
Exclude Linux: 0
Exclude Linux64: 0
Exclude LinuxUniversal: 0
Exclude OSXUniversal: 0
Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 0
Exclude iOS: 1
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: None
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 1
settings:
CPU: x86
- first:
Standalone: Linux64
second:
enabled: 1
settings:
CPU: x86_64
- first:
Standalone: LinuxUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 1
settings:
CPU: AnyCPU
- first:
WebGL: WebGL
second:
enabled: 0
settings: {}
- first:
iPhone: iOS
second:
enabled: 0
settings:
AddToEmbeddedBinaries: false
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 7edc275f3670f4251b90301cbb9f7413
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 7edc275f3670f4251b90301cbb9f7413
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,80 +1,80 @@
fileFormatVersion: 2
guid: 1485de7d76884a64bac00df01454081e
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: ARM64
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 1485de7d76884a64bac00df01454081e
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: ARM64
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,80 +1,80 @@
fileFormatVersion: 2
guid: fa239cb4c47fc9447816815f80667fea
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: ARMv7
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: fa239cb4c47fc9447816815f80667fea
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: ARMv7
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,80 +1,80 @@
fileFormatVersion: 2
guid: 385bb48fb124b5f40a79bdecf421671f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: X64
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 385bb48fb124b5f40a79bdecf421671f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Android: 1
Exclude Editor: 1
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 0
- first:
Android: Android
second:
enabled: 0
settings:
CPU: ARMv7
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: X64
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 957ba76da095cd64aaa658f034ab78e5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 957ba76da095cd64aaa658f034ab78e5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,32 +1,32 @@
fileFormatVersion: 2
guid: 5317859893ad2cf48a6df1d585ebdd2c
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
DefaultValueInitialized: true
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 5317859893ad2cf48a6df1d585ebdd2c
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
DefaultValueInitialized: true
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,35 +1,35 @@
ENET Pre-compiled Binary Library Blobs
==========================
This folder contains pre-compiled binaries for a variety of different platforms.
A brief summary of these folders are as follows:
- Windows, Mac, Linux
-- 64bit (x64)
- Android (Kitkat 4.4 minimum target OS)
-- ARMv7 (armeabi-v7a), ARMv8/AArch64 (arm64-v8a)
- iOS
-- FAT Library (armv7 + arm64). Targeted for iOS 8 minimum. Unsigned library.
DEBUG VERSIONS
===============
Debug versions of the libraries can be obtained at https://github.com/SoftwareGuy/ENet-CSharp/releases.
Otherwise you can also compile the library yourself with Debug enabled.
DOT POINTS
===========
1. 32bit Support for Ignorance has been removed. Originally, I did not want to support 32bit operating systems,
however due to some countries in the world still stuck in the 32bit era (Brasil, some Russian areas, etc) I added them as a
goodwill gesture. However, since those who needed the libraries have now vanished, I have stopped building 32bit versions of ENet.
COMPILE THE CODE YOURSELF
=========================
If you don't trust the above binaries then git clone the ENET-CSharp repository (http://github.com/SoftwareGuy/ENet-CSharp) and read the readme.
EXCLUSION INSTRUCTIONS
======================
No need, the meta data will cover that for you.
ENET Pre-compiled Binary Library Blobs
==========================
This folder contains pre-compiled binaries for a variety of different platforms.
A brief summary of these folders are as follows:
- Windows, Mac, Linux
-- 64bit (x64)
- Android (Kitkat 4.4 minimum target OS)
-- ARMv7 (armeabi-v7a), ARMv8/AArch64 (arm64-v8a)
- iOS
-- FAT Library (armv7 + arm64). Targeted for iOS 8 minimum. Unsigned library.
DEBUG VERSIONS
===============
Debug versions of the libraries can be obtained at https://github.com/SoftwareGuy/ENet-CSharp/releases.
Otherwise you can also compile the library yourself with Debug enabled.
DOT POINTS
===========
1. 32bit Support for Ignorance has been removed. Originally, I did not want to support 32bit operating systems,
however due to some countries in the world still stuck in the 32bit era (Brasil, some Russian areas, etc) I added them as a
goodwill gesture. However, since those who needed the libraries have now vanished, I have stopped building 32bit versions of ENet.
COMPILE THE CODE YOURSELF
=========================
If you don't trust the above binaries then git clone the ENET-CSharp repository (http://github.com/SoftwareGuy/ENet-CSharp) and read the readme.
EXCLUSION INSTRUCTIONS
======================
No need, the meta data will cover that for you.
Still don't know what to do with these? Drop by the Mirror discord and post in the Ignorance channel.

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: a28193472bc84d341ab4aee18c471a93
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: a28193472bc84d341ab4aee18c471a93
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1 +1 @@
1.4.0b6
1.4.0r0 LTS

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: ad80075449f17c548877161f32a9841a
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ad80075449f17c548877161f32a9841a
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 953bb5ec5ab2346a092f58061e01ba65
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 953bb5ec5ab2346a092f58061e01ba65
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 7bdb797750d0a490684410110bf48192
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 7bdb797750d0a490684410110bf48192
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,342 +1,346 @@
//#if MIRROR <- commented out because MIRROR isn't defined on first import yet
using System;
using System.Linq;
using System.Net;
using UnityEngine;
using Mirror;
namespace kcp2k
{
[DisallowMultipleComponent]
public class KcpTransport : Transport
{
// scheme used by this transport
public const string Scheme = "kcp";
// common
[Header("Transport Configuration")]
public ushort Port = 7777;
[Tooltip("DualMode listens to IPv6 and IPv4 simultaneously. Disable if the platform only supports IPv4.")]
public bool DualMode = true;
[Tooltip("NoDelay is recommended to reduce latency. This also scales better without buffers getting full.")]
public bool NoDelay = true;
[Tooltip("KCP internal update interval. 100ms is KCP default, but a lower interval is recommended to minimize latency and to scale to more networked entities.")]
public uint Interval = 10;
[Tooltip("KCP timeout in milliseconds. Note that KCP sends a ping automatically.")]
public int Timeout = 10000;
[Header("Advanced")]
[Tooltip("KCP fastresend parameter. Faster resend for the cost of higher bandwidth. 0 in normal mode, 2 in turbo mode.")]
public int FastResend = 2;
[Tooltip("KCP congestion window. Enabled in normal mode, disabled in turbo mode. Disable this for high scale games if connections get choked regularly.")]
public bool CongestionWindow = false; // KCP 'NoCongestionWindow' is false by default. here we negate it for ease of use.
[Tooltip("KCP window size can be modified to support higher loads.")]
public uint SendWindowSize = 4096; //Kcp.WND_SND; 32 by default. Mirror sends a lot, so we need a lot more.
[Tooltip("KCP window size can be modified to support higher loads.")]
public uint ReceiveWindowSize = 4096; //Kcp.WND_RCV; 128 by default. Mirror sends a lot, so we need a lot more.
[Tooltip("Enable to use where-allocation NonAlloc KcpServer/Client/Connection versions. Highly recommended on all Unity platforms.")]
public bool NonAlloc = true;
// server & client (where-allocation NonAlloc versions)
KcpServer server;
KcpClient client;
// debugging
[Header("Debug")]
public bool debugLog;
// show statistics in OnGUI
public bool statisticsGUI;
// log statistics for headless servers that can't show them in GUI
public bool statisticsLog;
void Awake()
{
// logging
// Log.Info should use Debug.Log if enabled, or nothing otherwise
// (don't want to spam the console on headless servers)
if (debugLog)
Log.Info = Debug.Log;
else
Log.Info = _ => {};
Log.Warning = Debug.LogWarning;
Log.Error = Debug.LogError;
// client
client = NonAlloc
? new KcpClientNonAlloc(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
() => OnClientDisconnected.Invoke())
: new KcpClient(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
() => OnClientDisconnected.Invoke());
// server
server = NonAlloc
? new KcpServerNonAlloc(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
Interval,
FastResend,
CongestionWindow,
SendWindowSize,
ReceiveWindowSize,
Timeout)
: new KcpServer(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
Interval,
FastResend,
CongestionWindow,
SendWindowSize,
ReceiveWindowSize,
Timeout);
if (statisticsLog)
InvokeRepeating(nameof(OnLogStatistics), 1, 1);
Debug.Log("KcpTransport initialized!");
}
// all except WebGL
public override bool Available() =>
Application.platform != RuntimePlatform.WebGLPlayer;
// client
public override bool ClientConnected() => client.connected;
public override void ClientConnect(string address)
{
client.Connect(address, Port, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
client.Send(segment, KcpChannel.Unreliable);
break;
default:
client.Send(segment, KcpChannel.Reliable);
break;
}
}
public override void ClientDisconnect() => client.Disconnect();
// process incoming in early update
public override void ClientEarlyUpdate()
{
// scene change messages disable transports to stop them from
// processing while changing the scene.
// -> we need to check enabled here
// -> and in kcp's internal loops, see Awake() OnCheckEnabled setup!
// (see also: https://github.com/vis2k/Mirror/pull/379)
if (enabled) client.TickIncoming();
}
// process outgoing in late update
public override void ClientLateUpdate() => client.TickOutgoing();
// scene change message will disable transports.
// kcp processes messages in an internal loop which should be
// stopped immediately after scene change (= after disabled)
// => kcp has tests to guaranteed that calling .Pause() during the
// receive loop stops the receive loop immediately, not after.
void OnEnable()
{
// unpause when enabled again
client?.Unpause();
server?.Unpause();
}
void OnDisable()
{
// pause immediately when not enabled anymore
client?.Pause();
server?.Pause();
}
// server
public override Uri ServerUri()
{
UriBuilder builder = new UriBuilder();
builder.Scheme = Scheme;
builder.Host = Dns.GetHostName();
builder.Port = Port;
return builder.Uri;
}
public override bool ServerActive() => server.IsActive();
public override void ServerStart() => server.Start(Port);
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
server.Send(connectionId, segment, KcpChannel.Unreliable);
break;
default:
server.Send(connectionId, segment, KcpChannel.Reliable);
break;
}
}
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
public override void ServerStop() => server.Stop();
public override void ServerEarlyUpdate()
{
// scene change messages disable transports to stop them from
// processing while changing the scene.
// -> we need to check enabled here
// -> and in kcp's internal loops, see Awake() OnCheckEnabled setup!
// (see also: https://github.com/vis2k/Mirror/pull/379)
if (enabled) server.TickIncoming();
}
// process outgoing in late update
public override void ServerLateUpdate() => server.TickOutgoing();
// common
public override void Shutdown() {}
// max message size
public override int GetMaxPacketSize(int channelId = Channels.Reliable)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
return KcpConnection.UnreliableMaxMessageSize;
default:
return KcpConnection.ReliableMaxMessageSize;
}
}
// kcp reliable channel max packet size is MTU * WND_RCV
// this allows 144kb messages. but due to head of line blocking, all
// other messages would have to wait until the maxed size one is
// delivered. batching 144kb messages each time would be EXTREMELY slow
// and fill the send queue nearly immediately when using it over the
// network.
// => instead we always use MTU sized batches.
// => people can still send maxed size if needed.
public override int GetBatchThreshold(int channelId) =>
KcpConnection.UnreliableMaxMessageSize;
// server statistics
// LONG to avoid int overflows with connections.Sum.
// see also: https://github.com/vis2k/Mirror/pull/2777
public long GetAverageMaxSendRate() =>
server.connections.Count > 0
? server.connections.Values.Sum(conn => (long)conn.MaxSendRate) / server.connections.Count
: 0;
public long GetAverageMaxReceiveRate() =>
server.connections.Count > 0
? server.connections.Values.Sum(conn => (long)conn.MaxReceiveRate) / server.connections.Count
: 0;
long GetTotalSendQueue() =>
server.connections.Values.Sum(conn => conn.SendQueueCount);
long GetTotalReceiveQueue() =>
server.connections.Values.Sum(conn => conn.ReceiveQueueCount);
long GetTotalSendBuffer() =>
server.connections.Values.Sum(conn => conn.SendBufferCount);
long GetTotalReceiveBuffer() =>
server.connections.Values.Sum(conn => conn.ReceiveBufferCount);
// PrettyBytes function from DOTSNET
// pretty prints bytes as KB/MB/GB/etc.
// long to support > 2GB
// divides by floats to return "2.5MB" etc.
public static string PrettyBytes(long bytes)
{
// bytes
if (bytes < 1024)
return $"{bytes} B";
// kilobytes
else if (bytes < 1024L * 1024L)
return $"{(bytes / 1024f):F2} KB";
// megabytes
else if (bytes < 1024 * 1024L * 1024L)
return $"{(bytes / (1024f * 1024f)):F2} MB";
// gigabytes
return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB";
}
void OnGUI()
{
if (!statisticsGUI) return;
GUILayout.BeginArea(new Rect(5, 110, 300, 300));
if (ServerActive())
{
GUILayout.BeginVertical("Box");
GUILayout.Label("SERVER");
GUILayout.Label($" connections: {server.connections.Count}");
GUILayout.Label($" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s");
GUILayout.Label($" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s");
GUILayout.Label($" SendQueue: {GetTotalSendQueue()}");
GUILayout.Label($" ReceiveQueue: {GetTotalReceiveQueue()}");
GUILayout.Label($" SendBuffer: {GetTotalSendBuffer()}");
GUILayout.Label($" ReceiveBuffer: {GetTotalReceiveBuffer()}");
GUILayout.EndVertical();
}
if (ClientConnected())
{
GUILayout.BeginVertical("Box");
GUILayout.Label("CLIENT");
GUILayout.Label($" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s");
GUILayout.Label($" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s");
GUILayout.Label($" SendQueue: {client.connection.SendQueueCount}");
GUILayout.Label($" ReceiveQueue: {client.connection.ReceiveQueueCount}");
GUILayout.Label($" SendBuffer: {client.connection.SendBufferCount}");
GUILayout.Label($" ReceiveBuffer: {client.connection.ReceiveBufferCount}");
GUILayout.EndVertical();
}
GUILayout.EndArea();
}
void OnLogStatistics()
{
if (ServerActive())
{
string log = "kcp SERVER @ time: " + NetworkTime.localTime + "\n";
log += $" connections: {server.connections.Count}\n";
log += $" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s\n";
log += $" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s\n";
log += $" SendQueue: {GetTotalSendQueue()}\n";
log += $" ReceiveQueue: {GetTotalReceiveQueue()}\n";
log += $" SendBuffer: {GetTotalSendBuffer()}\n";
log += $" ReceiveBuffer: {GetTotalReceiveBuffer()}\n\n";
Debug.Log(log);
}
if (ClientConnected())
{
string log = "kcp CLIENT @ time: " + NetworkTime.localTime + "\n";
log += $" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s\n";
log += $" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s\n";
log += $" SendQueue: {client.connection.SendQueueCount}\n";
log += $" ReceiveQueue: {client.connection.ReceiveQueueCount}\n";
log += $" SendBuffer: {client.connection.SendBufferCount}\n";
log += $" ReceiveBuffer: {client.connection.ReceiveBufferCount}\n\n";
Debug.Log(log);
}
}
public override string ToString() => "KCP";
}
}
//#endif MIRROR <- commented out because MIRROR isn't defined on first import yet
//#if MIRROR <- commented out because MIRROR isn't defined on first import yet
using System;
using System.Linq;
using System.Net;
using UnityEngine;
using Mirror;
namespace kcp2k
{
[HelpURL("https://mirror-networking.gitbook.io/docs/transports/kcp-transport")]
[DisallowMultipleComponent]
public class KcpTransport : Transport
{
// scheme used by this transport
public const string Scheme = "kcp";
// common
[Header("Transport Configuration")]
public ushort Port = 7777;
[Tooltip("DualMode listens to IPv6 and IPv4 simultaneously. Disable if the platform only supports IPv4.")]
public bool DualMode = true;
[Tooltip("NoDelay is recommended to reduce latency. This also scales better without buffers getting full.")]
public bool NoDelay = true;
[Tooltip("KCP internal update interval. 100ms is KCP default, but a lower interval is recommended to minimize latency and to scale to more networked entities.")]
public uint Interval = 10;
[Tooltip("KCP timeout in milliseconds. Note that KCP sends a ping automatically.")]
public int Timeout = 10000;
[Header("Advanced")]
[Tooltip("KCP fastresend parameter. Faster resend for the cost of higher bandwidth. 0 in normal mode, 2 in turbo mode.")]
public int FastResend = 2;
[Tooltip("KCP congestion window. Enabled in normal mode, disabled in turbo mode. Disable this for high scale games if connections get choked regularly.")]
public bool CongestionWindow = false; // KCP 'NoCongestionWindow' is false by default. here we negate it for ease of use.
[Tooltip("KCP window size can be modified to support higher loads.")]
public uint SendWindowSize = 4096; //Kcp.WND_SND; 32 by default. Mirror sends a lot, so we need a lot more.
[Tooltip("KCP window size can be modified to support higher loads.")]
public uint ReceiveWindowSize = 4096; //Kcp.WND_RCV; 128 by default. Mirror sends a lot, so we need a lot more.
[Tooltip("Enable to use where-allocation NonAlloc KcpServer/Client/Connection versions. Highly recommended on all Unity platforms.")]
public bool NonAlloc = true;
// server & client (where-allocation NonAlloc versions)
KcpServer server;
KcpClient client;
// debugging
[Header("Debug")]
public bool debugLog;
// show statistics in OnGUI
public bool statisticsGUI;
// log statistics for headless servers that can't show them in GUI
public bool statisticsLog;
void Awake()
{
// logging
// Log.Info should use Debug.Log if enabled, or nothing otherwise
// (don't want to spam the console on headless servers)
if (debugLog)
Log.Info = Debug.Log;
else
Log.Info = _ => {};
Log.Warning = Debug.LogWarning;
Log.Error = Debug.LogError;
// client
client = NonAlloc
? new KcpClientNonAlloc(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
() => OnClientDisconnected.Invoke())
: new KcpClient(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
() => OnClientDisconnected.Invoke());
// server
server = NonAlloc
? new KcpServerNonAlloc(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
Interval,
FastResend,
CongestionWindow,
SendWindowSize,
ReceiveWindowSize,
Timeout)
: new KcpServer(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
Interval,
FastResend,
CongestionWindow,
SendWindowSize,
ReceiveWindowSize,
Timeout);
if (statisticsLog)
InvokeRepeating(nameof(OnLogStatistics), 1, 1);
Debug.Log("KcpTransport initialized!");
}
// all except WebGL
public override bool Available() =>
Application.platform != RuntimePlatform.WebGLPlayer;
// client
public override bool ClientConnected() => client.connected;
public override void ClientConnect(string address)
{
client.Connect(address, Port, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
client.Send(segment, KcpChannel.Unreliable);
break;
default:
client.Send(segment, KcpChannel.Reliable);
break;
}
}
public override void ClientDisconnect() => client.Disconnect();
// process incoming in early update
public override void ClientEarlyUpdate()
{
// scene change messages disable transports to stop them from
// processing while changing the scene.
// -> we need to check enabled here
// -> and in kcp's internal loops, see Awake() OnCheckEnabled setup!
// (see also: https://github.com/vis2k/Mirror/pull/379)
if (enabled) client.TickIncoming();
}
// process outgoing in late update
public override void ClientLateUpdate() => client.TickOutgoing();
// scene change message will disable transports.
// kcp processes messages in an internal loop which should be
// stopped immediately after scene change (= after disabled)
// => kcp has tests to guaranteed that calling .Pause() during the
// receive loop stops the receive loop immediately, not after.
void OnEnable()
{
// unpause when enabled again
client?.Unpause();
server?.Unpause();
}
void OnDisable()
{
// pause immediately when not enabled anymore
client?.Pause();
server?.Pause();
}
// server
public override Uri ServerUri()
{
UriBuilder builder = new UriBuilder();
builder.Scheme = Scheme;
builder.Host = Dns.GetHostName();
builder.Port = Port;
return builder.Uri;
}
public override bool ServerActive() => server.IsActive();
public override void ServerStart() => server.Start(Port);
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
server.Send(connectionId, segment, KcpChannel.Unreliable);
break;
default:
server.Send(connectionId, segment, KcpChannel.Reliable);
break;
}
}
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
public override void ServerStop() => server.Stop();
public override void ServerEarlyUpdate()
{
// scene change messages disable transports to stop them from
// processing while changing the scene.
// -> we need to check enabled here
// -> and in kcp's internal loops, see Awake() OnCheckEnabled setup!
// (see also: https://github.com/vis2k/Mirror/pull/379)
if (enabled) server.TickIncoming();
}
// process outgoing in late update
public override void ServerLateUpdate() => server.TickOutgoing();
// common
public override void Shutdown() {}
// max message size
public override int GetMaxPacketSize(int channelId = Channels.Reliable)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
return KcpConnection.UnreliableMaxMessageSize;
default:
return KcpConnection.ReliableMaxMessageSize;
}
}
// kcp reliable channel max packet size is MTU * WND_RCV
// this allows 144kb messages. but due to head of line blocking, all
// other messages would have to wait until the maxed size one is
// delivered. batching 144kb messages each time would be EXTREMELY slow
// and fill the send queue nearly immediately when using it over the
// network.
// => instead we always use MTU sized batches.
// => people can still send maxed size if needed.
public override int GetBatchThreshold(int channelId) =>
KcpConnection.UnreliableMaxMessageSize;
// server statistics
// LONG to avoid int overflows with connections.Sum.
// see also: https://github.com/vis2k/Mirror/pull/2777
public long GetAverageMaxSendRate() =>
server.connections.Count > 0
? server.connections.Values.Sum(conn => (long)conn.MaxSendRate) / server.connections.Count
: 0;
public long GetAverageMaxReceiveRate() =>
server.connections.Count > 0
? server.connections.Values.Sum(conn => (long)conn.MaxReceiveRate) / server.connections.Count
: 0;
long GetTotalSendQueue() =>
server.connections.Values.Sum(conn => conn.SendQueueCount);
long GetTotalReceiveQueue() =>
server.connections.Values.Sum(conn => conn.ReceiveQueueCount);
long GetTotalSendBuffer() =>
server.connections.Values.Sum(conn => conn.SendBufferCount);
long GetTotalReceiveBuffer() =>
server.connections.Values.Sum(conn => conn.ReceiveBufferCount);
// PrettyBytes function from DOTSNET
// pretty prints bytes as KB/MB/GB/etc.
// long to support > 2GB
// divides by floats to return "2.5MB" etc.
public static string PrettyBytes(long bytes)
{
// bytes
if (bytes < 1024)
return $"{bytes} B";
// kilobytes
else if (bytes < 1024L * 1024L)
return $"{(bytes / 1024f):F2} KB";
// megabytes
else if (bytes < 1024 * 1024L * 1024L)
return $"{(bytes / (1024f * 1024f)):F2} MB";
// gigabytes
return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB";
}
// OnGUI allocates even if it does nothing. avoid in release.
#if UNITY_EDITOR || DEVELOPMENT_BUILD
void OnGUI()
{
if (!statisticsGUI) return;
GUILayout.BeginArea(new Rect(5, 110, 300, 300));
if (ServerActive())
{
GUILayout.BeginVertical("Box");
GUILayout.Label("SERVER");
GUILayout.Label($" connections: {server.connections.Count}");
GUILayout.Label($" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s");
GUILayout.Label($" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s");
GUILayout.Label($" SendQueue: {GetTotalSendQueue()}");
GUILayout.Label($" ReceiveQueue: {GetTotalReceiveQueue()}");
GUILayout.Label($" SendBuffer: {GetTotalSendBuffer()}");
GUILayout.Label($" ReceiveBuffer: {GetTotalReceiveBuffer()}");
GUILayout.EndVertical();
}
if (ClientConnected())
{
GUILayout.BeginVertical("Box");
GUILayout.Label("CLIENT");
GUILayout.Label($" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s");
GUILayout.Label($" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s");
GUILayout.Label($" SendQueue: {client.connection.SendQueueCount}");
GUILayout.Label($" ReceiveQueue: {client.connection.ReceiveQueueCount}");
GUILayout.Label($" SendBuffer: {client.connection.SendBufferCount}");
GUILayout.Label($" ReceiveBuffer: {client.connection.ReceiveBufferCount}");
GUILayout.EndVertical();
}
GUILayout.EndArea();
}
#endif
void OnLogStatistics()
{
if (ServerActive())
{
string log = "kcp SERVER @ time: " + NetworkTime.localTime + "\n";
log += $" connections: {server.connections.Count}\n";
log += $" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s\n";
log += $" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s\n";
log += $" SendQueue: {GetTotalSendQueue()}\n";
log += $" ReceiveQueue: {GetTotalReceiveQueue()}\n";
log += $" SendBuffer: {GetTotalSendBuffer()}\n";
log += $" ReceiveBuffer: {GetTotalReceiveBuffer()}\n\n";
Debug.Log(log);
}
if (ClientConnected())
{
string log = "kcp CLIENT @ time: " + NetworkTime.localTime + "\n";
log += $" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s\n";
log += $" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s\n";
log += $" SendQueue: {client.connection.SendQueueCount}\n";
log += $" ReceiveQueue: {client.connection.ReceiveQueueCount}\n";
log += $" SendBuffer: {client.connection.SendBufferCount}\n";
log += $" ReceiveBuffer: {client.connection.ReceiveBufferCount}\n\n";
Debug.Log(log);
}
}
public override string ToString() => "KCP";
}
}
//#endif MIRROR <- commented out because MIRROR isn't defined on first import yet

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 6b0fecffa3f624585964b0d0eb21b18e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 6b0fecffa3f624585964b0d0eb21b18e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 71a1c8e8c022d4731a481c1808f37e5d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 71a1c8e8c022d4731a481c1808f37e5d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,24 +1,24 @@
MIT License
Copyright (c) 2016 limpo1989
Copyright (c) 2020 Paul Pacheco
Copyright (c) 2020 Lymdun
Copyright (c) 2020 vis2k
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
MIT License
Copyright (c) 2016 limpo1989
Copyright (c) 2020 Paul Pacheco
Copyright (c) 2020 Lymdun
Copyright (c) 2020 vis2k
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 9a3e8369060cf4e94ac117603de47aa6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 9a3e8369060cf4e94ac117603de47aa6
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,94 +1,94 @@
V1.12 [2021-07-16]
- where-allocation removed. will be optional in the future.
- Tests: don't depend on Unity anymore
- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls
OnDisconnected to let the user now.
- fix: KcpServer.DualMode is now configurable in the constructor instead of
using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too.
V1.11 rollback [2021-06-01]
- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime
resizing/allocations
V1.10 [2021-05-28]
- feature: configurable Timeout
- allocations explained with comments (C# ReceiveFrom / IPEndPoint.GetHashCode)
- fix: #17 KcpConnection.ReceiveNextReliable now assigns message default so it
works in .net too
- fix: Segment pool is not static anymore. Each kcp instance now has it's own
Pool<Segment>. fixes #18 concurrency issues
V1.9 [2021-03-02]
- Tick() split into TickIncoming()/TickOutgoing() to use in Mirror's new update
functions. allows to minimize latency.
=> original Tick() is still supported for convenience. simply processes both!
V1.8 [2021-02-14]
- fix: Unity IPv6 errors on Nintendo Switch
- fix: KcpConnection now disconnects if data message was received without content.
previously it would call OnData with an empty ArraySegment, causing all kinds of
weird behaviour in Mirror/DOTSNET. Added tests too.
- fix: KcpConnection.SendData: don't allow sending empty messages anymore. disconnect
and log a warning to make it completely obvious.
V1.7 [2021-01-13]
- fix: unreliable messages reset timeout now too
- perf: KcpConnection OnCheckEnabled callback changed to a simple 'paused' boolean.
This is faster than invoking a Func<bool> every time and allows us to fix #8 more
easily later by calling .Pause/.Unpause from OnEnable/OnDisable in MirrorTransport.
- fix #8: Unpause now resets timeout to fix a bug where Mirror would pause kcp,
change the scene which took >10s, then unpause and kcp would detect the lack of
any messages for >10s as timeout. Added test to make sure it never happens again.
- MirrorTransport: statistics logging for headless servers
- Mirror Transport: Send/Receive window size increased once more from 2048 to 4096.
V1.6 [2021-01-10]
- Unreliable channel added!
- perf: KcpHeader byte added to every kcp message to indicate
Handshake/Data/Ping/Disconnect instead of scanning each message for Hello/Byte/Ping
content via SegmentEquals. It's a lot cleaner, should be faster and should avoid
edge cases where a message content would equal Hello/Ping/Bye sequence accidentally.
- Kcp.Input: offset moved to parameters for cases where it's needed
- Kcp.SetMtu from original Kcp.c
V1.5 [2021-01-07]
- KcpConnection.MaxSend/ReceiveRate calculation based on the article
- MirrorTransport: large send/recv window size defaults to avoid high latencies caused
by packets not being processed fast enough
- MirrorTransport: show MaxSend/ReceiveRate in debug gui
- MirrorTransport: don't Log.Info to console in headless mode if debug log is disabled
V1.4 [2020-11-27]
- fix: OnCheckEnabled added. KcpConnection message processing while loop can now
be interrupted immediately. fixes Mirror Transport scene changes which need to stop
processing any messages immediately after a scene message)
- perf: Mirror KcpTransport: FastResend enabled by default. turbo mode according to:
https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration
- perf: Mirror KcpTransport: CongestionControl disabled by default (turbo mode)
V1.3 [2020-11-17]
- Log.Info/Warning/Error so logging doesn't depend on UnityEngine anymore
- fix: Server.Tick catches SocketException which happens if Android client is killed
- MirrorTransport: debugLog option added that can be checked in Unity Inspector
- Utils.Clamp so Kcp.cs doesn't depend on UnityEngine
- Utils.SegmentsEqual: use Linq SequenceEqual so it doesn't depend on UnityEngine
=> kcp2k can now be used in any C# project even without Unity
V1.2 [2020-11-10]
- more tests added
- fix: raw receive buffers are now all of MTU size
- fix: raw receive detects error where buffer was too small for msgLength and
result in excess data being dropped silently
- KcpConnection.MaxMessageSize added for use in high level
- KcpConnection.MaxMessageSize increased from 1200 bytes to to maximum allowed
message size of 145KB for kcp (based on mtu, overhead, wnd_rcv)
V1.1 [2020-10-30]
- high level cleanup, fixes, improvements
V1.0 [2020-10-22]
- Kcp.cs now mirrors original Kcp.c behaviour
(this fixes dozens of bugs)
V0.1
V1.12 [2021-07-16]
- where-allocation removed. will be optional in the future.
- Tests: don't depend on Unity anymore
- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls
OnDisconnected to let the user now.
- fix: KcpServer.DualMode is now configurable in the constructor instead of
using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too.
V1.11 rollback [2021-06-01]
- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime
resizing/allocations
V1.10 [2021-05-28]
- feature: configurable Timeout
- allocations explained with comments (C# ReceiveFrom / IPEndPoint.GetHashCode)
- fix: #17 KcpConnection.ReceiveNextReliable now assigns message default so it
works in .net too
- fix: Segment pool is not static anymore. Each kcp instance now has it's own
Pool<Segment>. fixes #18 concurrency issues
V1.9 [2021-03-02]
- Tick() split into TickIncoming()/TickOutgoing() to use in Mirror's new update
functions. allows to minimize latency.
=> original Tick() is still supported for convenience. simply processes both!
V1.8 [2021-02-14]
- fix: Unity IPv6 errors on Nintendo Switch
- fix: KcpConnection now disconnects if data message was received without content.
previously it would call OnData with an empty ArraySegment, causing all kinds of
weird behaviour in Mirror/DOTSNET. Added tests too.
- fix: KcpConnection.SendData: don't allow sending empty messages anymore. disconnect
and log a warning to make it completely obvious.
V1.7 [2021-01-13]
- fix: unreliable messages reset timeout now too
- perf: KcpConnection OnCheckEnabled callback changed to a simple 'paused' boolean.
This is faster than invoking a Func<bool> every time and allows us to fix #8 more
easily later by calling .Pause/.Unpause from OnEnable/OnDisable in MirrorTransport.
- fix #8: Unpause now resets timeout to fix a bug where Mirror would pause kcp,
change the scene which took >10s, then unpause and kcp would detect the lack of
any messages for >10s as timeout. Added test to make sure it never happens again.
- MirrorTransport: statistics logging for headless servers
- Mirror Transport: Send/Receive window size increased once more from 2048 to 4096.
V1.6 [2021-01-10]
- Unreliable channel added!
- perf: KcpHeader byte added to every kcp message to indicate
Handshake/Data/Ping/Disconnect instead of scanning each message for Hello/Byte/Ping
content via SegmentEquals. It's a lot cleaner, should be faster and should avoid
edge cases where a message content would equal Hello/Ping/Bye sequence accidentally.
- Kcp.Input: offset moved to parameters for cases where it's needed
- Kcp.SetMtu from original Kcp.c
V1.5 [2021-01-07]
- KcpConnection.MaxSend/ReceiveRate calculation based on the article
- MirrorTransport: large send/recv window size defaults to avoid high latencies caused
by packets not being processed fast enough
- MirrorTransport: show MaxSend/ReceiveRate in debug gui
- MirrorTransport: don't Log.Info to console in headless mode if debug log is disabled
V1.4 [2020-11-27]
- fix: OnCheckEnabled added. KcpConnection message processing while loop can now
be interrupted immediately. fixes Mirror Transport scene changes which need to stop
processing any messages immediately after a scene message)
- perf: Mirror KcpTransport: FastResend enabled by default. turbo mode according to:
https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration
- perf: Mirror KcpTransport: CongestionControl disabled by default (turbo mode)
V1.3 [2020-11-17]
- Log.Info/Warning/Error so logging doesn't depend on UnityEngine anymore
- fix: Server.Tick catches SocketException which happens if Android client is killed
- MirrorTransport: debugLog option added that can be checked in Unity Inspector
- Utils.Clamp so Kcp.cs doesn't depend on UnityEngine
- Utils.SegmentsEqual: use Linq SequenceEqual so it doesn't depend on UnityEngine
=> kcp2k can now be used in any C# project even without Unity
V1.2 [2020-11-10]
- more tests added
- fix: raw receive buffers are now all of MTU size
- fix: raw receive detects error where buffer was too small for msgLength and
result in excess data being dropped silently
- KcpConnection.MaxMessageSize added for use in high level
- KcpConnection.MaxMessageSize increased from 1200 bytes to to maximum allowed
message size of 145KB for kcp (based on mtu, overhead, wnd_rcv)
V1.1 [2020-10-30]
- high level cleanup, fixes, improvements
V1.0 [2020-10-22]
- Kcp.cs now mirrors original Kcp.c behaviour
(this fixes dozens of bugs)
V0.1
- initial kcp-csharp based version

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: ed3f2cf1bbf1b4d53a6f2c103d311f71
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ed3f2cf1bbf1b4d53a6f2c103d311f71
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 5a54d18b954cb4407a28b633fc32ea6d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 5a54d18b954cb4407a28b633fc32ea6d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,10 +1,10 @@
namespace kcp2k
{
// channel type and header for raw messages
public enum KcpChannel : byte
{
// don't react on 0x00. might help to filter out random noise.
Reliable = 0x01,
Unreliable = 0x02
}
namespace kcp2k
{
// channel type and header for raw messages
public enum KcpChannel : byte
{
// don't react on 0x00. might help to filter out random noise.
Reliable = 0x01,
Unreliable = 0x02
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 9e852b2532fb248d19715cfebe371db3
timeCreated: 1610081248
fileFormatVersion: 2
guid: 9e852b2532fb248d19715cfebe371db3
timeCreated: 1610081248

View File

@@ -1,120 +1,120 @@
// kcp client logic abstracted into a class.
// for use in Mirror, DOTSNET, testing, etc.
using System;
namespace kcp2k
{
public class KcpClient
{
// events
public Action OnConnected;
public Action<ArraySegment<byte>> OnData;
public Action OnDisconnected;
// state
public KcpClientConnection connection;
public bool connected;
public KcpClient(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
{
this.OnConnected = OnConnected;
this.OnData = OnData;
this.OnDisconnected = OnDisconnected;
}
// CreateConnection can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
protected virtual KcpClientConnection CreateConnection() =>
new KcpClientConnection();
public void Connect(string address, ushort port, bool noDelay, uint interval, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = KcpConnection.DEFAULT_TIMEOUT)
{
if (connected)
{
Log.Warning("KCP: client already connected!");
return;
}
// create connection
connection = CreateConnection();
// setup events
connection.OnAuthenticated = () =>
{
Log.Info($"KCP: OnClientConnected");
connected = true;
OnConnected.Invoke();
};
connection.OnData = (message) =>
{
//Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(message);
};
connection.OnDisconnected = () =>
{
Log.Info($"KCP: OnClientDisconnected");
connected = false;
connection = null;
OnDisconnected.Invoke();
};
// connect
connection.Connect(address, port, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
}
public void Send(ArraySegment<byte> segment, KcpChannel channel)
{
if (connected)
{
connection.SendData(segment, channel);
}
else Log.Warning("KCP: can't send because client not connected!");
}
public void Disconnect()
{
// only if connected
// otherwise we end up in a deadlock because of an open Mirror bug:
// https://github.com/vis2k/Mirror/issues/2353
if (connected)
{
// call Disconnect and let the connection handle it.
// DO NOT set it to null yet. it needs to be updated a few more
// times first. let the connection handle it!
connection?.Disconnect();
}
}
// process incoming messages. should be called before updating the world.
public void TickIncoming()
{
// recv on socket first, then process incoming
// (even if we didn't receive anything. need to tick ping etc.)
// (connection is null if not active)
connection?.RawReceive();
connection?.TickIncoming();
}
// process outgoing messages. should be called after updating the world.
public void TickOutgoing()
{
// process outgoing
// (connection is null if not active)
connection?.TickOutgoing();
}
// process incoming and outgoing for convenience
// => ideally call ProcessIncoming() before updating the world and
// ProcessOutgoing() after updating the world for minimum latency
public void Tick()
{
TickIncoming();
TickOutgoing();
}
// pause/unpause to safely support mirror scene handling and to
// immediately pause the receive while loop if needed.
public void Pause() => connection?.Pause();
public void Unpause() => connection?.Unpause();
}
}
// kcp client logic abstracted into a class.
// for use in Mirror, DOTSNET, testing, etc.
using System;
namespace kcp2k
{
public class KcpClient
{
// events
public Action OnConnected;
public Action<ArraySegment<byte>> OnData;
public Action OnDisconnected;
// state
public KcpClientConnection connection;
public bool connected;
public KcpClient(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
{
this.OnConnected = OnConnected;
this.OnData = OnData;
this.OnDisconnected = OnDisconnected;
}
// CreateConnection can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
protected virtual KcpClientConnection CreateConnection() =>
new KcpClientConnection();
public void Connect(string address, ushort port, bool noDelay, uint interval, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = KcpConnection.DEFAULT_TIMEOUT)
{
if (connected)
{
Log.Warning("KCP: client already connected!");
return;
}
// create connection
connection = CreateConnection();
// setup events
connection.OnAuthenticated = () =>
{
Log.Info($"KCP: OnClientConnected");
connected = true;
OnConnected.Invoke();
};
connection.OnData = (message) =>
{
//Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(message);
};
connection.OnDisconnected = () =>
{
Log.Info($"KCP: OnClientDisconnected");
connected = false;
connection = null;
OnDisconnected.Invoke();
};
// connect
connection.Connect(address, port, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
}
public void Send(ArraySegment<byte> segment, KcpChannel channel)
{
if (connected)
{
connection.SendData(segment, channel);
}
else Log.Warning("KCP: can't send because client not connected!");
}
public void Disconnect()
{
// only if connected
// otherwise we end up in a deadlock because of an open Mirror bug:
// https://github.com/vis2k/Mirror/issues/2353
if (connected)
{
// call Disconnect and let the connection handle it.
// DO NOT set it to null yet. it needs to be updated a few more
// times first. let the connection handle it!
connection?.Disconnect();
}
}
// process incoming messages. should be called before updating the world.
public void TickIncoming()
{
// recv on socket first, then process incoming
// (even if we didn't receive anything. need to tick ping etc.)
// (connection is null if not active)
connection?.RawReceive();
connection?.TickIncoming();
}
// process outgoing messages. should be called after updating the world.
public void TickOutgoing()
{
// process outgoing
// (connection is null if not active)
connection?.TickOutgoing();
}
// process incoming and outgoing for convenience
// => ideally call ProcessIncoming() before updating the world and
// ProcessOutgoing() after updating the world for minimum latency
public void Tick()
{
TickIncoming();
TickOutgoing();
}
// pause/unpause to safely support mirror scene handling and to
// immediately pause the receive while loop if needed.
public void Pause() => connection?.Pause();
public void Unpause() => connection?.Unpause();
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 6aa069a28ed24fedb533c102d9742b36
timeCreated: 1603786960
fileFormatVersion: 2
guid: 6aa069a28ed24fedb533c102d9742b36
timeCreated: 1603786960

View File

@@ -1,109 +1,109 @@
using System.Net;
using System.Net.Sockets;
namespace kcp2k
{
public class KcpClientConnection : KcpConnection
{
// IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even
// if MaxMessageSize is larger. kcp always sends in MTU
// segments and having a buffer smaller than MTU would
// silently drop excess data.
// => we need the MTU to fit channel + message!
readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF];
// helper function to resolve host to IPAddress
public static bool ResolveHostname(string hostname, out IPAddress[] addresses)
{
try
{
addresses = Dns.GetHostAddresses(hostname);
return addresses.Length >= 1;
}
catch (SocketException)
{
Log.Info($"Failed to resolve host: {hostname}");
addresses = null;
return false;
}
}
// EndPoint & Receive functions can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
// NOTE: Client's SendTo doesn't allocate, don't need a virtual.
protected virtual void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) =>
remoteEndPoint = new IPEndPoint(addresses[0], port);
protected virtual int ReceiveFrom(byte[] buffer) =>
socket.ReceiveFrom(buffer, ref remoteEndPoint);
public void Connect(string host, ushort port, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT)
{
Log.Info($"KcpClient: connect to {host}:{port}");
// try resolve host name
if (ResolveHostname(host, out IPAddress[] addresses))
{
// create remote endpoint
CreateRemoteEndPoint(addresses, port);
// create socket
socket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
socket.Connect(remoteEndPoint);
// set up kcp
SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
// client should send handshake to server as very first message
SendHandshake();
RawReceive();
}
// otherwise call OnDisconnected to let the user know.
else OnDisconnected();
}
// call from transport update
public void RawReceive()
{
try
{
if (socket != null)
{
while (socket.Poll(0, SelectMode.SelectRead))
{
int msgLength = ReceiveFrom(rawReceiveBuffer);
// IMPORTANT: detect if buffer was too small for the
// received msgLength. otherwise the excess
// data would be silently lost.
// (see ReceiveFrom documentation)
if (msgLength <= rawReceiveBuffer.Length)
{
//Log.Debug($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
RawInput(rawReceiveBuffer, msgLength);
}
else
{
Log.Error($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting.");
Disconnect();
}
}
}
}
// this is fine, the socket might have been closed in the other end
catch (SocketException) {}
}
protected override void Dispose()
{
socket.Close();
socket = null;
}
protected override void RawSend(byte[] data, int length)
{
socket.Send(data, length, SocketFlags.None);
}
}
}
using System.Net;
using System.Net.Sockets;
namespace kcp2k
{
public class KcpClientConnection : KcpConnection
{
// IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even
// if MaxMessageSize is larger. kcp always sends in MTU
// segments and having a buffer smaller than MTU would
// silently drop excess data.
// => we need the MTU to fit channel + message!
readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF];
// helper function to resolve host to IPAddress
public static bool ResolveHostname(string hostname, out IPAddress[] addresses)
{
try
{
addresses = Dns.GetHostAddresses(hostname);
return addresses.Length >= 1;
}
catch (SocketException)
{
Log.Info($"Failed to resolve host: {hostname}");
addresses = null;
return false;
}
}
// EndPoint & Receive functions can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
// NOTE: Client's SendTo doesn't allocate, don't need a virtual.
protected virtual void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) =>
remoteEndPoint = new IPEndPoint(addresses[0], port);
protected virtual int ReceiveFrom(byte[] buffer) =>
socket.ReceiveFrom(buffer, ref remoteEndPoint);
public void Connect(string host, ushort port, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT)
{
Log.Info($"KcpClient: connect to {host}:{port}");
// try resolve host name
if (ResolveHostname(host, out IPAddress[] addresses))
{
// create remote endpoint
CreateRemoteEndPoint(addresses, port);
// create socket
socket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
socket.Connect(remoteEndPoint);
// set up kcp
SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
// client should send handshake to server as very first message
SendHandshake();
RawReceive();
}
// otherwise call OnDisconnected to let the user know.
else OnDisconnected();
}
// call from transport update
public void RawReceive()
{
try
{
if (socket != null)
{
while (socket.Poll(0, SelectMode.SelectRead))
{
int msgLength = ReceiveFrom(rawReceiveBuffer);
// IMPORTANT: detect if buffer was too small for the
// received msgLength. otherwise the excess
// data would be silently lost.
// (see ReceiveFrom documentation)
if (msgLength <= rawReceiveBuffer.Length)
{
//Log.Debug($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
RawInput(rawReceiveBuffer, msgLength);
}
else
{
Log.Error($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting.");
Disconnect();
}
}
}
}
// this is fine, the socket might have been closed in the other end
catch (SocketException) {}
}
protected override void Dispose()
{
socket.Close();
socket = null;
}
protected override void RawSend(byte[] data, int length)
{
socket.Send(data, length, SocketFlags.None);
}
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 96512e74aa8214a6faa8a412a7a07877
timeCreated: 1602601237
fileFormatVersion: 2
guid: 96512e74aa8214a6faa8a412a7a07877
timeCreated: 1602601237

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 3915c7c62b72d4dc2a9e4e76c94fc484
timeCreated: 1602600432
fileFormatVersion: 2
guid: 3915c7c62b72d4dc2a9e4e76c94fc484
timeCreated: 1602600432

View File

@@ -1,19 +1,19 @@
namespace kcp2k
{
// header for messages processed by kcp.
// this is NOT for the raw receive messages(!) because handshake/disconnect
// need to be sent reliably. it's not enough to have those in rawreceive
// because those messages might get lost without being resent!
public enum KcpHeader : byte
{
// don't react on 0x00. might help to filter out random noise.
Handshake = 0x01,
// ping goes over reliable & KcpHeader for now. could go over reliable
// too. there is no real difference except that this is easier because
// we already have a KcpHeader for reliable messages.
// ping is only used to keep it alive, so latency doesn't matter.
Ping = 0x02,
Data = 0x03,
Disconnect = 0x04
}
namespace kcp2k
{
// header for messages processed by kcp.
// this is NOT for the raw receive messages(!) because handshake/disconnect
// need to be sent reliably. it's not enough to have those in rawreceive
// because those messages might get lost without being resent!
public enum KcpHeader : byte
{
// don't react on 0x00. might help to filter out random noise.
Handshake = 0x01,
// ping goes over reliable & KcpHeader for now. could go over reliable
// too. there is no real difference except that this is easier because
// we already have a KcpHeader for reliable messages.
// ping is only used to keep it alive, so latency doesn't matter.
Ping = 0x02,
Data = 0x03,
Disconnect = 0x04
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 91b5edac31224a49bd76f960ae018942
timeCreated: 1610081248
fileFormatVersion: 2
guid: 91b5edac31224a49bd76f960ae018942
timeCreated: 1610081248

View File

@@ -1,337 +1,337 @@
// kcp server logic abstracted into a class.
// for use in Mirror, DOTSNET, testing, etc.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace kcp2k
{
public class KcpServer
{
// events
public Action<int> OnConnected;
public Action<int, ArraySegment<byte>> OnData;
public Action<int> OnDisconnected;
// configuration
// DualMode uses both IPv6 and IPv4. not all platforms support it.
// (Nintendo Switch, etc.)
public bool DualMode;
// NoDelay is recommended to reduce latency. This also scales better
// without buffers getting full.
public bool NoDelay;
// KCP internal update interval. 100ms is KCP default, but a lower
// interval is recommended to minimize latency and to scale to more
// networked entities.
public uint Interval;
// KCP fastresend parameter. Faster resend for the cost of higher
// bandwidth.
public int FastResend;
// KCP 'NoCongestionWindow' is false by default. here we negate it for
// ease of use. This can be disabled for high scale games if connections
// choke regularly.
public bool CongestionWindow;
// KCP window size can be modified to support higher loads.
// for example, Mirror Benchmark requires:
// 128, 128 for 4k monsters
// 512, 512 for 10k monsters
// 8192, 8192 for 20k monsters
public uint SendWindowSize;
public uint ReceiveWindowSize;
// timeout in milliseconds
public int Timeout;
// state
protected Socket socket;
EndPoint newClientEP;
// IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even
// if MaxMessageSize is larger. kcp always sends in MTU
// segments and having a buffer smaller than MTU would
// silently drop excess data.
// => we need the mtu to fit channel + message!
readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF];
// connections <connectionId, connection> where connectionId is EndPoint.GetHashCode
public Dictionary<int, KcpServerConnection> connections = new Dictionary<int, KcpServerConnection>();
public KcpServer(Action<int> OnConnected,
Action<int, ArraySegment<byte>> OnData,
Action<int> OnDisconnected,
bool DualMode,
bool NoDelay,
uint Interval,
int FastResend = 0,
bool CongestionWindow = true,
uint SendWindowSize = Kcp.WND_SND,
uint ReceiveWindowSize = Kcp.WND_RCV,
int Timeout = KcpConnection.DEFAULT_TIMEOUT)
{
this.OnConnected = OnConnected;
this.OnData = OnData;
this.OnDisconnected = OnDisconnected;
this.DualMode = DualMode;
this.NoDelay = NoDelay;
this.Interval = Interval;
this.FastResend = FastResend;
this.CongestionWindow = CongestionWindow;
this.SendWindowSize = SendWindowSize;
this.ReceiveWindowSize = ReceiveWindowSize;
this.Timeout = Timeout;
// create newClientEP either IPv4 or IPv6
newClientEP = DualMode
? new IPEndPoint(IPAddress.IPv6Any, 0)
: new IPEndPoint(IPAddress.Any, 0);
}
public bool IsActive() => socket != null;
public void Start(ushort port)
{
// only start once
if (socket != null)
{
Log.Warning("KCP: server already started!");
}
// listen
if (DualMode)
{
// IPv6 socket with DualMode
socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
socket.DualMode = true;
socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
}
else
{
// IPv4 socket
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(new IPEndPoint(IPAddress.Any, port));
}
}
public void Send(int connectionId, ArraySegment<byte> segment, KcpChannel channel)
{
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
connection.SendData(segment, channel);
}
}
public void Disconnect(int connectionId)
{
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
connection.Disconnect();
}
}
public string GetClientAddress(int connectionId)
{
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
return (connection.GetRemoteEndPoint() as IPEndPoint).Address.ToString();
}
return "";
}
// EndPoint & Receive functions can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
protected virtual int ReceiveFrom(byte[] buffer, out int connectionHash)
{
// NOTE: ReceiveFrom allocates.
// we pass our IPEndPoint to ReceiveFrom.
// receive from calls newClientEP.Create(socketAddr).
// IPEndPoint.Create always returns a new IPEndPoint.
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761
int read = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP);
// calculate connectionHash from endpoint
// NOTE: IPEndPoint.GetHashCode() allocates.
// it calls m_Address.GetHashCode().
// m_Address is an IPAddress.
// GetHashCode() allocates for IPv6:
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699
//
// => using only newClientEP.Port wouldn't work, because
// different connections can have the same port.
connectionHash = newClientEP.GetHashCode();
return read;
}
protected virtual KcpServerConnection CreateConnection() =>
new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
// process incoming messages. should be called before updating the world.
HashSet<int> connectionsToRemove = new HashSet<int>();
public void TickIncoming()
{
while (socket != null && socket.Poll(0, SelectMode.SelectRead))
{
try
{
// receive
int msgLength = ReceiveFrom(rawReceiveBuffer, out int connectionId);
//Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
// IMPORTANT: detect if buffer was too small for the received
// msgLength. otherwise the excess data would be
// silently lost.
// (see ReceiveFrom documentation)
if (msgLength <= rawReceiveBuffer.Length)
{
// is this a new connection?
if (!connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
// create a new KcpConnection based on last received
// EndPoint. can be overwritten for where-allocation.
connection = CreateConnection();
// DO NOT add to connections yet. only if the first message
// is actually the kcp handshake. otherwise it's either:
// * random data from the internet
// * or from a client connection that we just disconnected
// but that hasn't realized it yet, still sending data
// from last session that we should absolutely ignore.
//
//
// TODO this allocates a new KcpConnection for each new
// internet connection. not ideal, but C# UDP Receive
// already allocated anyway.
//
// expecting a MAGIC byte[] would work, but sending the raw
// UDP message without kcp's reliability will have low
// probability of being received.
//
// for now, this is fine.
// setup authenticated event that also adds to connections
connection.OnAuthenticated = () =>
{
// only send handshake to client AFTER we received his
// handshake in OnAuthenticated.
// we don't want to reply to random internet messages
// with handshakes each time.
connection.SendHandshake();
// add to connections dict after being authenticated.
connections.Add(connectionId, connection);
Log.Info($"KCP: server added connection({connectionId})");
// setup Data + Disconnected events only AFTER the
// handshake. we don't want to fire OnServerDisconnected
// every time we receive invalid random data from the
// internet.
// setup data event
connection.OnData = (message) =>
{
// call mirror event
//Log.Info($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(connectionId, message);
};
// setup disconnected event
connection.OnDisconnected = () =>
{
// flag for removal
// (can't remove directly because connection is updated
// and event is called while iterating all connections)
connectionsToRemove.Add(connectionId);
// call mirror event
Log.Info($"KCP: OnServerDisconnected({connectionId})");
OnDisconnected.Invoke(connectionId);
};
// finally, call mirror OnConnected event
Log.Info($"KCP: OnServerConnected({connectionId})");
OnConnected.Invoke(connectionId);
};
// now input the message & process received ones
// connected event was set up.
// tick will process the first message and adds the
// connection if it was the handshake.
connection.RawInput(rawReceiveBuffer, msgLength);
connection.TickIncoming();
// again, do not add to connections.
// if the first message wasn't the kcp handshake then
// connection will simply be garbage collected.
}
// existing connection: simply input the message into kcp
else
{
connection.RawInput(rawReceiveBuffer, msgLength);
}
}
else
{
Log.Error($"KCP Server: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting connectionId={connectionId}.");
Disconnect(connectionId);
}
}
// this is fine, the socket might have been closed in the other end
catch (SocketException) {}
}
// process inputs for all server connections
// (even if we didn't receive anything. need to tick ping etc.)
foreach (KcpServerConnection connection in connections.Values)
{
connection.TickIncoming();
}
// remove disconnected connections
// (can't do it in connection.OnDisconnected because Tick is called
// while iterating connections)
foreach (int connectionId in connectionsToRemove)
{
connections.Remove(connectionId);
}
connectionsToRemove.Clear();
}
// process outgoing messages. should be called after updating the world.
public void TickOutgoing()
{
// flush all server connections
foreach (KcpServerConnection connection in connections.Values)
{
connection.TickOutgoing();
}
}
// process incoming and outgoing for convenience.
// => ideally call ProcessIncoming() before updating the world and
// ProcessOutgoing() after updating the world for minimum latency
public void Tick()
{
TickIncoming();
TickOutgoing();
}
public void Stop()
{
socket?.Close();
socket = null;
}
// pause/unpause to safely support mirror scene handling and to
// immediately pause the receive while loop if needed.
public void Pause()
{
foreach (KcpServerConnection connection in connections.Values)
connection.Pause();
}
public void Unpause()
{
foreach (KcpServerConnection connection in connections.Values)
connection.Unpause();
}
}
}
// kcp server logic abstracted into a class.
// for use in Mirror, DOTSNET, testing, etc.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace kcp2k
{
public class KcpServer
{
// events
public Action<int> OnConnected;
public Action<int, ArraySegment<byte>> OnData;
public Action<int> OnDisconnected;
// configuration
// DualMode uses both IPv6 and IPv4. not all platforms support it.
// (Nintendo Switch, etc.)
public bool DualMode;
// NoDelay is recommended to reduce latency. This also scales better
// without buffers getting full.
public bool NoDelay;
// KCP internal update interval. 100ms is KCP default, but a lower
// interval is recommended to minimize latency and to scale to more
// networked entities.
public uint Interval;
// KCP fastresend parameter. Faster resend for the cost of higher
// bandwidth.
public int FastResend;
// KCP 'NoCongestionWindow' is false by default. here we negate it for
// ease of use. This can be disabled for high scale games if connections
// choke regularly.
public bool CongestionWindow;
// KCP window size can be modified to support higher loads.
// for example, Mirror Benchmark requires:
// 128, 128 for 4k monsters
// 512, 512 for 10k monsters
// 8192, 8192 for 20k monsters
public uint SendWindowSize;
public uint ReceiveWindowSize;
// timeout in milliseconds
public int Timeout;
// state
protected Socket socket;
EndPoint newClientEP;
// IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even
// if MaxMessageSize is larger. kcp always sends in MTU
// segments and having a buffer smaller than MTU would
// silently drop excess data.
// => we need the mtu to fit channel + message!
readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF];
// connections <connectionId, connection> where connectionId is EndPoint.GetHashCode
public Dictionary<int, KcpServerConnection> connections = new Dictionary<int, KcpServerConnection>();
public KcpServer(Action<int> OnConnected,
Action<int, ArraySegment<byte>> OnData,
Action<int> OnDisconnected,
bool DualMode,
bool NoDelay,
uint Interval,
int FastResend = 0,
bool CongestionWindow = true,
uint SendWindowSize = Kcp.WND_SND,
uint ReceiveWindowSize = Kcp.WND_RCV,
int Timeout = KcpConnection.DEFAULT_TIMEOUT)
{
this.OnConnected = OnConnected;
this.OnData = OnData;
this.OnDisconnected = OnDisconnected;
this.DualMode = DualMode;
this.NoDelay = NoDelay;
this.Interval = Interval;
this.FastResend = FastResend;
this.CongestionWindow = CongestionWindow;
this.SendWindowSize = SendWindowSize;
this.ReceiveWindowSize = ReceiveWindowSize;
this.Timeout = Timeout;
// create newClientEP either IPv4 or IPv6
newClientEP = DualMode
? new IPEndPoint(IPAddress.IPv6Any, 0)
: new IPEndPoint(IPAddress.Any, 0);
}
public bool IsActive() => socket != null;
public void Start(ushort port)
{
// only start once
if (socket != null)
{
Log.Warning("KCP: server already started!");
}
// listen
if (DualMode)
{
// IPv6 socket with DualMode
socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
socket.DualMode = true;
socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
}
else
{
// IPv4 socket
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(new IPEndPoint(IPAddress.Any, port));
}
}
public void Send(int connectionId, ArraySegment<byte> segment, KcpChannel channel)
{
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
connection.SendData(segment, channel);
}
}
public void Disconnect(int connectionId)
{
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
connection.Disconnect();
}
}
public string GetClientAddress(int connectionId)
{
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
return (connection.GetRemoteEndPoint() as IPEndPoint).Address.ToString();
}
return "";
}
// EndPoint & Receive functions can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
protected virtual int ReceiveFrom(byte[] buffer, out int connectionHash)
{
// NOTE: ReceiveFrom allocates.
// we pass our IPEndPoint to ReceiveFrom.
// receive from calls newClientEP.Create(socketAddr).
// IPEndPoint.Create always returns a new IPEndPoint.
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761
int read = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP);
// calculate connectionHash from endpoint
// NOTE: IPEndPoint.GetHashCode() allocates.
// it calls m_Address.GetHashCode().
// m_Address is an IPAddress.
// GetHashCode() allocates for IPv6:
// https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699
//
// => using only newClientEP.Port wouldn't work, because
// different connections can have the same port.
connectionHash = newClientEP.GetHashCode();
return read;
}
protected virtual KcpServerConnection CreateConnection() =>
new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
// process incoming messages. should be called before updating the world.
HashSet<int> connectionsToRemove = new HashSet<int>();
public void TickIncoming()
{
while (socket != null && socket.Poll(0, SelectMode.SelectRead))
{
try
{
// receive
int msgLength = ReceiveFrom(rawReceiveBuffer, out int connectionId);
//Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
// IMPORTANT: detect if buffer was too small for the received
// msgLength. otherwise the excess data would be
// silently lost.
// (see ReceiveFrom documentation)
if (msgLength <= rawReceiveBuffer.Length)
{
// is this a new connection?
if (!connections.TryGetValue(connectionId, out KcpServerConnection connection))
{
// create a new KcpConnection based on last received
// EndPoint. can be overwritten for where-allocation.
connection = CreateConnection();
// DO NOT add to connections yet. only if the first message
// is actually the kcp handshake. otherwise it's either:
// * random data from the internet
// * or from a client connection that we just disconnected
// but that hasn't realized it yet, still sending data
// from last session that we should absolutely ignore.
//
//
// TODO this allocates a new KcpConnection for each new
// internet connection. not ideal, but C# UDP Receive
// already allocated anyway.
//
// expecting a MAGIC byte[] would work, but sending the raw
// UDP message without kcp's reliability will have low
// probability of being received.
//
// for now, this is fine.
// setup authenticated event that also adds to connections
connection.OnAuthenticated = () =>
{
// only send handshake to client AFTER we received his
// handshake in OnAuthenticated.
// we don't want to reply to random internet messages
// with handshakes each time.
connection.SendHandshake();
// add to connections dict after being authenticated.
connections.Add(connectionId, connection);
Log.Info($"KCP: server added connection({connectionId})");
// setup Data + Disconnected events only AFTER the
// handshake. we don't want to fire OnServerDisconnected
// every time we receive invalid random data from the
// internet.
// setup data event
connection.OnData = (message) =>
{
// call mirror event
//Log.Info($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(connectionId, message);
};
// setup disconnected event
connection.OnDisconnected = () =>
{
// flag for removal
// (can't remove directly because connection is updated
// and event is called while iterating all connections)
connectionsToRemove.Add(connectionId);
// call mirror event
Log.Info($"KCP: OnServerDisconnected({connectionId})");
OnDisconnected.Invoke(connectionId);
};
// finally, call mirror OnConnected event
Log.Info($"KCP: OnServerConnected({connectionId})");
OnConnected.Invoke(connectionId);
};
// now input the message & process received ones
// connected event was set up.
// tick will process the first message and adds the
// connection if it was the handshake.
connection.RawInput(rawReceiveBuffer, msgLength);
connection.TickIncoming();
// again, do not add to connections.
// if the first message wasn't the kcp handshake then
// connection will simply be garbage collected.
}
// existing connection: simply input the message into kcp
else
{
connection.RawInput(rawReceiveBuffer, msgLength);
}
}
else
{
Log.Error($"KCP Server: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting connectionId={connectionId}.");
Disconnect(connectionId);
}
}
// this is fine, the socket might have been closed in the other end
catch (SocketException) {}
}
// process inputs for all server connections
// (even if we didn't receive anything. need to tick ping etc.)
foreach (KcpServerConnection connection in connections.Values)
{
connection.TickIncoming();
}
// remove disconnected connections
// (can't do it in connection.OnDisconnected because Tick is called
// while iterating connections)
foreach (int connectionId in connectionsToRemove)
{
connections.Remove(connectionId);
}
connectionsToRemove.Clear();
}
// process outgoing messages. should be called after updating the world.
public void TickOutgoing()
{
// flush all server connections
foreach (KcpServerConnection connection in connections.Values)
{
connection.TickOutgoing();
}
}
// process incoming and outgoing for convenience.
// => ideally call ProcessIncoming() before updating the world and
// ProcessOutgoing() after updating the world for minimum latency
public void Tick()
{
TickIncoming();
TickOutgoing();
}
public void Stop()
{
socket?.Close();
socket = null;
}
// pause/unpause to safely support mirror scene handling and to
// immediately pause the receive while loop if needed.
public void Pause()
{
foreach (KcpServerConnection connection in connections.Values)
connection.Pause();
}
public void Unpause()
{
foreach (KcpServerConnection connection in connections.Values)
connection.Unpause();
}
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 9759159c6589494a9037f5e130a867ed
timeCreated: 1603787747
fileFormatVersion: 2
guid: 9759159c6589494a9037f5e130a867ed
timeCreated: 1603787747

View File

@@ -1,22 +1,22 @@
using System.Net;
using System.Net.Sockets;
namespace kcp2k
{
public class KcpServerConnection : KcpConnection
{
// Constructor & Send functions can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
public KcpServerConnection(Socket socket, EndPoint remoteEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT)
{
this.socket = socket;
this.remoteEndPoint = remoteEndPoint;
SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
}
protected override void RawSend(byte[] data, int length)
{
socket.SendTo(data, 0, length, SocketFlags.None, remoteEndPoint);
}
}
}
using System.Net;
using System.Net.Sockets;
namespace kcp2k
{
public class KcpServerConnection : KcpConnection
{
// Constructor & Send functions can be overwritten for where-allocation:
// https://github.com/vis2k/where-allocation
public KcpServerConnection(Socket socket, EndPoint remoteEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT)
{
this.socket = socket;
this.remoteEndPoint = remoteEndPoint;
SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout);
}
protected override void RawSend(byte[] data, int length)
{
socket.SendTo(data, 0, length, SocketFlags.None, remoteEndPoint);
}
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 80a9b1ce9a6f14abeb32bfa9921d097b
timeCreated: 1602601483
fileFormatVersion: 2
guid: 80a9b1ce9a6f14abeb32bfa9921d097b
timeCreated: 1602601483

View File

@@ -1,14 +1,14 @@
// A simple logger class that uses Console.WriteLine by default.
// Can also do Logger.LogMethod = Debug.Log for Unity etc.
// (this way we don't have to depend on UnityEngine)
using System;
namespace kcp2k
{
public static class Log
{
public static Action<string> Info = Console.WriteLine;
public static Action<string> Warning = Console.WriteLine;
public static Action<string> Error = Console.Error.WriteLine;
}
}
// A simple logger class that uses Console.WriteLine by default.
// Can also do Logger.LogMethod = Debug.Log for Unity etc.
// (this way we don't have to depend on UnityEngine)
using System;
namespace kcp2k
{
public static class Log
{
public static Action<string> Info = Console.WriteLine;
public static Action<string> Warning = Console.WriteLine;
public static Action<string> Error = Console.Error.WriteLine;
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 7b5e1de98d6d84c3793a61cf7d8da9a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 7b5e1de98d6d84c3793a61cf7d8da9a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 0b320ff06046474eae7bce7240ea478c
timeCreated: 1626430641
fileFormatVersion: 2
guid: 0b320ff06046474eae7bce7240ea478c
timeCreated: 1626430641

View File

@@ -1,24 +1,24 @@
// where-allocation version of KcpClientConnection.
// may not be wanted on all platforms, so it's an extra optional class.
using System.Net;
using WhereAllocation;
namespace kcp2k
{
public class KcpClientConnectionNonAlloc : KcpClientConnection
{
IPEndPointNonAlloc reusableEP;
protected override void CreateRemoteEndPoint(IPAddress[] addresses, ushort port)
{
// create reusableEP with same address family as remoteEndPoint.
// otherwise ReceiveFrom_NonAlloc couldn't use it.
reusableEP = new IPEndPointNonAlloc(addresses[0], port);
base.CreateRemoteEndPoint(addresses, port);
}
// where-allocation nonalloc recv
protected override int ReceiveFrom(byte[] buffer) =>
socket.ReceiveFrom_NonAlloc(buffer, reusableEP);
}
// where-allocation version of KcpClientConnection.
// may not be wanted on all platforms, so it's an extra optional class.
using System.Net;
using WhereAllocation;
namespace kcp2k
{
public class KcpClientConnectionNonAlloc : KcpClientConnection
{
IPEndPointNonAlloc reusableEP;
protected override void CreateRemoteEndPoint(IPAddress[] addresses, ushort port)
{
// create reusableEP with same address family as remoteEndPoint.
// otherwise ReceiveFrom_NonAlloc couldn't use it.
reusableEP = new IPEndPointNonAlloc(addresses[0], port);
base.CreateRemoteEndPoint(addresses, port);
}
// where-allocation nonalloc recv
protected override int ReceiveFrom(byte[] buffer) =>
socket.ReceiveFrom_NonAlloc(buffer, reusableEP);
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 4c1b235bbe054706bef6d092f361006e
timeCreated: 1626430539
fileFormatVersion: 2
guid: 4c1b235bbe054706bef6d092f361006e
timeCreated: 1626430539

View File

@@ -1,17 +1,17 @@
// where-allocation version of KcpClientConnectionNonAlloc.
// may not be wanted on all platforms, so it's an extra optional class.
using System;
namespace kcp2k
{
public class KcpClientNonAlloc : KcpClient
{
public KcpClientNonAlloc(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
: base(OnConnected, OnData, OnDisconnected)
{
}
protected override KcpClientConnection CreateConnection() =>
new KcpClientConnectionNonAlloc();
}
// where-allocation version of KcpClientConnectionNonAlloc.
// may not be wanted on all platforms, so it's an extra optional class.
using System;
namespace kcp2k
{
public class KcpClientNonAlloc : KcpClient
{
public KcpClientNonAlloc(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
: base(OnConnected, OnData, OnDisconnected)
{
}
protected override KcpClientConnection CreateConnection() =>
new KcpClientConnectionNonAlloc();
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 2cf0ccf7d551480bb5af08fcbe169f84
timeCreated: 1626435264
fileFormatVersion: 2
guid: 2cf0ccf7d551480bb5af08fcbe169f84
timeCreated: 1626435264

View File

@@ -1,25 +1,25 @@
// where-allocation version of KcpServerConnection.
// may not be wanted on all platforms, so it's an extra optional class.
using System.Net;
using System.Net.Sockets;
using WhereAllocation;
namespace kcp2k
{
public class KcpServerConnectionNonAlloc : KcpServerConnection
{
IPEndPointNonAlloc reusableSendEndPoint;
public KcpServerConnectionNonAlloc(Socket socket, EndPoint remoteEndpoint, IPEndPointNonAlloc reusableSendEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT)
: base(socket, remoteEndpoint, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout)
{
this.reusableSendEndPoint = reusableSendEndPoint;
}
protected override void RawSend(byte[] data, int length)
{
// where-allocation nonalloc send
socket.SendTo_NonAlloc(data, 0, length, SocketFlags.None, reusableSendEndPoint);
}
}
// where-allocation version of KcpServerConnection.
// may not be wanted on all platforms, so it's an extra optional class.
using System.Net;
using System.Net.Sockets;
using WhereAllocation;
namespace kcp2k
{
public class KcpServerConnectionNonAlloc : KcpServerConnection
{
IPEndPointNonAlloc reusableSendEndPoint;
public KcpServerConnectionNonAlloc(Socket socket, EndPoint remoteEndpoint, IPEndPointNonAlloc reusableSendEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT)
: base(socket, remoteEndpoint, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout)
{
this.reusableSendEndPoint = reusableSendEndPoint;
}
protected override void RawSend(byte[] data, int length)
{
// where-allocation nonalloc send
socket.SendTo_NonAlloc(data, 0, length, SocketFlags.None, reusableSendEndPoint);
}
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 4e1b74cc224b4c83a0f6c8d8da9090ab
timeCreated: 1626430608
fileFormatVersion: 2
guid: 4e1b74cc224b4c83a0f6c8d8da9090ab
timeCreated: 1626430608

View File

@@ -1,51 +1,51 @@
// where-allocation version of KcpServer.
// may not be wanted on all platforms, so it's an extra optional class.
using System;
using System.Net;
using System.Net.Sockets;
using WhereAllocation;
namespace kcp2k
{
public class KcpServerNonAlloc : KcpServer
{
IPEndPointNonAlloc reusableClientEP;
public KcpServerNonAlloc(Action<int> OnConnected, Action<int, ArraySegment<byte>> OnData, Action<int> OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = Kcp.WND_SND, uint ReceiveWindowSize = Kcp.WND_RCV, int Timeout = KcpConnection.DEFAULT_TIMEOUT)
: base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout)
{
// create reusableClientEP either IPv4 or IPv6
reusableClientEP = DualMode
? new IPEndPointNonAlloc(IPAddress.IPv6Any, 0)
: new IPEndPointNonAlloc(IPAddress.Any, 0);
}
protected override int ReceiveFrom(byte[] buffer, out int connectionHash)
{
// where-allocation nonalloc ReceiveFrom.
int read = socket.ReceiveFrom_NonAlloc(buffer, 0, buffer.Length, SocketFlags.None, reusableClientEP);
SocketAddress remoteAddress = reusableClientEP.temp;
// where-allocation nonalloc GetHashCode
connectionHash = remoteAddress.GetHashCode();
return read;
}
protected override KcpServerConnection CreateConnection()
{
// IPEndPointNonAlloc is reused all the time.
// we can't store that as the connection's endpoint.
// we need a new copy!
IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint();
// for allocation free sending, we also need another
// IPEndPointNonAlloc...
IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port);
// create a new KcpConnection NonAlloc version
// -> where-allocation IPEndPointNonAlloc is reused.
// need to create a new one from the temp address.
return new KcpServerConnectionNonAlloc(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
}
}
// where-allocation version of KcpServer.
// may not be wanted on all platforms, so it's an extra optional class.
using System;
using System.Net;
using System.Net.Sockets;
using WhereAllocation;
namespace kcp2k
{
public class KcpServerNonAlloc : KcpServer
{
IPEndPointNonAlloc reusableClientEP;
public KcpServerNonAlloc(Action<int> OnConnected, Action<int, ArraySegment<byte>> OnData, Action<int> OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = Kcp.WND_SND, uint ReceiveWindowSize = Kcp.WND_RCV, int Timeout = KcpConnection.DEFAULT_TIMEOUT)
: base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout)
{
// create reusableClientEP either IPv4 or IPv6
reusableClientEP = DualMode
? new IPEndPointNonAlloc(IPAddress.IPv6Any, 0)
: new IPEndPointNonAlloc(IPAddress.Any, 0);
}
protected override int ReceiveFrom(byte[] buffer, out int connectionHash)
{
// where-allocation nonalloc ReceiveFrom.
int read = socket.ReceiveFrom_NonAlloc(buffer, 0, buffer.Length, SocketFlags.None, reusableClientEP);
SocketAddress remoteAddress = reusableClientEP.temp;
// where-allocation nonalloc GetHashCode
connectionHash = remoteAddress.GetHashCode();
return read;
}
protected override KcpServerConnection CreateConnection()
{
// IPEndPointNonAlloc is reused all the time.
// we can't store that as the connection's endpoint.
// we need a new copy!
IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint();
// for allocation free sending, we also need another
// IPEndPointNonAlloc...
IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port);
// create a new KcpConnection NonAlloc version
// -> where-allocation IPEndPointNonAlloc is reused.
// need to create a new one from the temp address.
return new KcpServerConnectionNonAlloc(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout);
}
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 54b8398dcd544c8a93bcad846214cc40
timeCreated: 1626432191
fileFormatVersion: 2
guid: 54b8398dcd544c8a93bcad846214cc40
timeCreated: 1626432191

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 5cafb8851a0084f3e94a580c207b3923
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 5cafb8851a0084f3e94a580c207b3923
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,3 +1,3 @@
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("kcp2k.Tests")]

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: aec6a15ac7bd43129317ea1f01f19782
timeCreated: 1602665988
fileFormatVersion: 2
guid: aec6a15ac7bd43129317ea1f01f19782
timeCreated: 1602665988

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: a59b1cae10a334faf807432ab472f212
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: a59b1cae10a334faf807432ab472f212
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,46 +1,46 @@
// Pool to avoid allocations (from libuv2k & Mirror)
using System;
using System.Collections.Generic;
namespace kcp2k
{
public class Pool<T>
{
// Mirror is single threaded, no need for concurrent collections
readonly Stack<T> objects = new Stack<T>();
// some types might need additional parameters in their constructor, so
// we use a Func<T> generator
readonly Func<T> objectGenerator;
// some types might need additional cleanup for returned objects
readonly Action<T> objectResetter;
public Pool(Func<T> objectGenerator, Action<T> objectResetter, int initialCapacity)
{
this.objectGenerator = objectGenerator;
this.objectResetter = objectResetter;
// allocate an initial pool so we have fewer (if any)
// allocations in the first few frames (or seconds).
for (int i = 0; i < initialCapacity; ++i)
objects.Push(objectGenerator());
}
// take an element from the pool, or create a new one if empty
public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator();
// return an element to the pool
public void Return(T item)
{
objectResetter(item);
objects.Push(item);
}
// clear the pool
public void Clear() => objects.Clear();
// count to see how many objects are in the pool. useful for tests.
public int Count => objects.Count;
}
}
// Pool to avoid allocations (from libuv2k & Mirror)
using System;
using System.Collections.Generic;
namespace kcp2k
{
public class Pool<T>
{
// Mirror is single threaded, no need for concurrent collections
readonly Stack<T> objects = new Stack<T>();
// some types might need additional parameters in their constructor, so
// we use a Func<T> generator
readonly Func<T> objectGenerator;
// some types might need additional cleanup for returned objects
readonly Action<T> objectResetter;
public Pool(Func<T> objectGenerator, Action<T> objectResetter, int initialCapacity)
{
this.objectGenerator = objectGenerator;
this.objectResetter = objectResetter;
// allocate an initial pool so we have fewer (if any)
// allocations in the first few frames (or seconds).
for (int i = 0; i < initialCapacity; ++i)
objects.Push(objectGenerator());
}
// take an element from the pool, or create a new one if empty
public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator();
// return an element to the pool
public void Return(T item)
{
objectResetter(item);
objects.Push(item);
}
// clear the pool
public void Clear() => objects.Clear();
// count to see how many objects are in the pool. useful for tests.
public int Count => objects.Count;
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: 35c07818fc4784bb4ba472c8e5029002
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 35c07818fc4784bb4ba472c8e5029002
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,62 +1,62 @@
using System.IO;
namespace kcp2k
{
// KCP Segment Definition
internal class Segment
{
internal uint conv; // conversation
internal uint cmd; // command, e.g. Kcp.CMD_ACK etc.
internal uint frg; // fragment
internal uint wnd; // window size that the receive can currently receive
internal uint ts; // timestamp
internal uint sn; // serial number
internal uint una;
internal uint resendts; // resend timestamp
internal int rto;
internal uint fastack;
internal uint xmit;
// we need an auto scaling byte[] with a WriteBytes function.
// MemoryStream does that perfectly, no need to reinvent the wheel.
// note: no need to pool it, because Segment is already pooled.
// -> MTU as initial capacity to avoid most runtime resizing/allocations
internal MemoryStream data = new MemoryStream(Kcp.MTU_DEF);
// ikcp_encode_seg
// encode a segment into buffer
internal int Encode(byte[] ptr, int offset)
{
int offset_ = offset;
offset += Utils.Encode32U(ptr, offset, conv);
offset += Utils.Encode8u(ptr, offset, (byte)cmd);
offset += Utils.Encode8u(ptr, offset, (byte)frg);
offset += Utils.Encode16U(ptr, offset, (ushort)wnd);
offset += Utils.Encode32U(ptr, offset, ts);
offset += Utils.Encode32U(ptr, offset, sn);
offset += Utils.Encode32U(ptr, offset, una);
offset += Utils.Encode32U(ptr, offset, (uint)data.Position);
return offset - offset_;
}
// reset to return a fresh segment to the pool
internal void Reset()
{
conv = 0;
cmd = 0;
frg = 0;
wnd = 0;
ts = 0;
sn = 0;
una = 0;
rto = 0;
xmit = 0;
resendts = 0;
fastack = 0;
// keep buffer for next pool usage, but reset length (= bytes written)
data.SetLength(0);
}
}
}
using System.IO;
namespace kcp2k
{
// KCP Segment Definition
internal class Segment
{
internal uint conv; // conversation
internal uint cmd; // command, e.g. Kcp.CMD_ACK etc.
internal uint frg; // fragment
internal uint wnd; // window size that the receive can currently receive
internal uint ts; // timestamp
internal uint sn; // serial number
internal uint una;
internal uint resendts; // resend timestamp
internal int rto;
internal uint fastack;
internal uint xmit;
// we need an auto scaling byte[] with a WriteBytes function.
// MemoryStream does that perfectly, no need to reinvent the wheel.
// note: no need to pool it, because Segment is already pooled.
// -> MTU as initial capacity to avoid most runtime resizing/allocations
internal MemoryStream data = new MemoryStream(Kcp.MTU_DEF);
// ikcp_encode_seg
// encode a segment into buffer
internal int Encode(byte[] ptr, int offset)
{
int offset_ = offset;
offset += Utils.Encode32U(ptr, offset, conv);
offset += Utils.Encode8u(ptr, offset, (byte)cmd);
offset += Utils.Encode8u(ptr, offset, (byte)frg);
offset += Utils.Encode16U(ptr, offset, (ushort)wnd);
offset += Utils.Encode32U(ptr, offset, ts);
offset += Utils.Encode32U(ptr, offset, sn);
offset += Utils.Encode32U(ptr, offset, una);
offset += Utils.Encode32U(ptr, offset, (uint)data.Position);
return offset - offset_;
}
// reset to return a fresh segment to the pool
internal void Reset()
{
conv = 0;
cmd = 0;
frg = 0;
wnd = 0;
ts = 0;
sn = 0;
una = 0;
rto = 0;
xmit = 0;
resendts = 0;
fastack = 0;
// keep buffer for next pool usage, but reset length (= bytes written)
data.SetLength(0);
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: fc58706a05dd3442c8fde858d5266855
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: fc58706a05dd3442c8fde858d5266855
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,76 +1,76 @@
using System.Runtime.CompilerServices;
namespace kcp2k
{
public static partial class Utils
{
// Clamp so we don't have to depend on UnityEngine
public static int Clamp(int value, int min, int max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
// encode 8 bits unsigned int
public static int Encode8u(byte[] p, int offset, byte c)
{
p[0 + offset] = c;
return 1;
}
// decode 8 bits unsigned int
public static int Decode8u(byte[] p, int offset, ref byte c)
{
c = p[0 + offset];
return 1;
}
// encode 16 bits unsigned int (lsb)
public static int Encode16U(byte[] p, int offset, ushort w)
{
p[0 + offset] = (byte)(w >> 0);
p[1 + offset] = (byte)(w >> 8);
return 2;
}
// decode 16 bits unsigned int (lsb)
public static int Decode16U(byte[] p, int offset, ref ushort c)
{
ushort result = 0;
result |= p[0 + offset];
result |= (ushort)(p[1 + offset] << 8);
c = result;
return 2;
}
// encode 32 bits unsigned int (lsb)
public static int Encode32U(byte[] p, int offset, uint l)
{
p[0 + offset] = (byte)(l >> 0);
p[1 + offset] = (byte)(l >> 8);
p[2 + offset] = (byte)(l >> 16);
p[3 + offset] = (byte)(l >> 24);
return 4;
}
// decode 32 bits unsigned int (lsb)
public static int Decode32U(byte[] p, int offset, ref uint c)
{
uint result = 0;
result |= p[0 + offset];
result |= (uint)(p[1 + offset] << 8);
result |= (uint)(p[2 + offset] << 16);
result |= (uint)(p[3 + offset] << 24);
c = result;
return 4;
}
// timediff was a macro in original Kcp. let's inline it if possible.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int TimeDiff(uint later, uint earlier)
{
return (int)(later - earlier);
}
}
}
using System.Runtime.CompilerServices;
namespace kcp2k
{
public static partial class Utils
{
// Clamp so we don't have to depend on UnityEngine
public static int Clamp(int value, int min, int max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
// encode 8 bits unsigned int
public static int Encode8u(byte[] p, int offset, byte c)
{
p[0 + offset] = c;
return 1;
}
// decode 8 bits unsigned int
public static int Decode8u(byte[] p, int offset, ref byte c)
{
c = p[0 + offset];
return 1;
}
// encode 16 bits unsigned int (lsb)
public static int Encode16U(byte[] p, int offset, ushort w)
{
p[0 + offset] = (byte)(w >> 0);
p[1 + offset] = (byte)(w >> 8);
return 2;
}
// decode 16 bits unsigned int (lsb)
public static int Decode16U(byte[] p, int offset, ref ushort c)
{
ushort result = 0;
result |= p[0 + offset];
result |= (ushort)(p[1 + offset] << 8);
c = result;
return 2;
}
// encode 32 bits unsigned int (lsb)
public static int Encode32U(byte[] p, int offset, uint l)
{
p[0 + offset] = (byte)(l >> 0);
p[1 + offset] = (byte)(l >> 8);
p[2 + offset] = (byte)(l >> 16);
p[3 + offset] = (byte)(l >> 24);
return 4;
}
// decode 32 bits unsigned int (lsb)
public static int Decode32U(byte[] p, int offset, ref uint c)
{
uint result = 0;
result |= p[0 + offset];
result |= (uint)(p[1 + offset] << 8);
result |= (uint)(p[2 + offset] << 16);
result |= (uint)(p[3 + offset] << 24);
c = result;
return 4;
}
// timediff was a macro in original Kcp. let's inline it if possible.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int TimeDiff(uint later, uint earlier)
{
return (int)(later - earlier);
}
}
}

View File

@@ -1,11 +1,11 @@
fileFormatVersion: 2
guid: ef959eb716205bd48b050f010a9a35ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ef959eb716205bd48b050f010a9a35ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,15 +1,15 @@
{
"name": "kcp2k",
"references": [
"GUID:63c380d6dae6946209ed0832388a657c"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
{
"name": "kcp2k",
"references": [
"GUID:63c380d6dae6946209ed0832388a657c"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 6806a62c384838046a3c66c44f06d75f
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 6806a62c384838046a3c66c44f06d75f
AssemblyDefinitionImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: e9de45e025f26411bbb52d1aefc8d5a5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: e9de45e025f26411bbb52d1aefc8d5a5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

Some files were not shown because too many files have changed in this diff Show More