Skip to content

Commit

Permalink
fix: kcp2k V1.13
Browse files Browse the repository at this point in the history
- uncorks max message size from 144 KB to as much as we want based on
  receive window size.
    fixes MirrorNetworking/kcp2k#22
    fixes skywind3000/kcp#291
- feature: OnData now includes channel it was received on
- fixes #2989
  • Loading branch information
vis2k committed Nov 28, 2021
1 parent 52111d1 commit 0a1bfad
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 57 deletions.
43 changes: 14 additions & 29 deletions Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ public class KcpTransport : Transport
// log statistics for headless servers that can't show them in GUI
public bool statisticsLog;

// translate Kcp <-> Mirror channels
static int KcpToMirrorChannel(KcpChannel channel) =>
channel == KcpChannel.Reliable ? Channels.Reliable : Channels.Unreliable;

static KcpChannel MirrorToKcpChannel(int channel) =>
channel == Channels.Reliable ? KcpChannel.Reliable : KcpChannel.Unreliable;

void Awake()
{
// logging
Expand All @@ -66,18 +73,18 @@ void Awake()
client = NonAlloc
? new KcpClientNonAlloc(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
(message, channel) => OnClientDataReceived.Invoke(message, KcpToMirrorChannel(channel)),
() => OnClientDisconnected.Invoke())
: new KcpClient(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
(message, channel) => OnClientDataReceived.Invoke(message, KcpToMirrorChannel(channel)),
() => OnClientDisconnected.Invoke());

// server
server = NonAlloc
? new KcpServerNonAlloc(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, KcpToMirrorChannel(channel)),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
Expand All @@ -89,7 +96,7 @@ void Awake()
Timeout)
: new KcpServer(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, KcpToMirrorChannel(channel)),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
Expand Down Expand Up @@ -126,18 +133,7 @@ public override void ClientConnect(Uri uri)
}
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;
}
client.Send(segment, MirrorToKcpChannel(channelId));
}
public override void ClientDisconnect() => client.Disconnect();
// process incoming in early update
Expand Down Expand Up @@ -185,18 +181,7 @@ public override Uri ServerUri()
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;
}
server.Send(connectionId, segment, MirrorToKcpChannel(channelId));
}
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
Expand Down Expand Up @@ -227,7 +212,7 @@ public override int GetMaxPacketSize(int channelId = Channels.Reliable)
case Channels.Unreliable:
return KcpConnection.UnreliableMaxMessageSize;
default:
return KcpConnection.ReliableMaxMessageSize;
return KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize);
}
}

Expand Down
11 changes: 10 additions & 1 deletion Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
V1.13 [2021-11-28]
- fix: perf: uncork max message size from 144 KB to as much as we want based on
receive window size.
fixes https://github.com/vis2k/kcp2k/issues/22
fixes https://github.com/skywind3000/kcp/pull/291
- feature: OnData now includes channel it was received on

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.
- fix: where-allocation made optional via virtuals and inheriting
KcpServer/Client/Connection NonAlloc classes. fixes a bug where some platforms
might not support where-allocation.

