From a31646a2ec9285cea9b5fdf90870bc57c4ef46a0 Mon Sep 17 00:00:00 2001 From: ALEXTANG <574809918@qq.com> Date: Sat, 22 Apr 2023 00:44:10 +0800 Subject: [PATCH] =?UTF-8?q?[+]=20=E5=9F=BA=E4=BA=8EUniTask=E7=9A=84Kcp?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [+] 基于UniTask的Kcp实现 --- Assets/TEngine/Libraries/System.meta | 8 + .../Libraries/System/System.Buffers.dll | Bin 0 -> 28304 bytes .../Libraries/System/System.Buffers.dll.meta | 33 + .../Libraries/System/System.Buffers.xml | 38 + .../Libraries/System/System.Buffers.xml.meta | 7 + .../Libraries/System/System.Memory.dll | Bin 0 -> 148720 bytes .../Libraries/System/System.Memory.dll.meta | 33 + .../Libraries/System/System.Memory.xml | 355 +++ .../Libraries/System/System.Memory.xml.meta | 7 + ...System.Runtime.CompilerServices.Unsafe.dll | Bin 0 -> 23600 bytes ...m.Runtime.CompilerServices.Unsafe.dll.meta | 33 + ...System.Runtime.CompilerServices.Unsafe.xml | 200 ++ ...m.Runtime.CompilerServices.Unsafe.xml.meta | 7 + .../GameFramework/Network/Kcp/Core.meta | 8 + .../Network/Kcp/Core/FakeKcpIO.cs | 71 + .../Network/Kcp/Core/FakeKcpIO.cs.meta | 11 + .../Network/Kcp/Core/IKcpInterface.cs | 147 ++ .../Network/Kcp/Core/IKcpInterface.cs.meta | 11 + .../Network/Kcp/Core/IKcpSegment.cs | 88 + .../Network/Kcp/Core/IKcpSegment.cs.meta | 11 + .../GameFramework/Network/Kcp/Core/Kcp.cs | 387 +++ .../Network/Kcp/Core/Kcp.cs.meta | 11 + .../GameFramework/Network/Kcp/Core/KcpCore.cs | 2207 +++++++++++++++++ .../Network/Kcp/Core/KcpCore.cs.meta | 11 + .../GameFramework/Network/Kcp/Core/KcpIO.cs | 262 ++ .../Network/Kcp/Core/KcpIO.cs.meta | 11 + .../Network/Kcp/Core/KcpOutputWriter.cs | 50 + .../Network/Kcp/Core/KcpOutputWriter.cs.meta | 11 + .../Network/Kcp/Core/KcpSegment.cs | 402 +++ .../Network/Kcp/Core/KcpSegment.cs.meta | 11 + .../Network/Kcp/Core/KcpTrace.cs | 79 + .../Network/Kcp/Core/KcpTrace.cs.meta | 11 + .../Network/Kcp/Core/SegManager.cs | 265 ++ .../Network/Kcp/Core/SegManager.cs.meta | 11 + .../Network/Kcp/Core/SimpleKcpClient.cs | 65 + .../Network/Kcp/Core/SimpleKcpClient.cs.meta | 11 + .../Network/Kcp/Core/SimpleKcpServer.cs | 48 + .../Network/Kcp/Core/SimpleKcpServer.cs.meta | 3 + .../GameFramework/Network/Kcp/Core/Utility.cs | 73 + .../Network/Kcp/Core/Utility.cs.meta | 11 + 40 files changed, 5008 insertions(+) create mode 100644 Assets/TEngine/Libraries/System.meta create mode 100644 Assets/TEngine/Libraries/System/System.Buffers.dll create mode 100644 Assets/TEngine/Libraries/System/System.Buffers.dll.meta create mode 100644 Assets/TEngine/Libraries/System/System.Buffers.xml create mode 100644 Assets/TEngine/Libraries/System/System.Buffers.xml.meta create mode 100644 Assets/TEngine/Libraries/System/System.Memory.dll create mode 100644 Assets/TEngine/Libraries/System/System.Memory.dll.meta create mode 100644 Assets/TEngine/Libraries/System/System.Memory.xml create mode 100644 Assets/TEngine/Libraries/System/System.Memory.xml.meta create mode 100644 Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.dll create mode 100644 Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.dll.meta create mode 100644 Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml create mode 100644 Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs.meta create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs create mode 100644 Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs.meta diff --git a/Assets/TEngine/Libraries/System.meta b/Assets/TEngine/Libraries/System.meta new file mode 100644 index 00000000..cca3fe87 --- /dev/null +++ b/Assets/TEngine/Libraries/System.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c6cc6a0f7dfa57346baacf8e3397b1bb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Libraries/System/System.Buffers.dll b/Assets/TEngine/Libraries/System/System.Buffers.dll new file mode 100644 index 0000000000000000000000000000000000000000..c517a3b62cc7e190fb347c00a960311a9349b53c GIT binary patch literal 28304 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 literal 0 HcmV?d00001 diff --git a/Assets/TEngine/Libraries/System/System.Buffers.dll.meta b/Assets/TEngine/Libraries/System/System.Buffers.dll.meta new file mode 100644 index 00000000..54a48363 --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Buffers.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 756621d6e10e8484ba8cbdc3baeab00a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Libraries/System/System.Buffers.xml b/Assets/TEngine/Libraries/System/System.Buffers.xml new file mode 100644 index 00000000..e243dcef --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Buffers.xml @@ -0,0 +1,38 @@ + + + System.Buffers + + + +

