123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- // kcp client logic abstracted into a class.
- // for use in Mirror, DOTSNET, testing, etc.
- using System;
- namespace kcp2k
- {
- public class KcpClient
- {
- // events
- public Action OnConnected;
- public Action<ArraySegment<byte>, KcpChannel> OnData;
- public Action OnDisconnected;
- // error callback instead of logging.
- // allows libraries to show popups etc.
- // (string instead of Exception for ease of use and to avoid user panic)
- public Action<ErrorCode, string> OnError;
- // state
- public KcpClientConnection connection;
- public bool connected;
- public KcpClient(Action OnConnected,
- Action<ArraySegment<byte>,
- KcpChannel> OnData,
- Action OnDisconnected,
- Action<ErrorCode, string> OnError)
- {
- this.OnConnected = OnConnected;
- this.OnData = OnData;
- this.OnDisconnected = OnDisconnected;
- this.OnError = OnError;
- }
- // CreateConnection can be overwritten for where-allocation:
- // https://github.com/vis2k/where-allocation
- protected virtual KcpClientConnection CreateConnection() =>
- new KcpClientConnection();
- public void Connect(string address,
- ushort port,
- 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,
- uint maxRetransmits = Kcp.DEADLINK,
- bool maximizeSendReceiveBuffersToOSLimit = false)
- {
- if (connected)
- {
- Log.Warning("KCP: client already connected!");
- return;
- }
- // create connection
- connection = CreateConnection();
- // setup events
- connection.OnAuthenticated = () =>
- {
- Log.Info($"KCP: OnClientConnected");
- connected = true;
- OnConnected();
- };
- connection.OnData = (message, channel) =>
- {
- //Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
- OnData(message, channel);
- };
- connection.OnDisconnected = () =>
- {
- Log.Info($"KCP: OnClientDisconnected");
- connected = false;
- connection = null;
- OnDisconnected();
- };
- connection.OnError = (error, reason) =>
- {
- OnError(error, reason);
- };
- // connect
- connection.Connect(address,
- port,
- noDelay,
- interval,
- fastResend,
- congestionWindow,
- sendWindowSize,
- receiveWindowSize,
- timeout,
- maxRetransmits,
- maximizeSendReceiveBuffersToOSLimit);
- }
- public void Send(ArraySegment<byte> segment, KcpChannel channel)
- {
- if (connected)
- {
- connection.SendData(segment, channel);
- }
- else Log.Warning("KCP: can't send because client not connected!");
- }
- public void Disconnect()
- {
- // only if connected
- // otherwise we end up in a deadlock because of an open Mirror bug:
- // https://github.com/vis2k/Mirror/issues/2353
- if (connected)
- {
- // call Disconnect and let the connection handle it.
- // DO NOT set it to null yet. it needs to be updated a few more
- // times first. let the connection handle it!
- connection?.Disconnect();
- }
- }
- // process incoming messages. should be called before updating the world.
- public void TickIncoming()
- {
- // 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)
- connection?.RawReceive();
- connection?.TickIncoming();
- }
- // process outgoing messages. should be called after updating the world.
- public void TickOutgoing()
- {
- // process outgoing
- // (connection is null if not active)
- connection?.TickOutgoing();
- }
- // process incoming and outgoing for convenience
- // => ideally call ProcessIncoming() before updating the world and
- // ProcessOutgoing() after updating the world for minimum latency
- public void Tick()
- {
- TickIncoming();
- TickOutgoing();
- }
- }
- }
|