V1.11 rollback [2021-06-01]
- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ public class KcpClient
{
// events
public Action OnConnected;
public Action<ArraySegment<byte>> OnData;
public Action<ArraySegment<byte>, KcpChannel> OnData;
public Action OnDisconnected;

// state
public KcpClientConnection connection;
public bool connected;

public KcpClient(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
public KcpClient(Action OnConnected, Action<ArraySegment<byte>, KcpChannel> OnData, Action OnDisconnected)
{
this.OnConnected = OnConnected;
this.OnData = OnData;
Expand Down Expand Up @@ -45,10 +45,10 @@ public void Connect(string address, ushort port, bool noDelay, uint interval, in
connected = true;
OnConnected.Invoke();
};
connection.OnData = (message) =>
connection.OnData = (message, channel) =>
{
//Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(message);
OnData.Invoke(message, channel);
};
connection.OnDisconnected = () =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public abstract class KcpConnection
KcpState state = KcpState.Disconnected;

public Action OnAuthenticated;
public Action<ArraySegment<byte>> OnData;
public Action<ArraySegment<byte>, KcpChannel> OnData;
public Action OnDisconnected;

// Mirror needs a way to stop the kcp message processing while loop
Expand All @@ -43,41 +43,42 @@ public abstract class KcpConnection
const int CHANNEL_HEADER_SIZE = 1;

// reliable channel (= kcp) MaxMessageSize so the outside knows largest
// allowed message to send the calculation in Send() is not obvious at
// allowed message to send. the calculation in Send() is not obvious at
// all, so let's provide the helper here.
//
// kcp does fragmentation, so max message is way larger than MTU.
//
// -> runtime MTU changes are disabled: mss is always MTU_DEF-OVERHEAD
// -> Send() checks if fragment count < WND_RCV, so we use WND_RCV - 1.
// note that Send() checks WND_RCV instead of wnd_rcv which may or
// may not be a bug in original kcp. but since it uses the define, we
// can use that here too.
// -> Send() checks if fragment count < rcv_wnd, so we use rcv_wnd - 1.
// NOTE that original kcp has a bug where WND_RCV default is used
// instead of configured rcv_wnd, limiting max message size to 144 KB
// https://github.com/skywind3000/kcp/pull/291
// we fixed this in kcp2k.
// -> we add 1 byte KcpHeader enum to each message, so -1
//
// IMPORTANT: max message is MTU * WND_RCV, in other words it completely
// IMPORTANT: max message is MTU * rcv_wnd, in other words it completely
// fills the receive window! due to head of line blocking,
// all other messages have to wait while a maxed size message
// is being delivered.
// => in other words, DO NOT use max size all the time like
// for batching.
// => sending UNRELIABLE max message size most of the time is
// best for performance (use that one for batching!)
public const int ReliableMaxMessageSize = (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * (Kcp.WND_RCV - 1) - 1;
public static int ReliableMaxMessageSize(uint rcv_wnd) => (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * ((int)rcv_wnd - 1) - 1;

// unreliable max message size is simply MTU - channel header size
public const int UnreliableMaxMessageSize = Kcp.MTU_DEF - CHANNEL_HEADER_SIZE;

// buffer to receive kcp's processed messages (avoids allocations).
// IMPORTANT: this is for KCP messages. so it needs to be of size:
// 1 byte header + MaxMessageSize content
byte[] kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize];
byte[] kcpMessageBuffer;// = new byte[1 + ReliableMaxMessageSize];

// send buffer for handing user messages to kcp for processing.
// (avoids allocations).
// IMPORTANT: needs to be of size:
// 1 byte header + MaxMessageSize content
byte[] kcpSendBuffer = new byte[1 + ReliableMaxMessageSize];
byte[] kcpSendBuffer;// = new byte[1 + ReliableMaxMessageSize];

// raw send buffer is exactly MTU.
byte[] rawSendBuffer = new byte[Kcp.MTU_DEF];
Expand Down Expand Up @@ -143,6 +144,11 @@ protected void SetupKcp(bool noDelay, uint interval = Kcp.INTERVAL, int fastRese
// message afterwards.
kcp.SetMtu(Kcp.MTU_DEF - CHANNEL_HEADER_SIZE);

// create message buffers AFTER window size is set
// see comments on buffer definition for the "+1" part
kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize(receiveWindowSize)];
kcpSendBuffer = new byte[1 + ReliableMaxMessageSize(receiveWindowSize)];

this.timeout = timeout;
state = KcpState.Connected;

Expand Down Expand Up @@ -328,7 +334,7 @@ void TickIncoming_Authenticated(uint time)
if (message.Count > 0)
{
//Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}");
OnData?.Invoke(message);
OnData?.Invoke(message, KcpChannel.Reliable);
}
// empty data = attacker, or something went wrong
else
Expand Down Expand Up @@ -489,7 +495,7 @@ public void RawInput(byte[] buffer, int msgLength)
if (!paused)
{
ArraySegment<byte> message = new ArraySegment<byte>(buffer, 1, msgLength - 1);
OnData?.Invoke(message);
OnData?.Invoke(message, KcpChannel.Unreliable);
}

// set last receive time to avoid timeout.
Expand Down Expand Up @@ -551,7 +557,7 @@ void SendReliable(KcpHeader header, ArraySegment<byte> content)
}
}
// otherwise content is larger than MaxMessageSize. let user know!
else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize}");
else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize(kcp.rcv_wnd)}");
}

void SendUnreliable(ArraySegment<byte> message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class KcpServer
{
// events
public Action<int> OnConnected;
public Action<int, ArraySegment<byte>> OnData;
public Action<int, ArraySegment<byte>, KcpChannel> OnData;
public Action<int> OnDisconnected;

// configuration
Expand Down Expand Up @@ -57,7 +57,7 @@ public class KcpServer
public Dictionary<int, KcpServerConnection> connections = new Dictionary<int, KcpServerConnection>();

public KcpServer(Action<int> OnConnected,
Action<int, ArraySegment<byte>> OnData,
Action<int, ArraySegment<byte>, KcpChannel> OnData,
Action<int> OnDisconnected,
bool DualMode,
bool NoDelay,
Expand Down Expand Up @@ -226,11 +226,11 @@ public void TickIncoming()
// internet.
// setup data event
connection.OnData = (message) =>
connection.OnData = (message, channel) =>
{
// call mirror event
//Log.Info($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(connectionId, message);
OnData.Invoke(connectionId, message, channel);
};
// setup disconnected event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace kcp2k
{
public class KcpClientNonAlloc : KcpClient
{
public KcpClientNonAlloc(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
public KcpClientNonAlloc(Action OnConnected, Action<ArraySegment<byte>, KcpChannel> OnData, Action OnDisconnected)
: base(OnConnected, OnData, OnDisconnected)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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)
public KcpServerNonAlloc(Action<int> OnConnected, Action<int, ArraySegment<byte>, KcpChannel> 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
Expand Down
10 changes: 6 additions & 4 deletions Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,12 @@ public int Send(byte[] buffer, int offset, int len)
if (len <= mss) count = 1;
else count = (int)((len + mss - 1) / mss);

// original kcp uses WND_RCV const even though rcv_wnd is the
// runtime variable. may or may not be correct, see also:
// see also: https://github.com/skywind3000/kcp/pull/291/files
if (count >= WND_RCV) return -2;
// original kcp uses WND_RCV const instead of rcv_wnd runtime:
// https://github.com/skywind3000/kcp/pull/291/files
// which always limits max message size to 144 KB:
//if (count >= WND_RCV) return -2;
// using configured rcv_wnd uncorks max message size to 'any':
if (count >= rcv_wnd) return -2;

if (count == 0) count = 1;

Expand Down

0 comments on commit 0a1bfad

Please sign in to comment.