Skip to content

Commit

Permalink
feat: new KCP transport (#393)
Browse files Browse the repository at this point in the history
New transport based on KCP on top of UDP.

This transport appears to have big performance improvement over the default TCP transport.

In the mosters sync benchmark these are the results (note units changed):
### TCP
> Time Millisecond Median:27.49 Min:3.69 Max:439.36 Avg:50.71 Std:69.79 SampleCount: 256 Sum: 12980.73
### KCP
> Time Microsecond Median:7699.10 Min:2381.30 Max:25384.80 Avg:8393.94 Std:3804.06 SampleCount: 256 Sum: 2148847.80

It is 600% faster and much smaller jitter in my machine.
  • Loading branch information
paulpach committed Oct 14, 2020
1 parent 9be4f6e commit 5de53e1
Show file tree
Hide file tree
Showing 29 changed files with 2,149 additions and 1 deletion.
8 changes: 8 additions & 0 deletions Assets/Mirror/Runtime/Transport/Kcp.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Assets/Mirror/Runtime/Transport/Kcp/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Mirror.Tests.Runtime")]
11 changes: 11 additions & 0 deletions Assets/Mirror/Runtime/Transport/Kcp/AssemblyInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

134 changes: 134 additions & 0 deletions Assets/Mirror/Runtime/Transport/Kcp/ByteBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;

namespace Mirror.KCP
{
class ByteBuffer : IDisposable
{
int writeIndex;
static readonly List<ByteBuffer> pool = new List<ByteBuffer>();
const int PoolMaxCount = 200;

ByteBuffer(int capacity)
{
RawBuffer = new byte[capacity];
Capacity = capacity;
ReaderIndex = 0;
writeIndex = 0;
}

/// <summary>
/// Construct a capacity length byte buffer ByteBuffer object
/// </summary>
/// <param name="capacity">Initial capacity</param>
/// <param name="fromPool">
/// true means to obtain a pooled ByteBuffer object, the pooled object must be pushed into the pool after calling Dispose, this method is thread-safe.
/// When true, the actual capacity value of the object obtained from the pool
/// </param>
/// <returns>ByteBuffer object</returns>
public static ByteBuffer Allocate(int capacity)
{
ByteBuffer bbuf;
if (pool.Count == 0)
{
bbuf = new ByteBuffer(capacity);
return bbuf;
}
int lastIndex = pool.Count - 1;
bbuf = pool[lastIndex];
pool.RemoveAt(lastIndex);
return bbuf;

}

/// <summary>
/// According to the value, determine the nearest 2nd power greater than this length, such as length=7, the return value is 8; length=12, then 16
/// </summary>
/// <param name="value">Reference capacity</param>
/// <returns>The nearest second power greater than the reference capacity</returns>
int FixLength(int value)
{
if (value == 0)
{
return 1;
}
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}

/// <summary>
/// Determine the size of the internal byte buffer array
/// </summary>
/// <param name="currLen">Current capacity</param>
/// <param name="futureLen">Future capacity</param>
/// <returns>The maximum capacity of the current buffer</returns>
void FixSizeAndReset(int currLen, int futureLen)
{
if (futureLen > currLen)
{
//Determine the size of the internal byte buffer with twice the original size to the power of 2
int size = FixLength(currLen) * 2;
if (futureLen > size)
{
//Determine the size of the internal byte buffer by twice the power of the future size
size = FixLength(futureLen) * 2;
}
byte[] newbuf = new byte[size];
Array.Copy(RawBuffer, 0, newbuf, 0, currLen);
RawBuffer = newbuf;
Capacity = size;
}
}

/// <summary>
/// Write length bytes starting from startIndex of bytes byte array to this buffer
/// </summary>
/// <param name="bytes">Byte data to be written</param>
/// <param name="startIndex">Start position of writing</param>
/// <param name="length">Length written</param>
public void WriteBytes(byte[] bytes, int startIndex, int length)
{
if (length <= 0 || startIndex < 0) return;

int total = length + writeIndex;
int len = RawBuffer.Length;
FixSizeAndReset(len, total);
Array.Copy(bytes, startIndex, RawBuffer, writeIndex, length);
writeIndex = total;
}

public int ReaderIndex { get; private set; }

public int ReadableBytes => writeIndex - ReaderIndex;

public int Capacity { get; private set; }

public byte[] RawBuffer { get; private set; }

public void Clear()
{
ReaderIndex = 0;
writeIndex = 0;
Capacity = RawBuffer.Length;
}

public void Dispose()
{
if (pool.Count < PoolMaxCount)
{
Clear();
pool.Add(this);
return;
}
ReaderIndex = 0;
writeIndex = 0;
Capacity = 0;
RawBuffer = null;
}
}
}
11 changes: 11 additions & 0 deletions Assets/Mirror/Runtime/Transport/Kcp/ByteBuffer.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions Assets/Mirror/Runtime/Transport/Kcp/KCP.asmdef
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "KCP",
"references": [
"GUID:30817c1a0e6d646d99c048fc403f5979",
"GUID:f51ebe6a0ceec4240a699833d6309b23"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
7 changes: 7 additions & 0 deletions Assets/Mirror/Runtime/Transport/Kcp/KCP.asmdef.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5de53e1

Please sign in to comment.