KcpClient.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // kcp client logic abstracted into a class.
  2. // for use in Mirror, DOTSNET, testing, etc.
  3. using System;
  4. namespace kcp2k
  5. {
  6. public class KcpClient
  7. {
  8. // events
  9. public Action OnConnected;
  10. public Action<ArraySegment<byte>, KcpChannel> OnData;
  11. public Action OnDisconnected;
  12. // error callback instead of logging.
  13. // allows libraries to show popups etc.
  14. // (string instead of Exception for ease of use and to avoid user panic)
  15. public Action<ErrorCode, string> OnError;
  16. // state
  17. public KcpClientConnection connection;
  18. public bool connected;
  19. public KcpClient(Action OnConnected,
  20. Action<ArraySegment<byte>,
  21. KcpChannel> OnData,
  22. Action OnDisconnected,
  23. Action<ErrorCode, string> OnError)
  24. {
  25. this.OnConnected = OnConnected;
  26. this.OnData = OnData;
  27. this.OnDisconnected = OnDisconnected;
  28. this.OnError = OnError;
  29. }
  30. // CreateConnection can be overwritten for where-allocation:
  31. // https://github.com/vis2k/where-allocation
  32. protected virtual KcpClientConnection CreateConnection() =>
  33. new KcpClientConnection();
  34. public void Connect(string address,
  35. ushort port,
  36. bool noDelay,
  37. uint interval,
  38. int fastResend = 0,
  39. bool congestionWindow = true,
  40. uint sendWindowSize = Kcp.WND_SND,
  41. uint receiveWindowSize = Kcp.WND_RCV,
  42. int timeout = KcpConnection.DEFAULT_TIMEOUT,
  43. uint maxRetransmits = Kcp.DEADLINK,
  44. bool maximizeSendReceiveBuffersToOSLimit = false)
  45. {
  46. if (connected)
  47. {
  48. Log.Warning("KCP: client already connected!");
  49. return;
  50. }
  51. // create connection
  52. connection = CreateConnection();
  53. // setup events
  54. connection.OnAuthenticated = () =>
  55. {
  56. Log.Info($"KCP: OnClientConnected");
  57. connected = true;
  58. OnConnected();
  59. };
  60. connection.OnData = (message, channel) =>
  61. {
  62. //Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
  63. OnData(message, channel);
  64. };
  65. connection.OnDisconnected = () =>
  66. {
  67. Log.Info($"KCP: OnClientDisconnected");
  68. connected = false;
  69. connection = null;
  70. OnDisconnected();
  71. };
  72. connection.OnError = (error, reason) =>
  73. {
  74. OnError(error, reason);
  75. };
  76. // connect
  77. connection.Connect(address,
  78. port,
  79. noDelay,
  80. interval,
  81. fastResend,
  82. congestionWindow,
  83. sendWindowSize,
  84. receiveWindowSize,
  85. timeout,
  86. maxRetransmits,
  87. maximizeSendReceiveBuffersToOSLimit);
  88. }
  89. public void Send(ArraySegment<byte> segment, KcpChannel channel)
  90. {
  91. if (connected)
  92. {
  93. connection.SendData(segment, channel);
  94. }
  95. else Log.Warning("KCP: can't send because client not connected!");
  96. }
  97. public void Disconnect()
  98. {
  99. // only if connected
  100. // otherwise we end up in a deadlock because of an open Mirror bug:
  101. // https://github.com/vis2k/Mirror/issues/2353
  102. if (connected)
  103. {
  104. // call Disconnect and let the connection handle it.
  105. // DO NOT set it to null yet. it needs to be updated a few more
  106. // times first. let the connection handle it!
  107. connection?.Disconnect();
  108. }
  109. }
  110. // process incoming messages. should be called before updating the world.
  111. public void TickIncoming()
  112. {
  113. // recv on socket first, then process incoming
  114. // (even if we didn't receive anything. need to tick ping etc.)
  115. // (connection is null if not active)
  116. connection?.RawReceive();
  117. connection?.TickIncoming();
  118. }
  119. // process outgoing messages. should be called after updating the world.
  120. public void TickOutgoing()
  121. {
  122. // process outgoing
  123. // (connection is null if not active)
  124. connection?.TickOutgoing();
  125. }
  126. // process incoming and outgoing for convenience
  127. // => ideally call ProcessIncoming() before updating the world and
  128. // ProcessOutgoing() after updating the world for minimum latency
  129. public void Tick()
  130. {
  131. TickIncoming();
  132. TickOutgoing();
  133. }
  134. }
  135. }