Provides a resource pool that enables reusing instances of type . + The type of the objects that are in the resource pool. + + + Initializes a new instance of the class. + + + Creates a new instance of the class. + A new instance of the class. + + + Creates a new instance of the class using the specifed configuration. + The maximum length of an array instance that may be stored in the pool. + The maximum number of array instances that may be stored in each bucket in the pool. The pool groups arrays of similar lengths into buckets for faster access. + A new instance of the class with the specified configuration. + + + Retrieves a buffer that is at least the requested length. + The minimum length of the array. + An array of type that is at least minimumLength in length. + + + Returns an array to the pool that was previously obtained using the method on the same instance. + A buffer to return to the pool that was previously obtained using the method. + Indicates whether the contents of the buffer should be cleared before reuse. If clearArray is set to true, and if the pool will store the buffer to enable subsequent reuse, the method will clear the array of its contents so that a subsequent caller using the method will not see the content of the previous caller. If clearArray is set to false or if the pool will release the buffer, the array&#39;s contents are left unchanged. + + + Gets a shared instance. + A shared instance. + + + \ No newline at end of file diff --git a/Assets/TEngine/Libraries/System/System.Buffers.xml.meta b/Assets/TEngine/Libraries/System/System.Buffers.xml.meta new file mode 100644 index 00000000..c06c2e03 --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Buffers.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2287aec89d99c9d47bc787f282793669 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Libraries/System/System.Memory.dll b/Assets/TEngine/Libraries/System/System.Memory.dll new file mode 100644 index 0000000000000000000000000000000000000000..2d54316954f4200997856156289df2e6115d51e0 GIT binary patch literal 148720 zcmdSC37i~PbuU~~dsp`?H9gZki!{}eEm7|3?pazRjTT#y7YXmSJhr<>vNhU_)mSsO zMq;rROKb_o1`;5N311ROATJO?7D8B(KoXu42#b+PAPMm^@FlYihoub^-r70gcuIvj-dsbPSOLk2t$`88~#TKy#2UJDL;XQ|^qV(}QUWsn{ z3!ln7BJuCWPpGKZj@J?>{cAjBkfQL{3%s||3-Hbu^dkRERr$_Uc;BHaDLmfVkMuC@ zQl7aw=?rZn{NhNy(upIlIDzoY4^T&>ab3A;4Io6%myRzTKZGcz4Kyky0?Wt~`P%_1 zOUI8qun0h+m1tH7mmWt5xT`5S`gaZS6@NsVsu;?%g)Zf$XA~vDv5Hbis>(M}<^bw{ zPR6bD7*l;;QJnl$s&)oc>Z*PQ1?c%P^%Mo^$>B%EQ{zy%U!-ab<6l7tGI=FEp(>*& z3>jT*UGgNzPz$}*yXonurf-28fO}YEURTuDX98m}kK6*ef1nEhpePBzhi~asmNSqh zbuj?MQh^E?@w+K$WT{9YK=x8_mwzWRs{XC`T`CDUml8_;U6H7?$T}_n21N)`%ZRcR zY$|e6Kn_#V@#Cn~gZ@6?^A0_BGb$;Hn-Vanw)ybY$AR=gze;7w_|>A6vVig^RPrkX zut7wv5mEaiQJ0CRdm~ZnIO;f39$b=Cy+}k&N21UUK!5xiib6cG3#_9OnWT*(3aq23 zdkDQ`7Fa?|-9!L+H8bm@P0UPwm%2O%evfMPnDwfr`(Q-yFtznZnL~z zw7rCQRrOLvU4#lcI{!{m3UN8FE^bDw)35kj@YFWgo+A>xq zp1PT!s!1y|*^B6GRls?hOW#6Ne0yCGYd?N>4k?M?2`^mkC=(tq0(w|_W{l(wu??Z2C{fJBq2SaD{qEY!PvR|%JLvdg6iJ@oJsW*m7lV97kli?RapRKQZs!7 z>#d$#lgUGgz#h}uUFZXK>%g_$-b9rY%e1rAl%1JKAgyY9|L^3i$*j*k@x&9uoBlWw zC~q-^bY91~^T)r;Q-N%T}R-HF1kekIHF3W`MO^P;HvqTjDD!7QKR{Rx&`yuTe( zv0U5P3Tis3X7aZ8^@+>Sx)MCY$rL7W?^nE4{tYmDL84!=kML{1qMo9r^da`th#?BUBEG zcf3~ANE(G56D;OGm@vo89&~j5hHQ1#%&%87B$JyETu zO)z#B3x#Xt=DQSSYTPw56MCi=BxLQs^GF(MPC{7dag%kH%%_w6$!BlPdpnYXPtW0x z_yj-ZSYECwqiGnx(R~!%*N9dSZJT3=9g9SMhayK1ITDHd1Vs)ZawrmsMzN_;$T~u7 zd-s#T7PbpsK(^nUPFbkSlTdfw1jegl}`!o8DUS zA~Gd2sbCn^gpI~-SaH`(h;+8aUU}+k^&IB$MRZ7oQan}#ss4+BaS=uXXpg^^(oPe% z$l*Dp(qWo+k1U%908F14`=L+jYDF8p(|+_$;RBZk(YGHpAEV#mDsAy5AbAR`9QAJ| zoVVboVECw(iBZn22*IJF^XjPD8;_p39WSC?&<9ck?mivxKw$nTW$3`VQD&FL>Gk6D zswj};;HCBQ(t1%d{*y&c+Xf2OtL=+6M6qv#xsQ{U0}cOe`5pI77Ai+oQ}`*V{&1r_TOjSp3F3RkM9sM$}>k)}As zLDh%OILZF5 zabTZ_{G?`WG_yYHi-K4Pxj3z2YSksor8wNzz}TXF-f2^}w0h&l5a?zOxz6pM7igG< z-$#tgG*F!~L^l?vv8TDgS4}i-%AIneAx#^`&0X>3RtE<9^n6})j)V}7r*(32>K#0P-+3A zDgOpM5D~PC;K}L;Polh7JVJRob73aR1*dR@IqaiG)V|mbrNbGtBa^e$*8J#QRUI=k{>@$`162MiDKpUzE}9xu6$(H!yr@A#U!4Z6954TG3LI&o` zGeMlrPsizmMmOVU-bd)Z)N;Z)Jr%7J@o#;Df0;VH+=e7)u~vMRCT7StKZ-o`_Y^t= z(x;#=5%noKo~I0`TM?kL^wXwECp!ZyJnp)cFmvf}{V`#gqUwvxOB@jfKG1M2S zPXsAT`A_Ooqs)eFVd5{YPn@+KFQh(EdYu=~SDzYmb>xNACxUFti>N-4{inZ(DBc{m zvr+v4w}o7&aeF)15JXK!nQVlirUCb<(I@4<&SgwQbhM=mEnZe8T9y$OhG9hAM!Zlq z8uPp>s@G7Y%uC{9qwQMx$g(B*$O1xqZlzbF25{k(4<6}cUnq|I89wi?|S#^1Q-h6D7*HIoN9|^LpJVyB#>2HvJTv~sl zPD8{|U97*s_2z!!^Y3re%ZpVf)DYI|i}yDe345&=b z2WU-!7FsQP>JPCWW$7$XVfjkammZ-vngK=aY3lR5c)SV^k@loUsT8hF%6Y*!v{sSn zm>*p&)_Jufmj$B#@V=SKU` zm>uI!1%}>LmtF@7G1;T}KSrc(Q6W7)jyHcbT8b7iUXOt0k5LFSI~>w|kAgIgenqi74kDo&h$H&S}Fj6j={^Ni_9+FpoEh6VOWo4us`*7ruWer<7 zOdq9$&@~3H&21W%(E>h<7CFQm!!w;|GxW^t3gq3YtDRcG5@HSdBM`?o8ggEH_n@|{bm0@NSz$7ypx&Vy7j{W@NXe8RH84RV3fV%$y@Hn%q*VkAyP1fqFJYkBZ8FNU{wY9<0Qw z#kUZvSYNIOuV6*76UXwpS+BsZw<1{*HLecEF*&Bb_%@^`E&oGkkyJmL=BFC)&~VNF zX@vb>r}p~M%lMf~DdRU~8Ja@twy^)MhNi;AiEWfjMzj;;PrLL1|Dr_&VL7z1OR#*j8V*a z-dx9-(TbYh5$$wR53{dOf*zvg`oXp zGK^{n41$;2>Nr~J5ku3i9_Z0AD)KWBFa;e2_xN96UDz>G_OnT=U|asrf!Yf8|54+d z{&@r}e*&-mBne2#|6K^|IjpDX`8SH)3z%3Lh2=Z*PJm)Z!n^1}I}&~oFZpGy*t9Ev z!D(|r>q{t}_5E+1$iRfmV5$DMdAy_?c#jSyZ|0aJJ_o(kr* zcp|K^7T*Zf{e-T(8u>0mzRs(sSc@mRB5U#QL6FztA?%H{cxK0`Mj>)7{;%7u#W$cj zuf-Eg+qHP+(kUuVti}H_-oOJx*@?QKABr_e&MO}E#eBISUtiLXqG_ziGcw>I8U7!XDMi}OcAigu)jnGD6n9*&wPm9&;Aa6&wQAG$tT|iy!59P2YBCBXaWTGe9T0L zX+{UEx<5t5Fz$cw%XlyJHq_kjfzQBJtPqPXunY*Yw55;K>6%l+5=5FVP_hn^|3=5b zMFUXPHW}DeO`DLYZp9Jx6ctVl`B6Om4IqJ!Uj%3nf;jy#{0Ja_VMk}LSFJ4Kba*X92K zFcj8{%F?dyz%hkGOBVV>sbW7=S+&qxc`Q23=wh?F}9& z;F)ZXqIvd@2))4bcp5512C0TPb^>A#4be^I{s^)G<3L1^kzf_U^OF=tiS!JO#s8G= zFbYpnatEVukkY6g!X`iq*d}B{n?Ns1f6PE6+ZE8=P5kD)rj(c`6GFATw7sUVB}8Rt zOa6r3g)R9M6+&&YfHE*9MgKn1uZ5eX((F9V)>3f^xwTbYVQZ_VV%w=S6kJFs5pO(7 z;=tw%;&6bg$MSHxUcY8L_Pp!b!REEEy|j7EgG;e_jAh^qvrpPQ&Pz5=lpxadGaSu! zDPr>oq;B&F3E4b?Y;W@jLfSk6TG8eaYO;BZ6*jL0E9A5tzPiohc7PRE3alT5&oPT# zd2@k|NCmYy|I>&{a$tGYJmzw;)%}I*WQkc(Vt2pBwi_D_{Y|_^*S1jFtQ}Cl#qwte zN_d;1$;jsf_PsSOFm0%8P3at3K!+G`x?u}r7SW6eof*sDOtGM!WO=Dep|KJ4yj~B+ zoh{!4qf2>q!z?kcU;~L)=#Vnymg)Z~f*rWUSz*{s|1%84G~nP$+L(C^MY}kfLFWYn z?L6rU1+kkh9x>D$G29$6(i}0`9I>i7Vs$LS7^~29lIi{H{_RJxm*3&V?)<@P&B4! zO3~;MUm?ypU~*&ypSoI#8TrPf^(=NnN;JYZ;@E2IzH6duMc+sOp&=Bsg_e*Rz`l`$ zux<>Sd?V#&-^d)Oc0k1#^w6BL&GEr+=z^60hHoT1ao>1|iiiDI6>1~l8(A-&j}3t@ zq&(ysNtg0_p3~<9{9uN$UJa`A;FR z`K!RH`(ME8(A@SfBB1gaoueED>)y>TiE0=Yd1u}WNPEn`fP5(vTdj9eEw*A?IePLq z_xujT%ec>rxS?p=r$pRvH13Z?+( z;K!ccc|9zl2#h?v>0x`MxdI~sQLF+zA}&?|Tg1gGpi+T?{A4g@XPa@O+l_<4QGzm0 zPk&jo!Y@!76M0nM_7(&n825wnqaok#+bVcd6}LM8<;7~ zTaq&}BzYo3vN>f)Hlz&6CX^xBcp^kLa29O1B)yzaNh3)Cghu96cDbZjp*Cu+kx!7r zszN0{IS4zVE5BdY3!xBkz4#ky1m+#|;>!p$>xD>!h9oL0=msQ^bR-F_h)L%r6ppx< z6c38Hn6!US2wF@lzR*VaJ|N;^Li)IfiwW~fMV!=4TvVZ4L^mMOVFhB+epQ#*pCP`33H{d9wZe*Um=AHFGXrWp(W1cmGEE;2H%q!~1 zh}03L#TNQP?FjtO}(;1?x&98~y3&Hw(4MJiXDfQZw)WZ`Ksi*FCD?a6j94 zJt?ew%3y=hE$quRXc;4v=8Xt6Ni!@5)e-t3t0l}$_lyJBgplEGIM|(J=F>X#ZR?vM ztrng?j;D$iZJ2zK{tB7?Ag4zKQ>5P~)9>ZjZReKq&c}Hyjc7~ z$J2v!LO3X_B;!=Pa8Od=1nqsYJfsQ-C73K?r++r2ZG50X7&)l!nS-o$GCd!%S~wFs z=Ac40%Cwk+vSSV^6dDN`b5P(7`KbSZ-eFDm_XvNI)&*0442^>**~NM# zHD$buh3gPl`OJ{ZLiy7A%uN7xb)kM~9o^5JtbYdF`_@$`+QM@HPh1_aHH9a7p;0|# zxF50{8(aU0eu)x=y7%bYFlvLG%5W%( zQsHr}htzbb@wOqtiQtG7OQHFWi#rvB<~y$4(0oKE8liMj*nw1k%1uf;kYFW{_3M*l zAXV4_*;XQY`ms9I3oLr5j#cV+$W(yLr&T68a zEjSAW41KV0zHuMC8%=`n5O{BM!zZ{YNpR&)9hnSKwa$E23isb5Q8W}MSw($DD$ncl_eF>A%?G=il3 zPvaTyZ+t|i&{$8jE!4Q99}b6h?Y!n&B&IiT8Lc!Pk?FXTtgY9^q^77Nr$_rlIIuz~ zKaAPOdN=qQ8aGPECWWG-7e0<6$dzz;OxW}G z$0HF8=pWRvbq9lXv@@KLong@wlCY<*K2;xJQ-9d*p1J+M-7%A29@L|LSvurq$4r*G z;h4`N)Z&Mo-+*J~8Bg4hClzm*E zgtH|a-FKu)=Me29kS{0SMZTQ8!KX>Vp#;_rYVmmmUd$domef8Qi}#?_BR`x2eZhfQ9P&MLen8&*0gv9`BKGKPTaS zis53`i23}Wgu7qDkx!2I!gfgr8V;kx^5&Thcx!m)&<4bF^~qc`jYM=RoQGXh!#k6K z5ZZ-t?^o)9rg3j)&ph)gir}LTMIAY&qy`gF1Cf*lLbht%LeyX( z!fI`_6VN05|Bk=j38rIN#>IN*bx_qUlL(6*Jobnn(eyD?!eSRqlNMek2Kiizr#Ian zoiPYXSYzG^3~Xz04O{)){|jW@@>ggE(ZlD?2Daf*=fmXR=}$%{@iZ}y%6R<01dyC@ zTV8b-OKneIdmfl95nv0S#5A)kZWqcAi>7xUr8>n!ri7+mPQ2hJ!-6@dESPgb)Ufm= z@a~p4cbA8-mfMQu%h_^8uB3i6T7rrD!SlwTL^kwREArZgky(s(W-oWB;Vqj<7!k_D2{O;%=~id)Qd5n4b*R z%Dgh$ZvR;nkGrd|a1hQCi;7BGp3;nJeT^p0XJ#oK8H#e-rh`CsX8haC=`q1+;xks$ z#r=3{wpKlWYwv8+&^63n&v}~<9~FD)1^_dNwpi^133cpXpF-Qsai{6TKbrX))La+- z=9az+g6Z}wKJTC_uRwdvAP?wt(1zC|Vat8ZZ5Yl@iAo7(4()yV+K;0^F(`RE-bAYZ zbrkUQwXf$0dS{`hX^k65!f&LXoA9GINBEe$b&9h#BVehOGaaHaH~d~O6GK8R7DLi# z!n|zop}hi+pe!EAn!MrtqB0|sFOe{^V@b-s>5f==H#e6@5#@0?Pmv1NNoYya_5R-i z2dzOBaJgbQxsei;5ru8~EUn*Z$~gFq{Xednw6J`cvh}Bj<|<+iuy9ysehke`&Dlrk z7P0`{nxVazrXb$BdX=Au>r;aneWco_+ShAaVZs!9sDd}{lnxO>1bC)sSO$KDU`8mA z(nrQW8PHXZBKOaV+$lXX@pr+7!fpbYGOk?X^} zyi9Avt5B)-Ys8d7D^fiqzs+mJl!#R+7}(Y|VqJLv-Qf|GCHo;WFG79wF*H$c7d-MF zWRlC<-L$;@cTk5dG*YHi*?VmZLu;l4BTZ6Un0HDTIi_z5LnCIwBj*L$!km;aa@5im z2KS3I9ytPP3xg|U8Ai@=w1p`H#uRgVjy|^S0y>iV$o7w+!PtIvX4=mIM zR$L^26*yMhCwb)rIFHkz35)5lZ^d>K7T}A6IK@eh<0$TfV#gDPB2EQqvFWR@Rb)QN ze;JBRUzxG#EAksOU9^qlE;;jF@DyDWl2ak;y*Ra)s)V^MEH6juE!oii=A zX4=P@uoJ3dNmsRI3OEyXOLffD-J0nKTqEqL>X?bfjZJl;EefiIT~{45y{|RX&u}K} z%<7nlW`CPXdI@L3?yZiQSQX+dr!_oltdcaW>e#lMA@_J0*-qwhrM5#qqPg^^5w&vL z{Z?zHU*JqDwcV>)G8K=j)(Q<*ygSa8;%;lClEgjfokg)Y(v&6K`SPdOs(?;(1zQvF zAN~la@@UJvwF>`NvwzZ*XSBM^(^}#R1#Ni(pz*S{ypE!@m>Mi0YGqd5(TZnBE1n|b zS&6&7t$1jer-`0#ajjP1`IbMWOmV@Y<(wuw?`AwJ>Cca~;yKZZ=K$kbNu{o7#Y0m_ zP4uj0JS%D0bFEtTZHkJw*I%%CKE~f! zt*OJ=#C=-OZK4umY3B_m=iX`uj>(Qq^dQny9xdqJgH0_{9ipDn!$12@<^y zJ*sli^m&;cRqbf{Hklr6>}dLsOpgjxH2t4Y6DmL2Gl-^RW{uMu?GR1$ip<(f0m3VTzg z^PDLvU(K2Jwq|-UXKJ)`a~6ybo6AWf${5>r3w5+cwm%0kGa!p-xRomTO_IiVCI1Xj zs&t1a))OL~-$z@nVWcRqR#k(19=B5C<-72xhR=nJUvcPbQwHXd0P` zPY>peJjlwUK6x-Ak8=Te$X6bMnTJF}+*#hght3Mi`%RFP9x`gW+7q*BP1V`*iPrAT z`zb2!zTb{0AX-Taj<+J(--_rafoLVoxV#mS+lpwJl%)q+-;t49{=8LN=zAhfRr_^; zXeHsIZFliT*>bWK(LsS|C4r;;b4`d|)QV_8AX-5Pw{QvTM&m#&{uEaOm?%jmw|B&v+ zh4d?=yFD=v*evZIq+D^ny|We38wH|{d@CM{<0#$}r=+kexDj*+26e{rq9!caIF_Q- z14s8egZUmE|DgNCBm6-7N$&9j{U@EoPdLsR2j!RxjGTKIYwCMZHz|I0;Td^$;cCVj>?_O%ZT@`*kIbKG;j~R#+DY(rZK4+bfCwVW18sDLUw6ChZaoP1sp}$)x+v7dviIx0k;Y$b7<*&;m?$t zGU)K<7on!~`osO(K1w?Ef1PbaCKyFz;vi`%cu{APxb+ASM@y+bQ(xpUluB!y%690A zl%{f4<9q(*9F-@^&1WmTJs_`z7aiinIn9JyVZz8+IBO?Ais_{{0M`!kG5&WDwfJ59 zi0c?R_>Tzkm2m#|@aC8U;`>dS*@xX?41rN@G{Zk5xcGhi;C!F8_%C=#*^B>*N71zx z{|!$cbGK$e&vrkPu@-Zb@a1P5t6*A-9s*y<dZAz7Hq{jHjD7vt zA5uL654M?%ihmb=mIpVH_gG#<5B7%mhN%o;`pfaRf~UpLQXIA*sD3@JP)Bn&qoGuw zVIXpge`lpPAqE`#uaXPA<0LPqLy{^{-vQuMzNSCC@plODH=7&e@H4; z3AX@{0~JEPtkSI>D!WagZXyQP)joUX0J=VO&n_m)=oX(-6c&aDXPP`+Hi0~2N<0wBrvb0PO;Kzi6L*E-a1pGNj!@2$VE@%EMuFv8a zaQu<|xC;;8ujbVGGZc+`i#XZqJA5I3umOuNF=|-qmO!<$HNdVby6gQtplAt8NY1)* z6qk20u#e)-ziS|q1Na;sAk@V!fH==+>z`4yLJEt)e1B#^)$r9mvRe>VrKeBPAje&A z*9vR=n~!ga{ss^`;NMJ* zGIBk>f~RCv-k9%<2)Ytuq8=!#a_bMYv=rG#%-P~ z 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 literal 0 HcmV?d00001 diff --git a/Assets/TEngine/Libraries/System/System.Memory.dll.meta b/Assets/TEngine/Libraries/System/System.Memory.dll.meta new file mode 100644 index 00000000..a004aef8 --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Memory.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 437db656869ad974fb3637ca83f6ecd6 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Libraries/System/System.Memory.xml b/Assets/TEngine/Libraries/System/System.Memory.xml new file mode 100644 index 00000000..4d12fd71 --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Memory.xml @@ -0,0 +1,355 @@ + + + System.Memory + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Assets/TEngine/Libraries/System/System.Memory.xml.meta b/Assets/TEngine/Libraries/System/System.Memory.xml.meta new file mode 100644 index 00000000..5ebf4f07 --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Memory.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d08838ffea10da9408f3fbc06d944a56 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.dll b/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.dll new file mode 100644 index 0000000000000000000000000000000000000000..3156239266db9cceede249731f00c3822e479a1f GIT binary patch literal 23600 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 OyIXQg_#$)@0sTLk9O=pc literal 0 HcmV?d00001 diff --git a/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.dll.meta b/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.dll.meta new file mode 100644 index 00000000..770c2add --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 0f49dd2bd3ef32e4da0cee305406f282 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml b/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml new file mode 100644 index 00000000..6a7cfcff --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml @@ -0,0 +1,200 @@ + + + System.Runtime.CompilerServices.Unsafe + + + + Contains generic, low-level functionality for manipulating pointers. + + + Adds an element offset to the given reference. + The reference to add the offset to. + The offset to add. + The type of reference. + A new reference that reflects the addition of offset to pointer. + + + Adds an element offset to the given reference. + The reference to add the offset to. + The offset to add. + The type of reference. + A new reference that reflects the addition of offset to pointer. + + + Adds a byte offset to the given reference. + The reference to add the offset to. + The offset to add. + The type of reference. + A new reference that reflects the addition of byte offset to pointer. + + + Determines whether the specified references point to the same location. + The first reference to compare. + The second reference to compare. + The type of reference. + true if left and right point to the same location; otherwise, false. + + + Casts the given object to the specified type. + The object to cast. + The type which the object will be cast to. + The original object, casted to the given type. + + + Reinterprets the given reference as a reference to a value of type TTo. + The reference to reinterpret. + The type of reference to reinterpret.. + The desired type of the reference. + A reference to a value of type TTo. + + + Returns a pointer to the given by-ref parameter. + The object whose pointer is obtained. + The type of object. + A pointer to the given value. + + + Reinterprets the given location as a reference to a value of type T. + The location of the value to reference. + The type of the interpreted location. + A reference to a value of type T. + + + Determines the byte offset from origin to target from the given references. + The reference to origin. + The reference to target. + The type of reference. + Byte offset from origin to target i.e. target - origin. + + + Copies a value of type T to the given location. + The location to copy to. + A reference to the value to copy. + The type of value to copy. + + + Copies a value of type T to the given location. + The location to copy to. + A pointer to the value to copy. + The type of value to copy. + + + Copies bytes from the source address to the destination address. + The destination address to copy to. + The source address to copy from. + The number of bytes to copy. + + + Copies bytes from the source address to the destination address. + The destination address to copy to. + The source address to copy from. + The number of bytes to copy. + + + Copies bytes from the source address to the destination address +without assuming architecture dependent alignment of the addresses. + The destination address to copy to. + The source address to copy from. + The number of bytes to copy. + + + Copies bytes from the source address to the destination address +without assuming architecture dependent alignment of the addresses. + The destination address to copy to. + The source address to copy from. + The number of bytes to copy. + + + Initializes a block of memory at the given location with a given initial value. + The address of the start of the memory block to initialize. + The value to initialize the block to. + The number of bytes to initialize. + + + Initializes a block of memory at the given location with a given initial value. + The address of the start of the memory block to initialize. + The value to initialize the block to. + The number of bytes to initialize. + + + Initializes a block of memory at the given location with a given initial value +without assuming architecture dependent alignment of the address. + The address of the start of the memory block to initialize. + The value to initialize the block to. + The number of bytes to initialize. + + + Initializes a block of memory at the given location with a given initial value +without assuming architecture dependent alignment of the address. + The address of the start of the memory block to initialize. + The value to initialize the block to. + The number of bytes to initialize. + + + Reads a value of type T from the given location. + The location to read from. + The type to read. + An object of type T read from the given location. + + + Reads a value of type T from the given location +without assuming architecture dependent alignment of the addresses. + The location to read from. + The type to read. + An object of type T read from the given location. + + + Reads a value of type T from the given location +without assuming architecture dependent alignment of the addresses. + The location to read from. + The type to read. + An object of type T read from the given location. + + + Returns the size of an object of the given type parameter. + The type of object whose size is retrieved. + The size of an object of type T. + + + Subtracts an element offset from the given reference. + The reference to subtract the offset from. + The offset to subtract. + The type of reference. + A new reference that reflects the subraction of offset from pointer. + + + Subtracts an element offset from the given reference. + The reference to subtract the offset from. + The offset to subtract. + The type of reference. + A new reference that reflects the subraction of offset from pointer. + + + Subtracts a byte offset from the given reference. + The reference to subtract the offset from. + + The type of reference. + A new reference that reflects the subraction of byte offset from pointer. + + + Writes a value of type T to the given location. + The location to write to. + The value to write. + The type of value to write. + + + Writes a value of type T to the given location +without assuming architecture dependent alignment of the addresses. + The location to write to. + The value to write. + The type of value to write. + + + Writes a value of type T to the given location +without assuming architecture dependent alignment of the addresses. + The location to write to. + The value to write. + The type of value to write. + + + \ No newline at end of file diff --git a/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml.meta b/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml.meta new file mode 100644 index 00000000..5d6e891b --- /dev/null +++ b/Assets/TEngine/Libraries/System/System.Runtime.CompilerServices.Unsafe.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3b7641a560428bd45aa6bbd10d8bb4ab +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core.meta new file mode 100644 index 00000000..b91893dc --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6dff8f4f5dd94df49bf6d616fb11cc9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs new file mode 100644 index 00000000..200b30e3 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs @@ -0,0 +1,71 @@ +using System.Buffers; +using Cysharp.Threading.Tasks; + +namespace System.Net.Sockets.Kcp +{ + /// + /// 用于调试的KCP IO 类,没有Kcp功能 + /// + public class FakeKcpIO : IKcpIO + { + QueuePipe recv = new QueuePipe(); + public int Input(ReadOnlySpan span) + { + byte[] buffer = new byte[span.Length]; + span.CopyTo(buffer); + recv.Write(buffer); + return 0; + } + + public int Input(ReadOnlySequence span) + { + byte[] buffer = new byte[span.Length]; + span.CopyTo(buffer); + return Input(buffer); + } + + public async UniTask RecvAsync(IBufferWriter writer, object options = null) + { + var buffer = await recv.ReadAsync().ConfigureAwait(false); + var target = writer.GetMemory(buffer.Length); + buffer.AsSpan().CopyTo(target.Span); + writer.Advance(buffer.Length); + } + + public async UniTask RecvAsync(ArraySegment buffer, object options = null) + { + var temp = await recv.ReadAsync().ConfigureAwait(false); + temp.AsSpan().CopyTo(buffer); + return temp.Length; + } + + QueuePipe send = new QueuePipe(); + public int Send(ReadOnlySpan span, object options = null) + { + byte[] buffer = new byte[span.Length]; + span.CopyTo(buffer); + send.Write(buffer); + return 0; + } + + public int Send(ReadOnlySequence span, object options = null) + { + byte[] buffer = new byte[span.Length]; + span.CopyTo(buffer); + return Send(buffer); + } + + public async UniTask OutputAsync(IBufferWriter writer, object options = null) + { + var buffer = await send.ReadAsync().ConfigureAwait(false); + Write(writer, buffer); + } + + private static void Write(IBufferWriter writer, byte[] buffer) + { + var span = writer.GetSpan(buffer.Length); + buffer.AsSpan().CopyTo(span); + writer.Advance(buffer.Length); + } + } +} diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs.meta new file mode 100644 index 00000000..3f00b35d --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/FakeKcpIO.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3c2f9de3c34ffc409549b86ac3fc530 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs new file mode 100644 index 00000000..72bfb079 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs @@ -0,0 +1,147 @@ +using BufferOwner = System.Buffers.IMemoryOwner; +using System.Buffers; +using Cysharp.Threading.Tasks; + +namespace System.Net.Sockets.Kcp +{ + /// + /// Kcp回调 + /// + public interface IKcpCallback + { + /// + /// kcp 发送方向输出 + /// + /// kcp 交出发送缓冲区控制权,缓冲区来自 + /// 数据的有效长度 + /// 不需要返回值 + /// 通过增加 avalidLength 能够在协议栈中有效的减少数据拷贝 + void Output(BufferOwner buffer, int avalidLength); + } + + /// + /// Kcp回调 + /// + /// + /// 失败设计,。IMemoryOwner是没有办法代替的。 + /// 这里只相当于把 IKcpCallback 和 IRentable 和并。 + /// + public interface IKcpOutputWriter : IBufferWriter + { + int UnflushedBytes { get; } + void Flush(); + } + + /// + /// 外部提供缓冲区,可以在外部链接一个内存池 + /// + public interface IRentable + { + /// + /// 外部提供缓冲区,可以在外部链接一个内存池 + /// + BufferOwner RentBuffer(int length); + } + + public interface IKcpSetting + { + int Interval(int interval); + /// + /// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) + /// + /// 0:disable(default), 1:enable + /// internal update timer interval in millisec, default is 100ms + /// 0:disable fast resend(default), 1:enable fast resend + /// 0:normal congestion control(default), 1:disable congestion control + /// + int NoDelay(int nodelay, int interval, int resend, int nc); + /// + /// change MTU size, default is 1400 + /// ** 这个方法不是线程安全的。请在没有发送和接收时调用 。 + /// + /// + /// + /// + /// 如果没有必要,不要修改Mtu。过小的Mtu会导致分片数大于接收窗口,造成kcp阻塞冻结。 + /// + int SetMtu(int mtu = 1400); + /// + /// set maximum window size: sndwnd=32, rcvwnd=128 by default + /// + /// + /// + /// + /// + /// 如果没有必要请不要修改。注意确保接收窗口必须大于最大分片数。 + /// + int WndSize(int sndwnd = 32, int rcvwnd = 128); + } + + public interface IKcpUpdate + { + void Update(in DateTimeOffset time); + } + + public interface IKcpSendable + { + /// + /// 将要发送到网络的数据Send到kcp协议中 + /// + /// + /// + int Send(ReadOnlySpan span, object options = null); + /// + /// 将要发送到网络的数据Send到kcp协议中 + /// + /// + /// + int Send(ReadOnlySequence span, object options = null); + } + + public interface IKcpInputable + { + /// + /// 下层收到数据后添加到kcp协议中 + /// + /// + int Input(ReadOnlySpan span); + /// + /// 下层收到数据后添加到kcp协议中 + /// + /// + int Input(ReadOnlySequence span); + } + + /// + /// kcp协议输入输出标准接口 + /// + public interface IKcpIO : IKcpSendable, IKcpInputable + { + /// + /// 从kcp中取出一个整合完毕的数据包 + /// + /// + UniTask RecvAsync(IBufferWriter writer, object options = null); + + /// + /// 从kcp中取出一个整合完毕的数据包 + /// + /// + /// + /// 接收数据长度 + UniTask RecvAsync(ArraySegment buffer, object options = null); + + /// + /// 从kcp协议中取出需要发送到网络的数据。 + /// + /// + /// + /// + UniTask OutputAsync(IBufferWriter writer, object options = null); + } + +} + + + + diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs.meta new file mode 100644 index 00000000..6fa4a162 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpInterface.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0ca98e42147b3948bc8d30f24e9c366 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs new file mode 100644 index 00000000..8308bf7d --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs @@ -0,0 +1,88 @@ +namespace System.Net.Sockets.Kcp +{ + /// + /// Kcp报头 + /// https://zhuanlan.zhihu.com/p/559191428 + /// + public interface IKcpHeader + { + /// + /// 会话编号,两方一致才会通信 + /// + uint conv { get; set; } + /// + /// 指令类型 + /// + /// + /// IKCP_CMD_PUSH = 81 // cmd: push data 数据报文 + /// IKCP_CMD_ACK = 82 // cmd: ack 确认报文 + /// IKCP_CMD_WASK = 83 // cmd: window probe (ask) 窗口探测报文,询问对端剩余接收窗口的大小. + /// IKCP_CMD_WINS = 84 // cmd: window size (tell) 窗口通知报文,通知对端剩余接收窗口的大小. + /// + byte cmd { get; set; } + /// + /// 剩余分片数量,表示随后还有多少个报文属于同一个包。 + /// + byte frg { get; set; } + /// + /// 自己可用窗口大小 + /// + ushort wnd { get; set; } + /// + /// 发送时的时间戳 + /// + uint ts { get; set; } + /// + /// 编号 确认编号或者报文编号 + /// + uint sn { get; set; } + /// + /// 代表编号前面的所有报都收到了的标志 + /// + uint una { get; set; } + /// + /// 数据内容长度 + /// + uint len { get; } + } + public interface IKcpSegment : IKcpHeader + { + /// + /// 重传的时间戳。超过当前时间重发这个包 + /// + uint resendts { get; set; } + /// + /// 超时重传时间,根据网络去定 + /// + uint rto { get; set; } + /// + /// 快速重传机制,记录被跳过的次数,超过次数进行快速重传 + /// + uint fastack { get; set; } + /// + /// 重传次数 + /// + uint xmit { get; set; } + + /// + /// 数据内容 + /// + Span data { get; } + /// + /// 将IKcpSegment编码成字节数组,并返回总长度(包括Kcp报头) + /// + /// + /// + int Encode(Span buffer); + } + + public interface ISegmentManager where Segment : IKcpSegment + { + Segment Alloc(int appendDateSize); + void Free(Segment seg); + } + +} + + + diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs.meta new file mode 100644 index 00000000..d3c132cd --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/IKcpSegment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a9259bb6b548e46459cc766d8fe2faeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs new file mode 100644 index 00000000..c0f6b43d --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs @@ -0,0 +1,387 @@ +using System.Buffers; +using BufferOwner = System.Buffers.IMemoryOwner; + +namespace System.Net.Sockets.Kcp +{ + public class Kcp : KcpCore + where Segment : IKcpSegment + { + /// + /// create a new kcp control object, 'conv' must equal in two endpoint + /// from the same connection. + /// + /// + /// + /// 可租用内存的回调 + public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null) + : base(conv_) + { + callbackHandle = callback; + this.rentable = rentable; + } + + + //extension 重构和新增加的部分============================================ + + IRentable rentable; + /// + /// 如果外部能够提供缓冲区则使用外部缓冲区,否则new byte[] + /// + /// + /// + internal protected override BufferOwner CreateBuffer(int needSize) + { + var res = rentable?.RentBuffer(needSize); + if (res == null) + { + return base.CreateBuffer(needSize); + } + else + { + if (res.Memory.Length < needSize) + { + throw new ArgumentException($"{nameof(rentable.RentBuffer)} 指定的委托不符合标准,返回的" + + $"BufferOwner.Memory.Length 小于 {nameof(needSize)}"); + } + } + + return res; + } + + /// + /// TryRecv Recv设计上同一时刻只允许一个线程调用。 + /// 因为要保证数据顺序,多个线程同时调用Recv也没有意义。 + /// 所以只需要部分加锁即可。 + /// + /// + public (BufferOwner buffer, int avalidLength) TryRecv() + { + var peekSize = -1; + lock (rcv_queueLock) + { + if (rcv_queue.Count == 0) + { + ///没有可用包 + return (null, -1); + } + + var seq = rcv_queue[0]; + + if (seq.frg == 0) + { + peekSize = (int)seq.len; + } + + if (rcv_queue.Count < seq.frg + 1) + { + ///没有足够的包 + return (null, -1); + } + + uint length = 0; + + foreach (var item in rcv_queue) + { + length += item.len; + if (item.frg == 0) + { + break; + } + } + + peekSize = (int)length; + + if (peekSize <= 0) + { + return (null, -2); + } + } + + var buffer = CreateBuffer(peekSize); + var recvlength = UncheckRecv(buffer.Memory.Span); + return (buffer, recvlength); + } + + /// + /// TryRecv Recv设计上同一时刻只允许一个线程调用。 + /// 因为要保证数据顺序,多个线程同时调用Recv也没有意义。 + /// 所以只需要部分加锁即可。 + /// + /// + /// + public int TryRecv(IBufferWriter writer) + { + var peekSize = -1; + lock (rcv_queueLock) + { + if (rcv_queue.Count == 0) + { + ///没有可用包 + return -1; + } + + var seq = rcv_queue[0]; + + if (seq.frg == 0) + { + peekSize = (int)seq.len; + } + + if (rcv_queue.Count < seq.frg + 1) + { + ///没有足够的包 + return -1; + } + + uint length = 0; + + foreach (var item in rcv_queue) + { + length += item.len; + if (item.frg == 0) + { + break; + } + } + + peekSize = (int)length; + + if (peekSize <= 0) + { + return -2; + } + } + + return UncheckRecv(writer); + } + + /// + /// user/upper level recv: returns size, returns below zero for EAGAIN + /// + /// + /// + public int Recv(Span buffer) + { + if (0 == rcv_queue.Count) + { + return -1; + } + + var peekSize = PeekSize(); + if (peekSize < 0) + { + return -2; + } + + if (peekSize > buffer.Length) + { + return -3; + } + + /// 拆分函数 + var recvLength = UncheckRecv(buffer); + + return recvLength; + } + + /// + /// user/upper level recv: returns size, returns below zero for EAGAIN + /// + /// + /// + public int Recv(IBufferWriter writer) + { + if (0 == rcv_queue.Count) + { + return -1; + } + + var peekSize = PeekSize(); + if (peekSize < 0) + { + return -2; + } + + //if (peekSize > buffer.Length) + //{ + // return -3; + //} + + /// 拆分函数 + var recvLength = UncheckRecv(writer); + + return recvLength; + } + + /// + /// 这个函数不检查任何参数 + /// + /// + /// + int UncheckRecv(Span buffer) + { + var recover = false; + if (rcv_queue.Count >= rcv_wnd) + { + recover = true; + } + + #region merge fragment. + /// merge fragment. + + var recvLength = 0; + lock (rcv_queueLock) + { + var count = 0; + foreach (var seg in rcv_queue) + { + seg.data.CopyTo(buffer.Slice(recvLength)); + recvLength += (int)seg.len; + + count++; + int frg = seg.frg; + + SegmentManager.Free(seg); + if (frg == 0) + { + break; + } + } + + if (count > 0) + { + rcv_queue.RemoveRange(0, count); + } + } + + #endregion + + Move_Rcv_buf_2_Rcv_queue(); + + #region fast recover + /// fast recover + if (rcv_queue.Count < rcv_wnd && recover) + { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + probe |= IKCP_ASK_TELL; + } + #endregion + return recvLength; + } + + /// + /// 这个函数不检查任何参数 + /// + /// + /// + int UncheckRecv(IBufferWriter writer) + { + var recover = false; + if (rcv_queue.Count >= rcv_wnd) + { + recover = true; + } + + #region merge fragment. + /// merge fragment. + + var recvLength = 0; + lock (rcv_queueLock) + { + var count = 0; + foreach (var seg in rcv_queue) + { + var len = (int)seg.len; + var destination = writer.GetSpan(len); + + seg.data.CopyTo(destination); + writer.Advance(len); + + recvLength += len; + + count++; + int frg = seg.frg; + + SegmentManager.Free(seg); + if (frg == 0) + { + break; + } + } + + if (count > 0) + { + rcv_queue.RemoveRange(0, count); + } + } + + #endregion + + Move_Rcv_buf_2_Rcv_queue(); + + #region fast recover + /// fast recover + if (rcv_queue.Count < rcv_wnd && recover) + { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + probe |= IKCP_ASK_TELL; + } + #endregion + return recvLength; + } + + /// + /// check the size of next message in the recv queue + /// + /// + public int PeekSize() + { + lock (rcv_queueLock) + { + if (rcv_queue.Count == 0) + { + ///没有可用包 + return -1; + } + + var seq = rcv_queue[0]; + + if (seq.frg == 0) + { + return (int)seq.len; + } + + if (rcv_queue.Count < seq.frg + 1) + { + ///没有足够的包 + return -1; + } + + uint length = 0; + + foreach (var seg in rcv_queue) + { + length += seg.len; + if (seg.frg == 0) + { + break; + } + } + + return (int)length; + } + } + } +} + + + + + + + + + + diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs.meta new file mode 100644 index 00000000..1dee2c42 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Kcp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3840e90e37f0194bbfafb564ff80d45 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs new file mode 100644 index 00000000..a7680e0f --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs @@ -0,0 +1,2207 @@ +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using static System.Math; +using BufferOwner = System.Buffers.IMemoryOwner; + +namespace System.Net.Sockets.Kcp +{ + public abstract class KcpConst + { + // 为了减少阅读难度,变量名尽量于 C版 统一 + /* + conv 会话ID + mtu 最大传输单元 + mss 最大分片大小 + state 连接状态(0xFFFFFFFF表示断开连接) + snd_una 第一个未确认的包 + snd_nxt 待发送包的序号 + rcv_nxt 待接收消息序号 + ssthresh 拥塞窗口阈值 + rx_rttvar ack接收rtt浮动值 + rx_srtt ack接收rtt静态值 + rx_rto 由ack接收延迟计算出来的复原时间 + rx_minrto 最小复原时间 + snd_wnd 发送窗口大小 + rcv_wnd 接收窗口大小 + rmt_wnd, 远端接收窗口大小 + cwnd, 拥塞窗口大小 + probe 探查变量,IKCP_ASK_TELL表示告知远端窗口大小。IKCP_ASK_SEND表示请求远端告知窗口大小 + interval 内部flush刷新间隔 + ts_flush 下次flush刷新时间戳 + nodelay 是否启动无延迟模式 + updated 是否调用过update函数的标识 + ts_probe, 下次探查窗口的时间戳 + probe_wait 探查窗口需要等待的时间 + dead_link 最大重传次数 + incr 可发送的最大数据量 + fastresend 触发快速重传的重复ack个数 + nocwnd 取消拥塞控制 + stream 是否采用流传输模式 + + snd_queue 发送消息的队列 + rcv_queue 接收消息的队列 + snd_buf 发送消息的缓存 + rcv_buf 接收消息的缓存 + acklist 待发送的ack列表 + buffer 存储消息字节流的内存 + output udp发送消息的回调函数 + */ + + #region Const + + public const int IKCP_RTO_NDL = 30; // no delay min rto + public const int IKCP_RTO_MIN = 100; // normal min rto + public const int IKCP_RTO_DEF = 200; + public const int IKCP_RTO_MAX = 60000; + /// + /// 数据报文 + /// + public const int IKCP_CMD_PUSH = 81; // cmd: push data + /// + /// 确认报文 + /// + public const int IKCP_CMD_ACK = 82; // cmd: ack + /// + /// 窗口探测报文,询问对端剩余接收窗口的大小. + /// + public const int IKCP_CMD_WASK = 83; // cmd: window probe (ask) + /// + /// 窗口通知报文,通知对端剩余接收窗口的大小. + /// + public const int IKCP_CMD_WINS = 84; // cmd: window size (tell) + /// + /// IKCP_ASK_SEND表示请求远端告知窗口大小 + /// + public const int IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK + /// + /// IKCP_ASK_TELL表示告知远端窗口大小。 + /// + public const int IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS + public const int IKCP_WND_SND = 32; + /// + /// 接收窗口默认值。必须大于最大分片数 + /// + public const int IKCP_WND_RCV = 128; // must >= max fragment size + /// + /// 默认最大传输单元 常见路由值 1492 1480 默认1400保证在路由层不会被分片 + /// + public const int IKCP_MTU_DEF = 1400; + public const int IKCP_ACK_FAST = 3; + public const int IKCP_INTERVAL = 100; + public const int IKCP_OVERHEAD = 24; + public const int IKCP_DEADLINK = 20; + public const int IKCP_THRESH_INIT = 2; + public const int IKCP_THRESH_MIN = 2; + /// + /// 窗口探查CD + /// + public const int IKCP_PROBE_INIT = 7000; // 7 secs to probe window size + public const int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window + public const int IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + #endregion + + /// + /// https://github.com/skywind3000/kcp/issues/53 + /// 按照 C版 设计,使用小端字节序 + /// + public static bool IsLittleEndian = true; + } + + /// + /// https://luyuhuang.tech/2020/12/09/kcp.html + /// https://github.com/skywind3000/kcp/wiki/Network-Layer + /// 外部buffer ----拆分拷贝----等待列表 -----移动----发送列表----拷贝----发送buffer---output + /// https://github.com/skywind3000/kcp/issues/118#issuecomment-338133930 + /// + public partial class KcpCore : KcpConst, IKcpSetting, IKcpUpdate, IDisposable + where Segment : IKcpSegment + { + #region kcp members + /// + /// 频道号 + /// + public uint conv { get; protected set; } + /// + /// 最大传输单元(Maximum Transmission Unit,MTU) + /// + protected uint mtu; + + /// + /// 缓冲区最小大小 + /// + protected int BufferNeedSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (int)((mtu/* + IKCP_OVERHEAD*/) /** 3*/); + } + } + + /// + /// 最大报文段长度 + /// + protected uint mss; + /// + /// 连接状态(0xFFFFFFFF表示断开连接) + /// + protected int state; + /// + /// 第一个未确认的包 + /// + protected uint snd_una; + /// + /// 待发送包的序号 + /// + protected uint snd_nxt; + /// + /// 下一个等待接收消息ID,待接收消息序号 + /// + protected uint rcv_nxt; + protected uint ts_recent; + protected uint ts_lastack; + /// + /// 拥塞窗口阈值 + /// + protected uint ssthresh; + /// + /// ack接收rtt浮动值 + /// + protected uint rx_rttval; + /// + /// ack接收rtt静态值 + /// + protected uint rx_srtt; + /// + /// 由ack接收延迟计算出来的复原时间。Retransmission TimeOut(RTO), 超时重传时间. + /// + protected uint rx_rto; + /// + /// 最小复原时间 + /// + protected uint rx_minrto; + /// + /// 发送窗口大小 + /// + protected uint snd_wnd; + /// + /// 接收窗口大小 + /// + protected uint rcv_wnd; + /// + /// 远端接收窗口大小 + /// + protected uint rmt_wnd; + /// + /// 拥塞窗口大小 + /// + protected uint cwnd; + /// + /// 探查变量,IKCP_ASK_TELL表示告知远端窗口大小。IKCP_ASK_SEND表示请求远端告知窗口大小 + /// + protected uint probe; + protected uint current; + /// + /// 内部flush刷新间隔 + /// + protected uint interval; + /// + /// 下次flush刷新时间戳 + /// + protected uint ts_flush; + protected uint xmit; + /// + /// 是否启动无延迟模式 + /// + protected uint nodelay; + /// + /// 是否调用过update函数的标识 + /// + protected uint updated; + /// + /// 下次探查窗口的时间戳 + /// + protected uint ts_probe; + /// + /// 探查窗口需要等待的时间 + /// + protected uint probe_wait; + /// + /// 最大重传次数 + /// + protected uint dead_link; + /// + /// 可发送的最大数据量 + /// + protected uint incr; + /// + /// 触发快速重传的重复ack个数 + /// + public int fastresend; + public int fastlimit; + /// + /// 取消拥塞控制 + /// + protected int nocwnd; + protected int logmask; + /// + /// 是否采用流传输模式 + /// + public int stream; + protected BufferOwner buffer; + + #endregion + + #region 锁和容器 + + /// + /// 增加锁保证发送线程安全,否则可能导致2个消息的分片交替入队。 + /// 用例:普通发送和广播可能会导致多个线程同时调用Send方法。 + /// + protected readonly object snd_queueLock = new object(); + protected readonly object snd_bufLock = new object(); + protected readonly object rcv_bufLock = new object(); + protected readonly object rcv_queueLock = new object(); + + /// + /// 发送 ack 队列 + /// + protected ConcurrentQueue<(uint sn, uint ts)> acklist = new ConcurrentQueue<(uint sn, uint ts)>(); + /// + /// 发送等待队列 + /// + internal ConcurrentQueue snd_queue = new ConcurrentQueue(); + /// + /// 正在发送列表 + /// + internal LinkedList snd_buf = new LinkedList(); + /// + /// 正在等待触发接收回调函数消息列表 + /// 需要执行的操作 添加 遍历 删除 + /// + internal List rcv_queue = new List(); + /// + /// 正在等待重组消息列表 + /// 需要执行的操作 添加 插入 遍历 删除 + /// + internal LinkedList rcv_buf = new LinkedList(); + + /// + /// get how many packet is waiting to be sent + /// + /// + public int WaitSnd => snd_buf.Count + snd_queue.Count; + + #endregion + + public ISegmentManager SegmentManager { get; set; } + public KcpCore(uint conv_) + { + conv = conv_; + + snd_wnd = IKCP_WND_SND; + rcv_wnd = IKCP_WND_RCV; + rmt_wnd = IKCP_WND_RCV; + mtu = IKCP_MTU_DEF; + mss = mtu - IKCP_OVERHEAD; + buffer = CreateBuffer(BufferNeedSize); + + rx_rto = IKCP_RTO_DEF; + rx_minrto = IKCP_RTO_MIN; + interval = IKCP_INTERVAL; + ts_flush = IKCP_INTERVAL; + ssthresh = IKCP_THRESH_INIT; + fastlimit = IKCP_FASTACK_LIMIT; + dead_link = IKCP_DEADLINK; + } + + #region IDisposable Support + private bool disposedValue = false; // 要检测冗余调用 + + /// + /// 是否正在释放 + /// + private bool m_disposing = false; + + protected bool CheckDispose() + { + if (m_disposing) + { + return true; + } + + if (disposedValue) + { + throw new ObjectDisposedException( + $"{nameof(Kcp)} [conv:{conv}]"); + } + + return false; + } + + protected virtual void Dispose(bool disposing) + { + try + { + m_disposing = true; + if (!disposedValue) + { + if (disposing) + { + // 释放托管状态(托管对象)。 + callbackHandle = null; + acklist = null; + buffer = null; + } + + // 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。 + // 将大型字段设置为 null。 + void FreeCollection(IEnumerable collection) + { + if (collection == null) + { + return; + } + foreach (var item in collection) + { + try + { + SegmentManager.Free(item); + } + catch (Exception) + { + //理论上此处不会有任何异常 + LogFail($"此处绝不应该出现异常。 Dispose 时出现预计外异常,联系作者"); + } + } + } + + lock (snd_queueLock) + { + while (snd_queue != null && + (snd_queue.TryDequeue(out var segment) + || !snd_queue.IsEmpty) + ) + { + try + { + SegmentManager.Free(segment); + } + catch (Exception) + { + //理论上这里没有任何异常; + } + } + snd_queue = null; + } + + lock (snd_bufLock) + { + FreeCollection(snd_buf); + snd_buf?.Clear(); + snd_buf = null; + } + + lock (rcv_bufLock) + { + FreeCollection(rcv_buf); + rcv_buf?.Clear(); + rcv_buf = null; + } + + lock (rcv_queueLock) + { + FreeCollection(rcv_queue); + rcv_queue?.Clear(); + rcv_queue = null; + } + + + disposedValue = true; + } + } + finally + { + m_disposing = false; + } + + } + + // 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。 + ~KcpCore() + { + // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。 + Dispose(false); + } + + // 添加此代码以正确实现可处置模式。 + /// + /// 释放不是严格线程安全的,尽量使用和Update相同的线程调用, + /// 或者等待析构时自动释放。 + /// + public void Dispose() + { + // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。 + Dispose(true); + // 如果在以上内容中替代了终结器,则取消注释以下行。 + GC.SuppressFinalize(this); + } + + #endregion + + internal protected IKcpCallback callbackHandle; + internal protected IKcpOutputWriter OutputWriter; + + protected static uint Ibound(uint lower, uint middle, uint upper) + { + return Min(Max(lower, middle), upper); + } + + protected static int Itimediff(uint later, uint earlier) + { + return ((int)(later - earlier)); + } + + internal protected virtual BufferOwner CreateBuffer(int needSize) + { + return new KcpInnerBuffer(needSize); + } + + internal protected class KcpInnerBuffer : BufferOwner + { + private readonly Memory _memory; + + public Memory Memory + { + get + { + if (alreadyDisposed) + { + throw new ObjectDisposedException(nameof(KcpInnerBuffer)); + } + return _memory; + } + } + + public KcpInnerBuffer(int size) + { + _memory = new Memory(new byte[size]); + } + + bool alreadyDisposed = false; + public void Dispose() + { + alreadyDisposed = true; + } + } + + + #region 功能逻辑 + + //功能函数 + + /// + /// Determine when should you invoke ikcp_update: + /// returns when you should invoke ikcp_update in millisec, if there + /// is no ikcp_input/_send calling. you can call ikcp_update in that + /// time, instead of call update repeatly. + /// + /// Important to reduce unnacessary ikcp_update invoking. use it to + /// schedule ikcp_update (eg. implementing an epoll-like mechanism, + /// or optimize ikcp_update when handling massive kcp connections) + /// + /// + /// + /// + public DateTimeOffset Check(in DateTimeOffset time) + { + if (CheckDispose()) + { + //检查释放 + return default; + } + + if (updated == 0) + { + return time; + } + + var current_ = time.ConvertTime(); + + var ts_flush_ = ts_flush; + var tm_flush_ = 0x7fffffff; + var tm_packet = 0x7fffffff; + var minimal = 0; + + if (Itimediff(current_, ts_flush_) >= 10000 || Itimediff(current_, ts_flush_) < -10000) + { + ts_flush_ = current_; + } + + if (Itimediff(current_, ts_flush_) >= 0) + { + return time; + } + + tm_flush_ = Itimediff(ts_flush_, current_); + + lock (snd_bufLock) + { + foreach (var seg in snd_buf) + { + var diff = Itimediff(seg.resendts, current_); + if (diff <= 0) + { + return time; + } + + if (diff < tm_packet) + { + tm_packet = diff; + } + } + } + + minimal = tm_packet < tm_flush_ ? tm_packet : tm_flush_; + if (minimal >= interval) minimal = (int)interval; + + return time + TimeSpan.FromMilliseconds(minimal); + } + + /// + /// move available data from rcv_buf -> rcv_queue + /// + protected void Move_Rcv_buf_2_Rcv_queue() + { + lock (rcv_bufLock) + { + while (rcv_buf.Count > 0) + { + var seg = rcv_buf.First.Value; + if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) + { + rcv_buf.RemoveFirst(); + lock (rcv_queueLock) + { + rcv_queue.Add(seg); + } + + rcv_nxt++; + } + else + { + break; + } + } + } + } + + /// + /// update ack. + /// + /// + protected void Update_ack(int rtt) + { + if (rx_srtt == 0) + { + rx_srtt = (uint)rtt; + rx_rttval = (uint)rtt / 2; + } + else + { + int delta = (int)((uint)rtt - rx_srtt); + + if (delta < 0) + { + delta = -delta; + } + + rx_rttval = (3 * rx_rttval + (uint)delta) / 4; + rx_srtt = (uint)((7 * rx_srtt + rtt) / 8); + + if (rx_srtt < 1) + { + rx_srtt = 1; + } + } + + var rto = rx_srtt + Max(interval, 4 * rx_rttval); + + rx_rto = Ibound(rx_minrto, rto, IKCP_RTO_MAX); + } + + protected void Shrink_buf() + { + lock (snd_bufLock) + { + snd_una = snd_buf.Count > 0 ? snd_buf.First.Value.sn : snd_nxt; + } + } + + protected void Parse_ack(uint sn) + { + if (Itimediff(sn, snd_una) < 0 || Itimediff(sn, snd_nxt) >= 0) + { + return; + } + + lock (snd_bufLock) + { + for (var p = snd_buf.First; p != null; p = p.Next) + { + var seg = p.Value; + if (sn == seg.sn) + { + snd_buf.Remove(p); + SegmentManager.Free(seg); + break; + } + + if (Itimediff(sn, seg.sn) < 0) + { + break; + } + } + } + } + + protected void Parse_una(uint una) + { + /// 删除给定时间之前的片段。保留之后的片段 + lock (snd_bufLock) + { + while (snd_buf.First != null) + { + var seg = snd_buf.First.Value; + if (Itimediff(una, seg.sn) > 0) + { + snd_buf.RemoveFirst(); + SegmentManager.Free(seg); + } + else + { + break; + } + } + } + + } + + protected void Parse_fastack(uint sn, uint ts) + { + if (Itimediff(sn, snd_una) < 0 || Itimediff(sn, snd_nxt) >= 0) + { + return; + } + + lock (snd_bufLock) + { + foreach (var item in snd_buf) + { + var seg = item; + if (Itimediff(sn, seg.sn) < 0) + { + break; + } + else if (sn != seg.sn) + { +#if !IKCP_FASTACK_CONSERVE + seg.fastack++; +#else + if (Itimediff(ts, seg.ts) >= 0) + { + seg.fastack++; + } +#endif + } + } + } + } + + /// + /// 处理下层网络收到的数据包 + /// + /// + internal virtual void Parse_data(Segment newseg) + { + var sn = newseg.sn; + + lock (rcv_bufLock) + { + if (Itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || Itimediff(sn, rcv_nxt) < 0) + { + // 如果接收到数据报文的编号大于 rcv_nxt + rcv_wnd 或小于 rcv_nxt, 这个报文就会被丢弃. + SegmentManager.Free(newseg); + return; + } + + var repeat = false; + + ///检查是否重复消息和插入位置 + LinkedListNode p; + for (p = rcv_buf.Last; p != null; p = p.Previous) + { + var seg = p.Value; + if (seg.sn == sn) + { + repeat = true; + break; + } + + if (Itimediff(sn, seg.sn) > 0) + { + break; + } + } + + if (!repeat) + { + if (CanLog(KcpLogMask.IKCP_LOG_PARSE_DATA)) + { + LogWriteLine($"{newseg.ToLogString()}", KcpLogMask.IKCP_LOG_PARSE_DATA.ToString()); + } + + if (p == null) + { + rcv_buf.AddFirst(newseg); + if (newseg.frg + 1 > rcv_wnd) + { + //分片数大于接收窗口,造成kcp阻塞冻结。 + //Console.WriteLine($"分片数大于接收窗口,造成kcp阻塞冻结。frgCount:{newseg.frg + 1} rcv_wnd:{rcv_wnd}"); + //百分之百阻塞冻结,打印日志没有必要。直接抛出异常。 + throw new NotSupportedException($"分片数大于接收窗口,造成kcp阻塞冻结。frgCount:{newseg.frg + 1} rcv_wnd:{rcv_wnd}"); + } + } + else + { + rcv_buf.AddAfter(p, newseg); + } + + } + else + { + SegmentManager.Free(newseg); + } + } + + Move_Rcv_buf_2_Rcv_queue(); + } + + protected ushort Wnd_unused() + { + ///此处没有加锁,所以不要内联变量,否则可能导致 判断变量和赋值变量不一致 + int waitCount = rcv_queue.Count; + + if (waitCount < rcv_wnd) + { + //Q:为什么要减去nrcv_que,rcv_queue中已经排好序了,还要算在接收窗口内,感觉不能理解? + //现在问题是如果一个超大数据包,分片数大于rcv_wnd接收窗口,会导致rcv_wnd持续为0,阻塞整个流程。 + //个人理解,rcv_queue中的数据是已经确认的数据,无论用户是否recv,都不应该影响收发。 + //A:现在在发送出加一个分片数检测,过大直接抛出异常。防止阻塞发送。 + //在接收端也加一个检测,如果(frg+1)分片数 > rcv_wnd,也要抛出一个异常或者警告。至少有个提示。 + + /// fix https://github.com/skywind3000/kcp/issues/126 + /// 实际上 rcv_wnd 不应该大于65535 + var count = rcv_wnd - waitCount; + return (ushort)Min(count, ushort.MaxValue); + } + + return 0; + } + + /// + /// flush pending data + /// + protected void Flush() + { + var current_ = current; + var buffer_ = buffer; + var change = 0; + var lost = 0; + var offset = 0; + + if (updated == 0) + { + return; + } + + ushort wnd_ = Wnd_unused(); + + unsafe + { + ///在栈上分配这个segment,这个segment随用随销毁,不会被保存 + const int len = KcpSegment.LocalOffset + KcpSegment.HeadOffset; + var ptr = stackalloc byte[len]; + KcpSegment seg = new KcpSegment(ptr, 0); + //seg = KcpSegment.AllocHGlobal(0); + + seg.conv = conv; + seg.cmd = IKCP_CMD_ACK; + //seg.frg = 0; + seg.wnd = wnd_; + seg.una = rcv_nxt; + //seg.len = 0; + //seg.sn = 0; + //seg.ts = 0; + + #region flush acknowledges + + if (CheckDispose()) + { + //检查释放 + return; + } + + while (acklist.TryDequeue(out var temp)) + { + if (offset + IKCP_OVERHEAD > mtu) + { + callbackHandle.Output(buffer, offset); + offset = 0; + buffer = CreateBuffer(BufferNeedSize); + + //IKcpOutputer outputer = null; + //var span = outputer.GetSpan(offset); + //buffer.Memory.Span.Slice(0, offset).CopyTo(span); + //outputer.Advance(offset); + //outputer.Flush(); + } + + seg.sn = temp.sn; + seg.ts = temp.ts; + offset += seg.Encode(buffer.Memory.Span.Slice(offset)); + } + + #endregion + + #region probe window size (if remote window size equals zero) + // probe window size (if remote window size equals zero) + if (rmt_wnd == 0) + { + if (probe_wait == 0) + { + probe_wait = IKCP_PROBE_INIT; + ts_probe = current + probe_wait; + } + else + { + if (Itimediff(current, ts_probe) >= 0) + { + if (probe_wait < IKCP_PROBE_INIT) + { + probe_wait = IKCP_PROBE_INIT; + } + + probe_wait += probe_wait / 2; + + if (probe_wait > IKCP_PROBE_LIMIT) + { + probe_wait = IKCP_PROBE_LIMIT; + } + + ts_probe = current + probe_wait; + probe |= IKCP_ASK_SEND; + } + } + } + else + { + ts_probe = 0; + probe_wait = 0; + } + #endregion + + #region flush window probing commands + // flush window probing commands + if ((probe & IKCP_ASK_SEND) != 0) + { + seg.cmd = IKCP_CMD_WASK; + if (offset + IKCP_OVERHEAD > (int)mtu) + { + callbackHandle.Output(buffer, offset); + offset = 0; + buffer = CreateBuffer(BufferNeedSize); + } + offset += seg.Encode(buffer.Memory.Span.Slice(offset)); + } + + if ((probe & IKCP_ASK_TELL) != 0) + { + seg.cmd = IKCP_CMD_WINS; + if (offset + IKCP_OVERHEAD > (int)mtu) + { + callbackHandle.Output(buffer, offset); + offset = 0; + buffer = CreateBuffer(BufferNeedSize); + } + offset += seg.Encode(buffer.Memory.Span.Slice(offset)); + } + + probe = 0; + #endregion + } + + #region 刷新,将发送等待列表移动到发送列表 + + // calculate window size + var cwnd_ = Min(snd_wnd, rmt_wnd); + if (nocwnd == 0) + { + cwnd_ = Min(cwnd, cwnd_); + } + + while (Itimediff(snd_nxt, snd_una + cwnd_) < 0) + { + if (snd_queue.TryDequeue(out var newseg)) + { + newseg.conv = conv; + newseg.cmd = IKCP_CMD_PUSH; + newseg.wnd = wnd_; + newseg.ts = current_; + newseg.sn = snd_nxt; + snd_nxt++; + newseg.una = rcv_nxt; + newseg.resendts = current_; + newseg.rto = rx_rto; + newseg.fastack = 0; + newseg.xmit = 0; + lock (snd_bufLock) + { + snd_buf.AddLast(newseg); + } + } + else + { + break; + } + } + + #endregion + + #region 刷新 发送列表,调用Output + + // calculate resent + var resent = fastresend > 0 ? (uint)fastresend : 0xffffffff; + var rtomin = nodelay == 0 ? (rx_rto >> 3) : 0; + + lock (snd_bufLock) + { + // flush data segments + foreach (var item in snd_buf) + { + var segment = item; + var needsend = false; + var debug = Itimediff(current_, segment.resendts); + if (segment.xmit == 0) + { + //新加入 snd_buf 中, 从未发送过的报文直接发送出去; + needsend = true; + segment.xmit++; + segment.rto = rx_rto; + segment.resendts = current_ + rx_rto + rtomin; + } + else if (Itimediff(current_, segment.resendts) >= 0) + { + //发送过的, 但是在 RTO 内未收到 ACK 的报文, 需要重传; + needsend = true; + segment.xmit++; + this.xmit++; + if (nodelay == 0) + { + segment.rto += Math.Max(segment.rto, rx_rto); + } + else + { + var step = nodelay < 2 ? segment.rto : rx_rto; + segment.rto += step / 2; + } + + segment.resendts = current_ + segment.rto; + lost = 1; + } + else if (segment.fastack >= resent) + { + //发送过的, 但是 ACK 失序若干次的报文, 需要执行快速重传. + if (segment.xmit <= fastlimit + || fastlimit <= 0) + { + needsend = true; + segment.xmit++; + segment.fastack = 0; + segment.resendts = current_ + segment.rto; + change++; + } + } + + if (needsend) + { + segment.ts = current_; + segment.wnd = wnd_; + segment.una = rcv_nxt; + + var need = IKCP_OVERHEAD + segment.len; + if (offset + need > mtu) + { + callbackHandle.Output(buffer, offset); + offset = 0; + buffer = CreateBuffer(BufferNeedSize); + } + + offset += segment.Encode(buffer.Memory.Span.Slice(offset)); + + if (CanLog(KcpLogMask.IKCP_LOG_NEED_SEND)) + { + LogWriteLine($"{segment.ToLogString(true)}", KcpLogMask.IKCP_LOG_NEED_SEND.ToString()); + } + + if (segment.xmit >= dead_link) + { + state = -1; + + if (CanLog(KcpLogMask.IKCP_LOG_DEAD_LINK)) + { + LogWriteLine($"state = -1; xmit:{segment.xmit} >= dead_link:{dead_link}", KcpLogMask.IKCP_LOG_DEAD_LINK.ToString()); + } + } + } + } + } + + // flash remain segments + if (offset > 0) + { + callbackHandle.Output(buffer, offset); + offset = 0; + buffer = CreateBuffer(BufferNeedSize); + } + + #endregion + + #region update ssthresh + // update ssthresh 根据丢包情况计算 ssthresh 和 cwnd. + if (change != 0) + { + var inflight = snd_nxt - snd_una; + ssthresh = inflight / 2; + if (ssthresh < IKCP_THRESH_MIN) + { + ssthresh = IKCP_THRESH_MIN; + } + + cwnd = ssthresh + resent; + incr = cwnd * mss; + } + + if (lost != 0) + { + ssthresh = cwnd / 2; + if (ssthresh < IKCP_THRESH_MIN) + { + ssthresh = IKCP_THRESH_MIN; + } + + cwnd = 1; + incr = mss; + } + + if (cwnd < 1) + { + cwnd = 1; + incr = mss; + } + #endregion + + if (state == -1) + { + OnDeadlink(); + } + } + + protected virtual void OnDeadlink() + { + + } + + /// + /// Test OutputWriter + /// + protected void Flush2() + { + var current_ = current; + var change = 0; + var lost = 0; + + if (updated == 0) + { + return; + } + + ushort wnd_ = Wnd_unused(); + + unsafe + { + ///在栈上分配这个segment,这个segment随用随销毁,不会被保存 + const int len = KcpSegment.LocalOffset + KcpSegment.HeadOffset; + var ptr = stackalloc byte[len]; + KcpSegment seg = new KcpSegment(ptr, 0); + //seg = KcpSegment.AllocHGlobal(0); + + seg.conv = conv; + seg.cmd = IKCP_CMD_ACK; + //seg.frg = 0; + seg.wnd = wnd_; + seg.una = rcv_nxt; + //seg.len = 0; + //seg.sn = 0; + //seg.ts = 0; + + #region flush acknowledges + + if (CheckDispose()) + { + //检查释放 + return; + } + + while (acklist.TryDequeue(out var temp)) + { + if (OutputWriter.UnflushedBytes + IKCP_OVERHEAD > mtu) + { + OutputWriter.Flush(); + } + + seg.sn = temp.sn; + seg.ts = temp.ts; + seg.Encode(OutputWriter); + } + + #endregion + + #region probe window size (if remote window size equals zero) + // probe window size (if remote window size equals zero) + if (rmt_wnd == 0) + { + if (probe_wait == 0) + { + probe_wait = IKCP_PROBE_INIT; + ts_probe = current + probe_wait; + } + else + { + if (Itimediff(current, ts_probe) >= 0) + { + if (probe_wait < IKCP_PROBE_INIT) + { + probe_wait = IKCP_PROBE_INIT; + } + + probe_wait += probe_wait / 2; + + if (probe_wait > IKCP_PROBE_LIMIT) + { + probe_wait = IKCP_PROBE_LIMIT; + } + + ts_probe = current + probe_wait; + probe |= IKCP_ASK_SEND; + } + } + } + else + { + ts_probe = 0; + probe_wait = 0; + } + #endregion + + #region flush window probing commands + // flush window probing commands + if ((probe & IKCP_ASK_SEND) != 0) + { + seg.cmd = IKCP_CMD_WASK; + if (OutputWriter.UnflushedBytes + IKCP_OVERHEAD > (int)mtu) + { + OutputWriter.Flush(); + } + seg.Encode(OutputWriter); + } + + if ((probe & IKCP_ASK_TELL) != 0) + { + seg.cmd = IKCP_CMD_WINS; + if (OutputWriter.UnflushedBytes + IKCP_OVERHEAD > (int)mtu) + { + OutputWriter.Flush(); + } + seg.Encode(OutputWriter); + } + + probe = 0; + #endregion + } + + #region 刷新,将发送等待列表移动到发送列表 + + // calculate window size + var cwnd_ = Min(snd_wnd, rmt_wnd); + if (nocwnd == 0) + { + cwnd_ = Min(cwnd, cwnd_); + } + + while (Itimediff(snd_nxt, snd_una + cwnd_) < 0) + { + if (snd_queue.TryDequeue(out var newseg)) + { + newseg.conv = conv; + newseg.cmd = IKCP_CMD_PUSH; + newseg.wnd = wnd_; + newseg.ts = current_; + newseg.sn = snd_nxt; + snd_nxt++; + newseg.una = rcv_nxt; + newseg.resendts = current_; + newseg.rto = rx_rto; + newseg.fastack = 0; + newseg.xmit = 0; + lock (snd_bufLock) + { + snd_buf.AddLast(newseg); + } + } + else + { + break; + } + } + + #endregion + + #region 刷新 发送列表,调用Output + + // calculate resent + var resent = fastresend > 0 ? (uint)fastresend : 0xffffffff; + var rtomin = nodelay == 0 ? (rx_rto >> 3) : 0; + + lock (snd_bufLock) + { + // flush data segments + foreach (var item in snd_buf) + { + var segment = item; + var needsend = false; + var debug = Itimediff(current_, segment.resendts); + if (segment.xmit == 0) + { + //新加入 snd_buf 中, 从未发送过的报文直接发送出去; + needsend = true; + segment.xmit++; + segment.rto = rx_rto; + segment.resendts = current_ + rx_rto + rtomin; + } + else if (Itimediff(current_, segment.resendts) >= 0) + { + //发送过的, 但是在 RTO 内未收到 ACK 的报文, 需要重传; + needsend = true; + segment.xmit++; + this.xmit++; + if (nodelay == 0) + { + segment.rto += Math.Max(segment.rto, rx_rto); + } + else + { + var step = nodelay < 2 ? segment.rto : rx_rto; + segment.rto += step / 2; + } + + segment.resendts = current_ + segment.rto; + lost = 1; + } + else if (segment.fastack >= resent) + { + //发送过的, 但是 ACK 失序若干次的报文, 需要执行快速重传. + if (segment.xmit <= fastlimit + || fastlimit <= 0) + { + needsend = true; + segment.xmit++; + segment.fastack = 0; + segment.resendts = current_ + segment.rto; + change++; + } + } + + if (needsend) + { + segment.ts = current_; + segment.wnd = wnd_; + segment.una = rcv_nxt; + + var need = IKCP_OVERHEAD + segment.len; + if (OutputWriter.UnflushedBytes + need > mtu) + { + OutputWriter.Flush(); + } + + segment.Encode(OutputWriter); + + if (CanLog(KcpLogMask.IKCP_LOG_NEED_SEND)) + { + LogWriteLine($"{segment.ToLogString(true)}", KcpLogMask.IKCP_LOG_NEED_SEND.ToString()); + } + + if (segment.xmit >= dead_link) + { + state = -1; + + if (CanLog(KcpLogMask.IKCP_LOG_DEAD_LINK)) + { + LogWriteLine($"state = -1; xmit:{segment.xmit} >= dead_link:{dead_link}", KcpLogMask.IKCP_LOG_DEAD_LINK.ToString()); + } + } + } + } + } + + + // flash remain segments + if (OutputWriter.UnflushedBytes > 0) + { + OutputWriter.Flush(); + } + + #endregion + + #region update ssthresh + // update ssthresh 根据丢包情况计算 ssthresh 和 cwnd. + if (change != 0) + { + var inflight = snd_nxt - snd_una; + ssthresh = inflight / 2; + if (ssthresh < IKCP_THRESH_MIN) + { + ssthresh = IKCP_THRESH_MIN; + } + + cwnd = ssthresh + resent; + incr = cwnd * mss; + } + + if (lost != 0) + { + ssthresh = cwnd / 2; + if (ssthresh < IKCP_THRESH_MIN) + { + ssthresh = IKCP_THRESH_MIN; + } + + cwnd = 1; + incr = mss; + } + + if (cwnd < 1) + { + cwnd = 1; + incr = mss; + } + #endregion + + if (state == -1) + { + OnDeadlink(); + } + } + + /// + /// update state (call it repeatedly, every 10ms-100ms), or you can ask + /// ikcp_check when to call it again (without ikcp_input/_send calling). + /// + /// DateTime.UtcNow + public void Update(in DateTimeOffset time) + { + if (CheckDispose()) + { + //检查释放 + return; + } + + current = time.ConvertTime(); + + if (updated == 0) + { + updated = 1; + ts_flush = current; + } + + var slap = Itimediff(current, ts_flush); + + if (slap >= 10000 || slap < -10000) + { + ts_flush = current; + slap = 0; + } + + if (slap >= 0) + { + ts_flush += interval; + if (Itimediff(current, ts_flush) >= 0) + { + ts_flush = current + interval; + } + + Flush(); + } + } + + #endregion + + #region 设置控制 + + public int SetMtu(int mtu = IKCP_MTU_DEF) + { + if (mtu < 50 || mtu < IKCP_OVERHEAD) + { + return -1; + } + + var buffer_ = CreateBuffer(BufferNeedSize); + if (null == buffer_) + { + return -2; + } + + this.mtu = (uint)mtu; + mss = this.mtu - IKCP_OVERHEAD; + buffer.Dispose(); + buffer = buffer_; + return 0; + } + + /// + /// + /// + /// + /// + public int Interval(int interval_) + { + if (interval_ > 5000) + { + interval_ = 5000; + } + else if (interval_ < 0) + { + /// 将最小值 10 改为 0; + ///在特殊形况下允许CPU满负荷运转; + interval_ = 0; + } + interval = (uint)interval_; + return 0; + } + + public int NoDelay(int nodelay_, int interval_, int resend_, int nc_) + { + + if (nodelay_ > 0) + { + nodelay = (uint)nodelay_; + if (nodelay_ != 0) + { + rx_minrto = IKCP_RTO_NDL; + } + else + { + rx_minrto = IKCP_RTO_MIN; + } + } + + if (resend_ >= 0) + { + fastresend = resend_; + } + + if (nc_ >= 0) + { + nocwnd = nc_; + } + + return Interval(interval_); + } + + public int WndSize(int sndwnd = IKCP_WND_SND, int rcvwnd = IKCP_WND_RCV) + { + if (sndwnd > 0) + { + snd_wnd = (uint)sndwnd; + } + + if (rcvwnd > 0) + { + rcv_wnd = (uint)rcvwnd; + } + + return 0; + } + + #endregion + + + } + + public partial class KcpCore : IKcpSendable + { + /// + /// user/upper level send, returns below zero for error + /// + /// + /// + /// + public int Send(ReadOnlySpan span, object options = null) + { + if (CheckDispose()) + { + //检查释放 + return -4; + } + + if (mss <= 0) + { + throw new InvalidOperationException($" mss <= 0 "); + } + + + if (span.Length == 0) + { + return -1; + } + var offset = 0; + int count; + + #region append to previous segment in streaming mode (if possible) + /// 基于线程安全和数据结构的等原因,移除了追加数据到最后一个包行为。 + #endregion + + #region fragment + + if (span.Length <= mss) + { + count = 1; + } + else + { + count = (int)(span.Length + mss - 1) / (int)mss; + } + + if (count > IKCP_WND_RCV) + { + return -2; + } + + if (count == 0) + { + count = 1; + } + + lock (snd_queueLock) + { + for (var i = 0; i < count; i++) + { + int size; + if (span.Length - offset > mss) + { + size = (int)mss; + } + else + { + size = (int)span.Length - offset; + } + + var seg = SegmentManager.Alloc(size); + span.Slice(offset, size).CopyTo(seg.data); + offset += size; + seg.frg = (byte)(count - i - 1); + snd_queue.Enqueue(seg); + } + } + + #endregion + + return 0; + } + + //public int Send(Span span) + //{ + // return Send((ReadOnlySpan)span); + //} + + public int Send(ReadOnlySequence span, object options = null) + { + if (CheckDispose()) + { + //检查释放 + return -4; + } + + if (mss <= 0) + { + throw new InvalidOperationException($" mss <= 0 "); + } + + + if (span.Length == 0) + { + return -1; + } + var offset = 0; + int count; + + #region append to previous segment in streaming mode (if possible) + /// 基于线程安全和数据结构的等原因,移除了追加数据到最后一个包行为。 + #endregion + + #region fragment + + if (span.Length <= mss) + { + count = 1; + } + else + { + count = (int)(span.Length + mss - 1) / (int)mss; + } + + if (count > IKCP_WND_RCV) + { + return -2; + } + + if (count == 0) + { + count = 1; + } + + lock (snd_queueLock) + { + for (var i = 0; i < count; i++) + { + int size; + if (span.Length - offset > mss) + { + size = (int)mss; + } + else + { + size = (int)span.Length - offset; + } + + var seg = SegmentManager.Alloc(size); + span.Slice(offset, size).CopyTo(seg.data); + offset += size; + seg.frg = (byte)(count - i - 1); + snd_queue.Enqueue(seg); + } + } + + #endregion + + return 0; + } + } + + public partial class KcpCore : IKcpInputable + { + /// + /// when you received a low level packet (eg. UDP packet), call it + /// + /// + /// + public int Input(ReadOnlySpan span) + { + if (CheckDispose()) + { + //检查释放 + return -4; + } + + if (CanLog(KcpLogMask.IKCP_LOG_INPUT)) + { + LogWriteLine($"[RI] {span.Length} bytes", KcpLogMask.IKCP_LOG_INPUT.ToString()); + } + + if (span.Length < IKCP_OVERHEAD) + { + return -1; + } + + uint prev_una = snd_una; + var offset = 0; + int flag = 0; + uint maxack = 0; + uint latest_ts = 0; + while (true) + { + uint ts = 0; + uint sn = 0; + uint length = 0; + uint una = 0; + uint conv_ = 0; + ushort wnd = 0; + byte cmd = 0; + byte frg = 0; + + if (span.Length - offset < IKCP_OVERHEAD) + { + break; + } + + Span header = stackalloc byte[24]; + span.Slice(offset, 24).CopyTo(header); + offset += ReadHeader(header, + ref conv_, + ref cmd, + ref frg, + ref wnd, + ref ts, + ref sn, + ref una, + ref length); + + if (conv != conv_) + { + return -1; + } + + if (span.Length - offset < length || (int)length < 0) + { + return -2; + } + + switch (cmd) + { + case IKCP_CMD_PUSH: + case IKCP_CMD_ACK: + case IKCP_CMD_WASK: + case IKCP_CMD_WINS: + break; + default: + return -3; + } + + rmt_wnd = wnd; + Parse_una(una); + Shrink_buf(); + + if (IKCP_CMD_ACK == cmd) + { + if (Itimediff(current, ts) >= 0) + { + Update_ack(Itimediff(current, ts)); + } + Parse_ack(sn); + Shrink_buf(); + + if (flag == 0) + { + flag = 1; + maxack = sn; + latest_ts = ts; + } + else if (Itimediff(sn, maxack) > 0) + { +#if !IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; +#else + if (Itimediff(ts, latest_ts) > 0) + { + maxack = sn; + latest_ts = ts; + } +#endif + } + + if (CanLog(KcpLogMask.IKCP_LOG_IN_ACK)) + { + LogWriteLine($"input ack: sn={sn} rtt={Itimediff(current, ts)} rto={rx_rto}", KcpLogMask.IKCP_LOG_IN_ACK.ToString()); + } + } + else if (IKCP_CMD_PUSH == cmd) + { + if (CanLog(KcpLogMask.IKCP_LOG_IN_DATA)) + { + LogWriteLine($"input psh: sn={sn} ts={ts}", KcpLogMask.IKCP_LOG_IN_DATA.ToString()); + } + + if (Itimediff(sn, rcv_nxt + rcv_wnd) < 0) + { + ///instead of ikcp_ack_push + acklist.Enqueue((sn, ts)); + + if (Itimediff(sn, rcv_nxt) >= 0) + { + var seg = SegmentManager.Alloc((int)length); + seg.conv = conv_; + seg.cmd = cmd; + seg.frg = frg; + seg.wnd = wnd; + seg.ts = ts; + seg.sn = sn; + seg.una = una; + //seg.len = length; 长度在分配时确定,不能改变 + + if (length > 0) + { + span.Slice(offset, (int)length).CopyTo(seg.data); + } + + Parse_data(seg); + } + } + } + else if (IKCP_CMD_WASK == cmd) + { + // ready to send back IKCP_CMD_WINS in Ikcp_flush + // tell remote my window size + probe |= IKCP_ASK_TELL; + + if (CanLog(KcpLogMask.IKCP_LOG_IN_PROBE)) + { + LogWriteLine($"input probe", KcpLogMask.IKCP_LOG_IN_PROBE.ToString()); + } + } + else if (IKCP_CMD_WINS == cmd) + { + // do nothing + if (CanLog(KcpLogMask.IKCP_LOG_IN_WINS)) + { + LogWriteLine($"input wins: {wnd}", KcpLogMask.IKCP_LOG_IN_WINS.ToString()); + } + } + else + { + return -3; + } + + offset += (int)length; + } + + if (flag != 0) + { + Parse_fastack(maxack, latest_ts); + } + + if (Itimediff(this.snd_una, prev_una) > 0) + { + if (cwnd < rmt_wnd) + { + if (cwnd < ssthresh) + { + cwnd++; + incr += mss; + } + else + { + if (incr < mss) + { + incr = mss; + } + incr += (mss * mss) / incr + (mss / 16); + if ((cwnd + 1) * mss <= incr) + { +#if true + cwnd = (incr + mss - 1) / ((mss > 0) ? mss : 1); +#else + cwnd++; +#endif + } + } + if (cwnd > rmt_wnd) + { + cwnd = rmt_wnd; + incr = rmt_wnd * mss; + } + } + } + + return 0; + } + + /// + /// + /// + /// + /// + public int Input(ReadOnlySequence span) + { + if (CheckDispose()) + { + //检查释放 + return -4; + } + + if (CanLog(KcpLogMask.IKCP_LOG_INPUT)) + { + LogWriteLine($"[RI] {span.Length} bytes", KcpLogMask.IKCP_LOG_INPUT.ToString()); + } + + if (span.Length < IKCP_OVERHEAD) + { + return -1; + } + + uint prev_una = snd_una; + var offset = 0; + int flag = 0; + uint maxack = 0; + uint latest_ts = 0; + while (true) + { + uint ts = 0; + uint sn = 0; + uint length = 0; + uint una = 0; + uint conv_ = 0; + ushort wnd = 0; + byte cmd = 0; + byte frg = 0; + + if (span.Length - offset < IKCP_OVERHEAD) + { + break; + } + + Span header = stackalloc byte[24]; + span.Slice(offset, 24).CopyTo(header); + offset += ReadHeader(header, + ref conv_, + ref cmd, + ref frg, + ref wnd, + ref ts, + ref sn, + ref una, + ref length); + + if (conv != conv_) + { + return -1; + } + + if (span.Length - offset < length || (int)length < 0) + { + return -2; + } + + switch (cmd) + { + case IKCP_CMD_PUSH: + case IKCP_CMD_ACK: + case IKCP_CMD_WASK: + case IKCP_CMD_WINS: + break; + default: + return -3; + } + + rmt_wnd = wnd; + Parse_una(una); + Shrink_buf(); + + if (IKCP_CMD_ACK == cmd) + { + if (Itimediff(current, ts) >= 0) + { + Update_ack(Itimediff(current, ts)); + } + Parse_ack(sn); + Shrink_buf(); + + if (flag == 0) + { + flag = 1; + maxack = sn; + latest_ts = ts; + } + else if (Itimediff(sn, maxack) > 0) + { +#if !IKCP_FASTACK_CONSERVE + maxack = sn; + latest_ts = ts; +#else + if (Itimediff(ts, latest_ts) > 0) + { + maxack = sn; + latest_ts = ts; + } +#endif + } + + + if (CanLog(KcpLogMask.IKCP_LOG_IN_ACK)) + { + LogWriteLine($"input ack: sn={sn} rtt={Itimediff(current, ts)} rto={rx_rto}", KcpLogMask.IKCP_LOG_IN_ACK.ToString()); + } + } + else if (IKCP_CMD_PUSH == cmd) + { + if (CanLog(KcpLogMask.IKCP_LOG_IN_DATA)) + { + LogWriteLine($"input psh: sn={sn} ts={ts}", KcpLogMask.IKCP_LOG_IN_DATA.ToString()); + } + + if (Itimediff(sn, rcv_nxt + rcv_wnd) < 0) + { + ///instead of ikcp_ack_push + acklist.Enqueue((sn, ts)); + + if (Itimediff(sn, rcv_nxt) >= 0) + { + var seg = SegmentManager.Alloc((int)length); + seg.conv = conv_; + seg.cmd = cmd; + seg.frg = frg; + seg.wnd = wnd; + seg.ts = ts; + seg.sn = sn; + seg.una = una; + //seg.len = length; 长度在分配时确定,不能改变 + + if (length > 0) + { + span.Slice(offset, (int)length).CopyTo(seg.data); + } + + Parse_data(seg); + } + } + } + else if (IKCP_CMD_WASK == cmd) + { + // ready to send back IKCP_CMD_WINS in Ikcp_flush + // tell remote my window size + probe |= IKCP_ASK_TELL; + + if (CanLog(KcpLogMask.IKCP_LOG_IN_PROBE)) + { + LogWriteLine($"input probe", KcpLogMask.IKCP_LOG_IN_PROBE.ToString()); + } + } + else if (IKCP_CMD_WINS == cmd) + { + // do nothing + if (CanLog(KcpLogMask.IKCP_LOG_IN_WINS)) + { + LogWriteLine($"input wins: {wnd}", KcpLogMask.IKCP_LOG_IN_WINS.ToString()); + } + } + else + { + return -3; + } + + offset += (int)length; + } + + if (flag != 0) + { + Parse_fastack(maxack, latest_ts); + } + + if (Itimediff(this.snd_una, prev_una) > 0) + { + if (cwnd < rmt_wnd) + { + if (cwnd < ssthresh) + { + cwnd++; + incr += mss; + } + else + { + if (incr < mss) + { + incr = mss; + } + incr += (mss * mss) / incr + (mss / 16); + if ((cwnd + 1) * mss <= incr) + { +#if true + cwnd = (incr + mss - 1) / ((mss > 0) ? mss : 1); +#else + cwnd++; +#endif + } + } + if (cwnd > rmt_wnd) + { + cwnd = rmt_wnd; + incr = rmt_wnd * mss; + } + } + } + + return 0; + } + + public static int ReadHeader(ReadOnlySpan header, + ref uint conv_, + ref byte cmd, + ref byte frg, + ref ushort wnd, + ref uint ts, + ref uint sn, + ref uint una, + ref uint length) + { + var offset = 0; + if (IsLittleEndian) + { + conv_ = BinaryPrimitives.ReadUInt32LittleEndian(header.Slice(offset)); + offset += 4; + + cmd = header[offset]; + offset += 1; + frg = header[offset]; + offset += 1; + wnd = BinaryPrimitives.ReadUInt16LittleEndian(header.Slice(offset)); + offset += 2; + + ts = BinaryPrimitives.ReadUInt32LittleEndian(header.Slice(offset)); + offset += 4; + sn = BinaryPrimitives.ReadUInt32LittleEndian(header.Slice(offset)); + offset += 4; + una = BinaryPrimitives.ReadUInt32LittleEndian(header.Slice(offset)); + offset += 4; + length = BinaryPrimitives.ReadUInt32LittleEndian(header.Slice(offset)); + offset += 4; + } + else + { + conv_ = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(offset)); + offset += 4; + cmd = header[offset]; + offset += 1; + frg = header[offset]; + offset += 1; + wnd = BinaryPrimitives.ReadUInt16BigEndian(header.Slice(offset)); + offset += 2; + + ts = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(offset)); + offset += 4; + sn = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(offset)); + offset += 4; + una = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(offset)); + offset += 4; + length = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(offset)); + offset += 4; + } + + + return offset; + } + } +} diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs.meta new file mode 100644 index 00000000..cd9858f7 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpCore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fcc0172c723e55a4389ad2f62b68a3fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs new file mode 100644 index 00000000..bfc6cea0 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs @@ -0,0 +1,262 @@ +using System.Buffers; +using System.Collections.Generic; +using System.Threading.Tasks; +using Cysharp.Threading.Tasks; +using BufferOwner = System.Buffers.IMemoryOwner; + +namespace System.Net.Sockets.Kcp +{ + /// + /// + /// 这是个简单的实现,更复杂使用微软官方实现 + /// + /// + internal class QueuePipe : Queue + { + readonly object _innerLock = new object(); + private TaskCompletionSource source; + + //线程同步上下文由Task机制保证,无需额外处理 + //SynchronizationContext callbackContext; + //public bool UseSynchronizationContext { get; set; } = true; + + public virtual void Write(T item) + { + lock (_innerLock) + { + if (source == null) + { + Enqueue(item); + } + else + { + if (Count > 0) + { + throw new Exception("内部顺序错误,不应该出现,请联系作者"); + } + + var next = source; + source = null; + next.TrySetResult(item); + } + } + } + + public new void Enqueue(T item) + { + lock (_innerLock) + { + base.Enqueue(item); + } + } + + public void Flush() + { + lock (_innerLock) + { + if (Count > 0) + { + var res = Dequeue(); + var next = source; + source = null; + next?.TrySetResult(res); + } + } + } + + public virtual Task ReadAsync() + { + lock (_innerLock) + { + if (this.Count > 0) + { + var next = Dequeue(); + return Task.FromResult(next); + } + else + { + source = new TaskCompletionSource(); + return source.Task; + } + } + } + + public UniTask ReadValueTaskAsync() + { + throw new NotImplementedException(); + } + } + + public class KcpIO : KcpCore, IKcpIO + where Segment : IKcpSegment + { + OutputQ outq; + + public KcpIO(uint conv_) : base(conv_) + { + outq = new OutputQ(); + callbackHandle = outq; + } + + internal override void Parse_data(Segment newseg) + { + base.Parse_data(newseg); + + lock (rcv_queueLock) + { + var recover = false; + if (rcv_queue.Count >= rcv_wnd) + { + recover = true; + } + + while (TryRecv(out var arraySegment) > 0) + { + recvSignal.Enqueue(arraySegment); + } + + recvSignal.Flush(); + + #region fast recover + + /// fast recover + if (rcv_queue.Count < rcv_wnd && recover) + { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + probe |= IKCP_ASK_TELL; + } + + #endregion + } + } + + QueuePipe> recvSignal = new QueuePipe>(); + + internal int TryRecv(out ArraySegment package) + { + package = default; + lock (rcv_queueLock) + { + var peekSize = -1; + if (rcv_queue.Count == 0) + { + ///没有可用包 + return -1; + } + + var seq = rcv_queue[0]; + + if (seq.frg == 0) + { + peekSize = (int)seq.len; + } + + if (rcv_queue.Count < seq.frg + 1) + { + ///没有足够的包 + return -1; + } + + uint length = 0; + + Segment[] kcpSegments = ArrayPool.Shared.Rent(seq.frg + 1); + + var index = 0; + foreach (var item in rcv_queue) + { + kcpSegments[index] = item; + index++; + length += item.len; + if (item.frg == 0) + { + break; + } + } + + if (index > 0) + { + rcv_queue.RemoveRange(0, index); + } + + package = new ArraySegment(kcpSegments, 0, index); + + peekSize = (int)length; + + if (peekSize <= 0) + { + return -2; + } + + return peekSize; + } + } + + public async UniTask RecvAsync(IBufferWriter writer, object options = null) + { + var arraySegment = await recvSignal.ReadAsync().ConfigureAwait(false); + for (int i = arraySegment.Offset; i < arraySegment.Count; i++) + { + WriteRecv(writer, arraySegment.Array[i]); + } + + ArrayPool.Shared.Return(arraySegment.Array, true); + } + + private void WriteRecv(IBufferWriter writer, Segment seg) + { + var curCount = (int)seg.len; + var target = writer.GetSpan(curCount); + seg.data.CopyTo(target); + SegmentManager.Free(seg); + writer.Advance(curCount); + } + + public async UniTask RecvAsync(ArraySegment buffer, object options = null) + { + var arraySegment = await recvSignal.ReadAsync().ConfigureAwait(false); + int start = buffer.Offset; + for (int i = arraySegment.Offset; i < arraySegment.Count; i++) + { + var target = new Memory(buffer.Array, start, buffer.Array.Length - start); + + var seg = arraySegment.Array[i]; + seg.data.CopyTo(target.Span); + start += seg.data.Length; + + SegmentManager.Free(seg); + } + + ArrayPool.Shared.Return(arraySegment.Array, true); + return start - buffer.Offset; + } + + public async UniTask OutputAsync(IBufferWriter writer, object options = null) + { + var (Owner, Count) = await outq.ReadAsync().ConfigureAwait(false); + WriteOut(writer, Owner, Count); + } + + private static void WriteOut(IBufferWriter writer, BufferOwner Owner, int Count) + { + var target = writer.GetSpan(Count); + Owner.Memory.Span.Slice(0, Count).CopyTo(target); + writer.Advance(Count); + Owner.Dispose(); + } + + protected internal override BufferOwner CreateBuffer(int needSize) + { + return MemoryPool.Shared.Rent(needSize); + } + + internal class OutputQ : QueuePipe<(BufferOwner Owner, int Count)>, + IKcpCallback + { + public void Output(BufferOwner buffer, int avalidLength) + { + Write((buffer, avalidLength)); + } + } + } +} \ No newline at end of file diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs.meta new file mode 100644 index 00000000..5aa66275 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpIO.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c30d30fa46372948b60bd56eec47fe6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs new file mode 100644 index 00000000..b850bdf3 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs @@ -0,0 +1,50 @@ +using System.Buffers; + +namespace System.Net.Sockets.Kcp +{ + public abstract class KcpOutputWriter : IKcpOutputWriter + { + public int UnflushedBytes { get; set; } + public IMemoryOwner MemoryOwner { get; set; } + public void Flush() + { + Output(MemoryOwner, UnflushedBytes); + MemoryOwner = null; + UnflushedBytes = 0; + } + + public void Advance(int count) + { + UnflushedBytes += count; + } + + public Memory GetMemory(int sizeHint = 0) + { + if (MemoryOwner == null) + { + MemoryOwner = MemoryPool.Shared.Rent(2048); + } + return MemoryOwner.Memory.Slice(UnflushedBytes); + } + + public Span GetSpan(int sizeHint = 0) + { + if (MemoryOwner == null) + { + MemoryOwner = MemoryPool.Shared.Rent(2048); + } + return MemoryOwner.Memory.Span.Slice(UnflushedBytes); + } + + /// + /// Socket发送是要pin byte[],为了不阻塞KcpFlush,动态缓存是必须的。 + /// + /// + /// + public abstract void Output(IMemoryOwner buffer, int avalidLength); + } +} + + + + diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs.meta new file mode 100644 index 00000000..0c610af5 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpOutputWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a058d8fa8a92dc94395bfd6e1f6fdb8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs new file mode 100644 index 00000000..71402217 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs @@ -0,0 +1,402 @@ +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace System.Net.Sockets.Kcp +{ + /// + /// 调整了没存布局,直接拷贝块提升性能。 + /// 结构体保存内容只有一个指针,不用担心参数传递过程中的性能 + /// https://github.com/skywind3000/kcp/issues/118#issuecomment-338133930 + /// 不要对没有初始化的KcpSegment(内部指针为0,所有属性都将指向位置区域) 进行任何赋值操作,可能导致内存损坏。 + /// 出于性能考虑,没有对此项进行安全检查。 + /// + public struct KcpSegment : IKcpSegment + { + internal readonly unsafe byte* ptr; + public unsafe KcpSegment(byte* intPtr, uint appendDateSize) + { + this.ptr = intPtr; + len = appendDateSize; + } + + /// + /// 使用完必须显示释放,否则内存泄漏 + /// + /// + /// + public static KcpSegment AllocHGlobal(int appendDateSize) + { + var total = LocalOffset + HeadOffset + appendDateSize; + IntPtr intPtr = Marshal.AllocHGlobal(total); + unsafe + { + ///清零 不知道是不是有更快的清0方法? + Span span = new Span(intPtr.ToPointer(), total); + span.Clear(); + + return new KcpSegment((byte*)intPtr.ToPointer(), (uint)appendDateSize); + } + } + + /// + /// 释放非托管内存 + /// + /// + public static void FreeHGlobal(KcpSegment seg) + { + unsafe + { + Marshal.FreeHGlobal((IntPtr)seg.ptr); + } + } + + /// 以下为本机使用的参数 + /// + /// offset = 0 + /// + public uint resendts + { + get + { + unsafe + { + return *(uint*)(ptr + 0); + } + } + set + { + unsafe + { + *(uint*)(ptr + 0) = value; + } + } + } + + /// + /// offset = 4 + /// + public uint rto + { + get + { + unsafe + { + return *(uint*)(ptr + 4); + } + } + set + { + unsafe + { + *(uint*)(ptr + 4) = value; + } + } + } + + /// + /// offset = 8 + /// + public uint fastack + { + get + { + unsafe + { + return *(uint*)(ptr + 8); + } + } + set + { + unsafe + { + *(uint*)(ptr + 8) = value; + } + } + } + + /// + /// offset = 12 + /// + public uint xmit + { + get + { + unsafe + { + return *(uint*)(ptr + 12); + } + } + set + { + unsafe + { + *(uint*)(ptr + 12) = value; + } + } + } + + ///以下为需要网络传输的参数 + public const int LocalOffset = 4 * 4; + public const int HeadOffset = KcpConst.IKCP_OVERHEAD; + + /// + /// offset = + /// + /// https://github.com/skywind3000/kcp/issues/134 + public uint conv + { + get + { + unsafe + { + return *(uint*)(LocalOffset + 0 + ptr); + } + } + set + { + unsafe + { + *(uint*)(LocalOffset + 0 + ptr) = value; + } + } + } + + /// + /// offset = + 4 + /// + public byte cmd + { + get + { + unsafe + { + return *(LocalOffset + 4 + ptr); + } + } + set + { + unsafe + { + *(LocalOffset + 4 + ptr) = value; + } + } + } + + /// + /// offset = + 5 + /// + public byte frg + { + get + { + unsafe + { + return *(LocalOffset + 5 + ptr); + } + } + set + { + unsafe + { + *(LocalOffset + 5 + ptr) = value; + } + } + } + + /// + /// offset = + 6 + /// + public ushort wnd + { + get + { + unsafe + { + return *(ushort*)(LocalOffset + 6 + ptr); + } + } + set + { + unsafe + { + *(ushort*)(LocalOffset + 6 + ptr) = value; + } + } + } + + /// + /// offset = + 8 + /// + public uint ts + { + get + { + unsafe + { + return *(uint*)(LocalOffset + 8 + ptr); + } + } + set + { + unsafe + { + *(uint*)(LocalOffset + 8 + ptr) = value; + } + } + } + + /// + /// SendNumber? + /// offset = + 12 + /// + public uint sn + { + get + { + unsafe + { + return *(uint*)(LocalOffset + 12 + ptr); + } + } + set + { + unsafe + { + *(uint*)(LocalOffset + 12 + ptr) = value; + } + } + } + + /// + /// offset = + 16 + /// + public uint una + { + get + { + unsafe + { + return *(uint*)(LocalOffset + 16 + ptr); + } + } + set + { + unsafe + { + *(uint*)(LocalOffset + 16 + ptr) = value; + } + } + } + + /// + /// AppendDateSize + /// offset = + 20 + /// + public uint len + { + get + { + unsafe + { + return *(uint*)(LocalOffset + 20 + ptr); + } + } + private set + { + unsafe + { + *(uint*)(LocalOffset + 20 + ptr) = value; + } + } + } + + /// + /// + /// + /// https://github.com/skywind3000/kcp/issues/35#issuecomment-263770736 + public Span data + { + get + { + unsafe + { + return new Span(LocalOffset + HeadOffset + ptr, (int)len); + } + } + } + + + + /// + /// 将片段中的要发送的数据拷贝到指定缓冲区 + /// + /// + /// + public int Encode(Span buffer) + { + var datelen = (int)(HeadOffset + len); + + ///备用偏移值 现阶段没有使用 + const int offset = 0; + + if (KcpConst.IsLittleEndian) + { + if (BitConverter.IsLittleEndian) + { + ///小端可以一次拷贝 + unsafe + { + ///要发送的数据从LocalOffset开始。 + ///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。 + Span sendDate = new Span(ptr + LocalOffset, datelen); + sendDate.CopyTo(buffer); + } + } + else + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), conv); + buffer[offset + 4] = cmd; + buffer[offset + 5] = frg; + BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(offset + 6), wnd); + + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8), ts); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12), sn); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16), una); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20), len); + + data.CopyTo(buffer.Slice(HeadOffset)); + } + } + else + { + if (BitConverter.IsLittleEndian) + { + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), conv); + buffer[offset + 4] = cmd; + buffer[offset + 5] = frg; + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset + 6), wnd); + + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 8), ts); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 12), sn); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 16), una); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 20), len); + + data.CopyTo(buffer.Slice(HeadOffset)); + } + else + { + ///大端可以一次拷贝 + unsafe + { + ///要发送的数据从LocalOffset开始。 + ///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。 + Span sendDate = new Span(ptr + LocalOffset, datelen); + sendDate.CopyTo(buffer); + } + } + } + + return datelen; + } + } +} diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs.meta new file mode 100644 index 00000000..2e5b4568 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpSegment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: daab08fc1f00355429c23a1e36993942 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs new file mode 100644 index 00000000..f288cb52 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace System.Net.Sockets.Kcp +{ + public partial class KcpCore + { + public KcpLogMask LogMask { get; set; } = KcpLogMask.IKCP_LOG_PARSE_DATA | KcpLogMask.IKCP_LOG_NEED_SEND | KcpLogMask.IKCP_LOG_DEAD_LINK; + + public virtual bool CanLog(KcpLogMask mask) + { + if ((mask & LogMask) == 0) + { + return false; + } + +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER + if (TraceListener != null) + { + return true; + } +#endif + return false; + } + +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER + public System.Diagnostics.TraceListener TraceListener { get; set; } +#endif + + public virtual void LogFail(string message) + { +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER + TraceListener?.Fail(message); +#endif + } + + public virtual void LogWriteLine(string message, string category) + { +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER + TraceListener?.WriteLine(message, category); +#endif + } + + [Obsolete("一定要先判断CanLog 内部判断是否存在TraceListener,避免在没有TraceListener时生成字符串", true)] + public virtual void LogWriteLine(string message, KcpLogMask mask) + { +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER + if (CanLog(mask)) + { + LogWriteLine(message, mask.ToString()); + } +#endif + } + } + + [Flags] + public enum KcpLogMask + { + IKCP_LOG_OUTPUT = 1 << 0, + IKCP_LOG_INPUT = 1 << 1, + IKCP_LOG_SEND = 1 << 2, + IKCP_LOG_RECV = 1 << 3, + IKCP_LOG_IN_DATA = 1 << 4, + IKCP_LOG_IN_ACK = 1 << 5, + IKCP_LOG_IN_PROBE = 1 << 6, + IKCP_LOG_IN_WINS = 1 << 7, + IKCP_LOG_OUT_DATA = 1 << 8, + IKCP_LOG_OUT_ACK = 1 << 9, + IKCP_LOG_OUT_PROBE = 1 << 10, + IKCP_LOG_OUT_WINS = 1 << 11, + + IKCP_LOG_PARSE_DATA = 1 << 12, + IKCP_LOG_NEED_SEND = 1 << 13, + IKCP_LOG_DEAD_LINK = 1 << 14, + } +} + + diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs.meta new file mode 100644 index 00000000..3841d0aa --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/KcpTrace.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39791b0c45bf4864e87385c477d9b50c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs new file mode 100644 index 00000000..c18a22d7 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs @@ -0,0 +1,265 @@ +using System.Runtime.InteropServices; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Buffers.Binary; + +namespace System.Net.Sockets.Kcp +{ + /// + /// 动态申请非托管内存 + /// + public class SimpleSegManager : ISegmentManager + { + public static SimpleSegManager Default { get; } = new SimpleSegManager(); + public KcpSegment Alloc(int appendDateSize) + { + return KcpSegment.AllocHGlobal(appendDateSize); + } + + public void Free(KcpSegment seg) + { + KcpSegment.FreeHGlobal(seg); + } + + public class Kcp : Kcp + { + public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null) + : base(conv_, callback, rentable) + { + SegmentManager = Default; + } + } + + public class KcpIO : KcpIO + { + public KcpIO(uint conv_) + : base(conv_) + { + SegmentManager = Default; + } + } + } + + /// + /// 申请固定大小非托管内存。使用这个就不能SetMtu了,大小已经写死。 + /// + /// 需要大量测试 + public unsafe class UnSafeSegManager : ISegmentManager + { + public static UnSafeSegManager Default { get; } = new UnSafeSegManager(); + /// + /// 因为默认mtu是1400,并且内存需要内存行/内存页对齐。这里直接512对齐。 + /// + public const int blockSize = 512 * 3; + public HashSet header = new HashSet(); + public Stack blocks = new Stack(); + public readonly object locker = new object(); + public UnSafeSegManager() + { + Alloc(); + } + + void Alloc() + { + int count = 50; + IntPtr intPtr = Marshal.AllocHGlobal(blockSize * count); + header.Add(intPtr); + for (int i = 0; i < count; i++) + { + blocks.Push(intPtr + blockSize * i); + } + } + + ~UnSafeSegManager() + { + foreach (var item in header) + { + Marshal.FreeHGlobal(item); + } + } + + public KcpSegment Alloc(int appendDateSize) + { + lock (locker) + { + var total = KcpSegment.LocalOffset + KcpSegment.HeadOffset + appendDateSize; + if (total > blockSize) + { + throw new ArgumentOutOfRangeException(); + } + + if (blocks.Count > 0) + { + + } + else + { + Alloc(); + } + + var ptr = blocks.Pop(); + Span span = new Span(ptr.ToPointer(), blockSize); + span.Clear(); + return new KcpSegment((byte*)ptr.ToPointer(), (uint)appendDateSize); + } + } + + public void Free(KcpSegment seg) + { + IntPtr ptr = (IntPtr)seg.ptr; + blocks.Push(ptr); + } + + public class Kcp : Kcp + { + public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null) + : base(conv_, callback, rentable) + { + SegmentManager = Default; + } + } + + public class KcpIO : KcpIO + { + public KcpIO(uint conv_) + : base(conv_) + { + SegmentManager = Default; + } + } + } + + + /// + /// 使用内存池,而不是非托管内存,有内存alloc,但是不多。可以解决Marshal.AllocHGlobal 内核调用带来的性能问题 + /// + public class PoolSegManager : ISegmentManager + { + public static PoolSegManager Default { get; } = new PoolSegManager(); + + /// + /// 因为默认mtu是1400,并且内存需要内存行/内存页对齐。这里直接512对齐。 + /// + public const int blockSize = 512 * 3; + public class Seg : IKcpSegment + { + byte[] cache; + public Seg(int blockSize) + { + cache = Buffers.ArrayPool.Shared.Rent(blockSize); + } + + ///以下为需要网络传输的参数 + public const int LocalOffset = 4 * 4; + public const int HeadOffset = Kcp.IKCP_OVERHEAD; + + public byte cmd { get; set; } + public uint conv { get; set; } + public Span data => cache.AsSpan().Slice(0, (int)len); + public uint fastack { get; set; } + public byte frg { get; set; } + public uint len { get; internal set; } + public uint resendts { get; set; } + public uint rto { get; set; } + public uint sn { get; set; } + public uint ts { get; set; } + public uint una { get; set; } + public ushort wnd { get; set; } + public uint xmit { get; set; } + + public int Encode(Span buffer) + { + var datelen = (int)(HeadOffset + len); + + ///备用偏移值 现阶段没有使用 + const int offset = 0; + + if (BitConverter.IsLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), conv); + buffer[offset + 4] = cmd; + buffer[offset + 5] = frg; + BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(offset + 6), wnd); + + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8), ts); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12), sn); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16), una); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20), len); + + data.CopyTo(buffer.Slice(HeadOffset)); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), conv); + buffer[offset + 4] = cmd; + buffer[offset + 5] = frg; + BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset + 6), wnd); + + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 8), ts); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 12), sn); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 16), una); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 20), len); + + data.CopyTo(buffer.Slice(HeadOffset)); + } + + return datelen; + } + } + ConcurrentStack Pool = new ConcurrentStack(); + public Seg Alloc(int appendDateSize) + { + if (appendDateSize > blockSize) + { + throw new NotSupportedException(); + } + if (Pool.TryPop(out var ret)) + { + } + else + { + ret = new Seg(blockSize); + } + ret.len = (uint)appendDateSize; + return ret; + } + + public void Free(Seg seg) + { + seg.cmd = 0; + seg.conv = 0; + seg.fastack = 0; + seg.frg = 0; + seg.len = 0; + seg.resendts = 0; + seg.rto = 0; + seg.sn = 0; + seg.ts = 0; + seg.una = 0; + seg.wnd = 0; + seg.xmit = 0; + Pool.Push(seg); + } + + public class Kcp : Kcp + { + public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null) + : base(conv_, callback, rentable) + { + SegmentManager = Default; + } + } + + public class KcpIO : KcpIO + { + public KcpIO(uint conv_) + : base(conv_) + { + SegmentManager = Default; + } + } + } +} + + + diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs.meta new file mode 100644 index 00000000..0d200168 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SegManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7f0d71aa1362b6488c3173d525fd284 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs new file mode 100644 index 00000000..f4c5399e --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs @@ -0,0 +1,65 @@ +using System.Buffers; +using System.Threading.Tasks; +using Cysharp.Threading.Tasks; + +namespace System.Net.Sockets.Kcp.Simple +{ + /// + /// 简单例子。 + /// + public class SimpleKcpClient : IKcpCallback + { + UdpClient client; + + public SimpleKcpClient(int port) + : this(port, null) + { + + } + + public SimpleKcpClient(int port, IPEndPoint endPoint) + { + client = new UdpClient(port); + kcp = new SimpleSegManager.Kcp(2001, this); + this.EndPoint = endPoint; + BeginRecv(); + } + + public SimpleSegManager.Kcp kcp { get; } + public IPEndPoint EndPoint { get; set; } + + public void Output(IMemoryOwner buffer, int avalidLength) + { + var s = buffer.Memory.Span.Slice(0, avalidLength).ToArray(); + client.SendAsync(s, s.Length, EndPoint); + buffer.Dispose(); + } + + public async void SendAsync(byte[] datagram, int bytes) + { + kcp.Send(datagram.AsSpan().Slice(0, bytes)); + } + + public async UniTask ReceiveAsync() + { + var (buffer, avalidLength) = kcp.TryRecv(); + while (buffer == null) + { + await Task.Delay(10); + (buffer, avalidLength) = kcp.TryRecv(); + } + + var s = buffer.Memory.Span.Slice(0, avalidLength).ToArray(); + return s; + } + + private async void BeginRecv() + { + var res = await client.ReceiveAsync(); + EndPoint = res.RemoteEndPoint; + kcp.Input(res.Buffer); + BeginRecv(); + } + } +} + diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs.meta new file mode 100644 index 00000000..530afc3c --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpClient.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff16ed9b89525df4aa6501c0edc679d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs new file mode 100644 index 00000000..e1ad21f1 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs @@ -0,0 +1,48 @@ +using System.Net.Sockets.Kcp.Simple; +using System.Threading.Tasks; + +namespace System.Net.Sockets.Kcp +{ + namespace TestServer + { + /// + /// 简单例子。 + /// + class SimpleKcpServer + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + + SimpleKcpClient kcpClient = new SimpleKcpClient(40001); + Task.Run(async () => + { + while (true) + { + kcpClient.kcp.Update(DateTimeOffset.UtcNow); + await Task.Delay(10); + } + }); + + StartRecv(kcpClient); + Console.ReadLine(); + } + + static async void StartRecv(SimpleKcpClient client) + { + while (true) + { + var res = await client.ReceiveAsync(); + var str = System.Text.Encoding.UTF8.GetString(res); + if ("发送一条消息" == str) + { + Console.WriteLine(str); + + var buffer = System.Text.Encoding.UTF8.GetBytes("回复一条消息"); + client.SendAsync(buffer, buffer.Length); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs.meta new file mode 100644 index 00000000..878b81d5 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/SimpleKcpServer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 97e221b7388e412da59b0f4d200cb891 +timeCreated: 1682095200 \ No newline at end of file diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs new file mode 100644 index 00000000..601a862f --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs @@ -0,0 +1,73 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +//[assembly: InternalsVisibleTo("UnitTestProject1")] + +namespace System.Net.Sockets.Kcp +{ + public static class KcpExtension_FDF71D0BC31D49C48EEA8FAA51F017D4 + { + private static readonly DateTime utc_time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + [Obsolete("", true)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertTime(this in DateTime time) + { + return (uint)(Convert.ToInt64(time.Subtract(utc_time).TotalMilliseconds) & 0xffffffff); + } + + private static readonly DateTimeOffset utc1970 = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertTimeOld(this in DateTimeOffset time) + { + return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertTime2(this in DateTimeOffset time) + { +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER + return (uint)(time.ToUnixTimeMilliseconds() & 0xffffffff); +#else + return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertTime(this in DateTimeOffset time) + { +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER + return (uint)(time.ToUnixTimeMilliseconds()); +#else + return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToLogString(this T segment, bool local = false) + where T : IKcpSegment + { + if (local) + { + return $"sn:{segment.sn,2} una:{segment.una,2} frg:{segment.frg,2} cmd:{segment.cmd,2} len:{segment.len,2} wnd:{segment.wnd} [ LocalValue: xmit:{segment.xmit} fastack:{segment.fastack} rto:{segment.rto} ]"; + } + else + { + return $"sn:{segment.sn,2} una:{segment.una,2} frg:{segment.frg,2} cmd:{segment.cmd,2} len:{segment.len,2} wnd:{segment.wnd}"; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Encode(this T Seg, IBufferWriter writer) + where T : IKcpSegment + { + var totalLength = (int)(KcpSegment.HeadOffset + Seg.len); + var span = writer.GetSpan(totalLength); + Seg.Encode(span); + writer.Advance(totalLength); + return totalLength; + } + } +} diff --git a/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs.meta b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs.meta new file mode 100644 index 00000000..be197e63 --- /dev/null +++ b/Assets/TEngine/Runtime/GameFramework/Network/Kcp/Core/Utility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd298c4aa310a7e448c4694ddc41337e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: