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