diff --git a/Assets/Mirror/Examples/AdditiveScenes/Scenes/MainScene.unity b/Assets/Mirror/Examples/AdditiveScenes/Scenes/MainScene.unity
index 7749e75df..96d32c856 100644
--- a/Assets/Mirror/Examples/AdditiveScenes/Scenes/MainScene.unity
+++ b/Assets/Mirror/Examples/AdditiveScenes/Scenes/MainScene.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
- m_IndirectSpecularColor: {r: 0.4366757, g: 0.48427194, b: 0.5645252, a: 1}
+ m_IndirectSpecularColor: {r: 0.43667564, g: 0.48427147, b: 0.56452364, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -1830,12 +1830,12 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 0
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene:
onlineScene:
- transport: {fileID: 1661834280}
+ transport: {fileID: 0}
networkAddress: localhost
maxConnections: 4
disconnectInactiveConnections: 0
@@ -1871,7 +1871,7 @@ MonoBehaviour:
m_GameObject: {fileID: 1661834277}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -1898,10 +1898,14 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- clientMaxMessageSize: 16384
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!114 &1661834281
MonoBehaviour:
m_ObjectHideFlags: 0
diff --git a/Assets/Mirror/Examples/Basic/Scenes/Example.unity b/Assets/Mirror/Examples/Basic/Scenes/Example.unity
index d6194365f..bb1555b2b 100644
--- a/Assets/Mirror/Examples/Basic/Scenes/Example.unity
+++ b/Assets/Mirror/Examples/Basic/Scenes/Example.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
- m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
+ m_IndirectSpecularColor: {r: 0.44657868, g: 0.49641263, b: 0.57481706, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -155,7 +155,7 @@ MonoBehaviour:
m_GameObject: {fileID: 249891953}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -182,10 +182,14 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- clientMaxMessageSize: 16384
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!114 &249891956
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -200,12 +204,12 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 0
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene:
onlineScene:
- transport: {fileID: 249891955}
+ transport: {fileID: 0}
networkAddress: localhost
maxConnections: 16
disconnectInactiveConnections: 0
diff --git a/Assets/Mirror/Examples/Benchmark/Scenes/Scene.unity b/Assets/Mirror/Examples/Benchmark/Scenes/Scene.unity
index 60824c652..b24d7f7ac 100644
--- a/Assets/Mirror/Examples/Benchmark/Scenes/Scene.unity
+++ b/Assets/Mirror/Examples/Benchmark/Scenes/Scene.unity
@@ -309,7 +309,7 @@ MonoBehaviour:
- {fileID: 449802645721213856, guid: 30b8f251d03d84284b70601e691d474f, type: 3}
spawnPrefab: {fileID: 449802645721213856, guid: 30b8f251d03d84284b70601e691d474f,
type: 3}
- spawnAmount: 4000
+ spawnAmount: 1000
interleave: 1
--- !u!114 &1282001521
MonoBehaviour:
@@ -320,7 +320,7 @@ MonoBehaviour:
m_GameObject: {fileID: 1282001517}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -347,12 +347,14 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- serverMaxReceivesPerTick: 10000
- clientMaxMessageSize: 16384
- clientMaxReceivesPerTick: 1000
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 0
+ SendWindowSize: 512
+ ReceiveWindowSize: 512
+ debugGUI: 1
--- !u!1 &2054208274
GameObject:
m_ObjectHideFlags: 0
diff --git a/Assets/Mirror/Examples/Chat/Scenes/Main.unity b/Assets/Mirror/Examples/Chat/Scenes/Main.unity
index 5906f17ce..ea21f7d0d 100644
--- a/Assets/Mirror/Examples/Chat/Scenes/Main.unity
+++ b/Assets/Mirror/Examples/Chat/Scenes/Main.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
- m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
+ m_IndirectSpecularColor: {r: 0.44657868, g: 0.49641263, b: 0.57481706, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -2895,7 +2895,7 @@ GameObject:
m_Component:
- component: {fileID: 1783103026}
- component: {fileID: 1783103025}
- - component: {fileID: 1783103024}
+ - component: {fileID: 1783103023}
m_Layer: 0
m_Name: NetworkManager
m_TagString: Untagged
@@ -2903,7 +2903,7 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!114 &1783103024
+--- !u!114 &1783103023
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -2912,7 +2912,7 @@ MonoBehaviour:
m_GameObject: {fileID: 1783103022}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -2939,10 +2939,14 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- clientMaxMessageSize: 16384
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!114 &1783103025
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -2957,12 +2961,12 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene:
onlineScene:
- transport: {fileID: 1783103024}
+ transport: {fileID: 1783103023}
networkAddress: localhost
maxConnections: 4
disconnectInactiveConnections: 0
diff --git a/Assets/Mirror/Examples/Cloud/PongWithListServer/Prefabs/NetworkManagerPong.prefab b/Assets/Mirror/Examples/Cloud/PongWithListServer/Prefabs/NetworkManagerPong.prefab
index ae23d06d8..f4b2105bf 100644
--- a/Assets/Mirror/Examples/Cloud/PongWithListServer/Prefabs/NetworkManagerPong.prefab
+++ b/Assets/Mirror/Examples/Cloud/PongWithListServer/Prefabs/NetworkManagerPong.prefab
@@ -12,7 +12,7 @@ GameObject:
- component: {fileID: 4798169800353248833}
- component: {fileID: 5399210850297115757}
- component: {fileID: 1442807832621757098}
- - component: {fileID: 4798169800353248834}
+ - component: {fileID: 7877660282335013212}
m_Layer: 0
m_Name: NetworkManagerPong
m_TagString: Untagged
@@ -51,8 +51,6 @@ MonoBehaviour:
_onServerListUpdated:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.Cloud.ServerListEvent, Mirror.Cloud, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
--- !u!114 &5399210850297115757
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -65,6 +63,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 1f6e5d5acb5879f45a2235ae0f44dc92, type: 3}
m_Name:
m_EditorClassIdentifier:
+ manager: {fileID: 0}
+ connector: {fileID: 0}
gameName: Pong Game
--- !u!114 &1442807832621757098
MonoBehaviour:
@@ -80,12 +80,12 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene: Assets/Mirror/Examples/Cloud/PongWithListServer/Scenes/ListServerLobbyScenePong.unity
onlineScene: Assets/Mirror/Examples/Cloud/PongWithListServer/Scenes/PongGameScene.unity
- transport: {fileID: 4798169800353248834}
+ transport: {fileID: 7877660282335013212}
networkAddress: localhost
maxConnections: 2
disconnectInactiveConnections: 0
@@ -97,7 +97,7 @@ MonoBehaviour:
playerSpawnMethod: 1
spawnPrefabs:
- {fileID: 1080679924113744, guid: a4b57f17790d9634ea5fd0fe80b214fd, type: 3}
---- !u!114 &4798169800353248834
+--- !u!114 &7877660282335013212
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -106,50 +106,38 @@ MonoBehaviour:
m_GameObject: {fileID: 4798169800353248846}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
OnClientDataReceived:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.ClientDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnClientError:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnClientDisconnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
OnServerConnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
OnServerDataReceived:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.ServerDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnServerError:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventIntException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- serverMaxReceivesPerTick: 10000
- clientMaxMessageSize: 16384
- clientMaxReceivesPerTick: 1000
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
diff --git a/Assets/Mirror/Examples/Cloud/TanksWithListServer/Prefabs/TanksNetworkManager.prefab b/Assets/Mirror/Examples/Cloud/TanksWithListServer/Prefabs/TanksNetworkManager.prefab
index 49938faf3..755b62e82 100644
--- a/Assets/Mirror/Examples/Cloud/TanksWithListServer/Prefabs/TanksNetworkManager.prefab
+++ b/Assets/Mirror/Examples/Cloud/TanksWithListServer/Prefabs/TanksNetworkManager.prefab
@@ -13,7 +13,6 @@ GameObject:
- component: {fileID: 5399210850297115757}
- component: {fileID: 5256484632236059241}
- component: {fileID: 1442807832621757098}
- - component: {fileID: 4798169800353248834}
m_Layer: 0
m_Name: TanksNetworkManager
m_TagString: Untagged
@@ -52,8 +51,6 @@ MonoBehaviour:
_onServerListUpdated:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.Cloud.ServerListEvent, Mirror.Cloud, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
--- !u!114 &5399210850297115757
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -66,6 +63,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 1f6e5d5acb5879f45a2235ae0f44dc92, type: 3}
m_Name:
m_EditorClassIdentifier:
+ manager: {fileID: 0}
+ connector: {fileID: 0}
gameName: Tanks Game
--- !u!114 &5256484632236059241
MonoBehaviour:
@@ -93,12 +92,12 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene: Assets/Mirror/Examples/Cloud/TanksWithListServer/Scenes/ListServerLobbySceneTanks.unity
onlineScene: Assets/Mirror/Examples/Tanks/Scenes/Scene.unity
- transport: {fileID: 4798169800353248834}
+ transport: {fileID: 0}
networkAddress: localhost
maxConnections: 4
disconnectInactiveConnections: 0
@@ -110,59 +109,3 @@ MonoBehaviour:
playerSpawnMethod: 1
spawnPrefabs:
- {fileID: 5890560936853567077, guid: b7dd46dbf38c643f09e206f9fa4be008, type: 3}
---- !u!114 &4798169800353248834
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 4798169800353248846}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
- m_Name:
- m_EditorClassIdentifier:
- OnClientConnected:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
- OnClientDataReceived:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: Mirror.ClientDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
- OnClientError:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: Mirror.UnityEventException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
- OnClientDisconnected:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
- OnServerConnected:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
- OnServerDataReceived:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: Mirror.ServerDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
- OnServerError:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: Mirror.UnityEventIntException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
- OnServerDisconnected:
- m_PersistentCalls:
- m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
- port: 7777
- NoDelay: 1
- serverMaxMessageSize: 16384
- serverMaxReceivesPerTick: 10000
- clientMaxMessageSize: 16384
- clientMaxReceivesPerTick: 1000
diff --git a/Assets/Mirror/Examples/Cloud/TanksWithListServer/Scenes/ListServerLobbySceneTanks.unity b/Assets/Mirror/Examples/Cloud/TanksWithListServer/Scenes/ListServerLobbySceneTanks.unity
index efb8f8009..28d7afb09 100644
--- a/Assets/Mirror/Examples/Cloud/TanksWithListServer/Scenes/ListServerLobbySceneTanks.unity
+++ b/Assets/Mirror/Examples/Cloud/TanksWithListServer/Scenes/ListServerLobbySceneTanks.unity
@@ -451,6 +451,11 @@ PrefabInstance:
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
+ - target: {fileID: 4971607475644947932, guid: e16e442bbd8d4434cb606afd72bcd08b,
+ type: 3}
+ propertyPath: m_AnchoredPosition.y
+ value: 0.000091552734
+ objectReference: {fileID: 0}
- target: {fileID: 4971607476029058370, guid: e16e442bbd8d4434cb606afd72bcd08b,
type: 3}
propertyPath: m_AnchorMin.y
@@ -521,6 +526,11 @@ PrefabInstance:
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
+ - target: {fileID: 4971607476873278848, guid: e16e442bbd8d4434cb606afd72bcd08b,
+ type: 3}
+ propertyPath: m_AnchorMin.y
+ value: 0
+ objectReference: {fileID: 0}
- target: {fileID: 4971607476965432301, guid: e16e442bbd8d4434cb606afd72bcd08b,
type: 3}
propertyPath: m_LocalPosition.x
diff --git a/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity b/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity
index 43121930a..58d175ae1 100644
--- a/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity
+++ b/Assets/Mirror/Examples/Discovery/Scenes/Scene.unity
@@ -395,7 +395,7 @@ MonoBehaviour:
m_GameObject: {fileID: 1556883243}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -422,10 +422,14 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- clientMaxMessageSize: 16384
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!114 &1556883245
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -440,7 +444,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene:
diff --git a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scenes/Main.unity b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scenes/Main.unity
index ad79b98c4..1c05dc219 100644
--- a/Assets/Mirror/Examples/MultipleAdditiveScenes/Scenes/Main.unity
+++ b/Assets/Mirror/Examples/MultipleAdditiveScenes/Scenes/Main.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 690741348}
- m_IndirectSpecularColor: {r: 0.4366757, g: 0.48427194, b: 0.5645252, a: 1}
+ m_IndirectSpecularColor: {r: 0.43667564, g: 0.48427147, b: 0.56452364, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -54,7 +54,7 @@ LightmapSettings:
m_EnableBakedLightmaps: 0
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
- serializedVersion: 12
+ serializedVersion: 10
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
@@ -62,7 +62,6 @@ LightmapSettings:
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
- m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
@@ -77,16 +76,10 @@ LightmapSettings:
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
- m_PVREnvironmentSampleCount: 500
- m_PVREnvironmentReferencePointCount: 2048
- m_PVRFilteringMode: 2
- m_PVRDenoiserTypeDirect: 0
- m_PVRDenoiserTypeIndirect: 0
- m_PVRDenoiserTypeAO: 0
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
- m_PVREnvironmentMIS: 0
+ m_PVRFilteringMode: 2
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
@@ -94,8 +87,7 @@ LightmapSettings:
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
- m_ExportTrainingData: 0
- m_TrainingDataDestination: TrainingData
+ m_ShowResolutionOverlay: 1
m_LightingDataAsset: {fileID: 0}
m_UseShadowmask: 1
--- !u!196 &4
@@ -158,10 +150,9 @@ Camera:
m_ClearFlags: 1
m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0}
m_projectionMatrixMode: 1
- m_GateFitMode: 2
- m_FOVAxisMode: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
+ m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
@@ -213,8 +204,8 @@ GameObject:
m_Component:
- component: {fileID: 69965670}
- component: {fileID: 69965669}
- - component: {fileID: 69965668}
- component: {fileID: 69965667}
+ - component: {fileID: 69965668}
m_Layer: 0
m_Name: Network
m_TagString: Untagged
@@ -246,7 +237,7 @@ MonoBehaviour:
m_GameObject: {fileID: 69965666}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -273,12 +264,14 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- serverMaxReceivesPerTick: 10000
- clientMaxMessageSize: 16384
- clientMaxReceivesPerTick: 1000
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!114 &69965669
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -293,7 +286,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 0
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene:
@@ -480,13 +473,12 @@ Light:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 690741347}
m_Enabled: 1
- serializedVersion: 9
+ serializedVersion: 8
m_Type: 1
m_Color: {r: 0.990566, g: 0.9496818, b: 0.82702917, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
- m_InnerSpotAngle: 21.80208
m_CookieSize: 10
m_Shadows:
m_Type: 2
@@ -496,24 +488,6 @@ Light:
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
- m_CullingMatrixOverride:
- e00: 1
- e01: 0
- e02: 0
- e03: 0
- e10: 0
- e11: 1
- e12: 0
- e13: 0
- e20: 0
- e21: 0
- e22: 1
- e23: 0
- e30: 0
- e31: 0
- e32: 0
- e33: 1
- m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
@@ -521,15 +495,12 @@ Light:
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
- m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
- m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
- m_UseBoundingSphereOverride: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &690741349
diff --git a/Assets/Mirror/Examples/Pong/Scenes/Scene.unity b/Assets/Mirror/Examples/Pong/Scenes/Scene.unity
index c8a9e1c9f..6c30863c4 100644
--- a/Assets/Mirror/Examples/Pong/Scenes/Scene.unity
+++ b/Assets/Mirror/Examples/Pong/Scenes/Scene.unity
@@ -872,7 +872,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene:
@@ -900,7 +900,7 @@ MonoBehaviour:
m_GameObject: {fileID: 1886246549}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -927,7 +927,11 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- clientMaxMessageSize: 16384
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
diff --git a/Assets/Mirror/Examples/RigidbodyPhysics/Scenes/BounceScene.unity b/Assets/Mirror/Examples/RigidbodyPhysics/Scenes/BounceScene.unity
index e623d9b63..3514ab855 100644
--- a/Assets/Mirror/Examples/RigidbodyPhysics/Scenes/BounceScene.unity
+++ b/Assets/Mirror/Examples/RigidbodyPhysics/Scenes/BounceScene.unity
@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
- m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1}
+ m_IndirectSpecularColor: {r: 0.44657868, g: 0.49641263, b: 0.57481706, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
@@ -214,8 +214,8 @@ GameObject:
m_Component:
- component: {fileID: 492096636}
- component: {fileID: 492096635}
- - component: {fileID: 492096634}
- component: {fileID: 492096637}
+ - component: {fileID: 492096634}
m_Layer: 0
m_Name: NetworkManager
m_TagString: Untagged
@@ -232,53 +232,41 @@ MonoBehaviour:
m_GameObject: {fileID: 492096633}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
OnClientDataReceived:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.ClientDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnClientError:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnClientDisconnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
OnServerConnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
OnServerDataReceived:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.ServerDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnServerError:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventIntException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- serverMaxReceivesPerTick: 10000
- clientMaxMessageSize: 16384
- clientMaxReceivesPerTick: 1000
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!114 &492096635
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -293,7 +281,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene:
@@ -394,8 +382,6 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
- Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
@@ -881,8 +867,6 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
- Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
@@ -955,8 +939,6 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
- Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 24
@@ -1034,8 +1016,6 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
- Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 24
@@ -1296,8 +1276,6 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
- Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 24
@@ -1378,8 +1356,6 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
- Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
diff --git a/Assets/Mirror/Examples/Room/Scenes/OfflineScene.unity b/Assets/Mirror/Examples/Room/Scenes/OfflineScene.unity
index 6aa1dd8df..efe0160f7 100644
--- a/Assets/Mirror/Examples/Room/Scenes/OfflineScene.unity
+++ b/Assets/Mirror/Examples/Room/Scenes/OfflineScene.unity
@@ -222,7 +222,7 @@ MonoBehaviour:
m_GameObject: {fileID: 2008127829}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
@@ -249,10 +249,14 @@ MonoBehaviour:
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- port: 7777
+ Port: 7777
NoDelay: 1
- serverMaxMessageSize: 16384
- clientMaxMessageSize: 16384
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!114 &2008127831
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -267,7 +271,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
+ autoStartServerBuild: 1
showDebugMessages: 0
serverTickRate: 30
offlineScene: Assets/Mirror/Examples/Room/Scenes/OfflineScene.unity
@@ -290,7 +294,7 @@ MonoBehaviour:
type: 3}
RoomScene: Assets/Mirror/Examples/Room/Scenes/RoomScene.unity
GameplayScene: Assets/Mirror/Examples/Room/Scenes/OnlineScene.unity
- allPlayersReady: 0
+ _allPlayersReady: 0
roomSlots: []
--- !u!4 &2008127832
Transform:
diff --git a/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity b/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity
index fc8e49e02..84aa6ab1d 100644
--- a/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity
+++ b/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity
@@ -443,14 +443,17 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
- startOnHeadless: 1
- serverTickRate: 30
+ autoStartServerBuild: 1
showDebugMessages: 0
+ serverTickRate: 30
offlineScene:
onlineScene:
transport: {fileID: 1282001521}
networkAddress: localhost
maxConnections: 4
+ disconnectInactiveConnections: 0
+ disconnectInactiveTimeout: 60
+ authenticator: {fileID: 0}
playerPrefab: {fileID: 1916082411674582, guid: 6f43bf5488a7443d19ab2a83c6b91f35,
type: 3}
autoCreatePlayer: 1
@@ -466,49 +469,41 @@ MonoBehaviour:
m_GameObject: {fileID: 1282001517}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: c7424c1070fad4ba2a7a96b02fbeb4bb, type: 3}
+ m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
OnClientConnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
OnClientDataReceived:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventByteArray, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnClientError:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnClientDisconnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
OnServerConnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
OnServerDataReceived:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventIntByteArray, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnServerError:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventIntException, Mirror, Version=0.0.0.0, Culture=neutral,
- PublicKeyToken=null
OnServerDisconnected:
m_PersistentCalls:
m_Calls: []
- m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
- port: 7777
+ Port: 7777
NoDelay: 1
+ Interval: 10
+ FastResend: 0
+ CongestionWindow: 1
+ SendWindowSize: 128
+ ReceiveWindowSize: 128
+ debugGUI: 0
--- !u!1 &1458789072
GameObject:
m_ObjectHideFlags: 0
diff --git a/Assets/Mirror/Runtime/Mirror.asmdef b/Assets/Mirror/Runtime/Mirror.asmdef
index d6ba6f731..29dc6ef47 100644
--- a/Assets/Mirror/Runtime/Mirror.asmdef
+++ b/Assets/Mirror/Runtime/Mirror.asmdef
@@ -7,7 +7,7 @@
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
- "allowUnsafeCode": false,
+ "allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
diff --git a/Assets/Mirror/Runtime/NetworkManager.cs b/Assets/Mirror/Runtime/NetworkManager.cs
index 10a12ec65..031db098f 100644
--- a/Assets/Mirror/Runtime/NetworkManager.cs
+++ b/Assets/Mirror/Runtime/NetworkManager.cs
@@ -4,6 +4,7 @@
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
+using kcp2k;
namespace Mirror
{
@@ -44,7 +45,7 @@ public class NetworkManager : MonoBehaviour
///
/// Automatically invoke StartServer()
/// If the application is a Server Build, StartServer is automatically invoked.
- /// Server build is true when "Server build" is checked in build menu, or BuildOptions.EnableHeadlessMode flag is in BuildOptions
+ /// Server build is true when "Server build" is checked in build menu, or BuildOptions.EnableHeadlessMode flag is in BuildOptions
///
[Tooltip("Should the server auto-start when 'Server Build' is checked in build settings")]
[FormerlySerializedAs("startOnHeadless")]
@@ -204,7 +205,7 @@ public virtual void OnValidate()
transport = GetComponent();
if (transport == null)
{
- transport = gameObject.AddComponent();
+ transport = gameObject.AddComponent();
logger.Log("NetworkManager: added default Transport because there was none yet.");
}
#if UNITY_EDITOR
diff --git a/Assets/Mirror/Runtime/Transport/KCP.meta b/Assets/Mirror/Runtime/Transport/KCP.meta
new file mode 100644
index 000000000..ba9d1902a
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 953bb5ec5ab2346a092f58061e01ba65
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
new file mode 100644
index 000000000..dedea2fb5
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7bdb797750d0a490684410110bf48192
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs
new file mode 100644
index 000000000..9b5c89c4e
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs
@@ -0,0 +1,172 @@
+#if MIRROR
+using System;
+using System.Linq;
+using System.Net;
+using UnityEngine;
+using Mirror;
+
+namespace kcp2k
+{
+ public class KcpTransport : Transport
+ {
+ // scheme used by this transport
+ public const string Scheme = "kcp";
+
+ // common
+ [Header("Transport Configuration")]
+ public ushort Port = 7777;
+ [Tooltip("NoDelay is recommended to reduce latency. This also scales better without buffers getting full.")]
+ public bool NoDelay = true;
+ [Tooltip("KCP internal update interval. 100ms is KCP default, but a lower interval is recommended to minimize latency and to scale to more networked entities.")]
+ public uint Interval = 10;
+ [Header("Advanced")]
+ [Tooltip("KCP fastresend parameter. Faster resend for the cost of higher bandwidth.")]
+ public int FastResend = 0;
+ [Tooltip("KCP congestion window can be disabled. This is necessary to Mirror 10k Benchmark. Disable this for high scale games if connections get chocked regularly.")]
+ public bool CongestionWindow = true; // KCP 'NoCongestionWindow' is false by default. here we negate it for ease of use.
+ [Tooltip("KCP window size can be modified to support higher loads. For example, Mirror Benchmark requires 128 for 4k monsters, 256 for 10k monsters (if CongestionWindow is disabled.)")]
+ public uint SendWindowSize = 128; //Kcp.WND_SND; 32 by default. 128 is better for 4k Benchmark etc.
+ [Tooltip("KCP window size can be modified to support higher loads. For example, Mirror Benchmark requires 128 for 4k monsters, 256 for 10k monsters (if CongestionWindow is disabled.)")]
+ public uint ReceiveWindowSize = Kcp.WND_RCV;
+
+ // server & client
+ KcpServer server;
+ KcpClient client;
+
+ // debugging
+ [Header("Debug")]
+ public bool debugGUI;
+
+ void Awake()
+ {
+ // TODO simplify after converting Mirror Transport events to Action
+ client = new KcpClient(
+ () => OnClientConnected.Invoke(),
+ (message) => OnClientDataReceived.Invoke(message, Channels.DefaultReliable),
+ () => OnClientDisconnected.Invoke()
+ );
+ // TODO simplify after converting Mirror Transport events to Action
+ server = new KcpServer(
+ (connectionId) => OnServerConnected.Invoke(connectionId),
+ (connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.DefaultReliable),
+ (connectionId) => OnServerDisconnected.Invoke(connectionId),
+ NoDelay,
+ Interval,
+ FastResend,
+ CongestionWindow,
+ SendWindowSize,
+ ReceiveWindowSize
+ );
+ Debug.Log("KcpTransport initialized!");
+ }
+
+ // all except WebGL
+ public override bool Available() =>
+ Application.platform != RuntimePlatform.WebGLPlayer;
+
+ // client
+ public override bool ClientConnected() => client.connected;
+ public override void ClientConnect(string address)
+ {
+ client.Connect(address, Port, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize);
+ }
+ public override void ClientSend(int channelId, ArraySegment segment)
+ {
+ client.Send(segment);
+ }
+ public override void ClientDisconnect() => client.Disconnect();
+
+ // IMPORTANT: set script execution order to >1000 to call Transport's
+ // LateUpdate after all others. Fixes race condition where
+ // e.g. in uSurvival Transport would apply Cmds before
+ // ShoulderRotation.LateUpdate, resulting in projectile
+ // spawns at the point before shoulder rotation.
+ public void LateUpdate()
+ {
+ // note: we need to check enabled in case we set it to false
+ // when LateUpdate already started.
+ // (https://github.com/vis2k/Mirror/pull/379)
+ if (!enabled)
+ return;
+
+ server.Tick();
+ client.Tick();
+ }
+
+ // server
+ public override Uri ServerUri()
+ {
+ UriBuilder builder = new UriBuilder();
+ builder.Scheme = Scheme;
+ builder.Host = Dns.GetHostName();
+ builder.Port = Port;
+ return builder.Uri;
+ }
+ public override bool ServerActive() => server.IsActive();
+ public override void ServerStart() => server.Start(Port);
+ public override void ServerSend(int connectionId, int channelId, ArraySegment segment)
+ {
+ server.Send(connectionId, segment);
+ }
+ public override bool ServerDisconnect(int connectionId)
+ {
+ server.Disconnect(connectionId);
+ return true;
+ }
+ public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
+ public override void ServerStop() => server.Stop();
+
+ // common
+ public override void Shutdown() {}
+
+ // MTU
+ public override int GetMaxPacketSize(int channelId = Channels.DefaultReliable) => Kcp.MTU_DEF;
+
+ public override string ToString()
+ {
+ return "KCP";
+ }
+
+ int GetTotalSendQueue() =>
+ server.connections.Values.Sum(conn => conn.kcp.snd_queue.Count);
+ int GetTotalReceiveQueue() =>
+ server.connections.Values.Sum(conn => conn.kcp.rcv_queue.Count);
+ int GetTotalSendBuffer() =>
+ server.connections.Values.Sum(conn => conn.kcp.snd_buf.Count);
+ int GetTotalReceiveBuffer() =>
+ server.connections.Values.Sum(conn => conn.kcp.rcv_buf.Count);
+
+ void OnGUI()
+ {
+ if (!debugGUI) return;
+
+ GUILayout.BeginArea(new Rect(5, 100, 300, 300));
+
+ if (ServerActive())
+ {
+ GUILayout.BeginVertical("Box");
+ GUILayout.Label("SERVER");
+ GUILayout.Label(" connections: " + server.connections.Count);
+ GUILayout.Label(" SendQueue: " + GetTotalSendQueue());
+ GUILayout.Label(" ReceiveQueue: " + GetTotalReceiveQueue());
+ GUILayout.Label(" SendBuffer: " + GetTotalSendBuffer());
+ GUILayout.Label(" ReceiveBuffer: " + GetTotalReceiveBuffer());
+ GUILayout.EndVertical();
+ }
+
+ if (ClientConnected())
+ {
+ GUILayout.BeginVertical("Box");
+ GUILayout.Label("CLIENT");
+ GUILayout.Label(" SendQueue: " + client.connection.kcp.snd_queue.Count);
+ GUILayout.Label(" ReceiveQueue: " + client.connection.kcp.rcv_queue.Count);
+ GUILayout.Label(" SendBuffer: " + client.connection.kcp.snd_buf.Count);
+ GUILayout.Label(" ReceiveBuffer: " + client.connection.kcp.rcv_buf.Count);
+ GUILayout.EndVertical();
+ }
+
+ GUILayout.EndArea();
+ }
+ }
+}
+#endif
diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta
new file mode 100644
index 000000000..f7280c8b4
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6b0fecffa3f624585964b0d0eb21b18e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
new file mode 100644
index 000000000..1dceadfe5
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 71a1c8e8c022d4731a481c1808f37e5d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
new file mode 100755
index 000000000..c77582e85
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE
@@ -0,0 +1,24 @@
+MIT License
+
+Copyright (c) 2016 limpo1989
+Copyright (c) 2020 Paul Pacheco
+Copyright (c) 2020 Lymdun
+Copyright (c) 2020 vis2k
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
new file mode 100644
index 000000000..49dc767e9
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9a3e8369060cf4e94ac117603de47aa6
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
new file mode 100755
index 000000000..40ab2cbb3
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION
@@ -0,0 +1,9 @@
+V1.1
+- high level cleanup, fixes, improvements
+
+V1.0
+- Kcp.cs now mirrors original Kcp.c behaviour
+ (this fixes dozens of bugs)
+
+V0.1
+- initial kcp-csharp based version
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta
new file mode 100644
index 000000000..2a07daa5c
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ed3f2cf1bbf1b4d53a6f2c103d311f71
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta
new file mode 100644
index 000000000..1c11c3d32
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5a54d18b954cb4407a28b633fc32ea6d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs
new file mode 100644
index 000000000..cdff96c4f
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs
@@ -0,0 +1,95 @@
+// kcp client logic abstracted into a class.
+// for use in Mirror, DOTSNET, testing, etc.
+using System;
+using UnityEngine;
+
+namespace kcp2k
+{
+ public class KcpClient
+ {
+ // events
+ public Action OnConnected;
+ public Action> OnData;
+ public Action OnDisconnected;
+
+ // state
+ public KcpClientConnection connection;
+ public bool connected;
+
+ public KcpClient(Action OnConnected, Action> OnData, Action OnDisconnected)
+ {
+ this.OnConnected = OnConnected;
+ this.OnData = OnData;
+ this.OnDisconnected = OnDisconnected;
+ }
+
+ public void Connect(string address, ushort port, bool noDelay, uint interval, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV)
+ {
+ if (connected)
+ {
+ Debug.LogWarning("KCP: client already connected!");
+ return;
+ }
+
+ connection = new KcpClientConnection();
+
+ // setup events
+ connection.OnAuthenticated = () =>
+ {
+ Debug.Log($"KCP: OnClientConnected");
+ connected = true;
+ OnConnected.Invoke();
+ };
+ connection.OnData = (message) =>
+ {
+ //Debug.Log($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
+ OnData.Invoke(message);
+ };
+ connection.OnDisconnected = () =>
+ {
+ Debug.Log($"KCP: OnClientDisconnected");
+ connected = false;
+ connection = null;
+ OnDisconnected.Invoke();
+ };
+
+ // connect
+ connection.Connect(address, port, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize);
+ }
+
+ public void Send(ArraySegment segment)
+ {
+ if (connected)
+ {
+ connection.Send(segment);
+ }
+ else Debug.LogWarning("KCP: can't send because client not connected!");
+ }
+
+ public void Disconnect()
+ {
+ // only if connected
+ // otherwise we end up in a deadlock because of an open Mirror bug:
+ // https://github.com/vis2k/Mirror/issues/2353
+ if (connected)
+ {
+ // call Disconnect and let the connection handle it.
+ // DO NOT set it to null yet. it needs to be updated a few more
+ // times first. let the connection handle it!
+ connection?.Disconnect();
+ }
+ }
+
+ public void Tick()
+ {
+ // tick client connection
+ if (connection != null)
+ {
+ // recv on socket first
+ connection.RawReceive();
+ // then update
+ connection.Tick();
+ }
+ }
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta
new file mode 100644
index 000000000..e55306b45
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6aa069a28ed24fedb533c102d9742b36
+timeCreated: 1603786960
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs
new file mode 100644
index 000000000..5be131520
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs
@@ -0,0 +1,59 @@
+using UnityEngine;
+using System.Net;
+using System.Net.Sockets;
+
+namespace kcp2k
+{
+ public class KcpClientConnection : KcpConnection
+ {
+ readonly byte[] buffer = new byte[1500];
+
+ public void Connect(string host, ushort port, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV)
+ {
+ Debug.Log($"KcpClient: connect to {host}:{port}");
+ IPAddress[] ipAddress = Dns.GetHostAddresses(host);
+ if (ipAddress.Length < 1)
+ throw new SocketException((int)SocketError.HostNotFound);
+
+ remoteEndpoint = new IPEndPoint(ipAddress[0], port);
+ socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
+ socket.Connect(remoteEndpoint);
+ SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize);
+
+ // client should send handshake to server as very first message
+ SendHandshake();
+
+ RawReceive();
+ }
+
+ // call from transport update
+ public void RawReceive()
+ {
+ try
+ {
+ if (socket != null)
+ {
+ while (socket.Poll(0, SelectMode.SelectRead))
+ {
+ int msgLength = socket.ReceiveFrom(buffer, ref remoteEndpoint);
+ //Debug.Log($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
+ RawInput(buffer, msgLength);
+ }
+ }
+ }
+ // this is fine, the socket might have been closed in the other end
+ catch (SocketException) {}
+ }
+
+ protected override void Dispose()
+ {
+ socket.Close();
+ socket = null;
+ }
+
+ protected override void RawSend(byte[] data, int length)
+ {
+ socket.Send(data, length, SocketFlags.None);
+ }
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta
new file mode 100644
index 000000000..336991847
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 96512e74aa8214a6faa8a412a7a07877
+timeCreated: 1602601237
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs
new file mode 100644
index 000000000..87806b358
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs
@@ -0,0 +1,360 @@
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using Debug = UnityEngine.Debug;
+
+namespace kcp2k
+{
+ enum KcpState { Connected, Authenticated, Disconnected }
+
+ public abstract class KcpConnection
+ {
+ protected Socket socket;
+ protected EndPoint remoteEndpoint;
+ internal Kcp kcp;
+
+ // kcp can have several different states, let's use a state machine
+ KcpState state = KcpState.Disconnected;
+
+ public Action OnAuthenticated;
+ public Action> OnData;
+ public Action OnDisconnected;
+
+ // If we don't receive anything these many milliseconds
+ // then consider us disconnected
+ public const int TIMEOUT = 10000;
+ uint lastReceiveTime;
+
+ // internal time.
+ // StopWatch offers ElapsedMilliSeconds and should be more precise than
+ // Unity's time.deltaTime over long periods.
+ readonly Stopwatch refTime = new Stopwatch();
+
+ // recv buffer to avoid allocations
+ byte[] buffer = new byte[Kcp.MTU_DEF];
+
+ internal static readonly ArraySegment Hello = new ArraySegment(new byte[] { 0 });
+ static readonly ArraySegment Goodbye = new ArraySegment(new byte[] { 1 });
+ static readonly ArraySegment Ping = new ArraySegment(new byte[] { 2 });
+
+ // send a ping occasionally so we don't time out on the other end.
+ // for example, creating a character in an MMO could easily take a
+ // minute of no data being sent. which doesn't mean we want to time out.
+ // same goes for slow paced card games etc.
+ public const int PING_INTERVAL = 1000;
+ uint lastPingTime;
+
+ // if we send more than kcp can handle, we will get ever growing
+ // send/recv buffers and queues and minutes of latency.
+ // => if a connection can't keep up, it should be disconnected instead
+ // to protect the server under heavy load, and because there is no
+ // point in growing to gigabytes of memory or minutes of latency!
+ // => 2k isn't enough. we reach 2k when spawning 4k monsters at once
+ // easily, but it does recover over time.
+ // => 10k seems safe.
+ //
+ // note: we have a ChokeConnectionAutoDisconnects test for this too!
+ internal const int QueueDisconnectThreshold = 10000;
+
+ // NoDelay, interval, window size are the most important configurations.
+ // let's force require the parameters so we don't forget it anywhere.
+ protected void SetupKcp(bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV)
+ {
+ kcp = new Kcp(0, RawSend);
+ // set nodelay.
+ // note that kcp uses 'nocwnd' internally so we negate the parameter
+ kcp.SetNoDelay(noDelay ? 1u : 0u, interval, fastResend, !congestionWindow);
+ kcp.SetWindowSize(sendWindowSize, receiveWindowSize);
+ refTime.Start();
+ state = KcpState.Connected;
+
+ Tick();
+ }
+
+ void HandleTimeout(uint time)
+ {
+ // note: we are also sending a ping regularly, so timeout should
+ // only ever happen if the connection is truly gone.
+ if (time >= lastReceiveTime + TIMEOUT)
+ {
+ Debug.LogWarning($"KCP: Connection timed out after {TIMEOUT}ms. Disconnecting.");
+ Disconnect();
+ }
+ }
+
+ void HandleDeadLink()
+ {
+ // kcp has 'dead_link' detection. might as well use it.
+ if (kcp.state == -1)
+ {
+ Debug.LogWarning("KCP Connection dead_link detected. Disconnecting.");
+ Disconnect();
+ }
+ }
+
+ // send a ping occasionally in order to not time out on the other end.
+ void HandlePing(uint time)
+ {
+ // enough time elapsed since last ping?
+ if (time >= lastPingTime + PING_INTERVAL)
+ {
+ // ping again and reset time
+ //Debug.Log("KCP: sending ping...");
+ Send(Ping);
+ lastPingTime = time;
+ }
+ }
+
+ void HandleChoked()
+ {
+ // disconnect connections that can't process the load.
+ // see QueueSizeDisconnect comments.
+ int total = kcp.rcv_queue.Count + kcp.snd_queue.Count +
+ kcp.rcv_buf.Count + kcp.snd_buf.Count;
+ if (total >= QueueDisconnectThreshold)
+ {
+ Debug.LogWarning($"KCP: disconnecting connection because it can't process data fast enough.\n" +
+ $"Queue total {total}>{QueueDisconnectThreshold}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" +
+ $"* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n" +
+ $"* Or perhaps the network is simply too slow on our end, or on the other end.\n");
+
+ // let's clear all pending sends before disconnting with 'Bye'.
+ // otherwise a single Flush in Disconnect() won't be enough to
+ // flush thousands of messages to finally deliver 'Bye'.
+ // this is just faster and more robust.
+ kcp.snd_queue.Clear();
+
+ Disconnect();
+ }
+ }
+
+ // reads the next message from connection.
+ bool ReceiveNext(out ArraySegment message)
+ {
+ // read only one message
+ int msgSize = kcp.PeekSize();
+ if (msgSize > 0)
+ {
+ // only allow receiving up to MaxMessageSize sized messages.
+ // otherwise we would get BlockCopy ArgumentException anyway.
+ if (msgSize <= Kcp.MTU_DEF)
+ {
+ int received = kcp.Receive(buffer, msgSize);
+ if (received >= 0)
+ {
+ message = new ArraySegment(buffer, 0, msgSize);
+ lastReceiveTime = (uint)refTime.ElapsedMilliseconds;
+
+ // return false if it was a ping message. true otherwise.
+ if (Utils.SegmentsEqual(message, Ping))
+ {
+ //Debug.Log("KCP: received ping.");
+ return false;
+ }
+ return true;
+ }
+ else
+ {
+ // if receive failed, close everything
+ Debug.LogWarning($"Receive failed with error={received}. closing connection.");
+ Disconnect();
+ }
+ }
+ // we don't allow sending messages > Max, so this must be an
+ // attacker. let's disconnect to avoid allocation attacks etc.
+ else
+ {
+ Debug.LogWarning($"KCP: possible allocation attack for msgSize {msgSize} > max {Kcp.MTU_DEF}. Disconnecting the connection.");
+ Disconnect();
+ }
+ }
+ return false;
+ }
+
+ void TickConnected(uint time)
+ {
+ // detect common events & ping
+ HandleTimeout(time);
+ HandleDeadLink();
+ HandlePing(time);
+ HandleChoked();
+
+ kcp.Update(time);
+
+ // any message received?
+ if (ReceiveNext(out ArraySegment message))
+ {
+ // handshake message?
+ if (Utils.SegmentsEqual(message, Hello))
+ {
+ Debug.Log("KCP: received handshake");
+ state = KcpState.Authenticated;
+ OnAuthenticated?.Invoke();
+ }
+ // otherwise it's random data from the internet, not
+ // from a legitimate player. disconnect.
+ else
+ {
+ Debug.LogWarning("KCP: received random data before handshake. Disconnecting the connection.");
+ Disconnect();
+ }
+ }
+ }
+
+ void TickAuthenticated(uint time)
+ {
+ // detect common events & ping
+ HandleTimeout(time);
+ HandleDeadLink();
+ HandlePing(time);
+ HandleChoked();
+
+ kcp.Update(time);
+
+ // process all received messages
+ while (ReceiveNext(out ArraySegment message))
+ {
+ // disconnect message?
+ if (Utils.SegmentsEqual(message, Goodbye))
+ {
+ Debug.Log("KCP: received disconnect message");
+ Disconnect();
+ break;
+ }
+ // otherwise regular message
+ else
+ {
+ // only accept regular messages
+ //Debug.LogWarning($"Kcp recv msg: {BitConverter.ToString(buffer, 0, msgSize)}");
+ OnData?.Invoke(message);
+ }
+ }
+ }
+
+ public void Tick()
+ {
+ uint time = (uint)refTime.ElapsedMilliseconds;
+
+ try
+ {
+ switch (state)
+ {
+ case KcpState.Connected:
+ {
+ TickConnected(time);
+ break;
+ }
+ case KcpState.Authenticated:
+ {
+ TickAuthenticated(time);
+ break;
+ }
+ case KcpState.Disconnected:
+ {
+ // do nothing while disconnected
+ break;
+ }
+ }
+ }
+ catch (SocketException exception)
+ {
+ // this is ok, the connection was closed
+ Debug.Log($"KCP Connection: Disconnecting because {exception}. This is fine.");
+ Disconnect();
+ }
+ catch (ObjectDisposedException exception)
+ {
+ // fine, socket was closed
+ Debug.Log($"KCP Connection: Disconnecting because {exception}. This is fine.");
+ Disconnect();
+ }
+ catch (Exception ex)
+ {
+ // unexpected
+ Debug.LogException(ex);
+ Disconnect();
+ }
+ }
+
+ public void RawInput(byte[] buffer, int msgLength)
+ {
+ int input = kcp.Input(buffer, msgLength);
+ if (input != 0)
+ {
+ Debug.LogWarning($"Input failed with error={input} for buffer with length={msgLength}");
+ }
+ }
+
+ protected abstract void RawSend(byte[] data, int length);
+
+ public void Send(ArraySegment data)
+ {
+ // only allow sending up to MaxMessageSize sized messages.
+ // other end won't process bigger messages anyway.
+ if (data.Count <= Kcp.MTU_DEF)
+ {
+ int sent = kcp.Send(data.Array, data.Offset, data.Count);
+ if (sent < 0)
+ {
+ Debug.LogWarning($"Send failed with error={sent} for segment with length={data.Count}");
+ }
+ }
+ else Debug.LogError($"Failed to send message of size {data.Count} because it's larger than MaxMessageSize={Kcp.MTU_DEF}");
+ }
+
+ // server & client need to send handshake at different times, so we need
+ // to expose the function.
+ // * client should send it immediately.
+ // * server should send it as reply to client's handshake, not before
+ // (server should not reply to random internet messages with handshake)
+ public void SendHandshake()
+ {
+ Debug.Log("KcpConnection: sending Handshake to other end!");
+ Send(Hello);
+ }
+
+ protected virtual void Dispose()
+ {
+ }
+
+ // disconnect this connection
+ public void Disconnect()
+ {
+ // only if not disconnected yet
+ if (state == KcpState.Disconnected)
+ return;
+
+ // send a disconnect message
+ if (socket.Connected)
+ {
+ try
+ {
+ Send(Goodbye);
+ kcp.Flush();
+ }
+ catch (SocketException)
+ {
+ // this is ok, the connection was already closed
+ }
+ catch (ObjectDisposedException)
+ {
+ // this is normal when we stop the server
+ // the socket is stopped so we can't send anything anymore
+ // to the clients
+
+ // the clients will eventually timeout and realize they
+ // were disconnected
+ }
+ }
+
+ // set as Disconnected, call event
+ Debug.Log("KCP Connection: Disconnected.");
+ state = KcpState.Disconnected;
+ OnDisconnected?.Invoke();
+ }
+
+ // get remote endpoint
+ public EndPoint GetRemoteEndPoint() => remoteEndpoint;
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta
new file mode 100644
index 000000000..fa5dcff95
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3915c7c62b72d4dc2a9e4e76c94fc484
+timeCreated: 1602600432
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs
new file mode 100644
index 000000000..a6caf0a9a
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs
@@ -0,0 +1,229 @@
+// kcp server logic abstracted into a class.
+// for use in Mirror, DOTSNET, testing, etc.
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using UnityEngine;
+
+namespace kcp2k
+{
+ public class KcpServer
+ {
+ // events
+ public Action OnConnected;
+ public Action> OnData;
+ public Action OnDisconnected;
+
+ // configuration
+ // NoDelay is recommended to reduce latency. This also scales better
+ // without buffers getting full.
+ public bool NoDelay;
+ // KCP internal update interval. 100ms is KCP default, but a lower
+ // interval is recommended to minimize latency and to scale to more
+ // networked entities.
+ public uint Interval;
+ // KCP fastresend parameter. Faster resend for the cost of higher
+ // bandwidth.
+ public int FastResend;
+ // KCP 'NoCongestionWindow' is false by default. here we negate it for
+ // ease of use. This can be disabled for high scale games if connections
+ // choke regularly.
+ public bool CongestionWindow;
+ // KCP window size can be modified to support higher loads.
+ // for example, Mirror Benchmark requires:
+ // 128, 128 for 4k monsters
+ // 512, 512 for 10k monsters
+ // 8192, 8192 for 20k monsters
+ public uint SendWindowSize;
+ public uint ReceiveWindowSize;
+
+ // state
+ Socket socket;
+ EndPoint newClientEP = new IPEndPoint(IPAddress.IPv6Any, 0);
+ readonly byte[] buffer = new byte[Kcp.MTU_DEF];
+
+ // connections where connectionId is EndPoint.GetHashCode
+ public Dictionary connections = new Dictionary();
+
+ public KcpServer(Action OnConnected,
+ Action> OnData,
+ Action OnDisconnected,
+ bool NoDelay,
+ uint Interval,
+ int FastResend = 0,
+ bool CongestionWindow = true,
+ uint SendWindowSize = Kcp.WND_SND,
+ uint ReceiveWindowSize = Kcp.WND_RCV)
+ {
+ this.OnConnected = OnConnected;
+ this.OnData = OnData;
+ this.OnDisconnected = OnDisconnected;
+ this.NoDelay = NoDelay;
+ this.Interval = Interval;
+ this.FastResend = FastResend;
+ this.CongestionWindow = CongestionWindow;
+ this.SendWindowSize = SendWindowSize;
+ this.ReceiveWindowSize = ReceiveWindowSize;
+ }
+
+ public bool IsActive() => socket != null;
+
+ public void Start(ushort port)
+ {
+ // only start once
+ if (socket != null)
+ {
+ Debug.LogWarning("KCP: server already started!");
+ }
+
+ // listen
+ socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+ socket.DualMode = true;
+ socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
+ }
+
+ public void Send(int connectionId, ArraySegment segment)
+ {
+ if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
+ {
+ connection.Send(segment);
+ }
+ }
+ public void Disconnect(int connectionId)
+ {
+ if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
+ {
+ connection.Disconnect();
+ }
+ }
+
+ public string GetClientAddress(int connectionId)
+ {
+ if (connections.TryGetValue(connectionId, out KcpServerConnection connection))
+ {
+ return (connection.GetRemoteEndPoint() as IPEndPoint).Address.ToString();
+ }
+ return "";
+ }
+
+ HashSet connectionsToRemove = new HashSet();
+ public void Tick()
+ {
+ while (socket != null && socket.Poll(0, SelectMode.SelectRead))
+ {
+ int msgLength = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP);
+ //Debug.Log($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}");
+
+ // calculate connectionId from endpoint
+ int connectionId = newClientEP.GetHashCode();
+
+ // is this a new connection?
+ if (!connections.TryGetValue(connectionId, out KcpServerConnection connection))
+ {
+ // create a new KcpConnection
+ connection = new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize);
+
+ // DO NOT add to connections yet. only if the first message
+ // is actually the kcp handshake. otherwise it's either:
+ // * random data from the internet
+ // * or from a client connection that we just disconnected
+ // but that hasn't realized it yet, still sending data
+ // from last session that we should absolutely ignore.
+ //
+ //
+ // TODO this allocates a new KcpConnection for each new
+ // internet connection. not ideal, but C# UDP Receive
+ // already allocated anyway.
+ //
+ // expecting a MAGIC byte[] would work, but sending the raw
+ // UDP message without kcp's reliability will have low
+ // probability of being received.
+ //
+ // for now, this is fine.
+
+ // setup authenticated event that also adds to connections
+ connection.OnAuthenticated = () =>
+ {
+ // only send handshake to client AFTER we received his
+ // handshake in OnAuthenticated.
+ // we don't want to reply to random internet messages
+ // with handshakes each time.
+ connection.SendHandshake();
+
+ // add to connections dict after being authenticated.
+ connections.Add(connectionId, connection);
+ Debug.Log($"KCP: server added connection({connectionId}): {newClientEP}");
+
+ // setup Data + Disconnected events only AFTER the
+ // handshake. we don't want to fire OnServerDisconnected
+ // every time we receive invalid random data from the
+ // internet.
+
+ // setup data event
+ connection.OnData = (message) =>
+ {
+ // call mirror event
+ //Debug.Log($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})");
+ OnData.Invoke(connectionId, message);
+ };
+
+ // setup disconnected event
+ connection.OnDisconnected = () =>
+ {
+ // flag for removal
+ // (can't remove directly because connection is updated
+ // and event is called while iterating all connections)
+ connectionsToRemove.Add(connectionId);
+
+ // call mirror event
+ Debug.Log($"KCP: OnServerDisconnected({connectionId})");
+ OnDisconnected.Invoke(connectionId);
+ };
+
+ // finally, call mirror OnConnected event
+ Debug.Log($"KCP: OnServerConnected({connectionId})");
+ OnConnected.Invoke(connectionId);
+ };
+
+ // now input the message & tick
+ // connected event was set up.
+ // tick will process the first message and adds the
+ // connection if it was the handshake.
+ connection.RawInput(buffer, msgLength);
+ connection.Tick();
+
+ // again, do not add to connections.
+ // if the first message wasn't the kcp handshake then
+ // connection will simply be garbage collected.
+ }
+ // existing connection: simply input the message into kcp
+ else
+ {
+ connection.RawInput(buffer, msgLength);
+ }
+ }
+
+ // tick all server connections
+ foreach (KcpServerConnection connection in connections.Values)
+ {
+ connection.Tick();
+ }
+
+ // remove disconnected connections
+ // (can't do it in connection.OnDisconnected because Tick is called
+ // while iterating connections)
+ foreach (int connectionId in connectionsToRemove)
+ {
+ connections.Remove(connectionId);
+ }
+ connectionsToRemove.Clear();
+ }
+
+ public void Stop()
+ {
+ socket?.Close();
+ socket = null;
+ }
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta
new file mode 100644
index 000000000..ef720d4cd
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9759159c6589494a9037f5e130a867ed
+timeCreated: 1603787747
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs
new file mode 100644
index 000000000..bd2358efe
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs
@@ -0,0 +1,20 @@
+using System.Net;
+using System.Net.Sockets;
+
+namespace kcp2k
+{
+ public class KcpServerConnection : KcpConnection
+ {
+ public KcpServerConnection(Socket socket, EndPoint remoteEndpoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV)
+ {
+ this.socket = socket;
+ this.remoteEndpoint = remoteEndpoint;
+ SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize);
+ }
+
+ protected override void RawSend(byte[] data, int length)
+ {
+ socket.SendTo(data, 0, length, SocketFlags.None, remoteEndpoint);
+ }
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta
new file mode 100644
index 000000000..10d9803d8
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 80a9b1ce9a6f14abeb32bfa9921d097b
+timeCreated: 1602601483
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Utils.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Utils.cs
new file mode 100644
index 000000000..b67268edf
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Utils.cs
@@ -0,0 +1,23 @@
+using System;
+using Unity.Collections.LowLevel.Unsafe;
+
+namespace kcp2k
+{
+ public static partial class Utils
+ {
+ // ArraySegment content comparison without Linq
+ public static unsafe bool SegmentsEqual(ArraySegment a, ArraySegment b)
+ {
+ if (a.Count == b.Count)
+ {
+ fixed (byte* aPtr = &a.Array[a.Offset],
+ bPtr = &b.Array[b.Offset])
+ {
+ return UnsafeUtility.MemCmp(aPtr, bPtr, a.Count) == 0;
+ }
+ }
+ return false;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Utils.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Utils.cs.meta
new file mode 100644
index 000000000..7b7136283
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Utils.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f3a2f1efc7864cb2b01af9d99470613a
+timeCreated: 1603833478
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta
new file mode 100644
index 000000000..a7d6e1122
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5cafb8851a0084f3e94a580c207b3923
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs
new file mode 100644
index 000000000..5fe5547e1
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("kcp2k.Tests")]
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs.meta
new file mode 100644
index 000000000..6b442a944
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: aec6a15ac7bd43129317ea1f01f19782
+timeCreated: 1602665988
\ No newline at end of file
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs
new file mode 100755
index 000000000..199926fd4
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs
@@ -0,0 +1,1025 @@
+// Kcp based on https://github.com/skywind3000/kcp
+// Kept as close to original as possible.
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace kcp2k
+{
+ public class Kcp
+ {
+ // original Kcp has a define option, which is not defined by default:
+ // #define FASTACK_CONSERVE
+
+ public const int RTO_NDL = 30; // no delay min rto
+ public const int RTO_MIN = 100; // normal min rto
+ public const int RTO_DEF = 200; // default RTO
+ public const int RTO_MAX = 60000; // maximum RTO
+ public const int CMD_PUSH = 81; // cmd: push data
+ public const int CMD_ACK = 82; // cmd: ack
+ public const int CMD_WASK = 83; // cmd: window probe (ask)
+ public const int CMD_WINS = 84; // cmd: window size (tell)
+ public const int ASK_SEND = 1; // need to send CMD_WASK
+ public const int ASK_TELL = 2; // need to send CMD_WINS
+ public const int WND_SND = 32; // default send window
+ public const int WND_RCV = 128; // default receive window. must be >= max fragment size
+ public const int MTU_DEF = 1200; // default MTU (reduced to 1200 to fit all cases: https://en.wikipedia.org/wiki/Maximum_transmission_unit ; steam uses 1200 too!)
+ public const int ACK_FAST = 3;
+ public const int INTERVAL = 100;
+ public const int OVERHEAD = 24;
+ public const int DEADLINK = 20;
+ public const int THRESH_INIT = 2;
+ public const int THRESH_MIN = 2;
+ public const int PROBE_INIT = 7000; // 7 secs to probe window size
+ public const int PROBE_LIMIT = 120000; // up to 120 secs to probe window
+ public const int FASTACK_LIMIT = 5; // max times to trigger fastack
+
+ internal struct AckItem
+ {
+ internal uint serialNumber;
+ internal uint timestamp;
+ }
+
+ // kcp members.
+ internal int state;
+ readonly uint conv; // conversation
+ internal uint mtu;
+ internal uint mss; // maximum segment size
+ internal uint snd_una; // unacknowledged
+ internal uint snd_nxt;
+ internal uint rcv_nxt;
+ internal uint ssthresh; // slow start threshold
+ internal int rx_rttval;
+ internal int rx_srtt; // smoothed round trip time
+ internal int rx_rto;
+ internal int rx_minrto;
+ internal uint snd_wnd; // send window
+ internal uint rcv_wnd; // receive window
+ internal uint rmt_wnd; // remote window
+ internal uint cwnd; // congestion window
+ internal uint probe;
+ internal uint interval;
+ internal uint ts_flush;
+ internal uint xmit;
+ internal uint nodelay; // not a bool. original Kcp has '<2 else' check.
+ internal bool updated;
+ internal uint ts_probe; // timestamp probe
+ internal uint probe_wait;
+ internal uint dead_link;
+ internal uint incr;
+ internal uint current; // current time (milliseconds). set by Update.
+
+ internal int fastresend;
+ internal int fastlimit;
+ internal bool nocwnd; // no congestion window
+ internal readonly Queue snd_queue = new Queue(16); // send queue
+ internal readonly Queue rcv_queue = new Queue(16); // receive queue
+ // snd_buffer needs index removals.
+ // C# LinkedList allocates for each entry, so let's keep List for now.
+ internal readonly List snd_buf = new List(16); // send buffer
+ // rcv_buffer needs index insertions and backwards iteration.
+ // C# LinkedList allocates for each entry, so let's keep List for now.
+ internal readonly List rcv_buf = new List(16); // receive buffer
+ internal readonly List acklist = new List(16);
+
+ internal byte[] buffer;
+ readonly Action output; // buffer, size
+
+ // get how many packet is waiting to be sent
+ public int WaitSnd => snd_buf.Count + snd_queue.Count;
+
+ // ikcp_create
+ // create a new kcp control object, 'conv' must equal in two endpoint
+ // from the same connection.
+ public Kcp(uint conv, Action output)
+ {
+ this.conv = conv;
+ this.output = output;
+ snd_wnd = WND_SND;
+ rcv_wnd = WND_RCV;
+ rmt_wnd = WND_RCV;
+ mtu = MTU_DEF;
+ mss = mtu - OVERHEAD;
+ rx_rto = RTO_DEF;
+ rx_minrto = RTO_MIN;
+ interval = INTERVAL;
+ ts_flush = INTERVAL;
+ ssthresh = THRESH_INIT;
+ fastlimit = FASTACK_LIMIT;
+ dead_link = DEADLINK;
+ buffer = new byte[(mtu + OVERHEAD) * 3];
+ }
+
+ // ikcp_segment_new
+ // we keep the original function and add our pooling to it.
+ // this way we'll never miss it anywhere.
+ static Segment SegmentNew()
+ {
+ return Segment.Take();
+ }
+
+ // ikcp_segment_delete
+ // we keep the original function and add our pooling to it.
+ // this way we'll never miss it anywhere.
+ static void SegmentDelete(Segment seg)
+ {
+ Segment.Return(seg);
+ }
+
+ // ikcp_recv
+ // receive data from kcp state machine
+ // returns number of bytes read.
+ // returns negative on error.
+ // note: pass negative length to peek.
+ public int Receive(byte[] buffer, int len)
+ {
+ // kcp's ispeek feature is not supported.
+ // this makes 'merge fragment' code significantly easier because
+ // we can iterate while queue.Count > 0 and dequeue each time.
+ // if we had to consider ispeek then count would always be > 0 and
+ // we would have to remove only after the loop.
+ //
+ //bool ispeek = len < 0;
+ if (len < 0)
+ throw new NotSupportedException("Receive ispeek for negative len is not supported!");
+
+ if (rcv_queue.Count == 0)
+ return -1;
+
+ if (len < 0) len = -len;
+
+ int peeksize = PeekSize();
+
+ if (peeksize < 0)
+ return -2;
+
+ if (peeksize > len)
+ return -3;
+
+ bool recover = rcv_queue.Count >= rcv_wnd;
+
+ // merge fragment.
+ int offset = 0;
+ len = 0;
+ // original KCP iterates rcv_queue and deletes if !ispeek.
+ // removing from a c# queue while iterating is not possible, but
+ // we can change to 'while Count > 0' and remove every time.
+ // (we can remove every time because we removed ispeek support!)
+ while (rcv_queue.Count > 0)
+ {
+ // unlike original kcp, we dequeue instead of just getting the
+ // entry. this is fine because we remove it in ANY case.
+ Segment seg = rcv_queue.Dequeue();
+
+ Buffer.BlockCopy(seg.data.GetBuffer(), 0, buffer, offset, (int)seg.data.Position);
+ offset += (int)seg.data.Position;
+
+ len += (int)seg.data.Position;
+ uint fragment = seg.frg;
+
+ // note: ispeek is not supported in order to simplify this loop
+
+ // unlike original kcp, we don't need to remove seg from queue
+ // because we already dequeued it.
+ // simply delete it
+ SegmentDelete(seg);
+
+ if (fragment == 0)
+ break;
+ }
+
+ // move available data from rcv_buf -> rcv_queue
+ int removed = 0;
+ foreach (Segment seg in rcv_buf)
+ {
+ if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd)
+ {
+ // can't remove while iterating. remember how many to remove
+ // and do it after the loop.
+ // note: don't return segment. we only add it to rcv_queue
+ ++removed;
+ // add
+ rcv_queue.Enqueue(seg);
+ rcv_nxt++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ rcv_buf.RemoveRange(0, removed);
+
+ // fast recover
+ if (rcv_queue.Count < rcv_wnd && recover)
+ {
+ // ready to send back CMD_WINS in flush
+ // tell remote my window size
+ probe |= ASK_TELL;
+ }
+
+ return len;
+ }
+
+ // ikcp_peeksize
+ // check the size of next message in the recv queue
+ public int PeekSize()
+ {
+ int length = 0;
+
+ if (rcv_queue.Count == 0) return -1;
+
+ Segment seq = rcv_queue.Peek();
+ if (seq.frg == 0) return (int)seq.data.Position;
+
+ if (rcv_queue.Count < seq.frg + 1) return -1;
+
+ foreach (Segment seg in rcv_queue)
+ {
+ length += (int)seg.data.Position;
+ if (seg.frg == 0) break;
+ }
+
+ return length;
+ }
+
+ // ikcp_send
+ // sends byte[] to the other end.
+ public int Send(byte[] buffer, int offset, int len)
+ {
+ int count;
+
+ if (len < 0) return -1;
+
+ // streaming mode: removed. we never want to send 'hello' and
+ // receive 'he' 'll' 'o'. we want to always receive 'hello'.
+
+ if (len <= mss) count = 1;
+ else count = (int)((len + mss - 1) / mss);
+
+ // this might be a kcp bug.
+ // it's possible that we should check 'count >= rcv_wnd' instead of
+ // the constant here.
+ // see also: https://github.com/skywind3000/kcp/pull/291/files
+ if (count >= WND_RCV) return -2;
+
+ if (count == 0) count = 1;
+
+ // fragment
+ for (int i = 0; i < count; i++)
+ {
+ int size = len > (int)mss ? (int)mss : len;
+ Segment seg = SegmentNew();
+
+ if (len > 0)
+ {
+ seg.data.Write(buffer, offset, size);
+ }
+ // seg.len = size: WriteBytes sets segment.Position!
+ seg.frg = (byte)(count - i - 1);
+ snd_queue.Enqueue(seg);
+ offset += size;
+ len -= size;
+ }
+
+ return 0;
+ }
+
+ // ikcp_update_ack
+ void UpdateAck(int rtt) // round trip time
+ {
+ // https://tools.ietf.org/html/rfc6298
+ if (rx_srtt == 0)
+ {
+ rx_srtt = rtt;
+ rx_rttval = rtt / 2;
+ }
+ else
+ {
+ int delta = rtt - rx_srtt;
+ if (delta < 0) delta = -delta;
+ rx_rttval = (3 * rx_rttval + delta) / 4;
+ rx_srtt = (7 * rx_srtt + rtt) / 8;
+ if (rx_srtt < 1) rx_srtt = 1;
+ }
+ int rto = rx_srtt + Math.Max((int)interval, 4 * rx_rttval);
+ rx_rto = Mathf.Clamp(rto, rx_minrto, RTO_MAX);
+ }
+
+ // ikcp_shrink_buf
+ internal void ShrinkBuf()
+ {
+ if (snd_buf.Count > 0)
+ {
+ Segment seg = snd_buf[0];
+ snd_una = seg.sn;
+ }
+ else
+ {
+ snd_una = snd_nxt;
+ }
+ }
+
+ // ikcp_parse_ack
+ // removes the segment with 'sn' from send buffer
+ internal void ParseAck(uint sn)
+ {
+ if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0)
+ return;
+
+ // for-int so we can erase while iterating
+ for (int i = 0; i < snd_buf.Count; ++i)
+ {
+ Segment seg = snd_buf[i];
+ if (sn == seg.sn)
+ {
+ snd_buf.RemoveAt(i);
+ SegmentDelete(seg);
+ break;
+ }
+ if (Utils.TimeDiff(sn, seg.sn) < 0)
+ {
+ break;
+ }
+ }
+ }
+
+ // ikcp_parse_una
+ void ParseUna(uint una)
+ {
+ int removed = 0;
+ foreach (Segment seg in snd_buf)
+ {
+ if (Utils.TimeDiff(una, seg.sn) > 0)
+ {
+ // can't remove while iterating. remember how many to remove
+ // and do it after the loop.
+ ++removed;
+ SegmentDelete(seg);
+ }
+ else
+ {
+ break;
+ }
+ }
+ snd_buf.RemoveRange(0, removed);
+ }
+
+ // ikcp_parse_fastack
+ void ParseFastack(uint sn, uint ts)
+ {
+ if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0)
+ return;
+
+ foreach (Segment seg in snd_buf)
+ {
+ if (Utils.TimeDiff(sn, seg.sn) < 0)
+ {
+ break;
+ }
+ else if (sn != seg.sn)
+ {
+#if !FASTACK_CONSERVE
+ seg.fastack++;
+#else
+ if (Utils.TimeDiff(ts, seg.ts) >= 0)
+ seg.fastack++;
+#endif
+ }
+ }
+ }
+
+ // ikcp_ack_push
+ // appends an ack.
+ void AckPush(uint sn, uint ts)
+ {
+ acklist.Add(new AckItem{ serialNumber = sn, timestamp = ts });
+ }
+
+ // ikcp_parse_data
+ void ParseData(Segment newseg)
+ {
+ uint sn = newseg.sn;
+
+ if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) >= 0 ||
+ Utils.TimeDiff(sn, rcv_nxt) < 0)
+ {
+ SegmentDelete(newseg);
+ return;
+ }
+
+ InsertSegmentInReceiveBuffer(newseg);
+ MoveReceiveBufferDataToReceiveQueue();
+ }
+
+ // inserts the segment into rcv_buf, ordered by seg.sn.
+ // drops the segment if one with the same seg.sn already exists.
+ // goes through receive buffer in reverse order for performance.
+ //
+ // note: see KcpTests.InsertSegmentInReceiveBuffer test!
+ // note: 'insert or delete' can be done in different ways, but let's
+ // keep consistency with original C kcp.
+ internal void InsertSegmentInReceiveBuffer(Segment newseg)
+ {
+ bool repeat = false; // 'duplicate'
+
+ // original C iterates backwards, so we need to do that as well.
+ int i;
+ for (i = rcv_buf.Count - 1; i >= 0; i--)
+ {
+ Segment seg = rcv_buf[i];
+ if (seg.sn == newseg.sn)
+ {
+ // duplicate segment found. nothing will be added.
+ repeat = true;
+ break;
+ }
+ if (Utils.TimeDiff(newseg.sn, seg.sn) > 0)
+ {
+ // this entry's sn is < newseg.sn, so let's stop
+ break;
+ }
+ }
+
+ // no duplicate? then insert.
+ if (!repeat)
+ {
+ rcv_buf.Insert(i + 1, newseg);
+ }
+ // duplicate. just delete it.
+ else
+ {
+ SegmentDelete(newseg);
+ }
+ }
+
+ // move available data from rcv_buf -> rcv_queue
+ void MoveReceiveBufferDataToReceiveQueue()
+ {
+ int removed = 0;
+ foreach (Segment seg in rcv_buf)
+ {
+ if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd)
+ {
+ // can't remove while iterating. remember how many to remove
+ // and do it after the loop.
+ ++removed;
+ rcv_queue.Enqueue(seg);
+ rcv_nxt++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ rcv_buf.RemoveRange(0, removed);
+ }
+
+ // ikcp_input
+ /// used when you receive a low level packet (eg. UDP packet)
+ public int Input(byte[] data, int size)
+ {
+ uint prev_una = snd_una;
+ uint maxack = 0;
+ uint latest_ts = 0;
+ int flag = 0;
+
+ if (data == null || size < OVERHEAD) return -1;
+
+ int offset = 0;
+
+ while (true)
+ {
+ uint ts = 0;
+ uint sn = 0;
+ uint len = 0;
+ uint una = 0;
+ uint conv_ = 0;
+ ushort wnd = 0;
+ byte cmd = 0;
+ byte frg = 0;
+
+ if (size < OVERHEAD) break;
+
+ offset += Utils.Decode32U(data, offset, ref conv_);
+ if (conv_ != conv) return -1;
+
+ offset += Utils.Decode8u(data, offset, ref cmd);
+ offset += Utils.Decode8u(data, offset, ref frg);
+ offset += Utils.Decode16U(data, offset, ref wnd);
+ offset += Utils.Decode32U(data, offset, ref ts);
+ offset += Utils.Decode32U(data, offset, ref sn);
+ offset += Utils.Decode32U(data, offset, ref una);
+ offset += Utils.Decode32U(data, offset, ref len);
+
+ size -= OVERHEAD;
+
+ if (size < len || len < 0) return -2;
+
+ if (cmd != CMD_PUSH && cmd != CMD_ACK &&
+ cmd != CMD_WASK && cmd != CMD_WINS)
+ return -3;
+
+ rmt_wnd = wnd;
+ ParseUna(una);
+ ShrinkBuf();
+
+ if (cmd == CMD_ACK)
+ {
+ if (Utils.TimeDiff(current, ts) >= 0)
+ {
+ UpdateAck(Utils.TimeDiff(current, ts));
+ }
+ ParseAck(sn);
+ ShrinkBuf();
+ if (flag == 0)
+ {
+ flag = 1;
+ maxack = sn;
+ latest_ts = ts;
+ }
+ else
+ {
+ if (Utils.TimeDiff(sn, maxack) > 0)
+ {
+#if !FASTACK_CONSERVE
+ maxack = sn;
+ latest_ts = ts;
+#else
+ if (Utils.TimeDiff(ts, latest_ts) > 0)
+ {
+ maxack = sn;
+ latest_ts = ts;
+ }
+#endif
+ }
+ }
+ }
+ else if (cmd == CMD_PUSH)
+ {
+ if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) < 0)
+ {
+ AckPush(sn, ts);
+ if (Utils.TimeDiff(sn, rcv_nxt) >= 0)
+ {
+ Segment seg = SegmentNew();
+ seg.conv = conv_;
+ seg.cmd = cmd;
+ seg.frg = frg;
+ seg.wnd = wnd;
+ seg.ts = ts;
+ seg.sn = sn;
+ seg.una = una;
+ if (len > 0)
+ {
+ seg.data.Write(data, offset, (int)len);
+ }
+ ParseData(seg);
+ }
+ }
+ }
+ else if (cmd == CMD_WASK)
+ {
+ // ready to send back CMD_WINS in flush
+ // tell remote my window size
+ probe |= ASK_TELL;
+ }
+ else if (cmd == CMD_WINS)
+ {
+ // do nothing
+ }
+ else
+ {
+ return -3;
+ }
+
+ offset += (int)len;
+ size -= (int)len;
+ }
+
+ if (flag != 0)
+ {
+ ParseFastack(maxack, latest_ts);
+ }
+
+ // cwnd update when packet arrived
+ if (Utils.TimeDiff(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)
+ {
+ cwnd = (incr + mss - 1) / ((mss > 0) ? mss : 1);
+ }
+ }
+ if (cwnd > rmt_wnd)
+ {
+ cwnd = rmt_wnd;
+ incr = rmt_wnd * mss;
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ // ikcp_wnd_unused
+ uint WndUnused()
+ {
+ if (rcv_queue.Count < rcv_wnd)
+ return rcv_wnd - (uint)rcv_queue.Count;
+ return 0;
+ }
+
+ // ikcp_flush
+ // flush remain ack segments
+ public void Flush()
+ {
+ int offset = 0; // buffer ptr in original C
+ bool lost = false; // lost segments
+
+ // helper functions
+ void MakeSpace(int space)
+ {
+ if (offset + space > mtu)
+ {
+ output(buffer, offset);
+ offset = 0;
+ }
+ }
+
+ void FlushBuffer()
+ {
+ if (offset > 0)
+ {
+ output(buffer, offset);
+ }
+ }
+
+ // 'ikcp_update' haven't been called.
+ if (!updated) return;
+
+ // kcp only stack allocs a segment here for performance, leaving
+ // its data buffer null because this segment's data buffer is never
+ // used. that's fine in C, but in C# our segment is class so we need
+ // to allocate and most importantly, not forget to deallocate it
+ // before returning.
+ Segment seg = SegmentNew();
+ seg.conv = conv;
+ seg.cmd = CMD_ACK;
+ seg.wnd = WndUnused();
+ seg.una = rcv_nxt;
+
+ // flush acknowledges
+ foreach (AckItem ack in acklist)
+ {
+ MakeSpace(OVERHEAD);
+ // ikcp_ack_get assigns ack[i] to seg.sn, seg.ts
+ seg.sn = ack.serialNumber;
+ seg.ts = ack.timestamp;
+ offset += seg.Encode(buffer, offset);
+ }
+
+ acklist.Clear();
+
+ // probe window size (if remote window size equals zero)
+ if (rmt_wnd == 0)
+ {
+ if (probe_wait == 0)
+ {
+ probe_wait = PROBE_INIT;
+ ts_probe = current + probe_wait;
+ }
+ else
+ {
+ if (Utils.TimeDiff(current, ts_probe) >= 0)
+ {
+ if (probe_wait < PROBE_INIT)
+ probe_wait = PROBE_INIT;
+ probe_wait += probe_wait / 2;
+ if (probe_wait > PROBE_LIMIT)
+ probe_wait = PROBE_LIMIT;
+ ts_probe = current + probe_wait;
+ probe |= ASK_SEND;
+ }
+ }
+ }
+ else
+ {
+ ts_probe = 0;
+ probe_wait = 0;
+ }
+
+ // flush window probing commands
+ if ((probe & ASK_SEND) != 0)
+ {
+ seg.cmd = CMD_WASK;
+ MakeSpace(OVERHEAD);
+ offset += seg.Encode(buffer, offset);
+ }
+
+ // flush window probing commands
+ if ((probe & ASK_TELL) != 0)
+ {
+ seg.cmd = CMD_WINS;
+ MakeSpace(OVERHEAD);
+ offset += seg.Encode(buffer, offset);
+ }
+
+ probe = 0;
+
+ // calculate window size
+ uint cwnd_ = Math.Min(snd_wnd, rmt_wnd);
+ if (!nocwnd) cwnd_ = Math.Min(cwnd, cwnd_);
+
+ // move data from snd_queue to snd_buf
+ // sliding window, controlled by snd_nxt && sna_una+cwnd
+ while (Utils.TimeDiff(snd_nxt, snd_una + cwnd_) < 0)
+ {
+ if (snd_queue.Count == 0) break;
+
+ Segment newseg = snd_queue.Dequeue();
+
+ newseg.conv = conv;
+ newseg.cmd = CMD_PUSH;
+ newseg.wnd = seg.wnd;
+ newseg.ts = current;
+ newseg.sn = snd_nxt++;
+ newseg.una = rcv_nxt;
+ newseg.resendts = current;
+ newseg.rto = rx_rto;
+ newseg.fastack = 0;
+ newseg.xmit = 0;
+ snd_buf.Add(newseg);
+ }
+
+ // calculate resent
+ uint resent = fastresend > 0 ? (uint)fastresend : 0xffffffff;
+ uint rtomin = nodelay == 0 ? (uint)rx_rto >> 3 : 0;
+
+ // flush data segments
+ int change = 0;
+ foreach (Segment segment in snd_buf)
+ {
+ bool needsend = false;
+ // initial transmit
+ if (segment.xmit == 0)
+ {
+ needsend = true;
+ segment.xmit++;
+ segment.rto = rx_rto;
+ segment.resendts = current + (uint)segment.rto + rtomin;
+ }
+ // RTO
+ else if (Utils.TimeDiff(current, segment.resendts) >= 0)
+ {
+ needsend = true;
+ segment.xmit++;
+ xmit++;
+ if (nodelay == 0)
+ {
+ segment.rto += Math.Max(segment.rto, rx_rto);
+ }
+ else
+ {
+ int step = (nodelay < 2) ? segment.rto : rx_rto;
+ segment.rto += step / 2;
+ }
+ segment.resendts = current + (uint)segment.rto;
+ lost = true;
+ }
+ // fast retransmit
+ else if (segment.fastack >= resent)
+ {
+ if (segment.xmit <= fastlimit || fastlimit <= 0)
+ {
+ needsend = true;
+ segment.xmit++;
+ segment.fastack = 0;
+ segment.resendts = current + (uint)segment.rto;
+ change++;
+ }
+ }
+
+ if (needsend)
+ {
+ segment.ts = current;
+ segment.wnd = seg.wnd;
+ segment.una = rcv_nxt;
+
+ int need = OVERHEAD + (int)segment.data.Position;
+ MakeSpace(need);
+
+ offset += segment.Encode(buffer, offset);
+
+ if (segment.data.Position > 0)
+ {
+ Buffer.BlockCopy(segment.data.GetBuffer(), 0, buffer, offset, (int)segment.data.Position);
+ offset += (int)segment.data.Position;
+ }
+
+ if (segment.xmit >= dead_link)
+ {
+ state = -1;
+ }
+ }
+ }
+
+ // kcp stackallocs 'seg'. our C# segment is a class though, so we
+ // need to properly delete and return it to the pool now that we are
+ // done with it.
+ SegmentDelete(seg);
+
+ // flash remain segments
+ FlushBuffer();
+
+ // update ssthresh
+ // rate halving, https://tools.ietf.org/html/rfc6937
+ if (change > 0)
+ {
+ uint inflight = snd_nxt - snd_una;
+ ssthresh = inflight / 2;
+ if (ssthresh < THRESH_MIN)
+ ssthresh = THRESH_MIN;
+ cwnd = ssthresh + resent;
+ incr = cwnd * mss;
+ }
+
+ // congestion control, https://tools.ietf.org/html/rfc5681
+ if (lost)
+ {
+ // original C uses 'cwnd', not kcp->cwnd!
+ ssthresh = cwnd_ / 2;
+ if (ssthresh < THRESH_MIN)
+ ssthresh = THRESH_MIN;
+ cwnd = 1;
+ incr = mss;
+ }
+
+ if (cwnd < 1)
+ {
+ cwnd = 1;
+ incr = mss;
+ }
+ }
+
+ // ikcp_update
+ // update state (call it repeatedly, every 10ms-100ms), or you can ask
+ // Check() when to call it again (without Input/Send calling).
+ //
+ // 'current' - current timestamp in millisec. pass it to Kcp so that
+ // Kcp doesn't have to do any stopwatch/deltaTime/etc. code
+ public void Update(uint currentTimeMilliSeconds)
+ {
+ current = currentTimeMilliSeconds;
+
+ if (!updated)
+ {
+ updated = true;
+ ts_flush = current;
+ }
+
+ int slap = Utils.TimeDiff(current, ts_flush);
+
+ if (slap >= 10000 || slap < -10000)
+ {
+ ts_flush = current;
+ slap = 0;
+ }
+
+ if (slap >= 0)
+ {
+ ts_flush += interval;
+ if (Utils.TimeDiff(current, ts_flush) >= 0)
+ {
+ ts_flush = current + interval;
+ }
+ Flush();
+ }
+ }
+
+ // ikcp_check
+ // Determine when should you invoke update
+ // Returns when you should invoke update in millisec, if there is no
+ // input/send calling. you can call update in that time, instead of
+ // call update repeatly.
+ //
+ // Important to reduce unnecessary update invoking. use it to schedule
+ // update (eg. implementing an epoll-like mechanism, or optimize update
+ // when handling massive kcp connections).
+ public uint Check(uint current_)
+ {
+ uint ts_flush_ = ts_flush;
+ int tm_flush = 0x7fffffff;
+ int tm_packet = 0x7fffffff;
+
+ if (!updated)
+ {
+ return current_;
+ }
+
+ if (Utils.TimeDiff(current_, ts_flush_) >= 10000 ||
+ Utils.TimeDiff(current_, ts_flush_) < -10000)
+ {
+ ts_flush_ = current_;
+ }
+
+ if (Utils.TimeDiff(current_, ts_flush_) >= 0)
+ {
+ return current_;
+ }
+
+ tm_flush = Utils.TimeDiff(ts_flush_, current_);
+
+ foreach (Segment seg in snd_buf)
+ {
+ int diff = Utils.TimeDiff(seg.resendts, current_);
+ if (diff <= 0)
+ {
+ return current_;
+ }
+ if (diff < tm_packet) tm_packet = diff;
+ }
+
+ uint minimal = (uint)(tm_packet < tm_flush ? tm_packet : tm_flush);
+ if (minimal >= interval) minimal = interval;
+
+ return current_ + minimal;
+ }
+
+ // ikcp_setmtu
+ // Change MTU (Maximum Transmission Unit) size.
+ public void SetMtu(uint mtu)
+ {
+ if (mtu < 50 || mtu < OVERHEAD)
+ throw new ArgumentException("MTU must be higher than 50 and higher than OVERHEAD");
+
+ buffer = new byte[(mtu + OVERHEAD) * 3];
+ this.mtu = mtu;
+ mss = mtu - OVERHEAD;
+ }
+
+ // ikcp_interval
+ public void SetInterval(uint interval)
+ {
+ if (interval > 5000) interval = 5000;
+ else if (interval < 10) interval = 10;
+ this.interval = interval;
+ }
+
+ // ikcp_nodelay
+ // Normal: false, 40, 0, 0
+ // Fast: false, 30, 2, 1
+ // Fast2: true, 20, 2, 1
+ // Fast3: true, 10, 2, 1
+ public void SetNoDelay(uint nodelay, uint interval = INTERVAL, int resend = 0, bool nocwnd = false)
+ {
+ this.nodelay = nodelay;
+ if (nodelay != 0)
+ {
+ rx_minrto = RTO_NDL;
+ }
+ else
+ {
+ rx_minrto = RTO_MIN;
+ }
+
+ if (interval >= 0)
+ {
+ if (interval > 5000) interval = 5000;
+ else if (interval < 10) interval = 10;
+ this.interval = interval;
+ }
+
+ if (resend >= 0)
+ {
+ fastresend = resend;
+ }
+
+ this.nocwnd = nocwnd;
+ }
+
+ // ikcp_wndsize
+ public void SetWindowSize(uint sendWindow, uint receiveWindow)
+ {
+ if (sendWindow > 0)
+ {
+ snd_wnd = sendWindow;
+ }
+
+ if (receiveWindow > 0)
+ {
+ // must >= max fragment size
+ rcv_wnd = Math.Max(receiveWindow, WND_RCV);
+ }
+ }
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta
new file mode 100755
index 000000000..935b4239c
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a59b1cae10a334faf807432ab472f212
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs
new file mode 100755
index 000000000..ddea4c615
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace kcp2k
+{
+ // KCP Segment Definition
+ internal class Segment
+ {
+ internal uint conv; // conversation
+ internal uint cmd; // command, e.g. Kcp.CMD_ACK etc.
+ internal uint frg; // fragment
+ internal uint wnd; // window
+ internal uint ts; // timestamp
+ internal uint sn; // serial number
+ internal uint una;
+ internal uint resendts; // resend timestamp
+ internal int rto;
+ internal uint fastack;
+ internal uint xmit;
+ // we need a auto scaling byte[] with a WriteBytes function.
+ // MemoryStream does that perfectly, no need to reinvent the wheel.
+ // note: no need to pool it, because Segment is already pooled.
+ internal MemoryStream data = new MemoryStream();
+
+ // pool ////////////////////////////////////////////////////////////////
+ internal static readonly Stack Pool = new Stack(32);
+
+ public static Segment Take()
+ {
+ if (Pool.Count > 0)
+ {
+ Segment seg = Pool.Pop();
+ return seg;
+ }
+ return new Segment();
+ }
+
+ public static void Return(Segment seg)
+ {
+ seg.Reset();
+ Pool.Push(seg);
+ }
+ ////////////////////////////////////////////////////////////////////////
+
+ // ikcp_encode_seg
+ // encode a segment into buffer
+ internal int Encode(byte[] ptr, int offset)
+ {
+ int offset_ = offset;
+ offset += Utils.Encode32U(ptr, offset, conv);
+ offset += Utils.Encode8u(ptr, offset, (byte)cmd);
+ offset += Utils.Encode8u(ptr, offset, (byte)frg);
+ offset += Utils.Encode16U(ptr, offset, (ushort)wnd);
+ offset += Utils.Encode32U(ptr, offset, ts);
+ offset += Utils.Encode32U(ptr, offset, sn);
+ offset += Utils.Encode32U(ptr, offset, una);
+ offset += Utils.Encode32U(ptr, offset, (uint)data.Position);
+
+ return offset - offset_;
+ }
+
+ // reset to return a fresh segment to the pool
+ internal void Reset()
+ {
+ conv = 0;
+ cmd = 0;
+ frg = 0;
+ wnd = 0;
+ ts = 0;
+ sn = 0;
+ una = 0;
+ rto = 0;
+ xmit = 0;
+ resendts = 0;
+ fastack = 0;
+
+ // keep buffer for next pool usage, but reset length (= bytes written)
+ data.SetLength(0);
+ }
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta
new file mode 100755
index 000000000..d14dc1a1a
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fc58706a05dd3442c8fde858d5266855
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs
new file mode 100755
index 000000000..8d233958c
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs
@@ -0,0 +1,68 @@
+using System.Runtime.CompilerServices;
+
+namespace kcp2k
+{
+ public static partial class Utils
+ {
+ // encode 8 bits unsigned int
+ public static int Encode8u(byte[] p, int offset, byte c)
+ {
+ p[0 + offset] = c;
+ return 1;
+ }
+
+ // decode 8 bits unsigned int
+ public static int Decode8u(byte[] p, int offset, ref byte c)
+ {
+ c = p[0 + offset];
+ return 1;
+ }
+
+ // encode 16 bits unsigned int (lsb)
+ public static int Encode16U(byte[] p, int offset, ushort w)
+ {
+ p[0 + offset] = (byte)(w >> 0);
+ p[1 + offset] = (byte)(w >> 8);
+ return 2;
+ }
+
+ // decode 16 bits unsigned int (lsb)
+ public static int Decode16U(byte[] p, int offset, ref ushort c)
+ {
+ ushort result = 0;
+ result |= p[0 + offset];
+ result |= (ushort)(p[1 + offset] << 8);
+ c = result;
+ return 2;
+ }
+
+ // encode 32 bits unsigned int (lsb)
+ public static int Encode32U(byte[] p, int offset, uint l)
+ {
+ p[0 + offset] = (byte)(l >> 0);
+ p[1 + offset] = (byte)(l >> 8);
+ p[2 + offset] = (byte)(l >> 16);
+ p[3 + offset] = (byte)(l >> 24);
+ return 4;
+ }
+
+ // decode 32 bits unsigned int (lsb)
+ public static int Decode32U(byte[] p, int offset, ref uint c)
+ {
+ uint result = 0;
+ result |= p[0 + offset];
+ result |= (uint)(p[1 + offset] << 8);
+ result |= (uint)(p[2 + offset] << 16);
+ result |= (uint)(p[3 + offset] << 24);
+ c = result;
+ return 4;
+ }
+
+ // timediff was a macro in original Kcp. let's inline it if possible.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int TimeDiff(uint later, uint earlier)
+ {
+ return (int)(later - earlier);
+ }
+ }
+}
diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta
new file mode 100755
index 000000000..86118bc9f
--- /dev/null
+++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ef959eb716205bd48b050f010a9a35ae
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json
new file mode 100644
index 000000000..aba656396
--- /dev/null
+++ b/Packages/packages-lock.json
@@ -0,0 +1,273 @@
+{
+ "dependencies": {
+ "com.unity.package-manager-ui": {
+ "version": "2.0.13",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.test-framework.performance": {
+ "version": "0.1.50-preview",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.textmeshpro": {
+ "version": "1.4.1",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
+ "com.unity.modules.ai": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.animation": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.assetbundle": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.audio": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.cloth": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0"
+ }
+ },
+ "com.unity.modules.director": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.animation": "1.0.0"
+ }
+ },
+ "com.unity.modules.imageconversion": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.imgui": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.jsonserialize": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.particlesystem": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.physics": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.physics2d": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.screencapture": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.imageconversion": "1.0.0"
+ }
+ },
+ "com.unity.modules.subsystems": {
+ "version": "1.0.0",
+ "depth": 1,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.jsonserialize": "1.0.0"
+ }
+ },
+ "com.unity.modules.terrain": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.terrainphysics": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0",
+ "com.unity.modules.terrain": "1.0.0"
+ }
+ },
+ "com.unity.modules.tilemap": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics2d": "1.0.0"
+ }
+ },
+ "com.unity.modules.ui": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.uielements": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.imgui": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0",
+ "com.unity.modules.uielementsnative": "1.0.0"
+ }
+ },
+ "com.unity.modules.uielementsnative": {
+ "version": "1.0.0",
+ "depth": 1,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.imgui": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0"
+ }
+ },
+ "com.unity.modules.umbra": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.unityanalytics": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequest": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.unitywebrequestassetbundle": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.assetbundle": "1.0.0",
+ "com.unity.modules.unitywebrequest": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequestaudio": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.audio": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequesttexture": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.imageconversion": "1.0.0"
+ }
+ },
+ "com.unity.modules.unitywebrequestwww": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.unitywebrequest": "1.0.0",
+ "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
+ "com.unity.modules.unitywebrequestaudio": "1.0.0",
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.assetbundle": "1.0.0",
+ "com.unity.modules.imageconversion": "1.0.0"
+ }
+ },
+ "com.unity.modules.vehicles": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0"
+ }
+ },
+ "com.unity.modules.video": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.audio": "1.0.0",
+ "com.unity.modules.ui": "1.0.0",
+ "com.unity.modules.unitywebrequest": "1.0.0"
+ }
+ },
+ "com.unity.modules.vr": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.jsonserialize": "1.0.0",
+ "com.unity.modules.physics": "1.0.0",
+ "com.unity.modules.xr": "1.0.0"
+ }
+ },
+ "com.unity.modules.wind": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {}
+ },
+ "com.unity.modules.xr": {
+ "version": "1.0.0",
+ "depth": 0,
+ "source": "builtin",
+ "dependencies": {
+ "com.unity.modules.physics": "1.0.0",
+ "com.unity.modules.jsonserialize": "1.0.0",
+ "com.unity.modules.subsystems": "1.0.0"
+ }
+ }
+ }
+}
diff --git a/UserSettings/EditorUserSettings.asset b/UserSettings/EditorUserSettings.asset
new file mode 100644
index 000000000..dd68c2a8b
Binary files /dev/null and b/UserSettings/EditorUserSettings.asset differ