diff --git a/kcp2k/Assets/kcp2k/highlevel/KcpClient.cs b/kcp2k/Assets/kcp2k/highlevel/KcpClient.cs index 9aded53c..5a6a1a20 100644 --- a/kcp2k/Assets/kcp2k/highlevel/KcpClient.cs +++ b/kcp2k/Assets/kcp2k/highlevel/KcpClient.cs @@ -1,6 +1,7 @@ // kcp client logic abstracted into a class. // for use in Mirror, DOTSNET, testing, etc. using System; +using System.Diagnostics; using System.Net; using System.Net.Sockets; @@ -36,6 +37,9 @@ public class KcpClient // state public bool connected; + // time: we need milliseconds. stopwatch is easiest. + readonly Stopwatch watch = new Stopwatch(); + public KcpClient(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, @@ -45,6 +49,7 @@ public KcpClient(Action OnConnected, this.OnData = OnData; this.OnDisconnected = OnDisconnected; this.OnError = OnError; + watch.Start(); } public void Connect(string address, ushort port, KcpConfig config) @@ -189,6 +194,8 @@ public void Disconnect() // process incoming messages. should be called before updating the world. public void TickIncoming() { + uint time = (uint)watch.ElapsedMilliseconds; + // 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) @@ -196,11 +203,11 @@ public void TickIncoming() { while (RawReceive(out ArraySegment segment)) - peer.RawInput(segment); + peer.RawInput(time, segment); } // RawReceive may have disconnected peer. null check again. - peer?.TickIncoming(); + peer?.TickIncoming(time); } // process outgoing messages. should be called after updating the world. @@ -208,7 +215,8 @@ public void TickOutgoing() { // process outgoing // (connection is null if not active) - peer?.TickOutgoing(); + uint time = (uint)watch.ElapsedMilliseconds; + peer?.TickOutgoing(time); } // process incoming and outgoing for convenience diff --git a/kcp2k/Assets/kcp2k/highlevel/KcpPeer.cs b/kcp2k/Assets/kcp2k/highlevel/KcpPeer.cs index ff743cf6..54d09cab 100644 --- a/kcp2k/Assets/kcp2k/highlevel/KcpPeer.cs +++ b/kcp2k/Assets/kcp2k/highlevel/KcpPeer.cs @@ -1,9 +1,10 @@ // Kcp Peer, similar to UDP Peer but wrapped with reliability, channels, // timeouts, authentication, state, etc. // -// still IO agnostic to work with udp, nonalloc, relays, native, etc. +// => IO agnostic to work with udp, nonalloc, relays, native, etc. +// => time as parameter, just like original kcp. +// server doesn't need one stopwatch per connection. using System; -using System.Diagnostics; using System.Net.Sockets; namespace kcp2k @@ -36,11 +37,6 @@ public class KcpPeer public int timeout; uint lastReceiveTime; - // internal time. - // StopWatch offers ElapsedMilliSeconds and should be more precise than - // Unity's time.deltaTime over long periods. - readonly Stopwatch watch = new Stopwatch(); - // we need to subtract the channel byte from every MaxMessageSize // calculation. // we also need to tell kcp to use MTU-1 to leave space for the byte. @@ -166,15 +162,13 @@ public KcpPeer(Action> output, KcpConfig config) kcpSendBuffer = new byte[1 + ReliableMaxMessageSize(config.ReceiveWindowSize)]; timeout = config.Timeout; - - watch.Start(); } - void HandleTimeout(uint time) + void HandleTimeout(uint timeMilliseconds) { // note: we are also sending a ping regularly, so timeout should // only ever happen if the connection is truly gone. - if (time >= lastReceiveTime + timeout) + if (timeMilliseconds >= lastReceiveTime + timeout) { // pass error to user callback. no need to log it manually. // GetType() shows Server/ClientConn instead of just Connection. @@ -196,15 +190,15 @@ void HandleDeadLink() } // send a ping occasionally in order to not time out on the other end. - void HandlePing(uint time) + void HandlePing(uint timeMilliseconds) { // enough time elapsed since last ping? - if (time >= lastPingTime + PING_INTERVAL) + if (timeMilliseconds >= lastPingTime + PING_INTERVAL) { // ping again and reset time //Log.Debug("KCP: sending ping..."); SendPing(); - lastPingTime = time; + lastPingTime = timeMilliseconds; } } @@ -237,7 +231,7 @@ void HandleChoked() // reads the next reliable message type & content from kcp. // -> to avoid buffering, unreliable messages call OnData directly. - bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) + bool ReceiveNextReliable(uint timeMilliseconds, out KcpHeader header, out ArraySegment message) { message = default; header = KcpHeader.Disconnect; @@ -272,20 +266,20 @@ bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) // extract header & content without header header = (KcpHeader)kcpMessageBuffer[0]; message = new ArraySegment(kcpMessageBuffer, 1, msgSize - 1); - lastReceiveTime = (uint)watch.ElapsedMilliseconds; + lastReceiveTime = timeMilliseconds; return true; } - void TickIncoming_Connected(uint time) + void TickIncoming_Connected(uint timeMilliseconds) { // detect common events & ping - HandleTimeout(time); + HandleTimeout(timeMilliseconds); HandleDeadLink(); - HandlePing(time); + HandlePing(timeMilliseconds); HandleChoked(); // any reliable kcp message received? - if (ReceiveNextReliable(out KcpHeader header, out ArraySegment message)) + if (ReceiveNextReliable(timeMilliseconds, out KcpHeader header, out ArraySegment message)) { // message type FSM. no default so we never miss a case. switch (header) @@ -319,16 +313,16 @@ void TickIncoming_Connected(uint time) } } - void TickIncoming_Authenticated(uint time) + void TickIncoming_Authenticated(uint timeMilliseconds) { // detect common events & ping - HandleTimeout(time); + HandleTimeout(timeMilliseconds); HandleDeadLink(); - HandlePing(time); + HandlePing(timeMilliseconds); HandleChoked(); // process all received messages - while (ReceiveNextReliable(out KcpHeader header, out ArraySegment message)) + while (ReceiveNextReliable(timeMilliseconds, out KcpHeader header, out ArraySegment message)) { // message type FSM. no default so we never miss a case. switch (header) @@ -376,22 +370,20 @@ void TickIncoming_Authenticated(uint time) } } - public void TickIncoming() + public void TickIncoming(uint timeMilliseconds) { - uint time = (uint)watch.ElapsedMilliseconds; - try { switch (state) { case KcpState.Connected: { - TickIncoming_Connected(time); + TickIncoming_Connected(timeMilliseconds); break; } case KcpState.Authenticated: { - TickIncoming_Authenticated(time); + TickIncoming_Authenticated(timeMilliseconds); break; } case KcpState.Disconnected: @@ -428,10 +420,8 @@ public void TickIncoming() } } - public void TickOutgoing() + public void TickOutgoing(uint timeMilliseconds) { - uint time = (uint)watch.ElapsedMilliseconds; - try { switch (state) @@ -440,7 +430,7 @@ public void TickOutgoing() case KcpState.Authenticated: { // update flushes out messages - kcp.Update(time); + kcp.Update(timeMilliseconds); break; } case KcpState.Disconnected: @@ -480,7 +470,7 @@ public void TickOutgoing() // insert raw IO. usually from socket.Receive. // offset is useful for relays, where we may parse a header and then // feed the rest to kcp. - public void RawInput(ArraySegment segment) + public void RawInput(uint timeMilliseconds, ArraySegment segment) { // ensure valid size: at least 1 byte for channel if (segment.Count <= 0) return; @@ -537,7 +527,7 @@ public void RawInput(ArraySegment segment) // otherwise a connection might time out even // though unreliable were received, but no // reliable was received. - lastReceiveTime = (uint)watch.ElapsedMilliseconds; + lastReceiveTime = timeMilliseconds; } else { diff --git a/kcp2k/Assets/kcp2k/highlevel/KcpServer.cs b/kcp2k/Assets/kcp2k/highlevel/KcpServer.cs index 3a634696..c2f05e92 100644 --- a/kcp2k/Assets/kcp2k/highlevel/KcpServer.cs +++ b/kcp2k/Assets/kcp2k/highlevel/KcpServer.cs @@ -2,9 +2,10 @@ // for use in Mirror, DOTSNET, testing, etc. using System; using System.Collections.Generic; +using System.Diagnostics; using System.Net; using System.Net.Sockets; -using UnityEngine; +using Debug = UnityEngine.Debug; namespace kcp2k { @@ -36,6 +37,11 @@ public class KcpServer public Dictionary connections = new Dictionary(); + // time: we need milliseconds. stopwatch is easiest. + // one watch per server is enough, we don't need one per connection. + // .ElapsedMilliseconds shows in profiler. don't call it per-connection. + readonly Stopwatch watch = new Stopwatch(); + public KcpServer(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, @@ -48,6 +54,8 @@ public KcpServer(Action OnConnected, this.OnError = OnError; this.config = config; + watch.Start(); + // create newClientEP either IPv4 or IPv6 newClientEP = config.DualMode ? new IPEndPoint(IPAddress.IPv6Any, 0) @@ -205,7 +213,7 @@ protected virtual KcpServerConnection CreateConnection(int connectionId) // receive + add + process once. // best to call this as long as there is more data to receive. - void ProcessMessage(ArraySegment segment, int connectionId) + void ProcessMessage(uint time, ArraySegment segment, int connectionId) { //Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); @@ -288,8 +296,8 @@ void ProcessMessage(ArraySegment segment, int connectionId) // connected event was set up. // tick will process the first message and adds the // connection if it was the handshake. - connection.peer.RawInput(segment); - connection.peer.TickIncoming(); + connection.peer.RawInput(time, segment); + connection.peer.TickIncoming(time); // again, do not add to connections. // if the first message wasn't the kcp handshake then @@ -298,7 +306,7 @@ void ProcessMessage(ArraySegment segment, int connectionId) // existing connection: simply input the message into kcp else { - connection.peer.RawInput(segment); + connection.peer.RawInput(time, segment); } } @@ -307,17 +315,19 @@ void ProcessMessage(ArraySegment segment, int connectionId) readonly HashSet connectionsToRemove = new HashSet(); public virtual void TickIncoming() { + uint time = (uint)watch.ElapsedMilliseconds; + // input all received messages into kcp while (RawReceiveFrom(out ArraySegment segment, out int connectionId)) { - ProcessMessage(segment, connectionId); + ProcessMessage(time, segment, connectionId); } // 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.peer.TickIncoming(); + connection.peer.TickIncoming(time); } // remove disconnected connections @@ -334,10 +344,12 @@ public virtual void TickIncoming() // virtual because relay may need to inject their own ping or similar. public virtual void TickOutgoing() { + uint time = (uint)watch.ElapsedMilliseconds; + // flush all server connections foreach (KcpServerConnection connection in connections.Values) { - connection.peer.TickOutgoing(); + connection.peer.TickOutgoing(time); } }