Gonna move to server authority
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
43
Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs
Normal file
43
Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs
Normal 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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 765c88bc8183ed44cb51a5529d8f743e
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -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:
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7482955b4ae93c4081db9ddd04cae47
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09ee288deb259474c819a5e3daf54b80
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 09ee288deb259474c819a5e3daf54b80
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a28193472bc84d341ab4aee18c471a93
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: a28193472bc84d341ab4aee18c471a93
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.4.0b6
|
||||
1.4.0r0 LTS
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad80075449f17c548877161f32a9841a
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: ad80075449f17c548877161f32a9841a
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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.
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a3e8369060cf4e94ac117603de47aa6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 9a3e8369060cf4e94ac117603de47aa6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -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
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed3f2cf1bbf1b4d53a6f2c103d311f71
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: ed3f2cf1bbf1b4d53a6f2c103d311f71
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e852b2532fb248d19715cfebe371db3
|
||||
timeCreated: 1610081248
|
||||
fileFormatVersion: 2
|
||||
guid: 9e852b2532fb248d19715cfebe371db3
|
||||
timeCreated: 1610081248
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6aa069a28ed24fedb533c102d9742b36
|
||||
timeCreated: 1603786960
|
||||
fileFormatVersion: 2
|
||||
guid: 6aa069a28ed24fedb533c102d9742b36
|
||||
timeCreated: 1603786960
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96512e74aa8214a6faa8a412a7a07877
|
||||
timeCreated: 1602601237
|
||||
fileFormatVersion: 2
|
||||
guid: 96512e74aa8214a6faa8a412a7a07877
|
||||
timeCreated: 1602601237
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3915c7c62b72d4dc2a9e4e76c94fc484
|
||||
timeCreated: 1602600432
|
||||
fileFormatVersion: 2
|
||||
guid: 3915c7c62b72d4dc2a9e4e76c94fc484
|
||||
timeCreated: 1602600432
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91b5edac31224a49bd76f960ae018942
|
||||
timeCreated: 1610081248
|
||||
fileFormatVersion: 2
|
||||
guid: 91b5edac31224a49bd76f960ae018942
|
||||
timeCreated: 1610081248
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9759159c6589494a9037f5e130a867ed
|
||||
timeCreated: 1603787747
|
||||
fileFormatVersion: 2
|
||||
guid: 9759159c6589494a9037f5e130a867ed
|
||||
timeCreated: 1603787747
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80a9b1ce9a6f14abeb32bfa9921d097b
|
||||
timeCreated: 1602601483
|
||||
fileFormatVersion: 2
|
||||
guid: 80a9b1ce9a6f14abeb32bfa9921d097b
|
||||
timeCreated: 1602601483
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b320ff06046474eae7bce7240ea478c
|
||||
timeCreated: 1626430641
|
||||
fileFormatVersion: 2
|
||||
guid: 0b320ff06046474eae7bce7240ea478c
|
||||
timeCreated: 1626430641
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c1b235bbe054706bef6d092f361006e
|
||||
timeCreated: 1626430539
|
||||
fileFormatVersion: 2
|
||||
guid: 4c1b235bbe054706bef6d092f361006e
|
||||
timeCreated: 1626430539
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cf0ccf7d551480bb5af08fcbe169f84
|
||||
timeCreated: 1626435264
|
||||
fileFormatVersion: 2
|
||||
guid: 2cf0ccf7d551480bb5af08fcbe169f84
|
||||
timeCreated: 1626435264
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e1b74cc224b4c83a0f6c8d8da9090ab
|
||||
timeCreated: 1626430608
|
||||
fileFormatVersion: 2
|
||||
guid: 4e1b74cc224b4c83a0f6c8d8da9090ab
|
||||
timeCreated: 1626430608
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54b8398dcd544c8a93bcad846214cc40
|
||||
timeCreated: 1626432191
|
||||
fileFormatVersion: 2
|
||||
guid: 54b8398dcd544c8a93bcad846214cc40
|
||||
timeCreated: 1626432191
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("kcp2k.Tests")]
|
||||
@@ -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
@@ -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: ''
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: ''
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6806a62c384838046a3c66c44f06d75f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 6806a62c384838046a3c66c44f06d75f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData: ''
|
||||
assetBundleName: ''
|
||||
assetBundleVariant: ''
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user