mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
kcp2k V1.0
This commit is contained in:
parent
85be663b13
commit
ceb5dceacd
@ -268,8 +268,8 @@ GameObject:
|
|||||||
- component: {fileID: 1282001518}
|
- component: {fileID: 1282001518}
|
||||||
- component: {fileID: 1282001520}
|
- component: {fileID: 1282001520}
|
||||||
- component: {fileID: 1282001519}
|
- component: {fileID: 1282001519}
|
||||||
- component: {fileID: 1282001521}
|
|
||||||
- component: {fileID: 1282001522}
|
- component: {fileID: 1282001522}
|
||||||
|
- component: {fileID: 1282001521}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: NetworkManager
|
m_Name: NetworkManager
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@ -347,7 +347,7 @@ MonoBehaviour:
|
|||||||
m_GameObject: {fileID: 1282001517}
|
m_GameObject: {fileID: 1282001517}
|
||||||
m_Enabled: 1
|
m_Enabled: 1
|
||||||
m_EditorHideFlags: 0
|
m_EditorHideFlags: 0
|
||||||
m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
|
m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
OnClientConnected:
|
OnClientConnected:
|
||||||
@ -374,12 +374,9 @@ MonoBehaviour:
|
|||||||
OnServerDisconnected:
|
OnServerDisconnected:
|
||||||
m_PersistentCalls:
|
m_PersistentCalls:
|
||||||
m_Calls: []
|
m_Calls: []
|
||||||
port: 7777
|
Port: 7777
|
||||||
NoDelay: 1
|
NoDelay: 1
|
||||||
serverMaxMessageSize: 16384
|
Interval: 10
|
||||||
serverMaxReceivesPerTick: 10000
|
|
||||||
clientMaxMessageSize: 16384
|
|
||||||
clientMaxReceivesPerTick: 1000
|
|
||||||
--- !u!114 &1282001522
|
--- !u!114 &1282001522
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
8
Assets/Mirror/Runtime/Transport/KCP.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8f38345a2b94c4b5ca63a775eaad5584
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
16
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef
Executable file
16
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "KCP",
|
||||||
|
"references": [
|
||||||
|
"GUID:30817c1a0e6d646d99c048fc403f5979",
|
||||||
|
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||||
|
],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": true,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
7
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef.meta
Normal file
7
Assets/Mirror/Runtime/Transport/KCP/KCP.asmdef.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f34a216d4b31d4eb4872b2f30c5a6a11
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7bdb797750d0a490684410110bf48192
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,321 @@
|
|||||||
|
#if MIRROR
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using UnityEngine;
|
||||||
|
using kcp2k;
|
||||||
|
|
||||||
|
namespace Mirror.KCP
|
||||||
|
{
|
||||||
|
public class KcpTransport : Transport
|
||||||
|
{
|
||||||
|
// common
|
||||||
|
[Header("Transport Configuration")]
|
||||||
|
public ushort Port = 7777;
|
||||||
|
[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;
|
||||||
|
readonly byte[] buffer = new byte[Kcp.MTU_DEF];
|
||||||
|
|
||||||
|
// server
|
||||||
|
Socket serverSocket;
|
||||||
|
EndPoint serverNewClientEP = new IPEndPoint(IPAddress.IPv6Any, 0);
|
||||||
|
// connections <connectionId, connection> where connectionId is EndPoint.GetHashCode
|
||||||
|
Dictionary<int, KcpServerConnection> connections = new Dictionary<int, KcpServerConnection>();
|
||||||
|
|
||||||
|
// client
|
||||||
|
KcpClientConnection clientConnection;
|
||||||
|
bool clientConnected;
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
Debug.Log("KcpTransport initialized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// all except WebGL
|
||||||
|
public override bool Available() =>
|
||||||
|
Application.platform != RuntimePlatform.WebGLPlayer;
|
||||||
|
|
||||||
|
// use same Kcp configuration on server and client
|
||||||
|
void ConfigureKcpConnection(KcpConnection connection)
|
||||||
|
{
|
||||||
|
// TODO consider lower interval IF interval matters in nodelay mode
|
||||||
|
|
||||||
|
// we did this in previous test
|
||||||
|
connection.kcp.SetNoDelay(1, 10, 2, true);
|
||||||
|
|
||||||
|
// this works for 4k:
|
||||||
|
//connection.kcp.SetWindowSize(128, 128);
|
||||||
|
// this works for 10k:
|
||||||
|
connection.kcp.SetWindowSize(512, 512);
|
||||||
|
// this works for 20k:
|
||||||
|
//connection.kcp.SetWindowSize(8192, 8192);
|
||||||
|
}
|
||||||
|
|
||||||
|
// client
|
||||||
|
public override bool ClientConnected() => clientConnection != null;
|
||||||
|
public override void ClientConnect(string address)
|
||||||
|
{
|
||||||
|
if (clientConnected)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("KCP: client already connected!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConnection = new KcpClientConnection();
|
||||||
|
// setup events
|
||||||
|
clientConnection.OnConnected += () =>
|
||||||
|
{
|
||||||
|
Debug.Log($"KCP: OnClientConnected");
|
||||||
|
clientConnected = true;
|
||||||
|
OnClientConnected.Invoke();
|
||||||
|
};
|
||||||
|
clientConnection.OnData += (message) =>
|
||||||
|
{
|
||||||
|
//Debug.Log($"KCP: OnClientDataReceived({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
|
||||||
|
OnClientDataReceived.Invoke(message);
|
||||||
|
};
|
||||||
|
clientConnection.OnDisconnected += () =>
|
||||||
|
{
|
||||||
|
Debug.Log($"KCP: OnClientDisconnected");
|
||||||
|
clientConnected = false;
|
||||||
|
OnClientDisconnected.Invoke();
|
||||||
|
};
|
||||||
|
|
||||||
|
// connect
|
||||||
|
clientConnection.Connect(address, Port, NoDelay, Interval);
|
||||||
|
|
||||||
|
// configure connection for max scale
|
||||||
|
ConfigureKcpConnection(clientConnection);
|
||||||
|
}
|
||||||
|
public override bool ClientSend(int channelId, ArraySegment<byte> segment)
|
||||||
|
{
|
||||||
|
if (clientConnection != null)
|
||||||
|
{
|
||||||
|
clientConnection.Send(segment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Debug.LogWarning("KCP: can't send because client not connected!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ClientDisconnect()
|
||||||
|
{
|
||||||
|
// only if connected
|
||||||
|
// otherwise we end up in a deadlock because of an open Mirror bug:
|
||||||
|
// https://github.com/vis2k/Mirror/issues/2353
|
||||||
|
if (clientConnected)
|
||||||
|
{
|
||||||
|
clientConnection?.Disconnect();
|
||||||
|
clientConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<int> connectionsToRemove = new HashSet<int>();
|
||||||
|
void UpdateServer()
|
||||||
|
{
|
||||||
|
while (serverSocket != null && serverSocket.Poll(0, SelectMode.SelectRead))
|
||||||
|
{
|
||||||
|
int msgLength = serverSocket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref serverNewClientEP);
|
||||||
|
//Debug.Log($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
|
||||||
|
|
||||||
|
// calculate connectionId from endpoint
|
||||||
|
int connectionId = serverNewClientEP.GetHashCode();
|
||||||
|
|
||||||
|
// is this a new connection?
|
||||||
|
if (!connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||||
|
{
|
||||||
|
// add it to a queue
|
||||||
|
connection = new KcpServerConnection(serverSocket, serverNewClientEP, NoDelay, Interval);
|
||||||
|
|
||||||
|
// configure connection for max scale
|
||||||
|
ConfigureKcpConnection(connection);
|
||||||
|
|
||||||
|
//acceptedConnections.Writer.TryWrite(connection);
|
||||||
|
connections.Add(connectionId, connection);
|
||||||
|
Debug.Log($"KCP: server added connection {serverNewClientEP}");
|
||||||
|
|
||||||
|
// setup connected event
|
||||||
|
connection.OnConnected += () =>
|
||||||
|
{
|
||||||
|
// call mirror event
|
||||||
|
Debug.Log($"KCP: OnServerConnected({connectionId})");
|
||||||
|
OnServerConnected.Invoke(connectionId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// setup data event
|
||||||
|
connection.OnData += (message) =>
|
||||||
|
{
|
||||||
|
// call mirror event
|
||||||
|
//Debug.Log($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})");
|
||||||
|
OnServerDataReceived.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
|
||||||
|
Debug.Log($"KCP: OnServerDisconnected({connectionId})");
|
||||||
|
OnServerDisconnected.Invoke(connectionId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// send handshake
|
||||||
|
connection.Handshake();
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.RawInput(buffer, msgLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tick all server connections
|
||||||
|
foreach (KcpServerConnection connection in connections.Values)
|
||||||
|
{
|
||||||
|
connection.Tick();
|
||||||
|
connection.Receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateClient()
|
||||||
|
{
|
||||||
|
// tick client connection
|
||||||
|
if (clientConnection != null)
|
||||||
|
{
|
||||||
|
clientConnection.Tick();
|
||||||
|
// recv on socket
|
||||||
|
clientConnection.RawReceive();
|
||||||
|
// recv on kcp
|
||||||
|
clientConnection.Receive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: set script execution order to >1000 to call Transport's
|
||||||
|
// LateUpdate after all others. Fixes race condition where
|
||||||
|
// e.g. in uSurvival Transport would apply Cmds before
|
||||||
|
// ShoulderRotation.LateUpdate, resulting in projectile
|
||||||
|
// spawns at the point before shoulder rotation.
|
||||||
|
public void LateUpdate()
|
||||||
|
{
|
||||||
|
// note: we need to check enabled in case we set it to false
|
||||||
|
// when LateUpdate already started.
|
||||||
|
// (https://github.com/vis2k/Mirror/pull/379)
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdateServer();
|
||||||
|
UpdateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// server
|
||||||
|
public override bool ServerActive() => serverSocket != null;
|
||||||
|
public override void ServerStart()
|
||||||
|
{
|
||||||
|
// only start once
|
||||||
|
if (serverSocket != null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("KCP: server already started!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen
|
||||||
|
serverSocket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
serverSocket.DualMode = true;
|
||||||
|
serverSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, Port));
|
||||||
|
}
|
||||||
|
public override bool ServerSend(int connectionId, int channelId, ArraySegment<byte> segment)
|
||||||
|
{
|
||||||
|
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||||
|
{
|
||||||
|
connection.Send(segment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public override bool ServerDisconnect(int connectionId)
|
||||||
|
{
|
||||||
|
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||||
|
{
|
||||||
|
connection.Disconnect();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public override string ServerGetClientAddress(int connectionId)
|
||||||
|
{
|
||||||
|
if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||||
|
{
|
||||||
|
return (connection.GetRemoteEndPoint() as IPEndPoint).Address.ToString();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
public override void ServerStop()
|
||||||
|
{
|
||||||
|
serverSocket?.Close();
|
||||||
|
serverSocket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// common
|
||||||
|
public override void Shutdown() {}
|
||||||
|
|
||||||
|
// MTU
|
||||||
|
public override ushort GetMaxPacketSize() => Kcp.MTU_DEF;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "KCP";
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetTotalSendQueue() =>
|
||||||
|
connections.Values.Sum(conn => conn.kcp.snd_queue.Count);
|
||||||
|
int GetTotalReceiveQueue() =>
|
||||||
|
connections.Values.Sum(conn => conn.kcp.rcv_queue.Count);
|
||||||
|
int GetTotalSendBuffer() =>
|
||||||
|
connections.Values.Sum(conn => conn.kcp.snd_buf.Count);
|
||||||
|
int GetTotalReceiveBuffer() =>
|
||||||
|
connections.Values.Sum(conn => conn.kcp.rcv_buf.Count);
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
GUILayout.BeginArea(new Rect(5, 100, 300, 300));
|
||||||
|
|
||||||
|
if (ServerActive())
|
||||||
|
{
|
||||||
|
GUILayout.BeginVertical("Box");
|
||||||
|
GUILayout.Label("SERVER");
|
||||||
|
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(" SendQueue: " + clientConnection.kcp.snd_queue.Count);
|
||||||
|
GUILayout.Label(" ReceiveQueue: " + clientConnection.kcp.rcv_queue.Count);
|
||||||
|
GUILayout.Label(" SendBuffer: " + clientConnection.kcp.snd_buf.Count);
|
||||||
|
GUILayout.Label(" ReceiveBuffer: " + clientConnection.kcp.rcv_buf.Count);
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndArea();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b0fecffa3f624585964b0d0eb21b18e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b6fcfb8bb66ef4cac9bb90b51a0e932f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
24
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
Executable file
24
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
Executable file
@ -0,0 +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
|
||||||
|
SOFTWARE.
|
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
Normal file
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9a3e8369060cf4e94ac117603de47aa6
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
6
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
Executable file
6
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
V1.0
|
||||||
|
- Kcp.cs now mirrors original Kcp.c behaviour
|
||||||
|
(this fixes dozens of bugs)
|
||||||
|
|
||||||
|
V0.1
|
||||||
|
- initial kcp-csharp based version
|
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta
Normal file
7
Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ed3f2cf1bbf1b4d53a6f2c103d311f71
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 50a0273b27ec14f789a8e3c676e0bff4
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,73 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace kcp2k
|
||||||
|
{
|
||||||
|
public class KcpClientConnection : KcpConnection
|
||||||
|
{
|
||||||
|
readonly byte[] buffer = new byte[1500];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client connection, does not share the UDP client with anyone
|
||||||
|
/// so we can set up our own read loop
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
public KcpClientConnection() : base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(string host, ushort port, bool noDelay, uint interval = Kcp.INTERVAL)
|
||||||
|
{
|
||||||
|
Debug.Log($"KcpClient: connect to {host}:{port}");
|
||||||
|
IPAddress[] ipAddress = Dns.GetHostAddresses(host);
|
||||||
|
if (ipAddress.Length < 1)
|
||||||
|
throw new SocketException((int)SocketError.HostNotFound);
|
||||||
|
|
||||||
|
remoteEndpoint = new IPEndPoint(ipAddress[0], port);
|
||||||
|
socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
socket.Connect(remoteEndpoint);
|
||||||
|
SetupKcp(noDelay, interval);
|
||||||
|
|
||||||
|
RawReceive();
|
||||||
|
|
||||||
|
Handshake();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO call from transport update
|
||||||
|
public void RawReceive()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (socket != null)
|
||||||
|
{
|
||||||
|
while (socket.Poll(0, SelectMode.SelectRead))
|
||||||
|
{
|
||||||
|
int msgLength = socket.ReceiveFrom(buffer, ref remoteEndpoint);
|
||||||
|
//Debug.Log($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
|
||||||
|
RawInput(buffer, msgLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait a few MS to poll again
|
||||||
|
//await UniTask.Delay(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException)
|
||||||
|
{
|
||||||
|
// this is fine, the socket might have been closed in the other end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose()
|
||||||
|
{
|
||||||
|
socket.Close();
|
||||||
|
socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void RawSend(byte[] data, int length)
|
||||||
|
{
|
||||||
|
socket.Send(data, length, SocketFlags.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 96512e74aa8214a6faa8a412a7a07877
|
||||||
|
timeCreated: 1602601237
|
@ -0,0 +1,282 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
|
using Debug = UnityEngine.Debug;
|
||||||
|
|
||||||
|
namespace kcp2k
|
||||||
|
{
|
||||||
|
public abstract class KcpConnection
|
||||||
|
{
|
||||||
|
protected Socket socket;
|
||||||
|
protected EndPoint remoteEndpoint;
|
||||||
|
internal Kcp kcp;
|
||||||
|
volatile bool open;
|
||||||
|
|
||||||
|
public event Action OnConnected;
|
||||||
|
public event Action<ArraySegment<byte>> OnData;
|
||||||
|
public event Action OnDisconnected;
|
||||||
|
|
||||||
|
// If we don't receive anything these many milliseconds
|
||||||
|
// then consider us disconnected
|
||||||
|
public const int TIMEOUT = 3000;
|
||||||
|
|
||||||
|
// internal time.
|
||||||
|
// StopWatch offers ElapsedMilliSeconds and should be more precise than
|
||||||
|
// Unity's time.deltaTime over long periods.
|
||||||
|
readonly Stopwatch refTime = new Stopwatch();
|
||||||
|
|
||||||
|
// recv buffer to avoid allocations
|
||||||
|
byte[] buffer = new byte[Kcp.MTU_DEF];
|
||||||
|
|
||||||
|
volatile uint lastReceived;
|
||||||
|
|
||||||
|
internal static readonly ArraySegment<byte> Hello = new ArraySegment<byte>(new byte[] { 0 });
|
||||||
|
private static readonly ArraySegment<byte> Goodby = new ArraySegment<byte>(new byte[] { 1 });
|
||||||
|
|
||||||
|
// a connection is authenticated after sending the correct handshake.
|
||||||
|
// useful to protect against random data from the internet.
|
||||||
|
bool authenticated;
|
||||||
|
|
||||||
|
protected KcpConnection()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoDelay & interval are the most important configurations.
|
||||||
|
// let's force require the parameters so we don't forget it anywhere.
|
||||||
|
protected void SetupKcp(bool noDelay, uint interval = Kcp.INTERVAL)
|
||||||
|
{
|
||||||
|
kcp = new Kcp(0, RawSend);
|
||||||
|
kcp.SetNoDelay(noDelay ? 1u : 0u, interval);
|
||||||
|
refTime.Start();
|
||||||
|
open = true;
|
||||||
|
|
||||||
|
Tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uint time = (uint)refTime.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
// TODO MirorrNG KCP used to set lastReceived here. but this
|
||||||
|
// would make the below time check always true.
|
||||||
|
// should we set lastReceived after updating instead?
|
||||||
|
//lastReceived = time;
|
||||||
|
|
||||||
|
if (open && time < lastReceived + TIMEOUT)
|
||||||
|
{
|
||||||
|
kcp.Update(time);
|
||||||
|
|
||||||
|
// check can be used to skip updates IF:
|
||||||
|
// - time < what check returned
|
||||||
|
// - AND send / recv haven't been called in that time
|
||||||
|
// (see Check() comments)
|
||||||
|
//
|
||||||
|
// for now, let's just always update and not call check.
|
||||||
|
//uint check = kcp.Check();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException)
|
||||||
|
{
|
||||||
|
// this is ok, the connection was closed
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// fine, socket was closed, no more ticking needed
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
open = false;
|
||||||
|
Debug.LogException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RawInput(byte[] buffer, int msgLength)
|
||||||
|
{
|
||||||
|
int input = kcp.Input(buffer, msgLength);
|
||||||
|
if (input == 0)
|
||||||
|
{
|
||||||
|
lastReceived = (uint)refTime.ElapsedMilliseconds;
|
||||||
|
}
|
||||||
|
else Debug.LogWarning($"Input failed with error={input} for buffer with length={msgLength}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void RawSend(byte[] data, int length);
|
||||||
|
|
||||||
|
public void Send(ArraySegment<byte> data)
|
||||||
|
{
|
||||||
|
// only allow sending up to MaxMessageSize sized messages.
|
||||||
|
// other end won't process bigger messages anyway.
|
||||||
|
if (data.Count <= Kcp.MTU_DEF)
|
||||||
|
{
|
||||||
|
int sent = kcp.Send(data.Array, data.Offset, data.Count);
|
||||||
|
if (sent < 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Send failed with error={sent} for segment with length={data.Count}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else Debug.LogError($"Failed to send message of size {data.Count} because it's larger than MaxMessageSize={Kcp.MTU_DEF}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArraySegment content comparison without Linq
|
||||||
|
public static unsafe bool SegmentsEqual(ArraySegment<byte> a, ArraySegment<byte> b)
|
||||||
|
{
|
||||||
|
if (a.Count == b.Count)
|
||||||
|
{
|
||||||
|
fixed (byte* aPtr = &a.Array[a.Offset],
|
||||||
|
bPtr = &b.Array[b.Offset])
|
||||||
|
{
|
||||||
|
return UnsafeUtility.MemCmp(aPtr, bPtr, a.Count) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// reads a message from connection
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">buffer where the message will be written</param>
|
||||||
|
/// <returns>true if we got a message, false if we got disconnected</returns>
|
||||||
|
public void Receive()
|
||||||
|
{
|
||||||
|
if (!open)
|
||||||
|
{
|
||||||
|
OnDisconnected?.Invoke();
|
||||||
|
Debug.LogWarning("DISCO a");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read as long as we have things to read
|
||||||
|
int msgSize;
|
||||||
|
while ((msgSize = kcp.PeekSize()) > 0)
|
||||||
|
{
|
||||||
|
// only allow receiving up to MaxMessageSize sized messages.
|
||||||
|
// otherwise we would get BlockCopy ArgumentException anyway.
|
||||||
|
if (msgSize <= Kcp.MTU_DEF)
|
||||||
|
{
|
||||||
|
int received = kcp.Receive(buffer, msgSize);
|
||||||
|
if (received >= 0)
|
||||||
|
{
|
||||||
|
ArraySegment<byte> dataSegment = new ArraySegment<byte>(buffer, 0, msgSize);
|
||||||
|
|
||||||
|
// not authenticated yet?
|
||||||
|
if (!authenticated)
|
||||||
|
{
|
||||||
|
// handshake message?
|
||||||
|
if (SegmentsEqual(dataSegment, Hello))
|
||||||
|
{
|
||||||
|
// we are only connected if we received the handshake.
|
||||||
|
// not just after receiving any first data.
|
||||||
|
authenticated = true;
|
||||||
|
//Debug.Log("KCP: received handshake");
|
||||||
|
OnConnected?.Invoke();
|
||||||
|
}
|
||||||
|
// otherwise it's random data from the internet, not
|
||||||
|
// from a legitimate player.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning("KCP: received random data before handshake. Disconnecting the connection.");
|
||||||
|
open = false;
|
||||||
|
OnDisconnected?.Invoke();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// authenticated.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// disconnect message?
|
||||||
|
if (SegmentsEqual(dataSegment, Goodby))
|
||||||
|
{
|
||||||
|
// if we receive a disconnect message, then close everything
|
||||||
|
//Debug.Log("KCP: received disconnect message");
|
||||||
|
open = false;
|
||||||
|
OnDisconnected?.Invoke();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// otherwise regular message
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// only accept regular messages
|
||||||
|
//Debug.LogWarning($"Kcp recv msg: {BitConverter.ToString(buffer, 0, msgSize)}");
|
||||||
|
OnData?.Invoke(dataSegment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if receive failed, close everything
|
||||||
|
Debug.LogWarning($"Receive failed with error={received}. closing connection.");
|
||||||
|
open = false;
|
||||||
|
OnDisconnected?.Invoke();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we don't allow sending messages > Max, so this must be an
|
||||||
|
// attacker. let's disconnect to avoid allocation attacks etc.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"KCP: possible allocation attack for msgSize {msgSize} > max {Kcp.MTU_DEF}. Disconnecting the connection.");
|
||||||
|
open = false;
|
||||||
|
OnDisconnected?.Invoke();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handshake()
|
||||||
|
{
|
||||||
|
// send a greeting and see if the server replies
|
||||||
|
Debug.LogWarning("KcpConnection: sending Handshake to other end!");
|
||||||
|
Send(Hello);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disconnect this connection
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Disconnect()
|
||||||
|
{
|
||||||
|
// send a disconnect message and disconnect
|
||||||
|
if (open && socket.Connected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Send(Goodby);
|
||||||
|
kcp.Flush();
|
||||||
|
|
||||||
|
// call OnDisconnected event, even if we manually
|
||||||
|
// disconnected
|
||||||
|
OnDisconnected?.Invoke();
|
||||||
|
}
|
||||||
|
catch (SocketException)
|
||||||
|
{
|
||||||
|
// this is ok, the connection was already closed
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// this is normal when we stop the server
|
||||||
|
// the socket is stopped so we can't send anything anymore
|
||||||
|
// to the clients
|
||||||
|
|
||||||
|
// the clients will eventually timeout and realize they
|
||||||
|
// were disconnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open = false;
|
||||||
|
|
||||||
|
// EOF is now available
|
||||||
|
//dataAvailable?.TrySetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get remote endpoint
|
||||||
|
public EndPoint GetRemoteEndPoint() => remoteEndpoint;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3915c7c62b72d4dc2a9e4e76c94fc484
|
||||||
|
timeCreated: 1602600432
|
@ -0,0 +1,20 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
|
||||||
|
namespace kcp2k
|
||||||
|
{
|
||||||
|
public class KcpServerConnection : KcpConnection
|
||||||
|
{
|
||||||
|
public KcpServerConnection(Socket socket, EndPoint remoteEndpoint, bool noDelay, uint interval = Kcp.INTERVAL)
|
||||||
|
{
|
||||||
|
this.socket = socket;
|
||||||
|
this.remoteEndpoint = remoteEndpoint;
|
||||||
|
SetupKcp(noDelay, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void RawSend(byte[] data, int length)
|
||||||
|
{
|
||||||
|
socket.SendTo(data, 0, length, SocketFlags.None, remoteEndpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 80a9b1ce9a6f14abeb32bfa9921d097b
|
||||||
|
timeCreated: 1602601483
|
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta
Normal file
8
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: da30be07fda954844b621fec7c727b6a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("kcp2k.Tests")]
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: aec6a15ac7bd43129317ea1f01f19782
|
||||||
|
timeCreated: 1602665988
|
36
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs
Executable file
36
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
// byte[] buffer with Position, resizes automatically.
|
||||||
|
// There is no size limit because we will only use it with ~MTU sized arrays.
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace kcp2k
|
||||||
|
{
|
||||||
|
public class ByteBuffer
|
||||||
|
{
|
||||||
|
public int Position;
|
||||||
|
internal const int InitialCapacity = 1200;
|
||||||
|
public byte[] RawBuffer = new byte[InitialCapacity];
|
||||||
|
|
||||||
|
// resize to 'value' capacity if needed
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
void EnsureCapacity(int value)
|
||||||
|
{
|
||||||
|
if (RawBuffer.Length < value)
|
||||||
|
{
|
||||||
|
int capacity = Math.Max(value, RawBuffer.Length * 2);
|
||||||
|
Array.Resize(ref RawBuffer, capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write bytes at offset
|
||||||
|
public void WriteBytes(byte[] bytes, int offset, int count)
|
||||||
|
{
|
||||||
|
if (offset >= 0 && count > 0)
|
||||||
|
{
|
||||||
|
EnsureCapacity(Position + count);
|
||||||
|
Buffer.BlockCopy(bytes, offset, RawBuffer, Position, count);
|
||||||
|
Position += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/ByteBuffer.cs.meta
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a9e4197a65c254ec0bf52ae9be1b340b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
1021
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs
Executable file
1021
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs
Executable file
File diff suppressed because it is too large
Load Diff
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a59b1cae10a334faf807432ab472f212
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
84
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs
Executable file
84
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
internal ByteBuffer data;
|
||||||
|
|
||||||
|
// pool ////////////////////////////////////////////////////////////////
|
||||||
|
internal static readonly Stack<Segment> Pool = new Stack<Segment>(32);
|
||||||
|
|
||||||
|
public static Segment Take()
|
||||||
|
{
|
||||||
|
if (Pool.Count > 0)
|
||||||
|
{
|
||||||
|
Segment seg = Pool.Pop();
|
||||||
|
return seg;
|
||||||
|
}
|
||||||
|
return new Segment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Return(Segment seg)
|
||||||
|
{
|
||||||
|
seg.Reset();
|
||||||
|
Pool.Push(seg);
|
||||||
|
}
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
internal Segment()
|
||||||
|
{
|
||||||
|
// allocate the ByteBuffer once.
|
||||||
|
// note that we don't need to pool ByteBuffer, because Segment is
|
||||||
|
// already pooled.
|
||||||
|
data = new ByteBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 position
|
||||||
|
data.Position = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fc58706a05dd3442c8fde858d5266855
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
68
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs
Executable file
68
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace kcp2k
|
||||||
|
{
|
||||||
|
public static class Utils
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta
Executable file
11
Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ef959eb716205bd48b050f010a9a35ae
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user