From 0a1bfade25c92ce7cde0fb0dfecf9c505e7c18b8 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 28 Nov 2021 16:53:03 +0100 Subject: [PATCH] fix: kcp2k V1.13 - uncorks 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 - fixes #2989 --- .../KCP/MirrorTransport/KcpTransport.cs | 43 ++++++------------- .../Runtime/Transport/KCP/kcp2k/VERSION | 11 ++++- .../KCP/kcp2k/highlevel/KcpClient.cs | 8 ++-- .../KCP/kcp2k/highlevel/KcpConnection.cs | 32 ++++++++------ .../KCP/kcp2k/highlevel/KcpServer.cs | 8 ++-- .../highlevel/NonAlloc/KcpClientNonAlloc.cs | 2 +- .../highlevel/NonAlloc/KcpServerNonAlloc.cs | 2 +- .../Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs | 10 +++-- 8 files changed, 59 insertions(+), 57 deletions(-) diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs index 2b13efc84..378c75c2c 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs @@ -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 @@ -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, @@ -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, @@ -126,18 +133,7 @@ public override void ClientConnect(Uri uri) } public override void ClientSend(ArraySegment 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 @@ -185,18 +181,7 @@ public override Uri ServerUri() public override void ServerStart() => server.Start(Port); public override void ServerSend(int connectionId, ArraySegment 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); @@ -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); } } diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION index b8de4bc66..91c428a5a 100755 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION @@ -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 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs index 64a005a00..05e94070a 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs @@ -8,14 +8,14 @@ public class KcpClient { // events public Action OnConnected; - public Action> OnData; + public Action, KcpChannel> OnData; public Action OnDisconnected; // state public KcpClientConnection connection; public bool connected; - public KcpClient(Action OnConnected, Action> OnData, Action OnDisconnected) + public KcpClient(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected) { this.OnConnected = OnConnected; this.OnData = OnData; @@ -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 = () => { diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs index ecfe56228..55a02955c 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs @@ -17,7 +17,7 @@ public abstract class KcpConnection KcpState state = KcpState.Disconnected; public Action OnAuthenticated; - public Action> OnData; + public Action, KcpChannel> OnData; public Action OnDisconnected; // Mirror needs a way to stop the kcp message processing while loop @@ -43,19 +43,20 @@ 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. @@ -63,7 +64,7 @@ public abstract class KcpConnection // 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; @@ -71,13 +72,13 @@ public abstract class KcpConnection // 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]; @@ -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; @@ -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 @@ -489,7 +495,7 @@ public void RawInput(byte[] buffer, int msgLength) if (!paused) { ArraySegment message = new ArraySegment(buffer, 1, msgLength - 1); - OnData?.Invoke(message); + OnData?.Invoke(message, KcpChannel.Unreliable); } // set last receive time to avoid timeout. @@ -551,7 +557,7 @@ void SendReliable(KcpHeader header, ArraySegment 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 message) diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs index c9847dfce..a648fa89b 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs @@ -11,7 +11,7 @@ public class KcpServer { // events public Action OnConnected; - public Action> OnData; + public Action, KcpChannel> OnData; public Action OnDisconnected; // configuration @@ -57,7 +57,7 @@ public class KcpServer public Dictionary connections = new Dictionary(); public KcpServer(Action OnConnected, - Action> OnData, + Action, KcpChannel> OnData, Action OnDisconnected, bool DualMode, bool NoDelay, @@ -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 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs index acd8e6b11..e39920c44 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs @@ -6,7 +6,7 @@ namespace kcp2k { public class KcpClientNonAlloc : KcpClient { - public KcpClientNonAlloc(Action OnConnected, Action> OnData, Action OnDisconnected) + public KcpClientNonAlloc(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected) : base(OnConnected, OnData, OnDisconnected) { } diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs index ec571b5ff..f1041e854 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs @@ -11,7 +11,7 @@ public class KcpServerNonAlloc : KcpServer { IPEndPointNonAlloc reusableClientEP; - public KcpServerNonAlloc(Action OnConnected, Action> OnData, Action 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 OnConnected, Action, KcpChannel> OnData, Action 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 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs index 253757a4e..874ba8882 100755 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs @@ -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;