diff --git a/Assembly-CSharp-Editor.csproj b/Assembly-CSharp-Editor.csproj index b80fd4a..503cd53 100644 --- a/Assembly-CSharp-Editor.csproj +++ b/Assembly-CSharp-Editor.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;NET_STANDARD_2_0 + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;NET_STANDARD_2_0;DEVELOPMENT_BUILD prompt 4 0169 @@ -649,6 +649,10 @@ {2ba436fb-75e3-28e6-311a-9da88a14fba9} Unity.TextMeshPro.Editor + + {9c8046d9-3477-2d3b-eeb8-eea73ce7e4d4} + ParrelSync + {1a356b3a-d31e-3350-c514-470dfa96d5c8} where-allocations diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index 85772d6..8ea630b 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 @@ -36,9 +36,66 @@ false - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -47,23 +104,28 @@ + + - + + - + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.dll @@ -256,350 +318,359 @@ /media/RocketSpeed/Unity_Projects/NanoPark/Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/ref/2.0.0/netstandard.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/Microsoft.Win32.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.AppContext.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.Concurrent.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.NonGeneric.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.Specialized.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.EventBasedAsync.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.TypeConverter.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Console.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Data.Common.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Contracts.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Debug.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.FileVersionInfo.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Process.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.StackTrace.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.TextWriterTraceListener.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Tools.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.TraceSource.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Tracing.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Drawing.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Dynamic.Runtime.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.Calendars.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Compression.ZipFile.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Compression.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.DriveInfo.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.Watcher.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.IsolatedStorage.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.MemoryMappedFiles.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Pipes.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.UnmanagedMemoryStream.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Expressions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Parallel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Queryable.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Http.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.NameResolution.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.NetworkInformation.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Ping.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Requests.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Security.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Sockets.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebHeaderCollection.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebSockets.Client.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebSockets.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ObjectModel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.Reader.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.ResourceManager.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.Writer.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.CompilerServices.VisualC.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Handles.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.InteropServices.RuntimeInformation.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.InteropServices.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Numerics.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Formatters.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Json.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Xml.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Claims.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Algorithms.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Csp.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Encoding.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.X509Certificates.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Principal.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.SecureString.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.Encoding.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.Encoding.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.RegularExpressions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Overlapped.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Tasks.Parallel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Tasks.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Thread.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.ThreadPool.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Timer.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ValueTuple.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.ReaderWriter.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XDocument.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XPath.XDocument.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XPath.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XmlDocument.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XmlSerializer.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/Extensions/2.0.0/System.Numerics.Vectors.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/Extensions/2.0.0/System.Runtime.InteropServices.WindowsRuntime.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.ComponentModel.Composition.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Core.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Data.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Drawing.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.IO.Compression.FileSystem.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Net.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Numerics.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Runtime.Serialization.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.ServiceModel.Web.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Transactions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Web.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Windows.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.Linq.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.Serialization.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.dll + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/mscorlib.dll - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.dll + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.dll - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/mscorlib.dll + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Core.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Runtime.Serialization.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.Linq.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.Vectors.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Net.Http.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Microsoft.CSharp.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Data.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/Microsoft.Win32.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.AppContext.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Concurrent.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.NonGeneric.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Specialized.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Annotations.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.EventBasedAsync.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.TypeConverter.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Console.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Data.Common.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Contracts.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Debug.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.FileVersionInfo.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Process.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.StackTrace.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TextWriterTraceListener.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Tools.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TraceSource.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Drawing.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Dynamic.Runtime.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Calendars.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Compression.ZipFile.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.DriveInfo.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Watcher.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.IsolatedStorage.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.MemoryMappedFiles.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Pipes.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.UnmanagedMemoryStream.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Expressions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Parallel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Queryable.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Http.Rtc.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NameResolution.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NetworkInformation.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Ping.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Requests.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Security.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Sockets.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebHeaderCollection.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.Client.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ObjectModel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.ILGeneration.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.Lightweight.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Reader.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.ResourceManager.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Writer.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.CompilerServices.VisualC.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Handles.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.RuntimeInformation.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.WindowsRuntime.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Numerics.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Formatters.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Json.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Xml.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Claims.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Algorithms.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Csp.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Encoding.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.X509Certificates.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Principal.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.SecureString.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Duplex.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Http.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.NetTcp.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Security.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.RegularExpressions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Overlapped.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.Parallel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Thread.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.ThreadPool.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Timer.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ValueTuple.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.ReaderWriter.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XDocument.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.XDocument.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlDocument.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlSerializer.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/netstandard.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.Lang.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/Boo.Lang.dll @@ -607,10 +678,6 @@ {533199e0-035b-b106-d535-d93fe9aeea08} Unity.Timeline.Editor - - {7e3a9708-574f-f809-b966-c5b7ec3f91a7} - Mirror.Weaver - {0c9389be-bc21-f173-bc53-1836cfd21b9e} Unity.VSCode.Editor @@ -635,6 +702,10 @@ {2ba436fb-75e3-28e6-311a-9da88a14fba9} Unity.TextMeshPro.Editor + + {9c8046d9-3477-2d3b-eeb8-eea73ce7e4d4} + ParrelSync + {1a356b3a-d31e-3350-c514-470dfa96d5c8} where-allocations @@ -695,10 +766,6 @@ {d5284a81-f67c-7e2c-3e13-cdac4bdbd32c} UnityEditor.UI - - {9c8046d9-3477-2d3b-eeb8-eea73ce7e4d4} - ParrelSync - Sending to Server - while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket)) - { - // TODO: Revise this, could we tell the Peer to disconnect right here? - // Stop early if we get a client stop packet. - // if (outgoingPacket.Type == IgnorancePacketType.ClientWantsToStop) break; - - int ret = clientPeer.Send(outgoingPacket.Channel, ref outgoingPacket.Payload); - - if (ret < 0 && setupInfo.Verbosity > 0) - Debug.LogWarning($"Ignorance Client: ENet error code {ret} while sending packet to Peer {outgoingPacket.NativePeerId}."); - } - - // Step 2: - // <----- Receive Data packets - // This loops until polling is completed. It may take a while, if it's - // a slow networking day. - while (!pollComplete) - { - Packet incomingPacket; - Peer incomingPeer; - int incomingPacketLength; - - // Any events worth checking out? - if (clientENetHost.CheckEvents(out clientENetEvent) <= 0) - { - // If service time is met, break out of it. - if (clientENetHost.Service(setupInfo.PollTime, out clientENetEvent) <= 0) break; - - // Poll is done. - pollComplete = true; - } - - // Setup the packet references. - incomingPeer = clientENetEvent.Peer; - - // Now, let's handle those events. - switch (clientENetEvent.Type) - { - case EventType.None: - default: - break; - - case EventType.Connect: - ConnectionEvents.Enqueue(new IgnoranceConnectionEvent() - { - NativePeerId = incomingPeer.ID, - IP = incomingPeer.IP, - Port = incomingPeer.Port - }); - break; - - case EventType.Disconnect: - case EventType.Timeout: - ConnectionEvents.Enqueue(new IgnoranceConnectionEvent() - { - WasDisconnect = true - }); - break; - - - case EventType.Receive: - // Receive event type usually includes a packet; so cache its reference. - incomingPacket = clientENetEvent.Packet; - - if (!incomingPacket.IsSet) - { - if (setupInfo.Verbosity > 0) - Debug.LogWarning($"Ignorance Client: A receive event did not supply us with a packet to work with. This should never happen."); - break; - } - - incomingPacketLength = incomingPacket.Length; - - // Never consume more than we can have capacity for. - if (incomingPacketLength > setupInfo.PacketSizeLimit) - { - if (setupInfo.Verbosity > 0) - Debug.LogWarning($"Ignorance Client: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes."); - - incomingPacket.Dispose(); - break; - } - - IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket - { - Channel = clientENetEvent.ChannelID, - NativePeerId = incomingPeer.ID, - Payload = incomingPacket - }; - - Incoming.Enqueue(incomingQueuePacket); - break; - } - } - } - - Debug.Log("Ignorance Server: Shutdown commencing, disconnecting and flushing connection."); - - // Flush the client and disconnect. - clientPeer.Disconnect(0); - clientENetHost.Flush(); - - // Fix for client stuck in limbo, since the disconnection event may not be fired until next loop. - ConnectionEvents.Enqueue(new IgnoranceConnectionEvent() - { - WasDisconnect = true - }); - } - - // Deinitialize - Library.Deinitialize(); - if (setupInfo.Verbosity > 0) - Debug.Log("Ignorance Client: Shutdown complete."); - } - - - private struct ThreadParamInfo - { - public int Channels; - public int PollTime; - public int Port; - public int PacketSizeLimit; - public int Verbosity; - public string Address; - } - } -} +// Ignorance 1.4.x LTS (Long Term Support) +// https://github.com/SoftwareGuy/Ignorance +// ----------------- +// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64) +// Ignorance is licensed under the MIT license. Refer +// to the LICENSE file for more information. + +using System; +using System.Collections.Concurrent; +using System.Threading; +using ENet; +using IgnoranceThirdparty; +using UnityEngine; +using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine +using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine +using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine + +namespace IgnoranceCore +{ + public class IgnoranceClient + { + // Client connection address and port + public string ConnectAddress = "127.0.0.1"; + public int ConnectPort = 7777; + // How many channels are expected + public int ExpectedChannels = 2; + // Native poll waiting time + public int PollTime = 1; + // Maximum Packet Size + public int MaximumPacketSize = 33554432; + // General Verbosity by default. + public int Verbosity = 1; + // Maximum ring buffer capacity. + public int IncomingOutgoingBufferSize = 5000; + public int ConnectionEventBufferSize = 100; + // Queues + public RingBuffer Incoming; + public RingBuffer Outgoing; + public RingBuffer Commands; + public RingBuffer ConnectionEvents; + public RingBuffer StatusUpdates; + + public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive; + + private volatile bool CeaseOperation = false; + private Thread WorkerThread; + + public void Start() + { + if (WorkerThread != null && WorkerThread.IsAlive) + { + // Cannot do that. + Debug.LogError("Ignorance Client: A worker thread is already running. Cannot start another."); + return; + } + + // Setup the ring buffers. + SetupRingBuffersIfNull(); + + CeaseOperation = false; + ThreadParamInfo threadParams = new ThreadParamInfo() + { + Address = ConnectAddress, + Port = ConnectPort, + Channels = ExpectedChannels, + PollTime = PollTime, + PacketSizeLimit = MaximumPacketSize, + Verbosity = Verbosity + }; + + // Drain queues. + if (Incoming != null) while (Incoming.TryDequeue(out _)) ; + if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ; + if (Commands != null) while (Commands.TryDequeue(out _)) ; + if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ; + if (StatusUpdates != null) while (StatusUpdates.TryDequeue(out _)) ; + + WorkerThread = new Thread(ThreadWorker); + WorkerThread.Start(threadParams); + + Debug.Log("Ignorance Client: Dispatched worker thread."); + } + + public void Stop() + { + if (WorkerThread != null && !CeaseOperation) + { + Debug.Log("Ignorance Client: Stop acknowledged. This may take a while depending on network load..."); + + CeaseOperation = true; + } + } + + #region The meat and potatoes. + // This runs in a seperate thread, be careful accessing anything outside of it's thread + // or you may get an AccessViolation/crash. + private void ThreadWorker(Object parameters) + { + if (Verbosity > 0) + Debug.Log("Ignorance Client: Initializing. Please stand by..."); + + ThreadParamInfo setupInfo; + Address clientAddress = new Address(); + Peer clientPeer; // The peer object that represents the client's connection. + Host clientHost; // NOT related to Mirror "Client Host". This is the client's ENet Host Object. + Event clientEvent; // Used when clients get events on the network. + IgnoranceClientStats icsu = default; + bool alreadyNotifiedAboutDisconnect = false; + + // Grab the setup information. + if (parameters.GetType() == typeof(ThreadParamInfo)) + setupInfo = (ThreadParamInfo)parameters; + else + { + Debug.LogError("Ignorance Client: Startup failure; Invalid thread parameters. Aborting."); + return; + } + + // Attempt to initialize ENet inside the thread. + if (Library.Initialize()) + Debug.Log("Ignorance Client: ENet Native successfully initialized."); + else + { + Debug.LogError("Ignorance Client: Failed to initialize ENet Native. Aborting."); + return; + } + + // Attempt to connect to our target. + clientAddress.SetHost(setupInfo.Address); + clientAddress.Port = (ushort)setupInfo.Port; + + using (clientHost = new Host()) + { + try + { + clientHost.Create(); + clientPeer = clientHost.Connect(clientAddress, setupInfo.Channels); + } + catch (Exception ex) + { + // Oops, something failed. + Debug.LogError($"Ignorance Client: Looks like something went wrong. While attempting to create client object, we caught an exception:\n{ex.Message}"); + Debug.LogError($"You could try the debug-enabled version of the native ENet library which creates a logfile, or alternatively you could try restart " + + $"your device to ensure jank is cleared out of memory. If problems persist, please file a support ticket explaining what happened."); + + Library.Deinitialize(); + return; + } + + // Process network events as long as we're not ceasing operation. + while (!CeaseOperation) + { + bool pollComplete = false; + + while (Commands.TryDequeue(out IgnoranceCommandPacket ignoranceCommandPacket)) + { + switch (ignoranceCommandPacket.Type) + { + case IgnoranceCommandType.ClientStatusRequest: + // Respond with statistics so far. + if (!clientPeer.IsSet) + break; + + icsu.RTT = clientPeer.RoundTripTime; + + icsu.BytesReceived = clientPeer.BytesReceived; + icsu.BytesSent = clientPeer.BytesSent; + + icsu.PacketsReceived = clientHost.PacketsReceived; + icsu.PacketsSent = clientPeer.PacketsSent; + icsu.PacketsLost = clientPeer.PacketsLost; + + StatusUpdates.Enqueue(icsu); + break; + + case IgnoranceCommandType.ClientWantsToStop: + CeaseOperation = true; + break; + } + } + + // If something outside the thread has told us to stop execution, then we need to break out of this while loop. + if (CeaseOperation) + break; + + // Step 1: Sending to Server + while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket)) + { + // TODO: Revise this, could we tell the Peer to disconnect right here? + // Stop early if we get a client stop packet. + // if (outgoingPacket.Type == IgnorancePacketType.ClientWantsToStop) break; + + int ret = clientPeer.Send(outgoingPacket.Channel, ref outgoingPacket.Payload); + + if (ret < 0 && setupInfo.Verbosity > 0) + Debug.LogWarning($"Ignorance Client: ENet error {ret} while sending packet to Server via Peer {outgoingPacket.NativePeerId}."); + } + + // If something outside the thread has told us to stop execution, then we need to break out of this while loop. + // while loop to break out of is while(!CeaseOperation). + if (CeaseOperation) + break; + + // Step 2: Receive Data packets + // This loops until polling is completed. It may take a while, if it's + // a slow networking day. + while (!pollComplete) + { + Packet incomingPacket; + Peer incomingPeer; + int incomingPacketLength; + + // Any events worth checking out? + if (clientHost.CheckEvents(out clientEvent) <= 0) + { + // If service time is met, break out of it. + if (clientHost.Service(setupInfo.PollTime, out clientEvent) <= 0) break; + + // Poll is done. + pollComplete = true; + } + + // Setup the packet references. + incomingPeer = clientEvent.Peer; + + // Now, let's handle those events. + switch (clientEvent.Type) + { + case EventType.None: + default: + break; + + case EventType.Connect: + if (setupInfo.Verbosity > 0) + Debug.Log("Ignorance Client: ENet has connected to the server."); + + ConnectionEvents.Enqueue(new IgnoranceConnectionEvent + { + EventType = 0x00, + NativePeerId = incomingPeer.ID, + IP = incomingPeer.IP, + Port = incomingPeer.Port + }); + break; + + case EventType.Disconnect: + case EventType.Timeout: + if (setupInfo.Verbosity > 0) + Debug.Log("Ignorance Client: ENet has been disconnected from the server."); + + ConnectionEvents.Enqueue(new IgnoranceConnectionEvent { EventType = 0x01 }); + CeaseOperation = true; + alreadyNotifiedAboutDisconnect = true; + break; + + case EventType.Receive: + // Receive event type usually includes a packet; so cache its reference. + incomingPacket = clientEvent.Packet; + + if (!incomingPacket.IsSet) + { + if (setupInfo.Verbosity > 0) + Debug.LogWarning($"Ignorance Client: A receive event did not supply us with a packet to work with. This should never happen."); + break; + } + + incomingPacketLength = incomingPacket.Length; + + // Never consume more than we can have capacity for. + if (incomingPacketLength > setupInfo.PacketSizeLimit) + { + if (setupInfo.Verbosity > 0) + Debug.LogWarning($"Ignorance Client: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes."); + + incomingPacket.Dispose(); + break; + } + + IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket + { + Channel = clientEvent.ChannelID, + NativePeerId = incomingPeer.ID, + Payload = incomingPacket + }; + + Incoming.Enqueue(incomingQueuePacket); + break; + } + } + + // If something outside the thread has told us to stop execution, then we need to break out of this while loop. + // while loop to break out of is while(!CeaseOperation). + if (CeaseOperation) + break; + } + + Debug.Log("Ignorance Client: Thread shutdown commencing. Disconnecting and flushing connection."); + + // Flush the client and disconnect. + clientPeer.Disconnect(0); + clientHost.Flush(); + + // Fix for client stuck in limbo, since the disconnection event may not be fired until next loop. + if (!alreadyNotifiedAboutDisconnect) + { + ConnectionEvents.Enqueue(new IgnoranceConnectionEvent { EventType = 0x01 }); + alreadyNotifiedAboutDisconnect = true; + } + + } + + // Fix for client stuck in limbo, since the disconnection event may not be fired until next loop, again. + if (!alreadyNotifiedAboutDisconnect) + ConnectionEvents.Enqueue(new IgnoranceConnectionEvent { EventType = 0x01 }); + + // Deinitialize + Library.Deinitialize(); + + if (setupInfo.Verbosity > 0) + Debug.Log("Ignorance Client: Shutdown complete."); + } + #endregion + + private void SetupRingBuffersIfNull() + { + Debug.Log($"Ignorance: Setting up ring buffers if they're not already created. " + + $"If they are already, this step will be skipped."); + + if (Incoming == null) + Incoming = new RingBuffer(IncomingOutgoingBufferSize); + if (Outgoing == null) + Outgoing = new RingBuffer(IncomingOutgoingBufferSize); + if (Commands == null) + Commands = new RingBuffer(100); + if (ConnectionEvents == null) + ConnectionEvents = new RingBuffer(ConnectionEventBufferSize); + if (StatusUpdates == null) + StatusUpdates = new RingBuffer(10); + } + + private struct ThreadParamInfo + { + public int Channels; + public int PollTime; + public int Port; + public int PacketSizeLimit; + public int Verbosity; + public string Address; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceClient.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceClient.cs.meta index fe965ca..a854970 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceClient.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceClient.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: b7b9e2c091c3d42439840a02fe700252 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: b7b9e2c091c3d42439840a02fe700252 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs b/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs index 4f71947..7dd4f2e 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs @@ -1,328 +1,411 @@ -// Ignorance 1.4.x -// Ignorance. It really kicks the Unity LLAPIs ass. -// https://github.com/SoftwareGuy/Ignorance -// ----------------- -// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64) -// Ignorance Transport is licensed under the MIT license. Refer -// to the LICENSE file for more information. - -using ENet; -// using NetStack.Buffers; -using System.Collections.Concurrent; -using System.Threading; -using UnityEngine; -using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine -using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine -using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine - -namespace IgnoranceTransport -{ - public class IgnoranceServer - { - // Server Properties - // - Bind Settings - public string BindAddress = "127.0.0.1"; - public int BindPort = 7777; - // - Maximum allowed channels, peers, etc. - public int MaximumChannels = 2; - public int MaximumPeers = 100; - public int MaximumPacketSize = 33554432; // ENet.cs: uint maxPacketSize = 32 * 1024 * 1024 = 33554432 - // - Native poll waiting time - public int PollTime = 1; - public int Verbosity = 1; - - public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive; - - private volatile bool CeaseOperation = false; - - // Queues - public ConcurrentQueue Incoming = new ConcurrentQueue(); - public ConcurrentQueue Outgoing = new ConcurrentQueue(); - public ConcurrentQueue Commands = new ConcurrentQueue(); - public ConcurrentQueue ConnectionEvents = new ConcurrentQueue(); - public ConcurrentQueue DisconnectionEvents = new ConcurrentQueue(); - - // Thread - private Thread WorkerThread; - - public void Start() - { - if (WorkerThread != null && WorkerThread.IsAlive) - { - // Cannot do that. - Debug.LogError("A worker thread is already running. Cannot start another."); - return; - } - - CeaseOperation = false; - ThreadParamInfo threadParams = new ThreadParamInfo() - { - Address = BindAddress, - Port = BindPort, - Peers = MaximumPeers, - Channels = MaximumChannels, - PollTime = PollTime, - PacketSizeLimit = MaximumPacketSize, - Verbosity = Verbosity - }; - - // Drain queues. - if (Incoming != null) while (Incoming.TryDequeue(out _)) ; - if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ; - if (Commands != null) while (Commands.TryDequeue(out _)) ; - if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ; - if (DisconnectionEvents != null) while (DisconnectionEvents.TryDequeue(out _)) ; - - WorkerThread = new Thread(ThreadWorker); - WorkerThread.Start(threadParams); - - // Announce - if (Verbosity > 0) - Debug.Log("Server has dispatched worker thread."); - } - - public void Stop() - { - if (Verbosity > 0) - Debug.Log("Telling server thread to stop, this may take a while depending on network load"); - CeaseOperation = true; - } - - private void ThreadWorker(Object parameters) - { - if (Verbosity > 0) - Debug.Log("Ignorance Server: Initializing. Please stand by..."); - - // Thread cache items - ThreadParamInfo setupInfo; - Address serverAddress = new Address(); - Host serverENetHost; - Event serverENetEvent; - - Peer[] serverPeerArray; - - // Grab the setup information. - if (parameters.GetType() == typeof(ThreadParamInfo)) - { - setupInfo = (ThreadParamInfo)parameters; - } - else - { - Debug.LogError("Ignorance Server: Startup failure: Invalid thread parameters. Aborting."); - return; - } - - // Attempt to initialize ENet inside the thread. - if (Library.Initialize()) - { - Debug.Log("Ignorance Server: ENet initialized."); - } - else - { - Debug.LogError("Ignorance Server: Failed to initialize ENet. This threads' fucked."); - return; - } - - // Configure the server address. - serverAddress.SetHost(setupInfo.Address); - serverAddress.Port = (ushort)setupInfo.Port; - serverPeerArray = new Peer[setupInfo.Peers]; - - using (serverENetHost = new Host()) - { - // Create the server object. - serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels); - - // Loop until we're told to cease operations. - while (!CeaseOperation) - { - // Intermission: Command Handling - while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket)) - { - switch (commandPacket.Type) - { - default: - break; - - // Boot a Peer off the Server. - case IgnoranceCommandType.ServerKickPeer: - uint targetPeer = commandPacket.PeerId; - - if (!serverPeerArray[targetPeer].IsSet) continue; - if (setupInfo.Verbosity > 0) - Debug.Log($"Ignorance Server: Booting Peer {targetPeer} off this server instance."); - - IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent() - { - WasDisconnect = true, - NativePeerId = targetPeer - }; - - DisconnectionEvents.Enqueue(iced); - - // Disconnect and reset the peer array's entry for that peer. - serverPeerArray[targetPeer].DisconnectNow(0); - serverPeerArray[targetPeer] = default; - break; - } - } - - // Step One: - // ---> Sending to peers - while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket)) - { - // Only create a packet if the server knows the peer. - if (serverPeerArray[outgoingPacket.NativePeerId].IsSet) - { - int ret = serverPeerArray[outgoingPacket.NativePeerId].Send(outgoingPacket.Channel, ref outgoingPacket.Payload); - - if (ret < 0 && setupInfo.Verbosity > 0) - Debug.LogWarning($"Ignorance Server: ENet error code {ret} while sending packet to Peer {outgoingPacket.NativePeerId}."); - } - else - { - // A peer might have disconnected, this is OK - just log the packet if set to paranoid. - if (setupInfo.Verbosity > 1) - Debug.LogWarning("Ignorance Server: Can't send packet, a native peer object is not set. This may be normal if the Peer has disconnected before this send cycle."); - } - - } - - // Step 2 - // <--- Receiving from peers - bool pollComplete = false; - - while (!pollComplete) - { - Packet incomingPacket; - Peer incomingPeer; - int incomingPacketLength; - - // Any events happening? - if (serverENetHost.CheckEvents(out serverENetEvent) <= 0) - { - // If service time is met, break out of it. - if (serverENetHost.Service(setupInfo.PollTime, out serverENetEvent) <= 0) break; - - pollComplete = true; - } - - // Setup the packet references. - incomingPeer = serverENetEvent.Peer; - - switch (serverENetEvent.Type) - { - // Idle. - case EventType.None: - default: - break; - - // Connection Event. - case EventType.Connect: - if (setupInfo.Verbosity > 1) - Debug.Log("Ignorance Server: Here comes a new Peer connection."); - - IgnoranceConnectionEvent ice = new IgnoranceConnectionEvent() - { - NativePeerId = incomingPeer.ID, - IP = incomingPeer.IP, - Port = incomingPeer.Port - }; - - ConnectionEvents.Enqueue(ice); - - // Assign a reference to the Peer. - serverPeerArray[incomingPeer.ID] = incomingPeer; - break; - - // Disconnect/Timeout. Mirror doesn't care if it's either, so we lump them together. - case EventType.Disconnect: - case EventType.Timeout: - if (!serverPeerArray[incomingPeer.ID].IsSet) break; - - if (setupInfo.Verbosity > 1) - Debug.Log("Ignorance Server: Peer disconnection."); - - IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent() - { - WasDisconnect = true, - NativePeerId = incomingPeer.ID - }; - - DisconnectionEvents.Enqueue(iced); - - // Reset the peer array's entry for that peer. - serverPeerArray[incomingPeer.ID] = default; - break; - - case EventType.Receive: - // Receive event type usually includes a packet; so cache its reference. - incomingPacket = serverENetEvent.Packet; - if (!incomingPacket.IsSet) - { - if (setupInfo.Verbosity > 0) - Debug.LogWarning($"Ignorance Server: A receive event did not supply us with a packet to work with. This should never happen."); - break; - } - - incomingPacketLength = incomingPacket.Length; - - // Firstly check if the packet is too big. If it is, do not process it - drop it. - if (incomingPacketLength > setupInfo.PacketSizeLimit) - { - if (setupInfo.Verbosity > 0) - Debug.LogWarning($"Ignorance Server: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes."); - - incomingPacket.Dispose(); - break; - } - - IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket - { - Channel = serverENetEvent.ChannelID, - NativePeerId = incomingPeer.ID, - Payload = incomingPacket, - }; - - // Enqueue. - Incoming.Enqueue(incomingQueuePacket); - break; - } - } - } - - if (Verbosity > 0) - Debug.Log("Ignorance Server: Shutdown commencing, flushing connections."); - - // Cleanup and flush everything. - serverENetHost.Flush(); - - // Kick everyone. - for (int i = 0; i < serverPeerArray.Length; i++) - { - if (!serverPeerArray[i].IsSet) continue; - serverPeerArray[i].DisconnectNow(0); - } - } - - // Flush again to ensure ENet gets those Disconnection stuff out. - // May not be needed; better to err on side of caution - - if (setupInfo.Verbosity > 0) - Debug.Log("Ignorance Server: Shutdown complete."); - - Library.Deinitialize(); - } - - private struct ThreadParamInfo - { - public int Channels; - public int Peers; - public int PollTime; - public int Port; - public int PacketSizeLimit; - public int Verbosity; - public string Address; - } - } -} +// Ignorance 1.4.x LTS (Long Term Support) +// https://github.com/SoftwareGuy/Ignorance +// ----------------- +// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64) +// Ignorance is licensed under the MIT license. Refer +// to the LICENSE file for more information. +using System; +using System.Collections.Generic; +using System.Threading; +using ENet; +using IgnoranceThirdparty; +using UnityEngine; +using Event = ENet.Event; // fixes CS0104 ambigous reference between the same thing in UnityEngine +using EventType = ENet.EventType; // fixes CS0104 ambigous reference between the same thing in UnityEngine +using Object = System.Object; // fixes CS0104 ambigous reference between the same thing in UnityEngine + +namespace IgnoranceCore +{ + public class IgnoranceServer + { + // Server Properties + // - Bind Settings + public string BindAddress = "127.0.0.1"; + public int BindPort = 7777; + // - Maximum allowed channels, peers, etc. + public int MaximumChannels = 2; + public int MaximumPeers = 100; + public int MaximumPacketSize = 33554432; // ENet.cs: uint maxPacketSize = 32 * 1024 * 1024 = 33554432 + // - Native poll waiting time + public int PollTime = 1; + // - Verbosity. + public int Verbosity = 1; + // - Queue Sizing + public int IncomingOutgoingBufferSize = 5000; + public int ConnectionEventBufferSize = 100; + + public bool IsAlive => WorkerThread != null && WorkerThread.IsAlive; + + private volatile bool CeaseOperation = false; + + // Queues + // v1.4.0b9: Replace the queues with RingBuffers. + public RingBuffer Incoming; + public RingBuffer Outgoing; + public RingBuffer Commands; + public RingBuffer ConnectionEvents; + public RingBuffer DisconnectionEvents; + public RingBuffer StatusUpdates; + + public RingBuffer RecycledServerStatBlocks = new RingBuffer(100); + + // Thread + private Thread WorkerThread; + + public void Start() + { + if (WorkerThread != null && WorkerThread.IsAlive) + { + // Cannot do that. + Debug.LogError("Ignorance Server: A worker thread is already running. Cannot start another."); + return; + } + + // Setup the ring buffers. + SetupRingBuffersIfNull(); + + CeaseOperation = false; + ThreadParamInfo threadParams = new ThreadParamInfo() + { + Address = BindAddress, + Port = BindPort, + Peers = MaximumPeers, + Channels = MaximumChannels, + PollTime = PollTime, + PacketSizeLimit = MaximumPacketSize, + Verbosity = Verbosity + }; + + // Drain queues. + if (Incoming != null) while (Incoming.TryDequeue(out _)) ; + if (Outgoing != null) while (Outgoing.TryDequeue(out _)) ; + if (Commands != null) while (Commands.TryDequeue(out _)) ; + if (ConnectionEvents != null) while (ConnectionEvents.TryDequeue(out _)) ; + if (DisconnectionEvents != null) while (DisconnectionEvents.TryDequeue(out _)) ; + if (StatusUpdates != null) while (StatusUpdates.TryDequeue(out _)) ; + + WorkerThread = new Thread(ThreadWorker); + WorkerThread.Start(threadParams); + + // Announce + if (Verbosity > 0) + Debug.Log("Ignorance Server: Dispatched worker thread."); + } + + public void Stop() + { + // v1.4.0b7: Mirror may call this; if the worker thread isn't alive then don't announce it. + if (WorkerThread != null && WorkerThread.IsAlive) + { + if (Verbosity > 0) + Debug.Log("Ignorance Server: Server stop acknowledged. Depending on network load, this may take a moment or two..."); + + CeaseOperation = true; + } + } + + #region The meat and potatoes. + private void ThreadWorker(Object parameters) + { + if (Verbosity > 0) + Debug.Log("Ignorance Server: Initializing. Please stand by..."); + + // Thread cache items + ThreadParamInfo setupInfo; + Address serverAddress = new Address(); + Host serverENetHost; + Event serverENetEvent; + + Peer[] serverPeerArray; + IgnoranceClientStats peerStats = default; + + // Grab the setup information. + if (parameters.GetType() == typeof(ThreadParamInfo)) + { + setupInfo = (ThreadParamInfo)parameters; + } + else + { + Debug.LogError("Ignorance Server: Startup failure; Invalid thread parameters. Aborting."); + return; + } + + // Attempt to initialize ENet inside the thread. + if (Library.Initialize()) + { + Debug.Log("Ignorance Server: ENet Native successfully initialized."); + } + else + { + Debug.LogError("Ignorance Server: Failed to initialize ENet Native. Aborting."); + return; + } + + // Configure the server address. + serverAddress.SetHost(setupInfo.Address); + serverAddress.Port = (ushort)setupInfo.Port; + serverPeerArray = new Peer[setupInfo.Peers]; + + using (serverENetHost = new Host()) + { + // Create the server object. + try + { + serverENetHost.Create(serverAddress, setupInfo.Peers, setupInfo.Channels); + } + catch (Exception ex) + { + Debug.LogError($"Ignorance Server: While attempting to create server host object, we caught an exception:\n{ex.Message}"); + Debug.LogError($"If you are getting a \"Host creation call failed\" exception, please ensure you don't have a server already running on the same IP and Port.\n" + + $"Multiple server instances running on the same port are not supported. Also check to see if ports are not in-use by another application. In the worse case scenario, " + + $"restart your device to ensure no random background ENet threads are active that haven't been cleaned up correctly. If problems persist, please file a support ticket."); + + Library.Deinitialize(); + return; + } + + // Loop until we're told to cease operations. + while (!CeaseOperation) + { + // Intermission: Command Handling + while (Commands.TryDequeue(out IgnoranceCommandPacket commandPacket)) + { + switch (commandPacket.Type) + { + default: + break; + + // Boot a Peer off the Server. + case IgnoranceCommandType.ServerKickPeer: + uint targetPeer = commandPacket.PeerId; + + if (!serverPeerArray[targetPeer].IsSet) continue; + + if (setupInfo.Verbosity > 0) + Debug.Log($"Ignorance Server: Booting Peer {targetPeer} off"); + + IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent + { + EventType = 0x01, + NativePeerId = targetPeer + }; + + DisconnectionEvents.Enqueue(iced); + + // Disconnect and reset the peer array's entry for that peer. + serverPeerArray[targetPeer].DisconnectNow(0); + serverPeerArray[targetPeer] = default; + break; + + case IgnoranceCommandType.ServerStatusRequest: + IgnoranceServerStats serverStats; + if (!RecycledServerStatBlocks.TryDequeue(out serverStats)) + serverStats.PeerStats = new Dictionary(setupInfo.Peers); + + serverStats.PeerStats.Clear(); + + serverStats.BytesReceived = serverENetHost.BytesReceived; + serverStats.BytesSent = serverENetHost.BytesSent; + + serverStats.PacketsReceived = serverENetHost.PacketsReceived; + serverStats.PacketsSent = serverENetHost.PacketsSent; + + serverStats.PeersCount = serverENetHost.PeersCount; + + for (int i = 0; i < serverPeerArray.Length; i++) + { + if (!serverPeerArray[i].IsSet) continue; + + peerStats.RTT = serverPeerArray[i].RoundTripTime; + + peerStats.BytesReceived = serverPeerArray[i].BytesReceived; + peerStats.BytesSent = serverPeerArray[i].BytesSent; + + peerStats.PacketsSent = serverPeerArray[i].PacketsSent; + peerStats.PacketsLost = serverPeerArray[i].PacketsLost; + + serverStats.PeerStats.Add(i, peerStats); + } + + StatusUpdates.Enqueue(serverStats); + break; + } + } + + // Step One: + // ---> Sending to peers + while (Outgoing.TryDequeue(out IgnoranceOutgoingPacket outgoingPacket)) + { + // Only create a packet if the server knows the peer. + if (serverPeerArray[outgoingPacket.NativePeerId].IsSet) + { + int ret = serverPeerArray[outgoingPacket.NativePeerId].Send(outgoingPacket.Channel, ref outgoingPacket.Payload); + + if (ret < 0 && setupInfo.Verbosity > 0) + Debug.LogWarning($"Ignorance Server: ENet error {ret} while sending packet to Peer {outgoingPacket.NativePeerId}."); + } + else + { + // A peer might have disconnected, this is OK - just log the packet if set to paranoid. + if (setupInfo.Verbosity > 1) + Debug.LogWarning("Ignorance Server: Can't send packet, a native peer object is not set. This may be normal if the Peer has disconnected before this send cycle."); + } + + } + + // Step 2 + // <--- Receiving from peers + bool pollComplete = false; + + while (!pollComplete) + { + Packet incomingPacket; + Peer incomingPeer; + int incomingPacketLength; + + // Any events happening? + if (serverENetHost.CheckEvents(out serverENetEvent) <= 0) + { + // If service time is met, break out of it. + if (serverENetHost.Service(setupInfo.PollTime, out serverENetEvent) <= 0) break; + + pollComplete = true; + } + + // Setup the packet references. + incomingPeer = serverENetEvent.Peer; + + // What type are you? + switch (serverENetEvent.Type) + { + // Idle. + case EventType.None: + default: + break; + + // Connection Event. + case EventType.Connect: + if (setupInfo.Verbosity > 1) + Debug.Log($"Ignorance Server: Peer ID {incomingPeer.ID} says Hi."); + + IgnoranceConnectionEvent ice = new IgnoranceConnectionEvent() + { + NativePeerId = incomingPeer.ID, + IP = incomingPeer.IP, + Port = incomingPeer.Port + }; + + ConnectionEvents.Enqueue(ice); + + // Assign a reference to the Peer. + serverPeerArray[incomingPeer.ID] = incomingPeer; + break; + + // Disconnect/Timeout. Mirror doesn't care if it's either, so we lump them together. + case EventType.Disconnect: + case EventType.Timeout: + if (!serverPeerArray[incomingPeer.ID].IsSet) break; + + if (setupInfo.Verbosity > 1) + Debug.Log($"Ignorance Server: Peer {incomingPeer.ID} has disconnected."); + + IgnoranceConnectionEvent iced = new IgnoranceConnectionEvent + { + EventType = 0x01, + NativePeerId = incomingPeer.ID + }; + + DisconnectionEvents.Enqueue(iced); + + // Reset the peer array's entry for that peer. + serverPeerArray[incomingPeer.ID] = default; + break; + + case EventType.Receive: + // Receive event type usually includes a packet; so cache its reference. + incomingPacket = serverENetEvent.Packet; + if (!incomingPacket.IsSet) + { + if (setupInfo.Verbosity > 0) + Debug.LogWarning($"Ignorance Server: A receive event did not supply us with a packet to work with. This should never happen."); + break; + } + + incomingPacketLength = incomingPacket.Length; + + // Firstly check if the packet is too big. If it is, do not process it - drop it. + if (incomingPacketLength > setupInfo.PacketSizeLimit) + { + if (setupInfo.Verbosity > 0) + Debug.LogWarning($"Ignorance Server: Incoming packet is too big. My limit is {setupInfo.PacketSizeLimit} byte(s) whilest this packet is {incomingPacketLength} bytes."); + + incomingPacket.Dispose(); + break; + } + + IgnoranceIncomingPacket incomingQueuePacket = new IgnoranceIncomingPacket + { + Channel = serverENetEvent.ChannelID, + NativePeerId = incomingPeer.ID, + Payload = incomingPacket, + }; + + // Enqueue. + Incoming.Enqueue(incomingQueuePacket); + break; + } + } + } + + if (Verbosity > 0) + Debug.Log("Ignorance Server: Thread shutdown commencing. Flushing connections."); + + // Cleanup and flush everything. + serverENetHost.Flush(); + + // Kick everyone. + for (int i = 0; i < serverPeerArray.Length; i++) + { + if (!serverPeerArray[i].IsSet) continue; + serverPeerArray[i].DisconnectNow(0); + } + } + + if (setupInfo.Verbosity > 0) + Debug.Log("Ignorance Server: Shutdown complete."); + + Library.Deinitialize(); + } + #endregion + + private void SetupRingBuffersIfNull() + { + Debug.Log($"Ignorance: Setting up ring buffers if they're not already created. " + + $"If they are already, this step will be skipped."); + + if (Incoming == null) + Incoming = new RingBuffer(IncomingOutgoingBufferSize); + if (Outgoing == null) + Outgoing = new RingBuffer(IncomingOutgoingBufferSize); + if (Commands == null) + Commands = new RingBuffer(100); + if (ConnectionEvents == null) + ConnectionEvents = new RingBuffer(ConnectionEventBufferSize); + if (DisconnectionEvents == null) + DisconnectionEvents = new RingBuffer(ConnectionEventBufferSize); + if (StatusUpdates == null) + StatusUpdates = new RingBuffer(10); + } + + private struct ThreadParamInfo + { + public int Channels; + public int Peers; + public int PollTime; + public int Port; + public int PacketSizeLimit; + public int Verbosity; + public string Address; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs.meta index 8b3212b..1cd7db4 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Core/IgnoranceServer.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 1069f42b88a4adb4ab1990cec4949343 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 1069f42b88a4adb4ab1990cec4949343 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies.meta index 565dc3b..cbc1637 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 12f903db684732e45b130ad56f7c86c1 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 12f903db684732e45b130ad56f7c86c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs index cf4336c..a74d0dc 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs @@ -1,1411 +1,1416 @@ -/* - * Managed C# wrapper for an extended version of ENet - * This is a fork from upstream and is available at http://github.com/SoftwareGuy/ENet-CSharp - * - * Copyright (c) 2019 Matt Coburn (SoftwareGuy/Coburn64), Chris Burns (c6burns) - * Copyright (c) 2013 James Bellinger, 2016 Nate Shoffner, 2018 Stanislav Denisov - * - * 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. - */ - -using System; -using System.Runtime.InteropServices; -using System.Security; -using System.Text; - -namespace ENet -{ - [Flags] - public enum PacketFlags - { - None = 0, - Reliable = 1 << 0, - Unsequenced = 1 << 1, - NoAllocate = 1 << 2, - UnreliableFragmented = 1 << 3, - Instant = 1 << 4, - Unthrottled = 1 << 5, - Sent = 1 << 8 - } - - public enum EventType - { - None = 0, - Connect = 1, - Disconnect = 2, - Receive = 3, - Timeout = 4 - } - - public enum PeerState - { - Uninitialized = -1, - Disconnected = 0, - Connecting = 1, - AcknowledgingConnect = 2, - ConnectionPending = 3, - ConnectionSucceeded = 4, - Connected = 5, - DisconnectLater = 6, - Disconnecting = 7, - AcknowledgingDisconnect = 8, - Zombie = 9 - } - - [StructLayout(LayoutKind.Explicit, Size = 18)] - internal struct ENetAddress - { - [FieldOffset(16)] - public ushort port; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ENetEvent - { - public EventType type; - public IntPtr peer; - public byte channelID; - public uint data; - public IntPtr packet; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ENetCallbacks - { - public AllocCallback malloc; - public FreeCallback free; - public NoMemoryCallback noMemory; - } - - public delegate IntPtr AllocCallback(IntPtr size); - public delegate void FreeCallback(IntPtr memory); - public delegate void NoMemoryCallback(); - public delegate void PacketFreeCallback(Packet packet); - public delegate int InterceptCallback(ref Event @event, ref Address address, IntPtr receivedData, int receivedDataLength); - public delegate ulong ChecksumCallback(IntPtr buffers, int bufferCount); - - internal static class ArrayPool - { - [ThreadStatic] - private static byte[] byteBuffer; - [ThreadStatic] - private static IntPtr[] pointerBuffer; - - public static byte[] GetByteBuffer() - { - if (byteBuffer == null) - byteBuffer = new byte[64]; - - return byteBuffer; - } - - public static IntPtr[] GetPointerBuffer() - { - if (pointerBuffer == null) - pointerBuffer = new IntPtr[Library.maxPeers]; - - return pointerBuffer; - } - } - - public struct Address - { - private ENetAddress nativeAddress; - - internal ENetAddress NativeData - { - get - { - return nativeAddress; - } - - set - { - nativeAddress = value; - } - } - - internal Address(ENetAddress address) - { - nativeAddress = address; - } - - public ushort Port - { - get - { - return nativeAddress.port; - } - - set - { - nativeAddress.port = value; - } - } - - public string GetIP() - { - StringBuilder ip = new StringBuilder(1025); - - if (Native.enet_address_get_ip(ref nativeAddress, ip, (IntPtr)ip.Capacity) != 0) - return String.Empty; - - return ip.ToString(); - } - - public bool SetIP(string ip) - { - if (ip == null) - throw new ArgumentNullException("ip"); - - return Native.enet_address_set_ip(ref nativeAddress, ip) == 0; - } - - public string GetHost() - { - StringBuilder hostName = new StringBuilder(1025); - - if (Native.enet_address_get_hostname(ref nativeAddress, hostName, (IntPtr)hostName.Capacity) != 0) - return String.Empty; - - return hostName.ToString(); - } - - public bool SetHost(string hostName) - { - if (hostName == null) - throw new ArgumentNullException("hostName"); - - return Native.enet_address_set_hostname(ref nativeAddress, hostName) == 0; - } - } - - public struct Event - { - private ENetEvent nativeEvent; - - internal ENetEvent NativeData - { - get - { - return nativeEvent; - } - - set - { - nativeEvent = value; - } - } - - internal Event(ENetEvent @event) - { - nativeEvent = @event; - } - - public EventType Type - { - get - { - return nativeEvent.type; - } - } - - public Peer Peer - { - get - { - return new Peer(nativeEvent.peer); - } - } - - public byte ChannelID - { - get - { - return nativeEvent.channelID; - } - } - - public uint Data - { - get - { - return nativeEvent.data; - } - } - - public Packet Packet - { - get - { - return new Packet(nativeEvent.packet); - } - } - } - - public class Callbacks - { - private ENetCallbacks nativeCallbacks; - - internal ENetCallbacks NativeData - { - get - { - return nativeCallbacks; - } - - set - { - nativeCallbacks = value; - } - } - - public Callbacks(AllocCallback allocCallback, FreeCallback freeCallback, NoMemoryCallback noMemoryCallback) - { - nativeCallbacks.malloc = allocCallback; - nativeCallbacks.free = freeCallback; - nativeCallbacks.noMemory = noMemoryCallback; - } - } - - public struct Packet : IDisposable - { - private IntPtr nativePacket; - - internal IntPtr NativeData - { - get - { - return nativePacket; - } - - set - { - nativePacket = value; - } - } - - internal Packet(IntPtr packet) - { - nativePacket = packet; - } - - public void Dispose() - { - if (nativePacket != IntPtr.Zero) - { - Native.enet_packet_dispose(nativePacket); - nativePacket = IntPtr.Zero; - } - } - - public bool IsSet - { - get - { - return nativePacket != IntPtr.Zero; - } - } - - public IntPtr Data - { - get - { - ThrowIfNotCreated(); - - return Native.enet_packet_get_data(nativePacket); - } - } - - public IntPtr UserData - { - get - { - ThrowIfNotCreated(); - - return Native.enet_packet_get_user_data(nativePacket); - } - - set - { - ThrowIfNotCreated(); - - Native.enet_packet_set_user_data(nativePacket, value); - } - } - - public int Length - { - get - { - ThrowIfNotCreated(); - - return Native.enet_packet_get_length(nativePacket); - } - } - - public bool HasReferences - { - get - { - ThrowIfNotCreated(); - - return Native.enet_packet_check_references(nativePacket) != 0; - } - } - - internal void ThrowIfNotCreated() - { - if (nativePacket == IntPtr.Zero) - throw new InvalidOperationException("Packet not created"); - } - - public void SetFreeCallback(IntPtr callback) - { - ThrowIfNotCreated(); - - Native.enet_packet_set_free_callback(nativePacket, callback); - } - - public void SetFreeCallback(PacketFreeCallback callback) - { - ThrowIfNotCreated(); - - Native.enet_packet_set_free_callback(nativePacket, Marshal.GetFunctionPointerForDelegate(callback)); - } - - public void Create(byte[] data) - { - if (data == null) - throw new ArgumentNullException("data"); - - Create(data, data.Length); - } - - public void Create(byte[] data, int length) - { - Create(data, length, PacketFlags.None); - } - - public void Create(byte[] data, PacketFlags flags) - { - Create(data, data.Length, flags); - } - - public void Create(byte[] data, int length, PacketFlags flags) - { - if (data == null) - throw new ArgumentNullException("data"); - - if (length < 0 || length > data.Length) - throw new ArgumentOutOfRangeException("length"); - - nativePacket = Native.enet_packet_create(data, (IntPtr)length, flags); - } - - public void Create(IntPtr data, int length, PacketFlags flags) - { - if (data == IntPtr.Zero) - throw new ArgumentNullException("data"); - - if (length < 0) - throw new ArgumentOutOfRangeException("length"); - - nativePacket = Native.enet_packet_create(data, (IntPtr)length, flags); - } - - public void Create(byte[] data, int offset, int length, PacketFlags flags) - { - if (data == null) - throw new ArgumentNullException("data"); - - if (offset < 0) - throw new ArgumentOutOfRangeException("offset"); - - if (length < 0 || length > data.Length) - throw new ArgumentOutOfRangeException("length"); - - nativePacket = Native.enet_packet_create_offset(data, (IntPtr)length, (IntPtr)offset, flags); - } - - public void Create(IntPtr data, int offset, int length, PacketFlags flags) - { - if (data == IntPtr.Zero) - throw new ArgumentNullException("data"); - - if (offset < 0) - throw new ArgumentOutOfRangeException("offset"); - - if (length < 0) - throw new ArgumentOutOfRangeException("length"); - - nativePacket = Native.enet_packet_create_offset(data, (IntPtr)length, (IntPtr)offset, flags); - } - - public void CopyTo(byte[] destination, int startPos = 0) - { - if (destination == null) - throw new ArgumentNullException("destination"); - - // Fix by katori, prevents trying to copy a NULL - // from native world (ie. disconnect a client) - if (Data == null) - { - return; - } - - Marshal.Copy(Data, destination, startPos, Length); - } - } - - public class Host : IDisposable - { - private IntPtr nativeHost; - - internal IntPtr NativeData - { - get - { - return nativeHost; - } - - set - { - nativeHost = value; - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (nativeHost != IntPtr.Zero) - { - Native.enet_host_destroy(nativeHost); - nativeHost = IntPtr.Zero; - } - } - - ~Host() - { - Dispose(false); - } - - public bool IsSet - { - get - { - return nativeHost != IntPtr.Zero; - } - } - - public uint PeersCount - { - get - { - ThrowIfNotCreated(); - - return Native.enet_host_get_peers_count(nativeHost); - } - } - - public uint PacketsSent - { - get - { - ThrowIfNotCreated(); - - return Native.enet_host_get_packets_sent(nativeHost); - } - } - - public uint PacketsReceived - { - get - { - ThrowIfNotCreated(); - - return Native.enet_host_get_packets_received(nativeHost); - } - } - - public uint BytesSent - { - get - { - ThrowIfNotCreated(); - - return Native.enet_host_get_bytes_sent(nativeHost); - } - } - - public uint BytesReceived - { - get - { - ThrowIfNotCreated(); - - return Native.enet_host_get_bytes_received(nativeHost); - } - } - - internal void ThrowIfNotCreated() - { - if (nativeHost == IntPtr.Zero) - throw new InvalidOperationException("Host not created"); - } - - private static void ThrowIfChannelsExceeded(int channelLimit) - { - if (channelLimit < 0 || channelLimit > Library.maxChannelCount) - throw new ArgumentOutOfRangeException("channelLimit"); - } - - public void Create() - { - Create(null, 1, 0); - } - - public void Create(int bufferSize) - { - Create(null, 1, 0, 0, 0, bufferSize); - } - - public void Create(Address? address, int peerLimit) - { - Create(address, peerLimit, 0); - } - - public void Create(Address? address, int peerLimit, int channelLimit) - { - Create(address, peerLimit, channelLimit, 0, 0, 0); - } - - public void Create(int peerLimit, int channelLimit) - { - Create(null, peerLimit, channelLimit, 0, 0, 0); - } - - public void Create(int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth) - { - Create(null, peerLimit, channelLimit, incomingBandwidth, outgoingBandwidth, 0); - } - - public void Create(Address? address, int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth) - { - Create(address, peerLimit, channelLimit, incomingBandwidth, outgoingBandwidth, 0); - } - - public void Create(Address? address, int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize) - { - if (nativeHost != IntPtr.Zero) - throw new InvalidOperationException("Host already created"); - - if (peerLimit < 0 || peerLimit > Library.maxPeers) - throw new ArgumentOutOfRangeException("peerLimit"); - - ThrowIfChannelsExceeded(channelLimit); - - if (address != null) - { - var nativeAddress = address.Value.NativeData; - - nativeHost = Native.enet_host_create(ref nativeAddress, (IntPtr)peerLimit, (IntPtr)channelLimit, incomingBandwidth, outgoingBandwidth, bufferSize); - } - else - { - nativeHost = Native.enet_host_create(IntPtr.Zero, (IntPtr)peerLimit, (IntPtr)channelLimit, incomingBandwidth, outgoingBandwidth, bufferSize); - } - - if (nativeHost == IntPtr.Zero) - throw new InvalidOperationException("Host creation call failed"); - } - - public void PreventConnections(bool state) - { - ThrowIfNotCreated(); - - Native.enet_host_prevent_connections(nativeHost, (byte)(state ? 1 : 0)); - } - - public void Broadcast(byte channelID, ref Packet packet) - { - ThrowIfNotCreated(); - - packet.ThrowIfNotCreated(); - Native.enet_host_broadcast(nativeHost, channelID, packet.NativeData); - packet.NativeData = IntPtr.Zero; - } - - public void Broadcast(byte channelID, ref Packet packet, Peer excludedPeer) - { - ThrowIfNotCreated(); - - packet.ThrowIfNotCreated(); - Native.enet_host_broadcast_exclude(nativeHost, channelID, packet.NativeData, excludedPeer.NativeData); - packet.NativeData = IntPtr.Zero; - } - - public void Broadcast(byte channelID, ref Packet packet, Peer[] peers) - { - if (peers == null) - throw new ArgumentNullException("peers"); - - ThrowIfNotCreated(); - - packet.ThrowIfNotCreated(); - - if (peers.Length > 0) - { - IntPtr[] nativePeers = ArrayPool.GetPointerBuffer(); - int nativeCount = 0; - - for (int i = 0; i < peers.Length; i++) - { - if (peers[i].NativeData != IntPtr.Zero) - { - nativePeers[nativeCount] = peers[i].NativeData; - nativeCount++; - } - } - - Native.enet_host_broadcast_selective(nativeHost, channelID, packet.NativeData, nativePeers, (IntPtr)nativeCount); - } - - packet.NativeData = IntPtr.Zero; - } - - public int CheckEvents(out Event @event) - { - ThrowIfNotCreated(); - - ENetEvent nativeEvent; - - var result = Native.enet_host_check_events(nativeHost, out nativeEvent); - - if (result <= 0) - { - @event = default(Event); - - return result; - } - - @event = new Event(nativeEvent); - - return result; - } - - public Peer Connect(Address address) - { - return Connect(address, 0, 0); - } - - public Peer Connect(Address address, int channelLimit) - { - return Connect(address, channelLimit, 0); - } - - public Peer Connect(Address address, int channelLimit, uint data) - { - ThrowIfNotCreated(); - ThrowIfChannelsExceeded(channelLimit); - - var nativeAddress = address.NativeData; - var peer = new Peer(Native.enet_host_connect(nativeHost, ref nativeAddress, (IntPtr)channelLimit, data)); - - if (peer.NativeData == IntPtr.Zero) - throw new InvalidOperationException("Host connect call failed"); - - return peer; - } - - public int Service(int timeout, out Event @event) - { - if (timeout < 0) - throw new ArgumentOutOfRangeException("timeout"); - - ThrowIfNotCreated(); - - ENetEvent nativeEvent; - - var result = Native.enet_host_service(nativeHost, out nativeEvent, (uint)timeout); - - if (result <= 0) - { - @event = default(Event); - - return result; - } - - @event = new Event(nativeEvent); - - return result; - } - - public void SetBandwidthLimit(uint incomingBandwidth, uint outgoingBandwidth) - { - ThrowIfNotCreated(); - - Native.enet_host_bandwidth_limit(nativeHost, incomingBandwidth, outgoingBandwidth); - } - - public void SetChannelLimit(int channelLimit) - { - ThrowIfNotCreated(); - ThrowIfChannelsExceeded(channelLimit); - - Native.enet_host_channel_limit(nativeHost, (IntPtr)channelLimit); - } - - public void SetMaxDuplicatePeers(ushort number) - { - ThrowIfNotCreated(); - - Native.enet_host_set_max_duplicate_peers(nativeHost, number); - } - - public void SetInterceptCallback(IntPtr callback) - { - ThrowIfNotCreated(); - - Native.enet_host_set_intercept_callback(nativeHost, callback); - } - - public void SetInterceptCallback(InterceptCallback callback) - { - ThrowIfNotCreated(); - - Native.enet_host_set_intercept_callback(nativeHost, Marshal.GetFunctionPointerForDelegate(callback)); - } - - public void SetChecksumCallback(IntPtr callback) - { - ThrowIfNotCreated(); - - Native.enet_host_set_checksum_callback(nativeHost, callback); - } - - public void SetChecksumCallback(ChecksumCallback callback) - { - ThrowIfNotCreated(); - - Native.enet_host_set_checksum_callback(nativeHost, Marshal.GetFunctionPointerForDelegate(callback)); - } - - public void Flush() - { - ThrowIfNotCreated(); - - Native.enet_host_flush(nativeHost); - } - } - - public struct Peer - { - private IntPtr nativePeer; - private uint nativeID; - - internal IntPtr NativeData - { - get - { - return nativePeer; - } - - set - { - nativePeer = value; - } - } - - internal Peer(IntPtr peer) - { - nativePeer = peer; - nativeID = nativePeer != IntPtr.Zero ? Native.enet_peer_get_id(nativePeer) : 0; - } - - public bool IsSet - { - get - { - return nativePeer != IntPtr.Zero; - } - } - - public uint ID - { - get - { - return nativeID; - } - } - - public string IP - { - get - { - ThrowIfNotCreated(); - - byte[] ip = ArrayPool.GetByteBuffer(); - - if (Native.enet_peer_get_ip(nativePeer, ip, (IntPtr)ip.Length) == 0) - return Encoding.ASCII.GetString(ip, 0, ip.StringLength()); - else - return String.Empty; - } - } - - public ushort Port - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_port(nativePeer); - } - } - - public uint MTU - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_mtu(nativePeer); - } - } - - public PeerState State - { - get - { - return nativePeer == IntPtr.Zero ? PeerState.Uninitialized : Native.enet_peer_get_state(nativePeer); - } - } - - public uint RoundTripTime - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_rtt(nativePeer); - } - } - - public uint LastRoundTripTime - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_last_rtt(nativePeer); - } - } - - public uint LastSendTime - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_lastsendtime(nativePeer); - } - } - - public uint LastReceiveTime - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_lastreceivetime(nativePeer); - } - } - - public ulong PacketsSent - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_packets_sent(nativePeer); - } - } - - public ulong PacketsLost - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_packets_lost(nativePeer); - } - } - - public float PacketsThrottle - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_packets_throttle(nativePeer); - } - } - - public ulong BytesSent - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_bytes_sent(nativePeer); - } - } - - public ulong BytesReceived - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_bytes_received(nativePeer); - } - } - - public IntPtr Data - { - get - { - ThrowIfNotCreated(); - - return Native.enet_peer_get_data(nativePeer); - } - - set - { - ThrowIfNotCreated(); - - Native.enet_peer_set_data(nativePeer, value); - } - } - - internal void ThrowIfNotCreated() - { - if (nativePeer == IntPtr.Zero) - throw new InvalidOperationException("Peer not created"); - } - - public void ConfigureThrottle(uint interval, uint acceleration, uint deceleration, uint threshold) - { - ThrowIfNotCreated(); - - Native.enet_peer_throttle_configure(nativePeer, interval, acceleration, deceleration, threshold); - } - - public int Send(byte channelID, ref Packet packet) - { - ThrowIfNotCreated(); - - packet.ThrowIfNotCreated(); - - return Native.enet_peer_send(nativePeer, channelID, packet.NativeData); - } - - public bool Receive(out byte channelID, out Packet packet) - { - ThrowIfNotCreated(); - - IntPtr nativePacket = Native.enet_peer_receive(nativePeer, out channelID); - - if (nativePacket != IntPtr.Zero) - { - packet = new Packet(nativePacket); - - return true; - } - - packet = default(Packet); - - return false; - } - - public void Ping() - { - ThrowIfNotCreated(); - - Native.enet_peer_ping(nativePeer); - } - - public void PingInterval(uint interval) - { - ThrowIfNotCreated(); - - Native.enet_peer_ping_interval(nativePeer, interval); - } - - public void Timeout(uint timeoutLimit, uint timeoutMinimum, uint timeoutMaximum) - { - ThrowIfNotCreated(); - - Native.enet_peer_timeout(nativePeer, timeoutLimit, timeoutMinimum, timeoutMaximum); - } - - public void Disconnect(uint data) - { - ThrowIfNotCreated(); - - Native.enet_peer_disconnect(nativePeer, data); - } - - public void DisconnectNow(uint data) - { - ThrowIfNotCreated(); - - Native.enet_peer_disconnect_now(nativePeer, data); - } - - public void DisconnectLater(uint data) - { - ThrowIfNotCreated(); - - Native.enet_peer_disconnect_later(nativePeer, data); - } - - public void Reset() - { - ThrowIfNotCreated(); - - Native.enet_peer_reset(nativePeer); - } - } - - public static class Extensions - { - public static int StringLength(this byte[] data) - { - if (data == null) - throw new ArgumentNullException("data"); - - int i; - - for (i = 0; i < data.Length && data[i] != 0; i++) ; - - return i; - } - } - - public static class Library - { - public const uint maxChannelCount = 0xFF; - public const uint maxPeers = 0xFFF; - public const uint maxPacketSize = 32 * 1024 * 1024; - public const uint throttleThreshold = 40; - public const uint throttleScale = 32; - public const uint throttleAcceleration = 2; - public const uint throttleDeceleration = 2; - public const uint throttleInterval = 5000; - public const uint timeoutLimit = 32; - public const uint timeoutMinimum = 5000; - public const uint timeoutMaximum = 30000; - public const uint version = (2 << 16) | (4 << 8) | (7); - - public static uint Time - { - get - { - return Native.enet_time_get(); - } - } - - public static bool Initialize() - { - if (Native.enet_linked_version() != version) - throw new InvalidOperationException("ENet native is out of date. Download the latest release from https://github.com/SoftwareGuy/ENet-CSharp/releases"); - - return Native.enet_initialize() == 0; - } - - public static bool Initialize(Callbacks callbacks) - { - if (callbacks == null) - throw new ArgumentNullException("callbacks"); - - if (Native.enet_linked_version() != version) - throw new InvalidOperationException("ENet native is out of date. Download the latest release from https://github.com/SoftwareGuy/ENet-CSharp/releases"); - - ENetCallbacks nativeCallbacks = callbacks.NativeData; - - return Native.enet_initialize_with_callbacks(version, ref nativeCallbacks) == 0; - } - - public static void Deinitialize() - { - Native.enet_deinitialize(); - } - - public static ulong CRC64(IntPtr buffers, int bufferCount) - { - return Native.enet_crc64(buffers, bufferCount); - } - } - - [SuppressUnmanagedCodeSecurity] - internal static class Native - { - // This should address Unity usage and bug #66: Platform specific Enet / libenet - // https://github.com/SoftwareGuy/Ignorance/issues/66 -#if UNITY_EDITOR - // We are inside the Unity Editor. -#if UNITY_EDITOR_OSX - // Unity Editor on macOS needs to use libenet. - private const string nativeLibrary = "libenet"; -#else - private const string nativeLibrary = "enet"; -#endif -#endif - -#if !UNITY_EDITOR - // We're not inside the Unity Editor. -#if __APPLE__ && !(__IOS__ || UNITY_IOS) - // Use libenet on macOS. - private const string nativeLibrary = "libenet"; -#elif __IOS__ || UNITY_IOS - // We're building for a certain mobile fruity OS. - private const string nativeLibrary = "__Internal"; -#else - // Assume everything else, Windows et al. - private const string nativeLibrary = "enet"; -#endif -#endif - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_initialize(); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_initialize_with_callbacks(uint version, ref ENetCallbacks inits); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_deinitialize(); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_linked_version(); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_time_get(); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern ulong enet_crc64(IntPtr buffers, int bufferCount); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_address_set_ip(ref ENetAddress address, string ip); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_address_set_hostname(ref ENetAddress address, string hostName); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_address_get_ip(ref ENetAddress address, StringBuilder ip, IntPtr ipLength); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_address_get_hostname(ref ENetAddress address, StringBuilder hostName, IntPtr nameLength); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_packet_create(byte[] data, IntPtr dataLength, PacketFlags flags); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_packet_create(IntPtr data, IntPtr dataLength, PacketFlags flags); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_packet_create_offset(byte[] data, IntPtr dataLength, IntPtr dataOffset, PacketFlags flags); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_packet_create_offset(IntPtr data, IntPtr dataLength, IntPtr dataOffset, PacketFlags flags); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_packet_check_references(IntPtr packet); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_packet_get_data(IntPtr packet); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_packet_get_user_data(IntPtr packet); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_packet_set_user_data(IntPtr packet, IntPtr userData); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_packet_get_length(IntPtr packet); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_packet_set_free_callback(IntPtr packet, IntPtr callback); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_packet_dispose(IntPtr packet); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_host_create(ref ENetAddress address, IntPtr peerLimit, IntPtr channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_host_create(IntPtr address, IntPtr peerLimit, IntPtr channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_host_connect(IntPtr host, ref ENetAddress address, IntPtr channelCount, uint data); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_broadcast(IntPtr host, byte channelID, IntPtr packet); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_broadcast_exclude(IntPtr host, byte channelID, IntPtr packet, IntPtr excludedPeer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_broadcast_selective(IntPtr host, byte channelID, IntPtr packet, IntPtr[] peers, IntPtr peersLength); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_host_service(IntPtr host, out ENetEvent @event, uint timeout); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_host_check_events(IntPtr host, out ENetEvent @event); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_channel_limit(IntPtr host, IntPtr channelLimit); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_bandwidth_limit(IntPtr host, uint incomingBandwidth, uint outgoingBandwidth); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_host_get_peers_count(IntPtr host); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_host_get_packets_sent(IntPtr host); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_host_get_packets_received(IntPtr host); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_host_get_bytes_sent(IntPtr host); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_host_get_bytes_received(IntPtr host); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_set_max_duplicate_peers(IntPtr host, ushort number); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_set_intercept_callback(IntPtr host, IntPtr callback); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_set_checksum_callback(IntPtr host, IntPtr callback); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_flush(IntPtr host); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_destroy(IntPtr host); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_host_prevent_connections(IntPtr host, byte state); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_throttle_configure(IntPtr peer, uint interval, uint acceleration, uint deceleration, uint threshold); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_peer_get_id(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_peer_get_ip(IntPtr peer, byte[] ip, IntPtr ipLength); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern ushort enet_peer_get_port(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_peer_get_mtu(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern PeerState enet_peer_get_state(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_peer_get_rtt(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_peer_get_last_rtt(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_peer_get_lastsendtime(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint enet_peer_get_lastreceivetime(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern ulong enet_peer_get_packets_sent(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern ulong enet_peer_get_packets_lost(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern float enet_peer_get_packets_throttle(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern ulong enet_peer_get_bytes_sent(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern ulong enet_peer_get_bytes_received(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_peer_get_data(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_set_data(IntPtr peer, IntPtr data); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern int enet_peer_send(IntPtr peer, byte channelID, IntPtr packet); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr enet_peer_receive(IntPtr peer, out byte channelID); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_ping(IntPtr peer); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_ping_interval(IntPtr peer, uint pingInterval); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_timeout(IntPtr peer, uint timeoutLimit, uint timeoutMinimum, uint timeoutMaximum); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_disconnect(IntPtr peer, uint data); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_disconnect_now(IntPtr peer, uint data); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_disconnect_later(IntPtr peer, uint data); - - [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] - internal static extern void enet_peer_reset(IntPtr peer); - -#if UNITY_EDITOR - public static string nativeLibraryName { get { return nativeLibrary; } } -#endif - - } -} +/* + * Managed C# wrapper for an extended version of ENet + * This is a fork from upstream and is available at http://github.com/SoftwareGuy/ENet-CSharp + * + * Copyright (c) 2019 Matt Coburn (SoftwareGuy/Coburn64), Chris Burns (c6burns) + * Copyright (c) 2013 James Bellinger, 2016 Nate Shoffner, 2018 Stanislav Denisov + * + * 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. + */ + +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; + +namespace ENet +{ + [Flags] + public enum PacketFlags + { + None = 0, + Reliable = 1 << 0, + Unsequenced = 1 << 1, + NoAllocate = 1 << 2, + UnreliableFragmented = 1 << 3, + Instant = 1 << 4, + Unthrottled = 1 << 5, + Sent = 1 << 8 + } + + public enum EventType + { + None = 0, + Connect = 1, + Disconnect = 2, + Receive = 3, + Timeout = 4 + } + + public enum PeerState + { + Uninitialized = -1, + Disconnected = 0, + Connecting = 1, + AcknowledgingConnect = 2, + ConnectionPending = 3, + ConnectionSucceeded = 4, + Connected = 5, + DisconnectLater = 6, + Disconnecting = 7, + AcknowledgingDisconnect = 8, + Zombie = 9 + } + + [StructLayout(LayoutKind.Explicit, Size = 18)] + internal struct ENetAddress + { + [FieldOffset(16)] + public ushort port; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ENetEvent + { + public EventType type; + public IntPtr peer; + public byte channelID; + public uint data; + public IntPtr packet; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ENetCallbacks + { + public AllocCallback malloc; + public FreeCallback free; + public NoMemoryCallback noMemory; + } + + public delegate IntPtr AllocCallback(IntPtr size); + public delegate void FreeCallback(IntPtr memory); + public delegate void NoMemoryCallback(); + public delegate void PacketFreeCallback(Packet packet); + public delegate int InterceptCallback(ref Event @event, ref Address address, IntPtr receivedData, int receivedDataLength); + public delegate ulong ChecksumCallback(IntPtr buffers, int bufferCount); + + internal static class ArrayPool + { + [ThreadStatic] + private static byte[] byteBuffer; + [ThreadStatic] + private static IntPtr[] pointerBuffer; + + public static byte[] GetByteBuffer() + { + if (byteBuffer == null) + byteBuffer = new byte[64]; + + return byteBuffer; + } + + public static IntPtr[] GetPointerBuffer() + { + if (pointerBuffer == null) + pointerBuffer = new IntPtr[Library.maxPeers]; + + return pointerBuffer; + } + } + + public struct Address + { + private ENetAddress nativeAddress; + + internal ENetAddress NativeData + { + get + { + return nativeAddress; + } + + set + { + nativeAddress = value; + } + } + + internal Address(ENetAddress address) + { + nativeAddress = address; + } + + public ushort Port + { + get + { + return nativeAddress.port; + } + + set + { + nativeAddress.port = value; + } + } + + public string GetIP() + { + StringBuilder ip = new StringBuilder(1025); + + if (Native.enet_address_get_ip(ref nativeAddress, ip, (IntPtr)ip.Capacity) != 0) + return String.Empty; + + return ip.ToString(); + } + + public bool SetIP(string ip) + { + if (ip == null) + throw new ArgumentNullException("ip"); + + return Native.enet_address_set_ip(ref nativeAddress, ip) == 0; + } + + public string GetHost() + { + StringBuilder hostName = new StringBuilder(1025); + + if (Native.enet_address_get_hostname(ref nativeAddress, hostName, (IntPtr)hostName.Capacity) != 0) + return String.Empty; + + return hostName.ToString(); + } + + public bool SetHost(string hostName) + { + if (hostName == null) + throw new ArgumentNullException("hostName"); + + return Native.enet_address_set_hostname(ref nativeAddress, hostName) == 0; + } + } + + public struct Event + { + private ENetEvent nativeEvent; + + internal ENetEvent NativeData + { + get + { + return nativeEvent; + } + + set + { + nativeEvent = value; + } + } + + internal Event(ENetEvent @event) + { + nativeEvent = @event; + } + + public EventType Type + { + get + { + return nativeEvent.type; + } + } + + public Peer Peer + { + get + { + return new Peer(nativeEvent.peer); + } + } + + public byte ChannelID + { + get + { + return nativeEvent.channelID; + } + } + + public uint Data + { + get + { + return nativeEvent.data; + } + } + + public Packet Packet + { + get + { + return new Packet(nativeEvent.packet); + } + } + } + + public class Callbacks + { + private ENetCallbacks nativeCallbacks; + + internal ENetCallbacks NativeData + { + get + { + return nativeCallbacks; + } + + set + { + nativeCallbacks = value; + } + } + + public Callbacks(AllocCallback allocCallback, FreeCallback freeCallback, NoMemoryCallback noMemoryCallback) + { + nativeCallbacks.malloc = allocCallback; + nativeCallbacks.free = freeCallback; + nativeCallbacks.noMemory = noMemoryCallback; + } + } + + public struct Packet : IDisposable + { + private IntPtr nativePacket; + + internal IntPtr NativeData + { + get + { + return nativePacket; + } + + set + { + nativePacket = value; + } + } + + internal Packet(IntPtr packet) + { + nativePacket = packet; + } + + public void Dispose() + { + if (nativePacket != IntPtr.Zero) + { + Native.enet_packet_dispose(nativePacket); + nativePacket = IntPtr.Zero; + } + } + + public bool IsSet + { + get + { + return nativePacket != IntPtr.Zero; + } + } + + public IntPtr Data + { + get + { + ThrowIfNotCreated(); + + return Native.enet_packet_get_data(nativePacket); + } + } + + public IntPtr UserData + { + get + { + ThrowIfNotCreated(); + + return Native.enet_packet_get_user_data(nativePacket); + } + + set + { + ThrowIfNotCreated(); + + Native.enet_packet_set_user_data(nativePacket, value); + } + } + + public int Length + { + get + { + ThrowIfNotCreated(); + + return Native.enet_packet_get_length(nativePacket); + } + } + + public bool HasReferences + { + get + { + ThrowIfNotCreated(); + + return Native.enet_packet_check_references(nativePacket) != 0; + } + } + + internal void ThrowIfNotCreated() + { + if (nativePacket == IntPtr.Zero) + throw new InvalidOperationException("Packet not created"); + } + + public void SetFreeCallback(IntPtr callback) + { + ThrowIfNotCreated(); + + Native.enet_packet_set_free_callback(nativePacket, callback); + } + + public void SetFreeCallback(PacketFreeCallback callback) + { + ThrowIfNotCreated(); + + Native.enet_packet_set_free_callback(nativePacket, Marshal.GetFunctionPointerForDelegate(callback)); + } + + public void Create(byte[] data) + { + if (data == null) + throw new ArgumentNullException("data"); + + Create(data, data.Length); + } + + public void Create(byte[] data, int length) + { + Create(data, length, PacketFlags.None); + } + + public void Create(byte[] data, PacketFlags flags) + { + Create(data, data.Length, flags); + } + + public void Create(byte[] data, int length, PacketFlags flags) + { + if (data == null) + throw new ArgumentNullException("data"); + + if (length < 0 || length > data.Length) + throw new ArgumentOutOfRangeException("length"); + + nativePacket = Native.enet_packet_create(data, (IntPtr)length, flags); + } + + public void Create(IntPtr data, int length, PacketFlags flags) + { + if (data == IntPtr.Zero) + throw new ArgumentNullException("data"); + + if (length < 0) + throw new ArgumentOutOfRangeException("length"); + + nativePacket = Native.enet_packet_create(data, (IntPtr)length, flags); + } + + public void Create(byte[] data, int offset, int length, PacketFlags flags) + { + if (data == null) + throw new ArgumentNullException("data"); + + if (offset < 0) + throw new ArgumentOutOfRangeException("offset"); + + if (length < 0 || length > data.Length) + throw new ArgumentOutOfRangeException("length"); + + nativePacket = Native.enet_packet_create_offset(data, (IntPtr)length, (IntPtr)offset, flags); + } + + public void Create(IntPtr data, int offset, int length, PacketFlags flags) + { + if (data == IntPtr.Zero) + throw new ArgumentNullException("data"); + + if (offset < 0) + throw new ArgumentOutOfRangeException("offset"); + + if (length < 0) + throw new ArgumentOutOfRangeException("length"); + + nativePacket = Native.enet_packet_create_offset(data, (IntPtr)length, (IntPtr)offset, flags); + } + + public void CopyTo(byte[] destination, int startPos = 0) + { + if (destination == null) + throw new ArgumentNullException("destination"); + + // Fix by katori, prevents trying to copy a NULL + // from native world (ie. disconnect a client) + if (Data == null) + { + return; + } + + Marshal.Copy(Data, destination, startPos, Length); + } + } + + public class Host : IDisposable + { + private IntPtr nativeHost; + + internal IntPtr NativeData + { + get + { + return nativeHost; + } + + set + { + nativeHost = value; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (nativeHost != IntPtr.Zero) + { + Native.enet_host_destroy(nativeHost); + nativeHost = IntPtr.Zero; + } + } + + ~Host() + { + Dispose(false); + } + + public bool IsSet + { + get + { + return nativeHost != IntPtr.Zero; + } + } + + public uint PeersCount + { + get + { + ThrowIfNotCreated(); + + return Native.enet_host_get_peers_count(nativeHost); + } + } + + public uint PacketsSent + { + get + { + ThrowIfNotCreated(); + + return Native.enet_host_get_packets_sent(nativeHost); + } + } + + public uint PacketsReceived + { + get + { + ThrowIfNotCreated(); + + return Native.enet_host_get_packets_received(nativeHost); + } + } + + public uint BytesSent + { + get + { + ThrowIfNotCreated(); + + return Native.enet_host_get_bytes_sent(nativeHost); + } + } + + public uint BytesReceived + { + get + { + ThrowIfNotCreated(); + + return Native.enet_host_get_bytes_received(nativeHost); + } + } + + internal void ThrowIfNotCreated() + { + if (nativeHost == IntPtr.Zero) + throw new InvalidOperationException("Host not created"); + } + + private static void ThrowIfChannelsExceeded(int channelLimit) + { + if (channelLimit < 0 || channelLimit > Library.maxChannelCount) + throw new ArgumentOutOfRangeException("channelLimit"); + } + + public void Create() + { + Create(null, 1, 0); + } + + public void Create(int bufferSize) + { + Create(null, 1, 0, 0, 0, bufferSize); + } + + public void Create(Address? address, int peerLimit) + { + Create(address, peerLimit, 0); + } + + public void Create(Address? address, int peerLimit, int channelLimit) + { + Create(address, peerLimit, channelLimit, 0, 0, 0); + } + + public void Create(int peerLimit, int channelLimit) + { + Create(null, peerLimit, channelLimit, 0, 0, 0); + } + + public void Create(int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth) + { + Create(null, peerLimit, channelLimit, incomingBandwidth, outgoingBandwidth, 0); + } + + public void Create(Address? address, int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth) + { + Create(address, peerLimit, channelLimit, incomingBandwidth, outgoingBandwidth, 0); + } + + public void Create(Address? address, int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize) + { + if (nativeHost != IntPtr.Zero) + throw new InvalidOperationException("Host already created"); + + if (peerLimit < 0 || peerLimit > Library.maxPeers) + throw new ArgumentOutOfRangeException("peerLimit"); + + ThrowIfChannelsExceeded(channelLimit); + + if (address != null) + { + var nativeAddress = address.Value.NativeData; + + nativeHost = Native.enet_host_create(ref nativeAddress, (IntPtr)peerLimit, (IntPtr)channelLimit, incomingBandwidth, outgoingBandwidth, bufferSize); + } + else + { + nativeHost = Native.enet_host_create(IntPtr.Zero, (IntPtr)peerLimit, (IntPtr)channelLimit, incomingBandwidth, outgoingBandwidth, bufferSize); + } + + if (nativeHost == IntPtr.Zero) + throw new InvalidOperationException("Host creation call failed"); + } + + public void PreventConnections(bool state) + { + ThrowIfNotCreated(); + + Native.enet_host_prevent_connections(nativeHost, (byte)(state ? 1 : 0)); + } + + public void Broadcast(byte channelID, ref Packet packet) + { + ThrowIfNotCreated(); + + packet.ThrowIfNotCreated(); + Native.enet_host_broadcast(nativeHost, channelID, packet.NativeData); + packet.NativeData = IntPtr.Zero; + } + + public void Broadcast(byte channelID, ref Packet packet, Peer excludedPeer) + { + ThrowIfNotCreated(); + + packet.ThrowIfNotCreated(); + Native.enet_host_broadcast_exclude(nativeHost, channelID, packet.NativeData, excludedPeer.NativeData); + packet.NativeData = IntPtr.Zero; + } + + public void Broadcast(byte channelID, ref Packet packet, Peer[] peers) + { + if (peers == null) + throw new ArgumentNullException("peers"); + + ThrowIfNotCreated(); + + packet.ThrowIfNotCreated(); + + if (peers.Length > 0) + { + IntPtr[] nativePeers = ArrayPool.GetPointerBuffer(); + int nativeCount = 0; + + for (int i = 0; i < peers.Length; i++) + { + if (peers[i].NativeData != IntPtr.Zero) + { + nativePeers[nativeCount] = peers[i].NativeData; + nativeCount++; + } + } + + Native.enet_host_broadcast_selective(nativeHost, channelID, packet.NativeData, nativePeers, (IntPtr)nativeCount); + } + + packet.NativeData = IntPtr.Zero; + } + + public int CheckEvents(out Event @event) + { + ThrowIfNotCreated(); + + ENetEvent nativeEvent; + + var result = Native.enet_host_check_events(nativeHost, out nativeEvent); + + if (result <= 0) + { + @event = default(Event); + + return result; + } + + @event = new Event(nativeEvent); + + return result; + } + + public Peer Connect(Address address) + { + return Connect(address, 0, 0); + } + + public Peer Connect(Address address, int channelLimit) + { + return Connect(address, channelLimit, 0); + } + + public Peer Connect(Address address, int channelLimit, uint data) + { + ThrowIfNotCreated(); + ThrowIfChannelsExceeded(channelLimit); + + var nativeAddress = address.NativeData; + var peer = new Peer(Native.enet_host_connect(nativeHost, ref nativeAddress, (IntPtr)channelLimit, data)); + + if (peer.NativeData == IntPtr.Zero) + throw new InvalidOperationException("Host connect call failed"); + + return peer; + } + + public int Service(int timeout, out Event @event) + { + if (timeout < 0) + throw new ArgumentOutOfRangeException("timeout"); + + ThrowIfNotCreated(); + + ENetEvent nativeEvent; + + var result = Native.enet_host_service(nativeHost, out nativeEvent, (uint)timeout); + + if (result <= 0) + { + @event = default(Event); + + return result; + } + + @event = new Event(nativeEvent); + + return result; + } + + public void SetBandwidthLimit(uint incomingBandwidth, uint outgoingBandwidth) + { + ThrowIfNotCreated(); + + Native.enet_host_bandwidth_limit(nativeHost, incomingBandwidth, outgoingBandwidth); + } + + public void SetChannelLimit(int channelLimit) + { + ThrowIfNotCreated(); + ThrowIfChannelsExceeded(channelLimit); + + Native.enet_host_channel_limit(nativeHost, (IntPtr)channelLimit); + } + + public void SetMaxDuplicatePeers(ushort number) + { + ThrowIfNotCreated(); + + Native.enet_host_set_max_duplicate_peers(nativeHost, number); + } + + public void SetInterceptCallback(IntPtr callback) + { + ThrowIfNotCreated(); + + Native.enet_host_set_intercept_callback(nativeHost, callback); + } + + public void SetInterceptCallback(InterceptCallback callback) + { + ThrowIfNotCreated(); + + Native.enet_host_set_intercept_callback(nativeHost, Marshal.GetFunctionPointerForDelegate(callback)); + } + + public void SetChecksumCallback(IntPtr callback) + { + ThrowIfNotCreated(); + + Native.enet_host_set_checksum_callback(nativeHost, callback); + } + + public void SetChecksumCallback(ChecksumCallback callback) + { + ThrowIfNotCreated(); + + Native.enet_host_set_checksum_callback(nativeHost, Marshal.GetFunctionPointerForDelegate(callback)); + } + + public void Flush() + { + ThrowIfNotCreated(); + + Native.enet_host_flush(nativeHost); + } + } + + public struct Peer + { + private IntPtr nativePeer; + private uint nativeID; + + internal IntPtr NativeData + { + get + { + return nativePeer; + } + + set + { + nativePeer = value; + } + } + + internal Peer(IntPtr peer) + { + nativePeer = peer; + nativeID = nativePeer != IntPtr.Zero ? Native.enet_peer_get_id(nativePeer) : 0; + } + + public bool IsSet + { + get + { + return nativePeer != IntPtr.Zero; + } + } + + public uint ID + { + get + { + return nativeID; + } + } + + public string IP + { + get + { + ThrowIfNotCreated(); + + byte[] ip = ArrayPool.GetByteBuffer(); + + if (Native.enet_peer_get_ip(nativePeer, ip, (IntPtr)ip.Length) == 0) + return Encoding.ASCII.GetString(ip, 0, ip.StringLength()); + else + return String.Empty; + } + } + + public ushort Port + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_port(nativePeer); + } + } + + public uint MTU + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_mtu(nativePeer); + } + } + + public PeerState State + { + get + { + return nativePeer == IntPtr.Zero ? PeerState.Uninitialized : Native.enet_peer_get_state(nativePeer); + } + } + + public uint RoundTripTime + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_rtt(nativePeer); + } + } + + public uint LastRoundTripTime + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_last_rtt(nativePeer); + } + } + + public uint LastSendTime + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_lastsendtime(nativePeer); + } + } + + public uint LastReceiveTime + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_lastreceivetime(nativePeer); + } + } + + public ulong PacketsSent + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_packets_sent(nativePeer); + } + } + + public ulong PacketsLost + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_packets_lost(nativePeer); + } + } + + public float PacketsThrottle + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_packets_throttle(nativePeer); + } + } + + public ulong BytesSent + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_bytes_sent(nativePeer); + } + } + + public ulong BytesReceived + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_bytes_received(nativePeer); + } + } + + public IntPtr Data + { + get + { + ThrowIfNotCreated(); + + return Native.enet_peer_get_data(nativePeer); + } + + set + { + ThrowIfNotCreated(); + + Native.enet_peer_set_data(nativePeer, value); + } + } + + internal void ThrowIfNotCreated() + { + if (nativePeer == IntPtr.Zero) + throw new InvalidOperationException("Peer not created"); + } + + public void ConfigureThrottle(uint interval, uint acceleration, uint deceleration, uint threshold) + { + ThrowIfNotCreated(); + + Native.enet_peer_throttle_configure(nativePeer, interval, acceleration, deceleration, threshold); + } + + public int Send(byte channelID, ref Packet packet) + { + ThrowIfNotCreated(); + + packet.ThrowIfNotCreated(); + + return Native.enet_peer_send(nativePeer, channelID, packet.NativeData); + } + + public bool Receive(out byte channelID, out Packet packet) + { + ThrowIfNotCreated(); + + IntPtr nativePacket = Native.enet_peer_receive(nativePeer, out channelID); + + if (nativePacket != IntPtr.Zero) + { + packet = new Packet(nativePacket); + + return true; + } + + packet = default(Packet); + + return false; + } + + public void Ping() + { + ThrowIfNotCreated(); + + Native.enet_peer_ping(nativePeer); + } + + public void PingInterval(uint interval) + { + ThrowIfNotCreated(); + + Native.enet_peer_ping_interval(nativePeer, interval); + } + + public void Timeout(uint timeoutLimit, uint timeoutMinimum, uint timeoutMaximum) + { + ThrowIfNotCreated(); + + Native.enet_peer_timeout(nativePeer, timeoutLimit, timeoutMinimum, timeoutMaximum); + } + + public void Disconnect(uint data) + { + ThrowIfNotCreated(); + + Native.enet_peer_disconnect(nativePeer, data); + } + + public void DisconnectNow(uint data) + { + ThrowIfNotCreated(); + + Native.enet_peer_disconnect_now(nativePeer, data); + } + + public void DisconnectLater(uint data) + { + ThrowIfNotCreated(); + + Native.enet_peer_disconnect_later(nativePeer, data); + } + + public void Reset() + { + ThrowIfNotCreated(); + + Native.enet_peer_reset(nativePeer); + } + } + + public static class Extensions + { + public static int StringLength(this byte[] data) + { + if (data == null) + throw new ArgumentNullException("data"); + + int i; + + for (i = 0; i < data.Length && data[i] != 0; i++) ; + + return i; + } + } + + public static class Library + { + public const uint maxChannelCount = 0xFF; + public const uint maxPeers = 0xFFF; + public const uint maxPacketSize = 32 * 1024 * 1024; + public const uint throttleThreshold = 40; + public const uint throttleScale = 32; + public const uint throttleAcceleration = 2; + public const uint throttleDeceleration = 2; + public const uint throttleInterval = 5000; + public const uint timeoutLimit = 32; + public const uint timeoutMinimum = 5000; + public const uint timeoutMaximum = 30000; + public const uint version = (2 << 16) | (4 << 8) | (7); + + public static uint Time + { + get + { + return Native.enet_time_get(); + } + } + + public static bool Initialize() + { + if (Native.enet_linked_version() != version) + throw new InvalidOperationException("ENet native is out of date. Download the latest release from https://github.com/SoftwareGuy/ENet-CSharp/releases"); + + return Native.enet_initialize() == 0; + } + + public static bool Initialize(Callbacks callbacks) + { + if (callbacks == null) + throw new ArgumentNullException("callbacks"); + + if (Native.enet_linked_version() != version) + throw new InvalidOperationException("ENet native is out of date. Download the latest release from https://github.com/SoftwareGuy/ENet-CSharp/releases"); + + ENetCallbacks nativeCallbacks = callbacks.NativeData; + + return Native.enet_initialize_with_callbacks(version, ref nativeCallbacks) == 0; + } + + public static void Deinitialize() + { + Native.enet_deinitialize(); + } + + public static ulong CRC64(IntPtr buffers, int bufferCount) + { + return Native.enet_crc64(buffers, bufferCount); + } + } + + [SuppressUnmanagedCodeSecurity] + internal static class Native + { + // This should address Unity usage and bug #66: Platform specific Enet / libenet + // https://github.com/SoftwareGuy/Ignorance/issues/66 + + #region Editor Specific Native Library Names +#if UNITY_EDITOR +#if UNITY_EDITOR_OSX + // Unity Editor on macOS needs to use libenet. + private const string nativeLibrary = "libenet"; +#else + // All other platforms should be using "enet". + private const string nativeLibrary = "enet"; +#endif +#endif + #endregion + + + #region Standalone Specific Native Library Names +#if !UNITY_EDITOR +#if __APPLE__ && !(__IOS__ || UNITY_IOS) + // Use libenet on macOS. + private const string nativeLibrary = "libenet"; +#elif __IOS__ || UNITY_IOS + // We're building for a certain mobile fruity OS. + private const string nativeLibrary = "__Internal"; +#else + // Assume everything else, Windows et al. + private const string nativeLibrary = "enet"; +#endif +#endif + #endregion + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_initialize(); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_initialize_with_callbacks(uint version, ref ENetCallbacks inits); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_deinitialize(); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_linked_version(); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_time_get(); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong enet_crc64(IntPtr buffers, int bufferCount); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_address_set_ip(ref ENetAddress address, string ip); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_address_set_hostname(ref ENetAddress address, string hostName); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_address_get_ip(ref ENetAddress address, StringBuilder ip, IntPtr ipLength); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_address_get_hostname(ref ENetAddress address, StringBuilder hostName, IntPtr nameLength); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_packet_create(byte[] data, IntPtr dataLength, PacketFlags flags); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_packet_create(IntPtr data, IntPtr dataLength, PacketFlags flags); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_packet_create_offset(byte[] data, IntPtr dataLength, IntPtr dataOffset, PacketFlags flags); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_packet_create_offset(IntPtr data, IntPtr dataLength, IntPtr dataOffset, PacketFlags flags); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_packet_check_references(IntPtr packet); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_packet_get_data(IntPtr packet); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_packet_get_user_data(IntPtr packet); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_packet_set_user_data(IntPtr packet, IntPtr userData); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_packet_get_length(IntPtr packet); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_packet_set_free_callback(IntPtr packet, IntPtr callback); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_packet_dispose(IntPtr packet); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_host_create(ref ENetAddress address, IntPtr peerLimit, IntPtr channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_host_create(IntPtr address, IntPtr peerLimit, IntPtr channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_host_connect(IntPtr host, ref ENetAddress address, IntPtr channelCount, uint data); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_broadcast(IntPtr host, byte channelID, IntPtr packet); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_broadcast_exclude(IntPtr host, byte channelID, IntPtr packet, IntPtr excludedPeer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_broadcast_selective(IntPtr host, byte channelID, IntPtr packet, IntPtr[] peers, IntPtr peersLength); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_host_service(IntPtr host, out ENetEvent @event, uint timeout); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_host_check_events(IntPtr host, out ENetEvent @event); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_channel_limit(IntPtr host, IntPtr channelLimit); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_bandwidth_limit(IntPtr host, uint incomingBandwidth, uint outgoingBandwidth); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_host_get_peers_count(IntPtr host); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_host_get_packets_sent(IntPtr host); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_host_get_packets_received(IntPtr host); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_host_get_bytes_sent(IntPtr host); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_host_get_bytes_received(IntPtr host); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_set_max_duplicate_peers(IntPtr host, ushort number); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_set_intercept_callback(IntPtr host, IntPtr callback); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_set_checksum_callback(IntPtr host, IntPtr callback); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_flush(IntPtr host); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_destroy(IntPtr host); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_host_prevent_connections(IntPtr host, byte state); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_throttle_configure(IntPtr peer, uint interval, uint acceleration, uint deceleration, uint threshold); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_peer_get_id(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_peer_get_ip(IntPtr peer, byte[] ip, IntPtr ipLength); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern ushort enet_peer_get_port(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_peer_get_mtu(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern PeerState enet_peer_get_state(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_peer_get_rtt(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_peer_get_last_rtt(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_peer_get_lastsendtime(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint enet_peer_get_lastreceivetime(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong enet_peer_get_packets_sent(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong enet_peer_get_packets_lost(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern float enet_peer_get_packets_throttle(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong enet_peer_get_bytes_sent(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong enet_peer_get_bytes_received(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_peer_get_data(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_set_data(IntPtr peer, IntPtr data); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern int enet_peer_send(IntPtr peer, byte channelID, IntPtr packet); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr enet_peer_receive(IntPtr peer, out byte channelID); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_ping(IntPtr peer); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_ping_interval(IntPtr peer, uint pingInterval); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_timeout(IntPtr peer, uint timeoutLimit, uint timeoutMinimum, uint timeoutMaximum); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_disconnect(IntPtr peer, uint data); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_disconnect_now(IntPtr peer, uint data); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_disconnect_later(IntPtr peer, uint data); + + [DllImport(nativeLibrary, CallingConvention = CallingConvention.Cdecl)] + internal static extern void enet_peer_reset(IntPtr peer); + +#if UNITY_EDITOR + public static string nativeLibraryName { get { return nativeLibrary; } } +#endif + + } +} diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs.meta index d58413a..1983051 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/ENet.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 12a7875e95f5ebb4a9b58390441dd933 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 12a7875e95f5ebb4a9b58390441dd933 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/RingBuffer.cs b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/RingBuffer.cs new file mode 100644 index 0000000..3940780 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/RingBuffer.cs @@ -0,0 +1,279 @@ +// The following dependency was taken from https://github.com/dave-hillier/disruptor-unity3d +// The Apache License 2.0 this dependency follows is located at https://github.com/dave-hillier/disruptor-unity3d/blob/master/LICENSE. +// Modifications were made by SoftwareGuy (Coburn). + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace IgnoranceThirdparty +{ + /// + /// Implementation of the Disruptor pattern + /// + /// the type of item to be stored + public class RingBuffer + { + private readonly T[] _entries; + private readonly int _modMask; + private Volatile.PaddedLong _consumerCursor = new Volatile.PaddedLong(); + private Volatile.PaddedLong _producerCursor = new Volatile.PaddedLong(); + + /// + /// Creates a new RingBuffer with the given capacity + /// + /// The capacity of the buffer + /// Only a single thread may attempt to consume at any one time + public RingBuffer(int capacity) + { + capacity = NextPowerOfTwo(capacity); + _modMask = capacity - 1; + _entries = new T[capacity]; + } + + /// + /// The maximum number of items that can be stored + /// + public int Capacity + { + get { return _entries.Length; } + } + + public T this[long index] + { + get { unchecked { return _entries[index & _modMask]; } } + set { unchecked { _entries[index & _modMask] = value; } } + } + + /// + /// Removes an item from the buffer. + /// + /// The next available item + public T Dequeue() + { + var next = _consumerCursor.ReadAcquireFence() + 1; + while (_producerCursor.ReadAcquireFence() < next) // makes sure we read the data from _entries after we have read the producer cursor + { + Thread.SpinWait(1); + } + var result = this[next]; + _consumerCursor.WriteReleaseFence(next); // makes sure we read the data from _entries before we update the consumer cursor + return result; + } + + /// + /// Attempts to remove an items from the queue + /// + /// the items + /// True if successful + public bool TryDequeue(out T obj) + { + var next = _consumerCursor.ReadAcquireFence() + 1; + + if (_producerCursor.ReadAcquireFence() < next) + { + obj = default(T); + return false; + } + obj = Dequeue(); + return true; + } + + /// + /// Add an item to the buffer + /// + /// + public void Enqueue(T item) + { + var next = _producerCursor.ReadAcquireFence() + 1; + + long wrapPoint = next - _entries.Length; + long min = _consumerCursor.ReadAcquireFence(); + + while (wrapPoint > min) + { + min = _consumerCursor.ReadAcquireFence(); + Thread.SpinWait(1); + } + + this[next] = item; + _producerCursor.WriteReleaseFence(next); // makes sure we write the data in _entries before we update the producer cursor + } + + /// + /// The number of items in the buffer + /// + /// for indicative purposes only, may contain stale data + public int Count { get { return (int)(_producerCursor.ReadFullFence() - _consumerCursor.ReadFullFence()); } } + + private static int NextPowerOfTwo(int x) + { + var result = 2; + while (result < x) + { + result <<= 1; + } + return result; + } + + + } + public static class Volatile + { + private const int CacheLineSize = 64; + + [StructLayout(LayoutKind.Explicit, Size = CacheLineSize * 2)] + public struct PaddedLong + { + [FieldOffset(CacheLineSize)] + private long _value; + + /// + /// Create a new with the given initial value. + /// + /// Initial value + public PaddedLong(long value) + { + _value = value; + } + + /// + /// Read the value without applying any fence + /// + /// The current value + public long ReadUnfenced() + { + return _value; + } + + /// + /// Read the value applying acquire fence semantic + /// + /// The current value + public long ReadAcquireFence() + { + var value = _value; + Thread.MemoryBarrier(); + return value; + } + + /// + /// Read the value applying full fence semantic + /// + /// The current value + public long ReadFullFence() + { + Thread.MemoryBarrier(); + return _value; + } + + /// + /// Read the value applying a compiler only fence, no CPU fence is applied + /// + /// The current value + [MethodImpl(MethodImplOptions.NoOptimization)] + public long ReadCompilerOnlyFence() + { + return _value; + } + + /// + /// Write the value applying release fence semantic + /// + /// The new value + public void WriteReleaseFence(long newValue) + { + Thread.MemoryBarrier(); + _value = newValue; + } + + /// + /// Write the value applying full fence semantic + /// + /// The new value + public void WriteFullFence(long newValue) + { + Thread.MemoryBarrier(); + _value = newValue; + } + + /// + /// Write the value applying a compiler fence only, no CPU fence is applied + /// + /// The new value + [MethodImpl(MethodImplOptions.NoOptimization)] + public void WriteCompilerOnlyFence(long newValue) + { + _value = newValue; + } + + /// + /// Write without applying any fence + /// + /// The new value + public void WriteUnfenced(long newValue) + { + _value = newValue; + } + + /// + /// Atomically set the value to the given updated value if the current value equals the comparand + /// + /// The new value + /// The comparand (expected value) + /// + public bool AtomicCompareExchange(long newValue, long comparand) + { + return Interlocked.CompareExchange(ref _value, newValue, comparand) == comparand; + } + + /// + /// Atomically set the value to the given updated value + /// + /// The new value + /// The original value + public long AtomicExchange(long newValue) + { + return Interlocked.Exchange(ref _value, newValue); + } + + /// + /// Atomically add the given value to the current value and return the sum + /// + /// The value to be added + /// The sum of the current value and the given value + public long AtomicAddAndGet(long delta) + { + return Interlocked.Add(ref _value, delta); + } + + /// + /// Atomically increment the current value and return the new value + /// + /// The incremented value. + public long AtomicIncrementAndGet() + { + return Interlocked.Increment(ref _value); + } + + /// + /// Atomically increment the current value and return the new value + /// + /// The decremented value. + public long AtomicDecrementAndGet() + { + return Interlocked.Decrement(ref _value); + } + + /// + /// Returns the string representation of the current value. + /// + /// the string representation of the current value. + public override string ToString() + { + var value = ReadFullFence(); + return value.ToString(); + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/RingBuffer.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/RingBuffer.cs.meta new file mode 100644 index 0000000..32819c4 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Dependencies/RingBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f0c413335c890f4a87e1a9dbe32bd68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Editor.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Editor.meta index 40c6fd2..a6df713 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Editor.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Editor.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 0995e08af14888348b42ecaa6eb21544 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 0995e08af14888348b42ecaa6eb21544 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs index 0ff3186..23406fc 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs @@ -1,83 +1,102 @@ -#if UNITY_EDITOR -using System.Collections.Generic; -using System.Linq; -using UnityEditor; - -namespace IgnoranceTransport -{ - /// - /// Adds the given define symbols to PlayerSettings define symbols. - /// Just add your own define symbols to the Symbols property at the below. - /// - [InitializeOnLoad] - public class AddIgnoranceDefine : Editor - { - private static string existingDefines = string.Empty; - - /// - /// Symbols that will be added to the editor - /// - public static readonly string[] Symbols = new string[] { - "IGNORANCE", // Ignorance exists - "IGNORANCE_1", // Major version - "IGNORANCE_1_4" // Major and minor version - }; - - /// - /// Do not remove these symbols - /// - public static readonly string[] DoNotRemoveTheseSymbols = new string[] - { - "IGNORANCE_NO_UPNP", - "IGNORANCE_MIRROR_POLLING" - }; - - /// - /// Add define symbols as soon as Unity gets done compiling. - /// - static AddIgnoranceDefine() - { - // Get the current scripting defines - string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); - if (existingDefines == definesString) - { - // 1.2.6: There is no need to apply the changes, return. - return; - } - - // Convert the string to a list - List allDefines = definesString.Split(';').ToList(); - // Remove any old version defines from previous installs - allDefines.RemoveAll(IsSafeToRemove); - // x => x.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(x)); - // Add any symbols that weren't already in the list - allDefines.AddRange(Symbols.Except(allDefines)); - - string newDefines = string.Join(";", allDefines.ToArray()); - PlayerSettings.SetScriptingDefineSymbolsForGroup( - EditorUserBuildSettings.selectedBuildTargetGroup, - newDefines - ); - - existingDefines = newDefines; - } - - // 1.2.4: Workaround to stop things from eating custom IGNORANCE_ symbols - static bool DoesSymbolExistInBlacklist(string symbol) - { - foreach(string s in DoNotRemoveTheseSymbols) - { - if (s == symbol.Trim()) return true; - } - - return false; - } - - static bool IsSafeToRemove (string input) - { - if (input.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(input)) return true; - return false; - } - } -} -#endif +// Ignorance 1.4.x LTS (Long Term Support) +// https://github.com/SoftwareGuy/Ignorance +// ----------------- +// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64) +// Ignorance is licensed under the MIT license. Refer +// to the LICENSE file for more information. +#if UNITY_EDITOR +using System.Collections.Generic; +using System.Linq; +using UnityEditor; + +namespace IgnoranceTransport +{ + /// + /// Adds the given define symbols to PlayerSettings define symbols. + /// Just add your own define symbols to the Symbols property at the below. + /// + [InitializeOnLoad] + public class AddIgnoranceDefine : Editor + { + private static bool debugMode = false; + private static string existingDefines = string.Empty; + + /// + /// Symbols that will be added to the editor + /// + public static readonly string[] Symbols = new string[] { + "IGNORANCE", // Ignorance exists + "IGNORANCE_1", // Major version + "IGNORANCE_1_4" // Major and minor version + }; + + /// + /// Do not remove these symbols + /// + public static readonly string[] DoNotRemoveTheseSymbols = new string[] + { + "IGNORANCE", + "IGNORANCE_1", + "IGNORANCE_1_4", + "IGNORANCE_MIRROR_POLLING" + }; + + /// + /// Add define symbols as soon as Unity gets done compiling. + /// + static AddIgnoranceDefine() + { + // Get the current scripting defines + string definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); + if (existingDefines == definesString) + { + // 1.2.6: There is no need to apply the changes, return. + return; + } + + // Convert the string to a list + List allDefines = definesString.Split(';').ToList(); + // Remove any old version defines from previous installs + allDefines.RemoveAll(IsSafeToRemove); + // x => x.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(x)); + // Add any symbols that weren't already in the list + allDefines.AddRange(Symbols.Except(allDefines)); + + string newDefines = string.Join(";", allDefines.ToArray()); + PlayerSettings.SetScriptingDefineSymbolsForGroup( + EditorUserBuildSettings.selectedBuildTargetGroup, + newDefines + ); + + existingDefines = newDefines; + } + + // 1.2.4: Workaround to stop things from eating custom IGNORANCE_ symbols + static bool DoesSymbolExistInBlacklist(string symbol) + { + foreach(string s in DoNotRemoveTheseSymbols) + { + if(debugMode) + UnityEngine.Debug.Log($"{s.Trim()} matches blacklist"); + + if (s == symbol.Trim()) return true; + } + + return false; + } + + static bool IsSafeToRemove (string input) + { + if (input.StartsWith("IGNORANCE") && !DoesSymbolExistInBlacklist(input)) + { + if (debugMode) + UnityEngine.Debug.Log($"{input.Trim()} is safe to remove"); + + return true; + } + + return false; + } + } +} +#endif diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs.meta index dbff870..92c39e8 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/AddScriptingDefine.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: ed8acbde141f2d8469baf2142712de9e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: ed8acbde141f2d8469baf2142712de9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/IgnoranceToolbox.cs b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/IgnoranceToolbox.cs deleted file mode 100644 index dee6b62..0000000 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/IgnoranceToolbox.cs +++ /dev/null @@ -1,44 +0,0 @@ -#if UNITY_EDITOR -using System.Collections.Generic; -using UnityEditor; - -namespace IgnoranceTransport -{ - public class IgnoranceToolbox - { -#pragma warning disable IDE0051 - [MenuItem("Ignorance/Mirror/Switch Update Method")] - public static void SwitchIgnoranceUpdateMethod () - { - - } - - [MenuItem("Ignorance/Debug/Reveal ENet Native Library Name")] - public static void RevealEnetLibraryName() - { - EditorUtility.DisplayDialog("Enet Library Name", $"Use this for debugging.\nYour platform expects the native Enet library to be called: {ENet.Native.nativeLibraryName}", "Got it"); - } - - [MenuItem("Ignorance/RTFM/Github Repository")] - private static void LaunchGithubRepo() - { - UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance"); - } - - [MenuItem("Ignorance/RTFM/Github Issue Tracker")] - private static void LaunchGithubIssueTracker() - { - UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance/issues"); - } - - [MenuItem("Ignorance/RTFM/ENet-CSharp Fork")] - private static void LaunchENetCSharpForkRepo() - { - UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/ENet-CSharp"); - } - - -#pragma warning restore - } -} -#endif diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs new file mode 100644 index 0000000..2ed5c04 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs @@ -0,0 +1,43 @@ +// Ignorance 1.4.x LTS (Long Term Support) +// https://github.com/SoftwareGuy/Ignorance +// ----------------- +// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64) +// Ignorance is licensed under the MIT license. Refer +// to the LICENSE file for more information. +#if UNITY_EDITOR +using UnityEditor; + +namespace IgnoranceTransport +{ + public class Toolbox + { +#pragma warning disable IDE0051 + [MenuItem("Ignorance/Debug/Native Library Name")] + public static void RevealEnetLibraryName() + { + EditorUtility.DisplayDialog("ENet Library Name", $"Your platform expects the native library to be called: {ENet.Native.nativeLibraryName}.\n\n" + + $"This info is very useful when trying to diagnose issues with DLL loading.", "Got it"); + } + + [MenuItem("Ignorance/RTFM/Github Repo")] + private static void LaunchGithubRepo() + { + UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance"); + } + + [MenuItem("Ignorance/RTFM/Issue Tracker")] + private static void LaunchGithubIssueTracker() + { + UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/Ignorance/issues"); + } + + [MenuItem("Ignorance/RTFM/ENet-C# Repo")] + private static void LaunchENetCSharpForkRepo() + { + UnityEngine.Application.OpenURL("https://github.com/SoftwareGuy/ENet-CSharp"); + } + +#pragma warning restore + } +} +#endif diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/IgnoranceToolbox.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs.meta similarity index 95% rename from Assets/Mirror/Runtime/Transport/Ignorance/Editor/IgnoranceToolbox.cs.meta rename to Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs.meta index cf61ba3..cddac1e 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Editor/IgnoranceToolbox.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Editor/Toolbox.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 1fdecc996313d614ca16214d4e2b9162 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 1fdecc996313d614ca16214d4e2b9162 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs b/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs index 28dbf7a..bf4b4c7 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs @@ -1,746 +1,719 @@ -// Ignorance 1.4.x -// Ignorance. It really kicks the Unity LLAPIs ass. -// https://github.com/SoftwareGuy/Ignorance -// ----------------- -// Copyright (c) 2019 - 2020 Matt Coburn (SoftwareGuy/Coburn64) -// Ignorance Transport is licensed under the MIT license. Refer -// to the LICENSE file for more information. -// ----------------- -// Ignorance Experimental (New) Version -// ----------------- -using ENet; -using Mirror; -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace IgnoranceTransport -{ - [DisallowMultipleComponent] - public class Ignorance : Transport - { - #region Inspector options - public int port = 7777; - - [Header("Debug & Logging Configuration")] - [Tooltip("How verbose do you want Ignorance to be?")] - public IgnoranceLogType LogType = IgnoranceLogType.Standard; - [Tooltip("Uses OnGUI to present you with statistics for Server and Client backend instances.")] - public bool DebugDisplay = false; - - [Header("Server Configuration")] - [Tooltip("Should the server bind to all interfaces?")] - public bool serverBindsAll = true; - [Tooltip("This is only used if Server Binds All is unticked.")] - public string serverBindAddress = string.Empty; - [Tooltip("This tells ENet how many Peer slots to create. Helps performance, avoids looping over huge native arrays. Recommended: Max Mirror players, rounded to nearest 10. (Example: 16 -> 20).")] - public int serverMaxPeerCapacity = 50; - [Tooltip("How long ENet waits in native world. The higher this value, the more CPU usage. Lower values may/may not impact performance at high packet load.")] - public int serverMaxNativeWaitTime = 1; - - [Header("Client Configuration")] - [Tooltip("How long ENet waits in native world. The higher this value, the more CPU usage used. This is for the client, unlike the one above. Higher value probably trades CPU for more responsive networking.")] - public int clientMaxNativeWaitTime = 3; - [Tooltip("Interval between asking ENet for client status updates. Set to -1 to disable.")] - public int clientStatusUpdateInterval = -1; - - [Header("Channel Configuration")] - [Tooltip("You must define your channels in the array shown here, otherwise ENet will not know what channel delivery type to use.")] - public IgnoranceChannelTypes[] Channels; - - [Header("Low-level Tweaking")] - [Tooltip("Used internally to keep allocations to a minimum. This is how much memory will be consumed by the packet buffer on startup, and then reused.")] - public int PacketBufferCapacity = 4096; - - [Tooltip("For UDP based protocols, it's best to keep your data under the safe MTU of 1200 bytes. You can increase this, however beware this may open you up to allocation attacks.")] - public int MaxAllowedPacketSize = 33554432; - #endregion - - #region Public Statistics - public IgnoranceClientStats ClientStatistics; - #endregion - -#if MIRROR_26_0_OR_NEWER - public override bool Available() - { - // Ignorance is not available for Unity WebGL, the PS4 (no dev kit to confirm) or Switch (port exists but I have no access to said code). - // Ignorance is available for most other operating systems. -#if (UNITY_WEBGL || UNITY_PS4 || UNITY_SWITCH) - return false; -#else - return true; -#endif - } - - public void Awake() - { - if (LogType != IgnoranceLogType.Nothing) - Debug.Log($"Thanks for using Ignorance {IgnoranceInternals.Version}. Keep up to date, report bugs and support the developer at https://github.com/SoftwareGuy/Ignorance!"); - } - - public override string ToString() - { - return $"Ignorance v{IgnoranceInternals.Version}"; - } - - public override void ClientConnect(string address) - { - ClientState = ConnectionState.Connecting; - cachedConnectionAddress = address; - - // Initialize. - InitializeClientBackend(); - - // Get going. - ignoreDataPackets = false; - - // Start! - Client.Start(); - } - - public override void ClientConnect(Uri uri) - { - if (uri.Scheme != IgnoranceInternals.Scheme) - throw new ArgumentException($"You used an invalid URI: {uri}. Please use {IgnoranceInternals.Scheme}://host:port instead", nameof(uri)); - - if (!uri.IsDefaultPort) - // Set the communication port to the one specified. - port = uri.Port; - - // Pass onwards to the proper handler. - ClientConnect(uri.Host); - } - - public override bool ClientConnected() => ClientState == ConnectionState.Connected; - - public override void ClientDisconnect() - { - if (Client != null) - Client.Stop(); - - // TODO: Figure this one out to see if it's related to a race condition. - // Maybe experiment with a while loop to pause main thread when disconnecting, - // since client might not stop on a dime. - // while(Client.IsAlive) ; - // v1.4.0b1: Probably fixed in IgnoranceClient.cs; need further testing. - - // ignoreDataPackets = true; - ClientState = ConnectionState.Disconnected; - } - -#if !MIRROR_37_0_OR_NEWER - public override void ClientSend(int channelId, ArraySegment segment) -#else - // v1.4.0b6: Mirror rearranged the ClientSend params, so we need to apply a fix for that or - // we end up using the obsoleted version. The obsolete version isn't a fatal error, but - // it's best to stick with the new structures. - public override void ClientSend(ArraySegment segment, int channelId) -#endif - { - if (Client == null) - { - Debug.LogError("Client object is null, this shouldn't really happen but it did..."); - return; - } - - if (channelId < 0 || channelId > Channels.Length) - { - Debug.LogError("Channel ID is out of bounds."); - return; - } - - // Create our struct... - Packet clientOutgoingPacket = default; - int byteCount = segment.Count; - int byteOffset = segment.Offset; - // Set our desired flags... - PacketFlags desiredFlags = (PacketFlags)Channels[channelId]; - - // Warn if over recommended MTU... - bool flagsSet = (desiredFlags & ReliableOrUnreliableFragmented) > 0; - - if (LogType != IgnoranceLogType.Nothing && byteCount > 1200 && !flagsSet) - Debug.LogWarning($"Warning: Client trying to send a Unreliable packet bigger than the recommended ENet 1200 byte MTU ({byteCount} > 1200). ENet will force Reliable Fragmented delivery."); - - // Create the packet. - clientOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, desiredFlags); - // byteCount - - // Enqueue the packet. - IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket - { - Channel = (byte)channelId, - Payload = clientOutgoingPacket - }; - - // Pass the packet onto the thread for dispatch. - Client.Outgoing.Enqueue(dispatchPacket); - } - - public override bool ServerActive() - { - // Very simple check. - return Server != null && Server.IsAlive; - } - -#if !MIRROR_37_0_OR_NEWER - // Workaround for legacy Mirror versions. - public override bool ServerDisconnect(int connectionId) => ServerDisconnectLegacy(connectionId); -#else - public override void ServerDisconnect(int connectionId) - { - if (Server == null) - { - Debug.LogError("Cannot enqueue kick packet; our Server object is null. Something has gone wrong."); - // Return here because otherwise we will get a NRE when trying to enqueue the kick packet. - return; - } - - IgnoranceCommandPacket kickPacket = new IgnoranceCommandPacket - { - Type = IgnoranceCommandType.ServerKickPeer, - PeerId = (uint)connectionId - 1 // ENet's native peer ID will be ConnID - 1 - }; - - // Pass the packet onto the thread for dispatch. - Server.Commands.Enqueue(kickPacket); - } -#endif - - public override string ServerGetClientAddress(int connectionId) - { - if (ConnectionLookupDict.TryGetValue(connectionId, out PeerConnectionData details)) - return $"{details.IP}:{details.Port}"; - - return "(unavailable)"; - } - -#if !MIRROR_37_0_OR_NEWER - public override void ServerSend(int connectionId, int channelId, ArraySegment segment) -#else - // v1.4.0b6: Mirror rearranged the ServerSend params, so we need to apply a fix for that or - // we end up using the obsoleted version. The obsolete version isn't a fatal error, but - // it's best to stick with the new structures. - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) -#endif - { - // Debug.Log($"ServerSend({connectionId}, {channelId}, <{segment.Count} byte segment>)"); - - if (Server == null) - { - Debug.LogError("Cannot enqueue data packet; our Server object is null. Something has gone wrong."); - return; - } - - if (channelId < 0 || channelId > Channels.Length) - { - Debug.LogError("Channel ID is out of bounds."); - return; - } - - // Packet Struct - Packet serverOutgoingPacket = default; - int byteCount = segment.Count; - int byteOffset = segment.Offset; - PacketFlags desiredFlags = (PacketFlags)Channels[channelId]; - - // Warn if over recommended MTU - bool flagsSet = (desiredFlags & ReliableOrUnreliableFragmented) > 0; - - if (LogType != IgnoranceLogType.Nothing && byteCount > 1200 && !flagsSet) - Debug.LogWarning($"Warning: Server trying to send a Unreliable packet bigger than the recommended ENet 1200 byte MTU ({byteCount} > 1200). ENet will force Reliable Fragmented delivery."); - - // Create the packet. - serverOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, (PacketFlags)Channels[channelId]); - - // Enqueue the packet. - IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket - { - Channel = (byte)channelId, - NativePeerId = (uint)connectionId - 1, // ENet's native peer ID will be ConnID - 1 - Payload = serverOutgoingPacket - }; - - Server.Outgoing.Enqueue(dispatchPacket); - - } - - public override void ServerStart() - { - if (LogType != IgnoranceLogType.Nothing) - Debug.Log("Ignorance Server Instance starting up..."); - - InitializeServerBackend(); - - Server.Start(); - } - - public override void ServerStop() - { - if (Server != null) - { - if (LogType != IgnoranceLogType.Nothing) - Debug.Log("Ignorance Server Instance shutting down..."); - - Server.Stop(); - } - - ConnectionLookupDict.Clear(); - } - - public override Uri ServerUri() - { - UriBuilder builder = new UriBuilder - { - Scheme = IgnoranceInternals.Scheme, - Host = serverBindAddress, - Port = port - }; - - return builder.Uri; - } - - public override void Shutdown() - { - // TODO: Nothing needed here? - } - - // Check to ensure channels 0 and 1 mimic LLAPI. Override this at your own risk. - private void OnValidate() - { - if (Channels != null && Channels.Length >= 2) - { - // Check to make sure that Channel 0 and 1 are correct. - if (Channels[0] != IgnoranceChannelTypes.Reliable) - { - Debug.LogWarning("Please do not modify Ignorance Channel 0. The channel will be reset to Reliable delivery. If you need a channel with a different delivery, define and use it instead."); - Channels[0] = IgnoranceChannelTypes.Reliable; - } - if (Channels[1] != IgnoranceChannelTypes.Unreliable) - { - Debug.LogWarning("Please do not modify Ignorance Channel 1. The channel will be reset to Unreliable delivery. If you need a channel with a different delivery, define and use it instead."); - Channels[1] = IgnoranceChannelTypes.Unreliable; - } - } - else - { - Debug.LogWarning("Invalid Channels setting, fixing. If you've just added Ignorance to your NetworkManager GameObject, seeing this message is normal."); - Channels = new IgnoranceChannelTypes[2] - { - - IgnoranceChannelTypes.Reliable, - IgnoranceChannelTypes.Unreliable - }; - } - - // ENet only supports a maximum of 32MB packet size. - if (MaxAllowedPacketSize > 33554432) - MaxAllowedPacketSize = 33554432; - } - - private void InitializeServerBackend() - { - if (Server == null) - { - Debug.LogWarning("IgnoranceServer reference for Server mode was null. This shouldn't happen, but to be safe we'll reinitialize it."); - Server = new IgnoranceServer(); - } - - // Set up the new IgnoranceServer reference. - if (serverBindsAll) - // MacOS is special. It's also a massive thorn in my backside. - Server.BindAddress = IgnoranceInternals.BindAllMacs; - else - // Use the supplied bind address. - Server.BindAddress = serverBindAddress; - - // Sets port, maximum peers, max channels, the server poll time, maximum packet size and verbosity. - Server.BindPort = port; - Server.MaximumPeers = serverMaxPeerCapacity; - Server.MaximumChannels = Channels.Length; - Server.PollTime = serverMaxNativeWaitTime; - Server.MaximumPacketSize = MaxAllowedPacketSize; - Server.Verbosity = (int)LogType; - - // Initializes the packet buffer. - // Allocates once, that's it. - if (InternalPacketBuffer == null) - InternalPacketBuffer = new byte[PacketBufferCapacity]; - } - - private void InitializeClientBackend() - { - if (Client == null) - { - Debug.LogWarning("Ignorance: IgnoranceClient reference for Client mode was null. This shouldn't happen, but to be safe we'll reinitialize it."); - Client = new IgnoranceClient(); - } - - // Sets address, port, channels to expect, verbosity, the server poll time and maximum packet size. - Client.ConnectAddress = cachedConnectionAddress; - Client.ConnectPort = port; - Client.ExpectedChannels = Channels.Length; - Client.PollTime = clientMaxNativeWaitTime; - Client.MaximumPacketSize = MaxAllowedPacketSize; - Client.Verbosity = (int)LogType; - - // Initializes the packet buffer. - // Allocates once, that's it. - if (InternalPacketBuffer == null) - InternalPacketBuffer = new byte[PacketBufferCapacity]; - } - - private void ProcessServerPackets() - { - IgnoranceIncomingPacket incomingPacket; - IgnoranceConnectionEvent connectionEvent; - int adjustedConnectionId; - Packet payload; - - // Incoming connection events. - while (Server.ConnectionEvents.TryDequeue(out connectionEvent)) - { - adjustedConnectionId = (int)connectionEvent.NativePeerId + 1; - - if (LogType == IgnoranceLogType.Verbose) - Debug.Log($"Processing a server connection event from ENet native peer {connectionEvent.NativePeerId}. This peer would be Mirror ConnID {adjustedConnectionId}."); - - // TODO: Investigate ArgumentException: An item with the same key has already been added. Key: - ConnectionLookupDict.Add(adjustedConnectionId, new PeerConnectionData - { - NativePeerId = connectionEvent.NativePeerId, - IP = connectionEvent.IP, - Port = connectionEvent.Port - }); - - OnServerConnected?.Invoke(adjustedConnectionId); - } - - // Handle incoming data packets. - // Console.WriteLine($"Server Incoming Queue is {Server.Incoming.Count}"); - while (Server.Incoming.TryDequeue(out incomingPacket)) - { - adjustedConnectionId = (int)incomingPacket.NativePeerId + 1; - payload = incomingPacket.Payload; - - int length = payload.Length; - ArraySegment dataSegment; - - // Copy to working buffer and dispose of it. - if (length > InternalPacketBuffer.Length) - { - byte[] oneFreshNTastyGcAlloc = new byte[length]; - - payload.CopyTo(oneFreshNTastyGcAlloc); - dataSegment = new ArraySegment(oneFreshNTastyGcAlloc, 0, length); - } - else - { - payload.CopyTo(InternalPacketBuffer); - dataSegment = new ArraySegment(InternalPacketBuffer, 0, length); - } - - payload.Dispose(); - - OnServerDataReceived?.Invoke(adjustedConnectionId, dataSegment, incomingPacket.Channel); - - // Some messages can disable the transport - // If the transport was disabled by any of the messages, we have to break out of the loop and wait until we've been re-enabled. - if (!enabled) - break; - } - - // Disconnection events. - while (Server.DisconnectionEvents.TryDequeue(out IgnoranceConnectionEvent disconnectionEvent)) - { - adjustedConnectionId = (int)disconnectionEvent.NativePeerId + 1; - - if (LogType == IgnoranceLogType.Verbose) - Debug.Log($"ProcessServerPackets fired; handling disconnection event from native peer {disconnectionEvent.NativePeerId}."); - - ConnectionLookupDict.Remove(adjustedConnectionId); - - // Invoke Mirror handler. - OnServerDisconnected?.Invoke(adjustedConnectionId); - } - } - - private void ProcessClientPackets() - { - IgnoranceIncomingPacket incomingPacket; - IgnoranceCommandPacket commandPacket; - IgnoranceClientStats clientStats; - Packet payload; - IgnoranceConnectionEvent connectionEvent; - - // Handle connection events. - while (Client.ConnectionEvents.TryDequeue(out connectionEvent)) - { - if (LogType == IgnoranceLogType.Verbose) - Debug.Log($"ProcessClientConnectionEvents fired: processing a client ConnectionEvents queue item."); - - if (connectionEvent.WasDisconnect) - { - // Disconnected from server. - ClientState = ConnectionState.Disconnected; - - if (LogType != IgnoranceLogType.Nothing) - Debug.Log($"Ignorance Client has been disconnected from server."); - - ignoreDataPackets = true; - OnClientDisconnected?.Invoke(); - } - else - { - // Connected to server. - ClientState = ConnectionState.Connected; - - if (LogType != IgnoranceLogType.Nothing) - Debug.Log($"Ignorance Client successfully connected to server at address {connectionEvent.IP}:{connectionEvent.Port}"); - - ignoreDataPackets = false; - OnClientConnected?.Invoke(); - } - } - - // Now handle the incoming messages. - while (Client.Incoming.TryDequeue(out incomingPacket)) - { - // Temporary fix: if ENet thread is too fast for Mirror, then ignore the packet. - // This is seen sometimes if you stop the client and there's still stuff in the queue. - if (ignoreDataPackets) - { - if (LogType == IgnoranceLogType.Verbose) - Debug.Log("ProcessClientPackets cycle skipped; ignoring data packet"); - break; - } - - - // Otherwise client recieved data, advise Mirror. - // print($"Byte array: {incomingPacket.RentedByteArray.Length}. Packet Length: {incomingPacket.Length}"); - payload = incomingPacket.Payload; - int length = payload.Length; - ArraySegment dataSegment; - - // Copy to working buffer and dispose of it. - if (length > InternalPacketBuffer.Length) - { - // Unity's favourite: A fresh 'n' tasty GC Allocation! - byte[] oneFreshNTastyGcAlloc = new byte[length]; - - payload.CopyTo(oneFreshNTastyGcAlloc); - dataSegment = new ArraySegment(oneFreshNTastyGcAlloc, 0, length); - } - else - { - payload.CopyTo(InternalPacketBuffer); - dataSegment = new ArraySegment(InternalPacketBuffer, 0, length); - } - - payload.Dispose(); - - OnClientDataReceived?.Invoke(dataSegment, incomingPacket.Channel); - - // Some messages can disable the transport - // If the transport was disabled by any of the messages, we have to break out of the loop and wait until we've been re-enabled. - if (!enabled) - break; - } - - // Step 3: Handle other commands. - while (Client.Commands.TryDequeue(out commandPacket)) - { - switch (commandPacket.Type) - { - // ... - default: - break; - } - } - - // Step 4: Handle status updates. - if (Client.StatusUpdates.TryDequeue(out clientStats)) - { - ClientStatistics = clientStats; - } - } - - #region Main Thread Processing and Polling - // Ignorance 1.4.0b5: To use Mirror's polling or not use Mirror's polling, that is up to the developer to decide -#if !IGNORANCE_MIRROR_POLLING - // IMPORTANT: Set Ignorance' execution order before everything else. Yes, that's -32000 !! - // This ensures it has priority over other things. - - // FixedUpdate can be called many times per frame. - // Once we've handled stuff, we set a flag so that we don't poll again for this frame. - - private bool fixedUpdateCompletedWork; - public void FixedUpdate() - { - if (!enabled) return; - if (fixedUpdateCompletedWork) return; - - ProcessAndExecuteAllPackets(); - - // Flip the bool to signal we've done our work. - fixedUpdateCompletedWork = true; - } - - // Normally, Mirror blocks Update() due to poor design decisions... - // But thanks to Vincenzo, we've found a way to bypass that block. - // Update is called once per frame. We don't have to worry about this shit now. - public new void Update() - { - if (!enabled) return; - - // Process what FixedUpdate missed, only if the boolean is not set. - if (!fixedUpdateCompletedWork) - ProcessAndExecuteAllPackets(); - - // Flip back the bool, so it can be reset. - fixedUpdateCompletedWork = false; - } - - // Processes and Executes All Packets. - private void ProcessAndExecuteAllPackets() - { - // Process Server Events... - if (Server.IsAlive) - ProcessServerPackets(); - - // Process Client Events... - if (Client.IsAlive) - { - ProcessClientPackets(); - - if (ClientState == ConnectionState.Connected && clientStatusUpdateInterval > -1) - { - statusUpdateTimer += Time.deltaTime; - - if (statusUpdateTimer >= clientStatusUpdateInterval) - { - Client.Commands.Enqueue(new IgnoranceCommandPacket { Type = IgnoranceCommandType.ClientRequestsStatusUpdate }); - statusUpdateTimer = 0f; - } - } - } - } -#else - // This section will be compiled in instead if you enable IGNORANCE_MIRROR_POLLING. - - public override void ServerEarlyUpdate() { - // This is used by Mirror to consume the incoming server packets. - if (!enabled) return; - - // Process Server Events... - if (Server.IsAlive) - ProcessServerPackets(); - } - - public override void ClientEarlyUpdate() { - // This is used by Mirror to consume the incoming client packets. - if(!enabled) return; - - if(Client.IsAlive) - { - ProcessClientPackets(); - - if (ClientState == ConnectionState.Connected && clientStatusUpdateInterval > -1) - { - statusUpdateTimer += Time.deltaTime; - - if (statusUpdateTimer >= clientStatusUpdateInterval) - { - Client.Commands.Enqueue(new IgnoranceCommandPacket { Type = IgnoranceCommandType.ClientRequestsStatusUpdate }); - statusUpdateTimer = 0f; - } - } - } - - } - - /* - public override void ClientLateUpdate() { - // This is used by Mirror to send out the outgoing client packets. - if (!enabled) return; - } - - public override void ServerLateUpdate() { - // This is used by Mirror to send out the outgoing server packets. - if (!enabled) return; - } - */ -#endif - #endregion - - #region Debug - private void OnGUI() - { - if (DebugDisplay) - GUI.Box(new Rect( - new Vector2(32, Screen.height - 240), new Vector2(200, 160)), - - "-- CLIENT --\n" + - $"State: {ClientState}\n" + - $"Incoming Queue: {Client.Incoming.Count}\n" + - $"Outgoing Queue: {Client.Outgoing.Count}\n\n" + - - "-- SERVER --\n" + - $"Incoming Queue: {Server.Incoming.Count}\n" + - $"Outgoing Queue: {Server.Outgoing.Count}\n" + - $"ConnEvent Queue: {Server.ConnectionEvents.Count}" - ); - } - #endregion - - public override int GetMaxPacketSize(int channelId = 0) => MaxAllowedPacketSize; - - // UDP Recommended Max MTU = 1200. - public override int GetMaxBatchSize(int channelId) { - bool isFragmentedAlready = ((PacketFlags)Channels[channelId] & ReliableOrUnreliableFragmented) > 0; - return isFragmentedAlready ? MaxAllowedPacketSize : 1200; - } - - #region Internals - private bool ignoreDataPackets; - private string cachedConnectionAddress = string.Empty; - private IgnoranceServer Server = new IgnoranceServer(); - private IgnoranceClient Client = new IgnoranceClient(); - private Dictionary ConnectionLookupDict = new Dictionary(); - - private enum ConnectionState { Connecting, Connected, Disconnecting, Disconnected } - private ConnectionState ClientState = ConnectionState.Disconnected; - private byte[] InternalPacketBuffer; - - private const PacketFlags ReliableOrUnreliableFragmented = PacketFlags.Reliable | PacketFlags.UnreliableFragmented; - - private float statusUpdateTimer = 0f; - #endregion - - #region Legacy Overrides -#if !MIRROR_37_0_OR_NEWER - public bool ServerDisconnectLegacy(int connectionId) - { - if (Server == null) - { - Debug.LogError("Cannot enqueue kick packet; our Server object is null. Something has gone wrong."); - // Return here because otherwise we will get a NRE when trying to enqueue the kick packet. - return false; - } - - IgnoranceCommandPacket kickPacket = new IgnoranceCommandPacket - { - Type = IgnoranceCommandType.ServerKickPeer, - PeerId = (uint)connectionId - 1 // ENet's native peer ID will be ConnID - 1 - }; - - // Pass the packet onto the thread for dispatch. - Server.Commands.Enqueue(kickPacket); - return true; - } -#endif - #endregion -#endif - - } -} +// Ignorance 1.4.x LTS (Long Term Support) +// https://github.com/SoftwareGuy/Ignorance +// ----------------- +// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64) +// Ignorance is licensed under the MIT license. Refer +// to the LICENSE file for more information. +using System; +using System.Collections.Generic; +using ENet; +using IgnoranceCore; +using Mirror; +using UnityEngine; + +namespace IgnoranceTransport +{ + [DisallowMultipleComponent] + public class Ignorance : Transport + { + #region Inspector options + [Header("Essentials")] + public int port = 7777; + + [Header("Debug & Logging")] + [Tooltip("How verbose do you want Ignorance to be?")] + public IgnoranceLogType LogType = IgnoranceLogType.Standard; + [Tooltip("Uses OnGUI to present you with statistics for Server and Client backend instances.")] + public bool DebugDisplay = false; + + [Header("Server Configuration")] + [Tooltip("Should the server bind to all interfaces?")] + public bool serverBindsAll = true; + [Tooltip("This is only used if Server Binds All is unticked.")] + public string serverBindAddress = string.Empty; + [Tooltip("How many peers native ENet will support. Low sane numbers help performance, avoids looping over huge native arrays. Recommended: maximum Mirror players, rounded to nearest 10. (Example: 16 -> 20).")] + public int serverMaxPeerCapacity = 64; + [Tooltip("Server network performance/CPU utilization trade off. Lower numbers = Better performance, more CPU. Higher numbers = Potentially lower performance, less CPU. (Value is in milliseconds)")] + public int serverMaxNativeWaitTime = 1; + [Tooltip("Interval between asking ENet for server status updates. Set to <= 0 to disable.")] + public int serverStatusUpdateInterval = 0; + + [Header("Client Configuration")] + [Tooltip("Client network performance/CPU utilization trade off. Lower numbers = Better performance, more CPU. Higher numbers = Potentially lower performance, less CPU. (Value is in milliseconds)")] + public int clientMaxNativeWaitTime = 3; + [Tooltip("Interval between asking ENet for client status updates. Set to <= 0 to disable.")] + public int clientStatusUpdateInterval = 0; + + [Header("Channel Configuration")] + [Tooltip("You must define your channels in the array shown here, otherwise ENet will not know what channel delivery type to use.")] + public IgnoranceChannelTypes[] Channels; + + [Header("Ring Buffer Tweaking")] + [Tooltip("Affects client only. Defines the capacity of Client Incoming/Outgoing ring buffers. It is up to you to ensure that the buffer doesn't overflow - increase as required. This value translates to packets per second under a worse-case scenario.")] + public int ClientDataBufferSize = 1000; + [Tooltip("Affects client only. Defines the capacity of Client connection event buffer. This is probably best to keep small as connection events are literally minimal in Mirror.")] + public int ClientConnEventBufferSize = 10; + + [Tooltip("Affects Server only. Defines the capacity of Server Incoming/Outgoing ring buffers. It is up to you to ensure that the buffer doesn't overflow. This value translates to packets per second under a worse-case scenario.\n\n" + + "Unlike the client value, it is recommended that you keep this resonably high as Servers process more network IO than clients.")] + public int ServerDataBufferSize = 5000; + [Tooltip("Affects Server only. Defines the capacity of server connection event buffer. This is probably best to keep moderately small unless you expect to have a large influx of users connecting/disconnecting at once.")] + public int ServerConnEventBufferSize = 100; + + [Header("Danger: I hope you know what you're doing!")] + [Tooltip("Used internally to keep allocations to a minimum. This is how much memory will be consumed by the packet buffer on startup, and then reused. If you have large packets, change this to something larger. Default is 4096 bytes (4KB).")] + public int PacketBufferCapacity = 4096; + [Tooltip("For UDP based protocols, it's best to keep your data under the safe MTU of 1200 bytes. This is the maximum allowed packet size, however note that internally ENet can only go to 32MB.")] + public int MaxAllowedPacketSize = 33554432; + #endregion + + #region Public Statistics + public IgnoranceClientStats ClientStatistics; + public IgnoranceServerStats ServerStatistics; + #endregion + + public override bool Available() + { + // Ignorance is not available for Unity WebGL, the PS4 (no dev kit to confirm) or Switch (port exists but I have no access to said code). + // Ignorance is available for most other operating systems. +#if (UNITY_WEBGL || UNITY_PS4 || UNITY_SWITCH) + return false; +#else + return true; +#endif + } + + public void Awake() + { + if (LogType != IgnoranceLogType.Nothing) + print($"Ignorance {IgnoranceInternals.Version} has arrived. Keep up to date, report bugs and support the developer at https://github.com/SoftwareGuy/Ignorance!"); + } + + public override string ToString() + { + return $"Ignorance v{IgnoranceInternals.Version}"; + } + + public override void ClientConnect(string address) + { + ClientState = ConnectionState.Connecting; + cachedConnectionAddress = address; + + // Initialize. + InitializeClientBackend(); + + // Get going. + ignoreDataPackets = false; + + // Start! + Client.Start(); + } + + public override void ClientConnect(Uri uri) + { + if (uri.Scheme != IgnoranceInternals.Scheme) + throw new ArgumentException($"You used an invalid URI: {uri}. Please use {IgnoranceInternals.Scheme}://host:port instead", nameof(uri)); + + if (!uri.IsDefaultPort) + // Set the communication port to the one specified. + port = uri.Port; + + // Pass onwards to the proper handler. + ClientConnect(uri.Host); + } + + public override bool ClientConnected() => ClientState == ConnectionState.Connected; + + public override void ClientDisconnect() + { + ClientState = ConnectionState.Disconnecting; + + if (Client != null) + { + // Fix for the Client commands RingBuffer not being initialized if we're in host mode. + if (Client.Commands != null) + Client.Commands.Enqueue(new IgnoranceCommandPacket { Type = IgnoranceCommandType.ClientWantsToStop }); + + Client.Stop(); + } + else + { + throw new InvalidOperationException("Cannot disconnect the client instance. The Ignorance Client instance is null."); + } + + } + + public override void ClientSend(ArraySegment segment, int channelId) + { + if (Client == null) + { + Debug.LogError("Client object is null, this shouldn't really happen but it did..."); + return; + } + + if (channelId < 0 || channelId > Channels.Length) + { + Debug.LogError("Channel ID is out of bounds."); + return; + } + + // Create our struct... + Packet clientOutgoingPacket = default; + int byteCount = segment.Count; + int byteOffset = segment.Offset; + + // Set our desired flags... + PacketFlags desiredFlags = (PacketFlags)Channels[channelId]; + + // Warn if over recommended MTU... + bool flagsSet = (desiredFlags & ReliableOrUnreliableFragmented) > 0; + + if (LogType != IgnoranceLogType.Nothing && byteCount > 1200 && !flagsSet) + Debug.LogWarning($"Ignorance Client: Trying to send a Unreliable packet bigger than the recommended ENet 1200 byte MTU ({byteCount} > 1200). ENet will force Reliable Fragmented delivery."); + + // Create the packet. + clientOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, desiredFlags); + + // Enqueue the packet. + IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket + { + Channel = (byte)channelId, + Payload = clientOutgoingPacket + }; + + // Pass the packet onto the thread for dispatch. + Client.Outgoing.Enqueue(dispatchPacket); + } + + public override bool ServerActive() + { + // Very simple check. + return Server != null && Server.IsAlive; + } + + // Current version Mirror server disconnection routine. + public override void ServerDisconnect(int connectionId) + { + if (Server == null) + { + Debug.LogError("Cannot enqueue kick packet; our Server object is null. Something has gone wrong."); + // Return here because otherwise we will get a NRE when trying to enqueue the kick packet. + return; + } + + // Enqueue the kick packet. + IgnoranceCommandPacket kickPacket = new IgnoranceCommandPacket + { + Type = IgnoranceCommandType.ServerKickPeer, + PeerId = (uint)connectionId - 1 // ENet's native peer ID will be ConnID - 1 + }; + + // Pass the packet onto the thread for dispatch. + Server.Commands.Enqueue(kickPacket); + } + + public override string ServerGetClientAddress(int connectionId) + { + if (peerConnectionData == null) + return "(unavailable)"; + + // Need to adjust the string... + if (!string.IsNullOrEmpty(peerConnectionData[connectionId - 1].IP)) + return $"{peerConnectionData[connectionId - 1].IP}:{peerConnectionData[connectionId - 1].Port}"; + else + return "(unavailable)"; + } + + // v1.4.0b6: Mirror rearranged the ServerSend params, so we need to apply a fix for that or + // we end up using the obsoleted version. The obsolete version isn't a fatal error, but + // it's best to stick with the new structures. + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) + { + if (Server == null) + { + Debug.LogError("Ignorance Server: Cannot enqueue data packet; our Server object is null. Something has gone wrong."); + return; + } + + if (channelId < 0 || channelId > Channels.Length) + { + Debug.LogError("Ignorance Server: Channel ID is out of bounds."); + return; + } + + // Packet Struct + Packet serverOutgoingPacket = default; + int byteCount = segment.Count; + int byteOffset = segment.Offset; + PacketFlags desiredFlags = (PacketFlags)Channels[channelId]; + + // Warn if over recommended MTU + bool flagsSet = (desiredFlags & ReliableOrUnreliableFragmented) > 0; + + if (LogType != IgnoranceLogType.Nothing && byteCount > 1200 && !flagsSet) + Debug.LogWarning($"Ignorance Server: Trying to send a Unreliable packet bigger than the recommended ENet 1200 byte MTU ({byteCount} > 1200). ENet will force Reliable Fragmented delivery."); + + // Create the packet. + serverOutgoingPacket.Create(segment.Array, byteOffset, byteCount + byteOffset, (PacketFlags)Channels[channelId]); + + // Enqueue the packet. + IgnoranceOutgoingPacket dispatchPacket = new IgnoranceOutgoingPacket + { + Channel = (byte)channelId, + NativePeerId = (uint)connectionId - 1, // ENet's native peer ID will be ConnID - 1 + Payload = serverOutgoingPacket + }; + + Server.Outgoing.Enqueue(dispatchPacket); + } + + public override void ServerStart() + { + if (LogType != IgnoranceLogType.Nothing) + Debug.Log("Ignorance Server: Instance starting up..."); + + InitializeServerBackend(); + + Server.Start(); + } + + public override void ServerStop() + { + if (Server != null) + { + if (LogType != IgnoranceLogType.Nothing) + Debug.Log("Ignorance Server: Instance shutting down..."); + + Server.Stop(); + } + + peerConnectionData = null; + } + + public override Uri ServerUri() + { + UriBuilder builder = new UriBuilder + { + Scheme = IgnoranceInternals.Scheme, + Host = serverBindAddress, + Port = port + }; + + return builder.Uri; + } + + public override void Shutdown() + { + // TODO: Nothing needed here? + } + + // Check to ensure channels 0 and 1 mimic LLAPI. Override this at your own risk. + private void OnValidate() + { + if (Channels != null && Channels.Length >= 2) + { + // Check to make sure that Channel 0 and 1 are correct. + if (Channels[0] != IgnoranceChannelTypes.Reliable) + { + Debug.LogWarning("Please do not modify Ignorance Channel 0. The channel will be reset to Reliable delivery. If you need a channel with a different delivery, define and use it instead."); + Channels[0] = IgnoranceChannelTypes.Reliable; + } + + if (Channels[1] != IgnoranceChannelTypes.Unreliable) + { + Debug.LogWarning("Please do not modify Ignorance Channel 1. The channel will be reset to Unreliable delivery. If you need a channel with a different delivery, define and use it instead."); + Channels[1] = IgnoranceChannelTypes.Unreliable; + } + } + else + { + Debug.LogWarning("Invalid Channels setting, fixing. If you've just added Ignorance to your NetworkManager GameObject, seeing this message is normal."); + Channels = new IgnoranceChannelTypes[2] + { + IgnoranceChannelTypes.Reliable, + IgnoranceChannelTypes.Unreliable + }; + } + + // ENet only supports a maximum of 32MB packet size. + if (MaxAllowedPacketSize > 33554432) + MaxAllowedPacketSize = 33554432; + } + + private void InitializeServerBackend() + { + if (Server == null) + { + Debug.LogWarning("Ignorance Server: Reference for Server mode was null. This shouldn't happen, but to be safe we'll attempt to reinitialize it."); + Server = new IgnoranceServer(); + } + + // Set up the new IgnoranceServer reference. + if (serverBindsAll) + // MacOS is special. It's also a massive thorn in my backside. + Server.BindAddress = IgnoranceInternals.BindAnyAddress; + else + // Use the supplied bind address. + Server.BindAddress = serverBindAddress; + + // Sets port, maximum peers, max channels, the server poll time, maximum packet size and verbosity. + Server.BindPort = port; + Server.MaximumPeers = serverMaxPeerCapacity; + Server.MaximumChannels = Channels.Length; + Server.PollTime = serverMaxNativeWaitTime; + Server.MaximumPacketSize = MaxAllowedPacketSize; + Server.Verbosity = (int)LogType; + + Server.IncomingOutgoingBufferSize = ServerDataBufferSize; + Server.ConnectionEventBufferSize = ServerConnEventBufferSize; + + // Initializes the packet buffer. + // Allocates once, that's it. + if (InternalPacketBuffer == null) + InternalPacketBuffer = new byte[PacketBufferCapacity]; + + // This is required to ensure that ServerStatistics peer stats are initialised before first update + if (ServerStatistics.PeerStats == null) + ServerStatistics.PeerStats = new Dictionary(serverMaxPeerCapacity); + + // Setup the peer connection array. + peerConnectionData = new PeerConnectionData[serverMaxPeerCapacity]; + } + + private void InitializeClientBackend() + { + if (Client == null) + { + Debug.LogWarning("Ignorance Client: Backend instance reference was null. This shouldn't happen, but to be safe we'll reinitialize it."); + Client = new IgnoranceClient(); + } + + // Sets address, port, channels to expect, verbosity, the server poll time and maximum packet size. + Client.ConnectAddress = cachedConnectionAddress; + Client.ConnectPort = port; + Client.ExpectedChannels = Channels.Length; + Client.PollTime = clientMaxNativeWaitTime; + Client.MaximumPacketSize = MaxAllowedPacketSize; + Client.Verbosity = (int)LogType; + + Client.IncomingOutgoingBufferSize = ClientDataBufferSize; + Client.ConnectionEventBufferSize = ClientConnEventBufferSize; + + // Initializes the packet buffer. Allocates once, that's it. + if (InternalPacketBuffer == null) + InternalPacketBuffer = new byte[PacketBufferCapacity]; + } + + #region Main Thread Processing and Polling + public void ServerPump() + { + // Process Server Events... + if (Server.IsAlive) + { + ProcessServerPackets(); + + if (serverStatusUpdateInterval > 0) + { + serverStatusUpdateTimer += Time.deltaTime; + + if (serverStatusUpdateTimer >= serverStatusUpdateInterval) + { + Server.Commands.Enqueue(new IgnoranceCommandPacket { Type = IgnoranceCommandType.ServerStatusRequest }); + serverStatusUpdateTimer = 0f; + } + } + } + } + + public void ClientPump() + { + // Only process client packets if we're not disconnected... + if (ClientState != ConnectionState.Disconnected) + ProcessClientPackets(); + + // Used if we're connected and the client status update interval is above 0. + if (ClientState == ConnectionState.Connected && clientStatusUpdateInterval > 0) + { + clientStatusUpdateTimer += Time.deltaTime; + + if (clientStatusUpdateTimer >= clientStatusUpdateInterval) + { + Client.Commands.Enqueue(new IgnoranceCommandPacket { Type = IgnoranceCommandType.ClientStatusRequest }); + clientStatusUpdateTimer = 0f; + } + } + + } + + private void ProcessServerPackets() + { + IgnoranceIncomingPacket incomingPacket; + IgnoranceConnectionEvent connectionEvent; + int adjustedConnectionId; + Packet payload; + IgnoranceServerStats serverStats; + + // Incoming connection events. + while (Server.ConnectionEvents.TryDequeue(out connectionEvent)) + { + adjustedConnectionId = (int)connectionEvent.NativePeerId + 1; + + if (LogType == IgnoranceLogType.Verbose) + Debug.Log($"Processing a server connection event from ENet native peer {connectionEvent.NativePeerId}. This peer would be Mirror ConnID {adjustedConnectionId}."); + + // Cache that peer. + // NOTE: We cache the peers native id and do some magic later. + peerConnectionData[(int)connectionEvent.NativePeerId] = new PeerConnectionData + { + IP = connectionEvent.IP, + NativePeerId = connectionEvent.NativePeerId, + Port = connectionEvent.Port + }; + + OnServerConnected?.Invoke(adjustedConnectionId); + } + + // Handle incoming data packets. + // Console.WriteLine($"Server Incoming Queue is {Server.Incoming.Count}"); + while (Server.Incoming.TryDequeue(out incomingPacket)) + { + adjustedConnectionId = (int)incomingPacket.NativePeerId + 1; + payload = incomingPacket.Payload; + + int length = payload.Length; + ArraySegment dataSegment; + + // Copy to working buffer and dispose of it. + if (length > InternalPacketBuffer.Length) + { + byte[] oneFreshNTastyGcAlloc = new byte[length]; + + payload.CopyTo(oneFreshNTastyGcAlloc); + dataSegment = new ArraySegment(oneFreshNTastyGcAlloc, 0, length); + } + else + { + payload.CopyTo(InternalPacketBuffer); + dataSegment = new ArraySegment(InternalPacketBuffer, 0, length); + } + + payload.Dispose(); + + OnServerDataReceived?.Invoke(adjustedConnectionId, dataSegment, incomingPacket.Channel); + } + + // Disconnection events. + while (Server.DisconnectionEvents.TryDequeue(out IgnoranceConnectionEvent disconnectionEvent)) + { + adjustedConnectionId = (int)disconnectionEvent.NativePeerId + 1; + + // The array is no longer occupied. + peerConnectionData[(int)connectionEvent.NativePeerId] = default; + + if (LogType == IgnoranceLogType.Verbose) + Debug.Log($"Ignorance Server: Handling disconnection event from native peer {disconnectionEvent.NativePeerId}."); + + // Invoke Mirror handler. + OnServerDisconnected?.Invoke(adjustedConnectionId); + } + + // Handle status updates. + if (Server.StatusUpdates.TryDequeue(out serverStats)) + { + Server.RecycledServerStatBlocks.Enqueue(ServerStatistics); + ServerStatistics = serverStats; + } + } + + + private void ProcessClientPackets() + { + IgnoranceIncomingPacket incomingPacket; + IgnoranceClientStats clientStats; + Packet payload; + + // Handle connection events. + while (Client.ConnectionEvents.TryDequeue(out IgnoranceConnectionEvent connectionEvent)) + { + if (LogType == IgnoranceLogType.Verbose) + Debug.Log($"Ignorance Client Debug: Processing a client ConnectionEvents queue item. Type: {connectionEvent.EventType.ToString("{0:X2}")}"); + + switch (connectionEvent.EventType) + { + case 0x00: + // Connected to server. + ClientState = ConnectionState.Connected; + + if (LogType != IgnoranceLogType.Nothing) + Debug.Log($"Ignorance Client has successfully connected to server at {connectionEvent.IP}:{connectionEvent.Port}"); + + ignoreDataPackets = false; + OnClientConnected?.Invoke(); + break; + + case 0x01: + // Disconnected from server. + ClientState = ConnectionState.Disconnected; + + if (LogType != IgnoranceLogType.Nothing) + Debug.Log($"Ignorance Client has been disconnected from server."); + + ignoreDataPackets = true; + OnClientDisconnected?.Invoke(); + break; + + default: + // Unknown type. + if (LogType != IgnoranceLogType.Nothing) + Debug.LogWarning($"Ignorance Client: Unknown connection event type {connectionEvent.EventType.ToString("{0:X2}")}."); + break; + } + } + + // Handle the incoming messages. + while (Client.Incoming.TryDequeue(out incomingPacket)) + { + // Temporary fix: if ENet thread is too fast for Mirror, then ignore the packet. + // This is seen sometimes if you stop the client and there's still stuff in the queue. + if (ignoreDataPackets) + { + if (LogType == IgnoranceLogType.Verbose) + Debug.Log("Ignorance Client: ProcessClientPackets cycle skipped; ignoring data packet"); + break; + } + + + // Otherwise client recieved data, advise Mirror. + // print($"Byte array: {incomingPacket.RentedByteArray.Length}. Packet Length: {incomingPacket.Length}"); + payload = incomingPacket.Payload; + int length = payload.Length; + ArraySegment dataSegment; + + // Copy to working buffer and dispose of it. + if (length > InternalPacketBuffer.Length) + { + // Unity's favourite: A fresh 'n' tasty GC Allocation! + byte[] oneFreshNTastyGcAlloc = new byte[length]; + + payload.CopyTo(oneFreshNTastyGcAlloc); + dataSegment = new ArraySegment(oneFreshNTastyGcAlloc, 0, length); + } + else + { + payload.CopyTo(InternalPacketBuffer); + dataSegment = new ArraySegment(InternalPacketBuffer, 0, length); + } + + payload.Dispose(); + + OnClientDataReceived?.Invoke(dataSegment, incomingPacket.Channel); + } + + // Step 3: Handle status updates. + if (Client.StatusUpdates.TryDequeue(out clientStats)) + ClientStatistics = clientStats; + } + #endregion + + + #region Main Thread Processing and Polling - Ignorance Flavour +#if !IGNORANCE_MIRROR_POLLING + // FixedUpdate can be called many times per frame. + // Once we've handled stuff, we set a flag so that we don't poll again for this frame. + private bool fixedUpdateCompletedWork; + + private void FixedUpdate() + { + if (fixedUpdateCompletedWork) return; + + ServerPump(); + ClientPump(); + + // Flip the bool to signal we've done our work. + fixedUpdateCompletedWork = true; + } + + private new void Update() + { + // Process what FixedUpdate missed, only if the boolean is not set. + if (!fixedUpdateCompletedWork) + { + ServerPump(); + ClientPump(); + } + + // Flip back the bool, so it can be reset. + fixedUpdateCompletedWork = false; + } +#endif + #endregion + + #region Main Thread Processing and Polling - Mirror Flavour +#if IGNORANCE_MIRROR_POLLING + public override void ClientEarlyUpdate() + { + ClientPump(); + } + + public override void ServerEarlyUpdate() + { + ServerPump(); + } +#endif + #endregion + + #region Debug + private void OnGUI() + { + if (DebugDisplay) + { + if (Client != null) + GUI.Box(new Rect( + new Vector2(32, Screen.height - 220), new Vector2(240, 100)), + "-- CLIENT --\n" + + $"State: {ClientState} ({(Client.IsAlive ? "Alive" : "Dead")}) \n" + + $"Round Trip Time: {ClientStatistics.RTT} \n" + + $"Bytes In/Out: {ClientStatistics.BytesReceived} / {ClientStatistics.BytesSent} \n" + + $"Queue In/Out: {(Client.Incoming != null ? $"{Client.Incoming.Count}" : "0")} / {(Client.Outgoing != null ? $"{Client.Outgoing.Count}" : "0")} \n" + + $"ConnEvents: {(Client.ConnectionEvents != null ? $"{Client.ConnectionEvents.Count}" : "0")}" + ); + + if (Server != null) + GUI.Box(new Rect( + new Vector2(32, Screen.height - 110), new Vector2(240, 100)), + + "-- SERVER --\n" + + $"State: {(Server.IsAlive ? "Alive" : "Dead")} \n" + + $"Bytes In/Out: {ServerStatistics.BytesReceived} / {ServerStatistics.BytesSent} \n" + + $"Queue In/Out: {(Server.Incoming != null ? $"{Server.Incoming.Count}" : "0")} / {(Server.Outgoing != null ? $"{Server.Outgoing.Count}" : "0")} \n" + + $"ConnEvents: {(Server.ConnectionEvents != null ? $"{Server.ConnectionEvents.Count}" : "0")}" + ); + + } + } + #endregion + + // Mirror 46 (Mirror LTS) work arounds + public override int GetMaxPacketSize(int channelId = 0) + { + bool isFragmentedAlready = ((PacketFlags)Channels[channelId] & ReliableOrUnreliableFragmented) > 0; + return isFragmentedAlready ? MaxAllowedPacketSize : 1200; + } + + #region Internals + private bool ignoreDataPackets; + private string cachedConnectionAddress = string.Empty; + private IgnoranceServer Server = new IgnoranceServer(); + private IgnoranceClient Client = new IgnoranceClient(); + private PeerConnectionData[] peerConnectionData; + + private enum ConnectionState { Connecting, Connected, Disconnecting, Disconnected } + private ConnectionState ClientState = ConnectionState.Disconnected; + private byte[] InternalPacketBuffer; + + private const PacketFlags ReliableOrUnreliableFragmented = PacketFlags.Reliable | PacketFlags.UnreliableFragmented; + private float clientStatusUpdateTimer = 0f; + private float serverStatusUpdateTimer = 0f; + #endregion + } +} diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs.meta index 9703c34..47421a9 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Ignorance.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 872fa23ef6e77334ca452ce16f6cd091 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: -32000 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 872fa23ef6e77334ca452ce16f6cd091 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: -32000 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs b/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs index ca677ac..d14e1d5 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs +++ b/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs @@ -1,94 +1,109 @@ -using System; -using ENet; - -namespace IgnoranceTransport -{ - // Snipped from the transport files, as this will help - // me keep things up to date. - [Serializable] - public enum IgnoranceChannelTypes - { - Reliable = PacketFlags.Reliable, // TCP Emulation. - ReliableUnsequenced = PacketFlags.Reliable | PacketFlags.Unsequenced, // TCP Emulation, but no sequencing. - Unreliable = PacketFlags.Unsequenced, // Pure UDP. - UnreliableFragmented = PacketFlags.UnreliableFragmented, // Pure UDP, but fragmented. - UnreliableSequenced = PacketFlags.None, // Pure UDP, but sequenced. - Unthrottled = PacketFlags.Unthrottled, // Apparently ENet's version of Taco Bell. - } - - public class IgnoranceInternals - { - public const string Version = "1.4.0b6"; - public const string Scheme = "enet"; - public const string BindAllIPv4 = "0.0.0.0"; - public const string BindAllMacs = "::0"; - } - - public enum IgnoranceLogType - { - Nothing, - Standard, - Verbose - } - - // Struct optimized for cache efficiency. (Thanks Vincenzo!) - public struct IgnoranceIncomingPacket - { - public byte Channel; - public uint NativePeerId; - public Packet Payload; - } - - // Struct optimized for cache efficiency. (Thanks Vincenzo!) - public struct IgnoranceOutgoingPacket - { - public byte Channel; - public uint NativePeerId; - public Packet Payload; - } - - // Struct optimized for cache efficiency. (Thanks Vincenzo!) - public struct IgnoranceConnectionEvent - { - public bool WasDisconnect; - public ushort Port; - public uint NativePeerId; - public string IP; - } - - public struct IgnoranceCommandPacket - { - public IgnoranceCommandType Type; - public uint PeerId; - } - - public struct IgnoranceClientStats - { - // Stats only - may not always be used! - public uint RTT; - public ulong BytesReceived; - public ulong BytesSent; - public ulong PacketsReceived; - public ulong PacketsSent; - public ulong PacketsLost; - } - - public enum IgnoranceCommandType - { - // Client - ClientWantsToStop, - ClientRequestsStatusUpdate, - // ENet internal - ResponseToClientStatusRequest, - // Server - ServerKickPeer - } - - // TODO: Optimize struct for Cache performance. - public struct PeerConnectionData - { - public ushort Port; - public uint NativePeerId; - public string IP; - } -} +// Ignorance 1.4.x LTS (Long Term Support) +// https://github.com/SoftwareGuy/Ignorance +// ----------------- +// Copyright (c) 2019 - 2021 Matt Coburn (SoftwareGuy/Coburn64) +// Ignorance is licensed under the MIT license. Refer +// to the LICENSE file for more information. +using System; +using System.Collections.Generic; +using ENet; + +namespace IgnoranceCore +{ + // Snipped from the transport files, as this will help + // me keep things up to date. + [Serializable] + public enum IgnoranceChannelTypes + { + Reliable = PacketFlags.Reliable, // Reliable UDP (TCP-like emulation) + ReliableUnsequenced = PacketFlags.Reliable | PacketFlags.Unsequenced, // Reliable UDP (TCP-like emulation w/o sequencing) + Unreliable = PacketFlags.Unsequenced, // Pure UDP, high velocity packet action. + UnreliableFragmented = PacketFlags.UnreliableFragmented, // Pure UDP, but fragmented. + UnreliableSequenced = PacketFlags.None, // Pure UDP, but sequenced. + Unthrottled = PacketFlags.Unthrottled, // Pure UDP. Literally turbo mode. + } + + public class IgnoranceInternals + { + public const string Version = "1.4.0r0 (LTS)"; + public const string Scheme = "enet"; + public const string BindAnyAddress = "::0"; + } + + public enum IgnoranceLogType + { + Nothing, + Standard, + Verbose + } + + public struct IgnoranceIncomingPacket + { + public byte Channel; + public uint NativePeerId; + public Packet Payload; + } + + public struct IgnoranceOutgoingPacket + { + public byte Channel; + public uint NativePeerId; + public Packet Payload; + } + + public struct IgnoranceConnectionEvent + { + public byte EventType; + public ushort Port; + public uint NativePeerId; + public string IP; + } + + public struct IgnoranceCommandPacket + { + public IgnoranceCommandType Type; + public uint PeerId; + } + + // Stats only - may not always be used! + public struct IgnoranceClientStats + { + + public uint RTT; + public ulong BytesReceived; + public ulong BytesSent; + public ulong PacketsReceived; + public ulong PacketsSent; + public ulong PacketsLost; + } + + public enum IgnoranceCommandType + { + // Client + ClientWantsToStop, + ClientStatusRequest, + // Server + ServerKickPeer, + ServerStatusRequest + } + + // Stats only - may not always be used! + public struct IgnoranceServerStats + { + + public ulong BytesReceived; + public ulong BytesSent; + public ulong PacketsReceived; + public ulong PacketsSent; + public ulong PeersCount; + + public Dictionary PeerStats; + } + + public struct PeerConnectionData + { + public ushort Port; + public uint NativePeerId; + public string IP; + } +} diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs.meta b/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs.meta index 444855c..8eb3709 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/IgnoranceDefinitions.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 3b2d1f7f7d9d3d64297755419f6ab925 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 3b2d1f7f7d9d3d64297755419f6ab925 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins.meta index def60ed..29b25c9 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: e8bd04b9965420e40ac911fbf4294e1c -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: e8bd04b9965420e40ac911fbf4294e1c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android.meta index f51f451..2f43352 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: c90c88052305a054d87177362f63dea8 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: c90c88052305a054d87177362f63dea8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a.meta index 738b943..0b27aff 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: e4a42b5855436864693138f74335c094 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: e4a42b5855436864693138f74335c094 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a/libenet.so.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a/libenet.so.meta index 63cd799..26cd945 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a/libenet.so.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/arm64-v8a/libenet.so.meta @@ -1,111 +1,111 @@ -fileFormatVersion: 2 -guid: 4b3abd2268dea254da11190b00d16051 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 0 - Exclude Editor: 1 - Exclude Linux: 1 - Exclude Linux64: 1 - Exclude LinuxUniversal: 1 - Exclude OSXUniversal: 1 - Exclude WebGL: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 1 - - first: - Android: Android - second: - enabled: 1 - settings: - CPU: ARM64 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Facebook: Win - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Facebook: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Linux - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: LinuxUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - WebGL: WebGL - second: - enabled: 0 - settings: {} - - first: - iPhone: iOS - second: - enabled: 0 - settings: - AddToEmbeddedBinaries: false - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 4b3abd2268dea254da11190b00d16051 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux: 1 + Exclude Linux64: 1 + Exclude LinuxUniversal: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARM64 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: LinuxUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + WebGL: WebGL + second: + enabled: 0 + settings: {} + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a.meta index bbb65c6..4547d51 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: bfbaf4fbcf8987e4585ca666e28f4871 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: bfbaf4fbcf8987e4585ca666e28f4871 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a/libenet.so.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a/libenet.so.meta index 07ea880..3365543 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a/libenet.so.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/armeabi-v7a/libenet.so.meta @@ -1,106 +1,106 @@ -fileFormatVersion: 2 -guid: e3ce4b8600e09d74ead46137ba184d01 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 0 - Exclude Editor: 1 - Exclude Linux: 1 - Exclude Linux64: 1 - Exclude LinuxUniversal: 1 - Exclude OSXUniversal: 1 - Exclude WebGL: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 1 - - first: - Android: Android - second: - enabled: 1 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Facebook: Win - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Facebook: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Linux - second: - enabled: 0 - settings: - CPU: x86 - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: LinuxUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - iPhone: iOS - second: - enabled: 0 - settings: - AddToEmbeddedBinaries: false - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: e3ce4b8600e09d74ead46137ba184d01 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux: 1 + Exclude Linux64: 1 + Exclude LinuxUniversal: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: LinuxUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86.meta index 4491214..c06b7c6 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: b3486c84aaebff4489e2e11f5bd3030f -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: b3486c84aaebff4489e2e11f5bd3030f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86/libenet.so.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86/libenet.so.meta index c57aeaa..f93832a 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86/libenet.so.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Android/x86/libenet.so.meta @@ -1,80 +1,80 @@ -fileFormatVersion: 2 -guid: ba78b07719f786c4cae4c52b9528903f -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 0 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 1 - - first: - Android: Android - second: - enabled: 1 - settings: - CPU: x86 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - - first: - iPhone: iOS - second: - enabled: 0 - settings: - AddToEmbeddedBinaries: false - CPU: AnyCPU - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: ba78b07719f786c4cae4c52b9528903f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: x86 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux.meta index f1aae5d..dc256c0 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 8e3ca9b41c9f4064581a6b56ae549a9c -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 8e3ca9b41c9f4064581a6b56ae549a9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/README.txt b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/README.txt new file mode 100644 index 0000000..e00d2c3 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/README.txt @@ -0,0 +1,6 @@ +Rename this debug library without the "_debug" suffix and replace (maybe backup the original first) the library. +For example, to replace the x64 ENet Library with the Debug version, you'd first backup/move "libenet.so", then extract and rename "libenet_debug.so" to "libenet.so". + +If you have trouble, file a GitHub support ticket at https://github.com/SoftwareGuy/Ignorance. + +- Coburn diff --git a/Assets/Mirror/Notice.txt.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/README.txt.meta similarity index 75% rename from Assets/Mirror/Notice.txt.meta rename to Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/README.txt.meta index 3281bd8..e64f994 100644 --- a/Assets/Mirror/Notice.txt.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/README.txt.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1a7b49ad188074707b004e7bb8824857 +guid: 765c88bc8183ed44cb51a5529d8f743e TextScriptImporter: externalObjects: {} userData: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so index c64bfb7..69e104c 100644 Binary files a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so and b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so differ diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so.meta index d60a26a..109e845 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet.so.meta @@ -1,97 +1,97 @@ -fileFormatVersion: 2 -guid: f2bd341c8082ddd47b4b5cc9bfdf2332 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - '': Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 0 - Exclude Linux: 1 - Exclude Linux64: 0 - Exclude LinuxUniversal: 0 - Exclude OSXUniversal: 1 - Exclude WebGL: 1 - Exclude Win: 0 - Exclude Win64: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 1 - settings: - CPU: x86_64 - DefaultValueInitialized: true - OS: Linux - - first: - Facebook: Win - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Facebook: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Linux - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Linux64 - second: - enabled: 1 - settings: - CPU: x86_64 - - first: - Standalone: LinuxUniversal - second: - enabled: 1 - settings: - CPU: x86_64 - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 1 - settings: - CPU: AnyCPU - - first: - Standalone: Win64 - second: - enabled: 1 - settings: - CPU: AnyCPU - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f2bd341c8082ddd47b4b5cc9bfdf2332 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux: 1 + Exclude Linux64: 0 + Exclude LinuxUniversal: 0 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 0 + Exclude Win64: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: Linux + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet_debug.zip b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet_debug.zip new file mode 100644 index 0000000..773c2f0 Binary files /dev/null and b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet_debug.zip differ diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet_debug.zip.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet_debug.zip.meta new file mode 100644 index 0000000..8c5af9f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Linux/libenet_debug.zip.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c7482955b4ae93c4081db9ddd04cae47 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows.meta index 06c1b96..b52036e 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 8837cb1a7320b8b4f824a02e23f4a545 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 8837cb1a7320b8b4f824a02e23f4a545 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt index 1bb2563..95c34fc 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt @@ -1,3 +1,3 @@ -This is a Windows x64 build of ENet-CSharp. - +This is a Windows x64 build of ENet-CSharp. + If you require a version of ENet for 32 Bit computer systems (ie. Windows 7/8/10 32Bit) then please get in touch, or you can install the requirements yourself and compile it using ENet-CSharp's MSBuild-based build system. Get in touch if you want me to compile them for you, but keep in mind that I do not support them when reporting bugs. \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt.meta index b69d8f0..621c730 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/README.txt.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 09ee288deb259474c819a5e3daf54b80 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 09ee288deb259474c819a5e3daf54b80 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/enet.dll.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/enet.dll.meta index 99fefbb..4f9b318 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/enet.dll.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/Windows/enet.dll.meta @@ -1,111 +1,111 @@ -fileFormatVersion: 2 -guid: cc98033c6bc234a419ccd053e7173781 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - '': Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 0 - Exclude Linux: 0 - Exclude Linux64: 0 - Exclude LinuxUniversal: 0 - Exclude OSXUniversal: 0 - Exclude WebGL: 1 - Exclude Win: 1 - Exclude Win64: 0 - Exclude iOS: 1 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 1 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Facebook: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Facebook: Win64 - second: - enabled: 0 - settings: - CPU: AnyCPU - - first: - Standalone: Linux - second: - enabled: 1 - settings: - CPU: x86 - - first: - Standalone: Linux64 - second: - enabled: 1 - settings: - CPU: x86_64 - - first: - Standalone: LinuxUniversal - second: - enabled: 1 - settings: - CPU: AnyCPU - - first: - Standalone: OSXUniversal - second: - enabled: 1 - settings: - CPU: AnyCPU - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 1 - settings: - CPU: AnyCPU - - first: - WebGL: WebGL - second: - enabled: 0 - settings: {} - - first: - iPhone: iOS - second: - enabled: 0 - settings: - AddToEmbeddedBinaries: false - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: cc98033c6bc234a419ccd053e7173781 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + '': Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux: 0 + Exclude Linux64: 0 + Exclude LinuxUniversal: 0 + Exclude OSXUniversal: 0 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 0 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + WebGL: WebGL + second: + enabled: 0 + settings: {} + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS.meta index b6beac0..2eafac8 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 7edc275f3670f4251b90301cbb9f7413 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 7edc275f3670f4251b90301cbb9f7413 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-arm64.a.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-arm64.a.meta index 1504880..1d6c25d 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-arm64.a.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-arm64.a.meta @@ -1,80 +1,80 @@ -fileFormatVersion: 2 -guid: 1485de7d76884a64bac00df01454081e -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CPU: ARM64 - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 1485de7d76884a64bac00df01454081e +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: ARM64 + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-armv7.a.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-armv7.a.meta index aa56177..0ded111 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-armv7.a.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-armv7.a.meta @@ -1,80 +1,80 @@ -fileFormatVersion: 2 -guid: fa239cb4c47fc9447816815f80667fea -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CPU: ARMv7 - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: fa239cb4c47fc9447816815f80667fea +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: ARMv7 + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-simulator64.a.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-simulator64.a.meta index 7106afb..6f44db0 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-simulator64.a.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/iOS/libenet-release-simulator64.a.meta @@ -1,80 +1,80 @@ -fileFormatVersion: 2 -guid: 385bb48fb124b5f40a79bdecf421671f -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - : Any - second: - enabled: 0 - settings: - Exclude Android: 1 - Exclude Editor: 1 - Exclude Linux64: 1 - Exclude OSXUniversal: 1 - Exclude Win: 1 - Exclude Win64: 1 - Exclude iOS: 0 - - first: - Android: Android - second: - enabled: 0 - settings: - CPU: ARMv7 - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - - first: - Standalone: Linux64 - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: OSXUniversal - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win - second: - enabled: 0 - settings: - CPU: None - - first: - Standalone: Win64 - second: - enabled: 0 - settings: - CPU: None - - first: - iPhone: iOS - second: - enabled: 1 - settings: - AddToEmbeddedBinaries: false - CPU: X64 - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 385bb48fb124b5f40a79bdecf421671f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: X64 + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS.meta index b4c703f..c427d7d 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 957ba76da095cd64aaa658f034ab78e5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 957ba76da095cd64aaa658f034ab78e5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS/libenet.dylib.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS/libenet.dylib.meta index 92fe418..6e27fb9 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS/libenet.dylib.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/macOS/libenet.dylib.meta @@ -1,32 +1,32 @@ -fileFormatVersion: 2 -guid: 5317859893ad2cf48a6df1d585ebdd2c -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 1 - settings: - DefaultValueInitialized: true - - first: - Standalone: OSXUniversal - second: - enabled: 1 - settings: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 5317859893ad2cf48a6df1d585ebdd2c +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt index 66f084a..41087c5 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt @@ -1,35 +1,35 @@ -ENET Pre-compiled Binary Library Blobs -========================== -This folder contains pre-compiled binaries for a variety of different platforms. - -A brief summary of these folders are as follows: - -- Windows, Mac, Linux --- 64bit (x64) - -- Android (Kitkat 4.4 minimum target OS) --- ARMv7 (armeabi-v7a), ARMv8/AArch64 (arm64-v8a) - -- iOS --- FAT Library (armv7 + arm64). Targeted for iOS 8 minimum. Unsigned library. - -DEBUG VERSIONS -=============== -Debug versions of the libraries can be obtained at https://github.com/SoftwareGuy/ENet-CSharp/releases. -Otherwise you can also compile the library yourself with Debug enabled. - -DOT POINTS -=========== -1. 32bit Support for Ignorance has been removed. Originally, I did not want to support 32bit operating systems, -however due to some countries in the world still stuck in the 32bit era (Brasil, some Russian areas, etc) I added them as a -goodwill gesture. However, since those who needed the libraries have now vanished, I have stopped building 32bit versions of ENet. - -COMPILE THE CODE YOURSELF -========================= -If you don't trust the above binaries then git clone the ENET-CSharp repository (http://github.com/SoftwareGuy/ENet-CSharp) and read the readme. - -EXCLUSION INSTRUCTIONS -====================== -No need, the meta data will cover that for you. - +ENET Pre-compiled Binary Library Blobs +========================== +This folder contains pre-compiled binaries for a variety of different platforms. + +A brief summary of these folders are as follows: + +- Windows, Mac, Linux +-- 64bit (x64) + +- Android (Kitkat 4.4 minimum target OS) +-- ARMv7 (armeabi-v7a), ARMv8/AArch64 (arm64-v8a) + +- iOS +-- FAT Library (armv7 + arm64). Targeted for iOS 8 minimum. Unsigned library. + +DEBUG VERSIONS +=============== +Debug versions of the libraries can be obtained at https://github.com/SoftwareGuy/ENet-CSharp/releases. +Otherwise you can also compile the library yourself with Debug enabled. + +DOT POINTS +=========== +1. 32bit Support for Ignorance has been removed. Originally, I did not want to support 32bit operating systems, +however due to some countries in the world still stuck in the 32bit era (Brasil, some Russian areas, etc) I added them as a +goodwill gesture. However, since those who needed the libraries have now vanished, I have stopped building 32bit versions of ENet. + +COMPILE THE CODE YOURSELF +========================= +If you don't trust the above binaries then git clone the ENET-CSharp repository (http://github.com/SoftwareGuy/ENet-CSharp) and read the readme. + +EXCLUSION INSTRUCTIONS +====================== +No need, the meta data will cover that for you. + Still don't know what to do with these? Drop by the Mirror discord and post in the Ignorance channel. \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt.meta b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt.meta index 968eafe..e6cfefe 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/Plugins/readme.txt.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: a28193472bc84d341ab4aee18c471a93 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a28193472bc84d341ab4aee18c471a93 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/version.txt b/Assets/Mirror/Runtime/Transport/Ignorance/version.txt index e14bdc8..92108c0 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/version.txt +++ b/Assets/Mirror/Runtime/Transport/Ignorance/version.txt @@ -1 +1 @@ -1.4.0b6 \ No newline at end of file +1.4.0r0 LTS \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Ignorance/version.txt.meta b/Assets/Mirror/Runtime/Transport/Ignorance/version.txt.meta index bd4c745..cbca143 100644 --- a/Assets/Mirror/Runtime/Transport/Ignorance/version.txt.meta +++ b/Assets/Mirror/Runtime/Transport/Ignorance/version.txt.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: ad80075449f17c548877161f32a9841a -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: ad80075449f17c548877161f32a9841a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/KCP.meta b/Assets/Mirror/Runtime/Transport/KCP.meta index ba9d190..4e163f7 100644 --- a/Assets/Mirror/Runtime/Transport/KCP.meta +++ b/Assets/Mirror/Runtime/Transport/KCP.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 953bb5ec5ab2346a092f58061e01ba65 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +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 index dedea2f..a1a264d 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 7bdb797750d0a490684410110bf48192 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +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 index d75db74..8d1f41e 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs @@ -1,342 +1,346 @@ -//#if MIRROR <- commented out because MIRROR isn't defined on first import yet -using System; -using System.Linq; -using System.Net; -using UnityEngine; -using Mirror; - -namespace kcp2k -{ - [DisallowMultipleComponent] - public class KcpTransport : Transport - { - // scheme used by this transport - public const string Scheme = "kcp"; - - // common - [Header("Transport Configuration")] - public ushort Port = 7777; - [Tooltip("DualMode listens to IPv6 and IPv4 simultaneously. Disable if the platform only supports IPv4.")] - public bool DualMode = true; - [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; - [Tooltip("KCP timeout in milliseconds. Note that KCP sends a ping automatically.")] - public int Timeout = 10000; - - [Header("Advanced")] - [Tooltip("KCP fastresend parameter. Faster resend for the cost of higher bandwidth. 0 in normal mode, 2 in turbo mode.")] - public int FastResend = 2; - [Tooltip("KCP congestion window. Enabled in normal mode, disabled in turbo mode. Disable this for high scale games if connections get choked regularly.")] - public bool CongestionWindow = false; // 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.")] - public uint SendWindowSize = 4096; //Kcp.WND_SND; 32 by default. Mirror sends a lot, so we need a lot more. - [Tooltip("KCP window size can be modified to support higher loads.")] - public uint ReceiveWindowSize = 4096; //Kcp.WND_RCV; 128 by default. Mirror sends a lot, so we need a lot more. - [Tooltip("Enable to use where-allocation NonAlloc KcpServer/Client/Connection versions. Highly recommended on all Unity platforms.")] - public bool NonAlloc = true; - - // server & client (where-allocation NonAlloc versions) - KcpServer server; - KcpClient client; - - // debugging - [Header("Debug")] - public bool debugLog; - // show statistics in OnGUI - public bool statisticsGUI; - // log statistics for headless servers that can't show them in GUI - public bool statisticsLog; - - void Awake() - { - // logging - // Log.Info should use Debug.Log if enabled, or nothing otherwise - // (don't want to spam the console on headless servers) - if (debugLog) - Log.Info = Debug.Log; - else - Log.Info = _ => {}; - Log.Warning = Debug.LogWarning; - Log.Error = Debug.LogError; - - // client - client = NonAlloc - ? new KcpClientNonAlloc( - () => OnClientConnected.Invoke(), - (message) => OnClientDataReceived.Invoke(message, Channels.Reliable), - () => OnClientDisconnected.Invoke()) - : new KcpClient( - () => OnClientConnected.Invoke(), - (message) => OnClientDataReceived.Invoke(message, Channels.Reliable), - () => OnClientDisconnected.Invoke()); - - // server - server = NonAlloc - ? new KcpServerNonAlloc( - (connectionId) => OnServerConnected.Invoke(connectionId), - (connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable), - (connectionId) => OnServerDisconnected.Invoke(connectionId), - DualMode, - NoDelay, - Interval, - FastResend, - CongestionWindow, - SendWindowSize, - ReceiveWindowSize, - Timeout) - : new KcpServer( - (connectionId) => OnServerConnected.Invoke(connectionId), - (connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable), - (connectionId) => OnServerDisconnected.Invoke(connectionId), - DualMode, - NoDelay, - Interval, - FastResend, - CongestionWindow, - SendWindowSize, - ReceiveWindowSize, - Timeout); - - if (statisticsLog) - InvokeRepeating(nameof(OnLogStatistics), 1, 1); - - 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, Timeout); - } - public override void ClientSend(ArraySegment segment, int channelId) - { - // switch to kcp channel. - // unreliable or reliable. - // default to reliable just to be sure. - switch (channelId) - { - case Channels.Unreliable: - client.Send(segment, KcpChannel.Unreliable); - break; - default: - client.Send(segment, KcpChannel.Reliable); - break; - } - } - public override void ClientDisconnect() => client.Disconnect(); - // process incoming in early update - public override void ClientEarlyUpdate() - { - // scene change messages disable transports to stop them from - // processing while changing the scene. - // -> we need to check enabled here - // -> and in kcp's internal loops, see Awake() OnCheckEnabled setup! - // (see also: https://github.com/vis2k/Mirror/pull/379) - if (enabled) client.TickIncoming(); - } - // process outgoing in late update - public override void ClientLateUpdate() => client.TickOutgoing(); - - // scene change message will disable transports. - // kcp processes messages in an internal loop which should be - // stopped immediately after scene change (= after disabled) - // => kcp has tests to guaranteed that calling .Pause() during the - // receive loop stops the receive loop immediately, not after. - void OnEnable() - { - // unpause when enabled again - client?.Unpause(); - server?.Unpause(); - } - - void OnDisable() - { - // pause immediately when not enabled anymore - client?.Pause(); - server?.Pause(); - } - - // 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, ArraySegment segment, int channelId) - { - // switch to kcp channel. - // unreliable or reliable. - // default to reliable just to be sure. - switch (channelId) - { - case Channels.Unreliable: - server.Send(connectionId, segment, KcpChannel.Unreliable); - break; - default: - server.Send(connectionId, segment, KcpChannel.Reliable); - break; - } - } - public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId); - public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId); - public override void ServerStop() => server.Stop(); - public override void ServerEarlyUpdate() - { - // scene change messages disable transports to stop them from - // processing while changing the scene. - // -> we need to check enabled here - // -> and in kcp's internal loops, see Awake() OnCheckEnabled setup! - // (see also: https://github.com/vis2k/Mirror/pull/379) - if (enabled) server.TickIncoming(); - } - // process outgoing in late update - public override void ServerLateUpdate() => server.TickOutgoing(); - - // common - public override void Shutdown() {} - - // max message size - public override int GetMaxPacketSize(int channelId = Channels.Reliable) - { - // switch to kcp channel. - // unreliable or reliable. - // default to reliable just to be sure. - switch (channelId) - { - case Channels.Unreliable: - return KcpConnection.UnreliableMaxMessageSize; - default: - return KcpConnection.ReliableMaxMessageSize; - } - } - - // kcp reliable channel max packet size is MTU * WND_RCV - // this allows 144kb messages. but due to head of line blocking, all - // other messages would have to wait until the maxed size one is - // delivered. batching 144kb messages each time would be EXTREMELY slow - // and fill the send queue nearly immediately when using it over the - // network. - // => instead we always use MTU sized batches. - // => people can still send maxed size if needed. - public override int GetBatchThreshold(int channelId) => - KcpConnection.UnreliableMaxMessageSize; - - // server statistics - // LONG to avoid int overflows with connections.Sum. - // see also: https://github.com/vis2k/Mirror/pull/2777 - public long GetAverageMaxSendRate() => - server.connections.Count > 0 - ? server.connections.Values.Sum(conn => (long)conn.MaxSendRate) / server.connections.Count - : 0; - public long GetAverageMaxReceiveRate() => - server.connections.Count > 0 - ? server.connections.Values.Sum(conn => (long)conn.MaxReceiveRate) / server.connections.Count - : 0; - long GetTotalSendQueue() => - server.connections.Values.Sum(conn => conn.SendQueueCount); - long GetTotalReceiveQueue() => - server.connections.Values.Sum(conn => conn.ReceiveQueueCount); - long GetTotalSendBuffer() => - server.connections.Values.Sum(conn => conn.SendBufferCount); - long GetTotalReceiveBuffer() => - server.connections.Values.Sum(conn => conn.ReceiveBufferCount); - - // PrettyBytes function from DOTSNET - // pretty prints bytes as KB/MB/GB/etc. - // long to support > 2GB - // divides by floats to return "2.5MB" etc. - public static string PrettyBytes(long bytes) - { - // bytes - if (bytes < 1024) - return $"{bytes} B"; - // kilobytes - else if (bytes < 1024L * 1024L) - return $"{(bytes / 1024f):F2} KB"; - // megabytes - else if (bytes < 1024 * 1024L * 1024L) - return $"{(bytes / (1024f * 1024f)):F2} MB"; - // gigabytes - return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB"; - } - - void OnGUI() - { - if (!statisticsGUI) return; - - GUILayout.BeginArea(new Rect(5, 110, 300, 300)); - - if (ServerActive()) - { - GUILayout.BeginVertical("Box"); - GUILayout.Label("SERVER"); - GUILayout.Label($" connections: {server.connections.Count}"); - GUILayout.Label($" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s"); - GUILayout.Label($" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s"); - 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($" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s"); - GUILayout.Label($" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s"); - GUILayout.Label($" SendQueue: {client.connection.SendQueueCount}"); - GUILayout.Label($" ReceiveQueue: {client.connection.ReceiveQueueCount}"); - GUILayout.Label($" SendBuffer: {client.connection.SendBufferCount}"); - GUILayout.Label($" ReceiveBuffer: {client.connection.ReceiveBufferCount}"); - GUILayout.EndVertical(); - } - - GUILayout.EndArea(); - } - - void OnLogStatistics() - { - if (ServerActive()) - { - string log = "kcp SERVER @ time: " + NetworkTime.localTime + "\n"; - log += $" connections: {server.connections.Count}\n"; - log += $" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s\n"; - log += $" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s\n"; - log += $" SendQueue: {GetTotalSendQueue()}\n"; - log += $" ReceiveQueue: {GetTotalReceiveQueue()}\n"; - log += $" SendBuffer: {GetTotalSendBuffer()}\n"; - log += $" ReceiveBuffer: {GetTotalReceiveBuffer()}\n\n"; - Debug.Log(log); - } - - if (ClientConnected()) - { - string log = "kcp CLIENT @ time: " + NetworkTime.localTime + "\n"; - log += $" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s\n"; - log += $" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s\n"; - log += $" SendQueue: {client.connection.SendQueueCount}\n"; - log += $" ReceiveQueue: {client.connection.ReceiveQueueCount}\n"; - log += $" SendBuffer: {client.connection.SendBufferCount}\n"; - log += $" ReceiveBuffer: {client.connection.ReceiveBufferCount}\n\n"; - Debug.Log(log); - } - } - - public override string ToString() => "KCP"; - } -} -//#endif MIRROR <- commented out because MIRROR isn't defined on first import yet +//#if MIRROR <- commented out because MIRROR isn't defined on first import yet +using System; +using System.Linq; +using System.Net; +using UnityEngine; +using Mirror; + +namespace kcp2k +{ + [HelpURL("https://mirror-networking.gitbook.io/docs/transports/kcp-transport")] + [DisallowMultipleComponent] + public class KcpTransport : Transport + { + // scheme used by this transport + public const string Scheme = "kcp"; + + // common + [Header("Transport Configuration")] + public ushort Port = 7777; + [Tooltip("DualMode listens to IPv6 and IPv4 simultaneously. Disable if the platform only supports IPv4.")] + public bool DualMode = true; + [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; + [Tooltip("KCP timeout in milliseconds. Note that KCP sends a ping automatically.")] + public int Timeout = 10000; + + [Header("Advanced")] + [Tooltip("KCP fastresend parameter. Faster resend for the cost of higher bandwidth. 0 in normal mode, 2 in turbo mode.")] + public int FastResend = 2; + [Tooltip("KCP congestion window. Enabled in normal mode, disabled in turbo mode. Disable this for high scale games if connections get choked regularly.")] + public bool CongestionWindow = false; // 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.")] + public uint SendWindowSize = 4096; //Kcp.WND_SND; 32 by default. Mirror sends a lot, so we need a lot more. + [Tooltip("KCP window size can be modified to support higher loads.")] + public uint ReceiveWindowSize = 4096; //Kcp.WND_RCV; 128 by default. Mirror sends a lot, so we need a lot more. + [Tooltip("Enable to use where-allocation NonAlloc KcpServer/Client/Connection versions. Highly recommended on all Unity platforms.")] + public bool NonAlloc = true; + + // server & client (where-allocation NonAlloc versions) + KcpServer server; + KcpClient client; + + // debugging + [Header("Debug")] + public bool debugLog; + // show statistics in OnGUI + public bool statisticsGUI; + // log statistics for headless servers that can't show them in GUI + public bool statisticsLog; + + void Awake() + { + // logging + // Log.Info should use Debug.Log if enabled, or nothing otherwise + // (don't want to spam the console on headless servers) + if (debugLog) + Log.Info = Debug.Log; + else + Log.Info = _ => {}; + Log.Warning = Debug.LogWarning; + Log.Error = Debug.LogError; + + // client + client = NonAlloc + ? new KcpClientNonAlloc( + () => OnClientConnected.Invoke(), + (message) => OnClientDataReceived.Invoke(message, Channels.Reliable), + () => OnClientDisconnected.Invoke()) + : new KcpClient( + () => OnClientConnected.Invoke(), + (message) => OnClientDataReceived.Invoke(message, Channels.Reliable), + () => OnClientDisconnected.Invoke()); + + // server + server = NonAlloc + ? new KcpServerNonAlloc( + (connectionId) => OnServerConnected.Invoke(connectionId), + (connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable), + (connectionId) => OnServerDisconnected.Invoke(connectionId), + DualMode, + NoDelay, + Interval, + FastResend, + CongestionWindow, + SendWindowSize, + ReceiveWindowSize, + Timeout) + : new KcpServer( + (connectionId) => OnServerConnected.Invoke(connectionId), + (connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable), + (connectionId) => OnServerDisconnected.Invoke(connectionId), + DualMode, + NoDelay, + Interval, + FastResend, + CongestionWindow, + SendWindowSize, + ReceiveWindowSize, + Timeout); + + if (statisticsLog) + InvokeRepeating(nameof(OnLogStatistics), 1, 1); + + 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, Timeout); + } + public override void ClientSend(ArraySegment segment, int channelId) + { + // switch to kcp channel. + // unreliable or reliable. + // default to reliable just to be sure. + switch (channelId) + { + case Channels.Unreliable: + client.Send(segment, KcpChannel.Unreliable); + break; + default: + client.Send(segment, KcpChannel.Reliable); + break; + } + } + public override void ClientDisconnect() => client.Disconnect(); + // process incoming in early update + public override void ClientEarlyUpdate() + { + // scene change messages disable transports to stop them from + // processing while changing the scene. + // -> we need to check enabled here + // -> and in kcp's internal loops, see Awake() OnCheckEnabled setup! + // (see also: https://github.com/vis2k/Mirror/pull/379) + if (enabled) client.TickIncoming(); + } + // process outgoing in late update + public override void ClientLateUpdate() => client.TickOutgoing(); + + // scene change message will disable transports. + // kcp processes messages in an internal loop which should be + // stopped immediately after scene change (= after disabled) + // => kcp has tests to guaranteed that calling .Pause() during the + // receive loop stops the receive loop immediately, not after. + void OnEnable() + { + // unpause when enabled again + client?.Unpause(); + server?.Unpause(); + } + + void OnDisable() + { + // pause immediately when not enabled anymore + client?.Pause(); + server?.Pause(); + } + + // 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, ArraySegment segment, int channelId) + { + // switch to kcp channel. + // unreliable or reliable. + // default to reliable just to be sure. + switch (channelId) + { + case Channels.Unreliable: + server.Send(connectionId, segment, KcpChannel.Unreliable); + break; + default: + server.Send(connectionId, segment, KcpChannel.Reliable); + break; + } + } + public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId); + public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId); + public override void ServerStop() => server.Stop(); + public override void ServerEarlyUpdate() + { + // scene change messages disable transports to stop them from + // processing while changing the scene. + // -> we need to check enabled here + // -> and in kcp's internal loops, see Awake() OnCheckEnabled setup! + // (see also: https://github.com/vis2k/Mirror/pull/379) + if (enabled) server.TickIncoming(); + } + // process outgoing in late update + public override void ServerLateUpdate() => server.TickOutgoing(); + + // common + public override void Shutdown() {} + + // max message size + public override int GetMaxPacketSize(int channelId = Channels.Reliable) + { + // switch to kcp channel. + // unreliable or reliable. + // default to reliable just to be sure. + switch (channelId) + { + case Channels.Unreliable: + return KcpConnection.UnreliableMaxMessageSize; + default: + return KcpConnection.ReliableMaxMessageSize; + } + } + + // kcp reliable channel max packet size is MTU * WND_RCV + // this allows 144kb messages. but due to head of line blocking, all + // other messages would have to wait until the maxed size one is + // delivered. batching 144kb messages each time would be EXTREMELY slow + // and fill the send queue nearly immediately when using it over the + // network. + // => instead we always use MTU sized batches. + // => people can still send maxed size if needed. + public override int GetBatchThreshold(int channelId) => + KcpConnection.UnreliableMaxMessageSize; + + // server statistics + // LONG to avoid int overflows with connections.Sum. + // see also: https://github.com/vis2k/Mirror/pull/2777 + public long GetAverageMaxSendRate() => + server.connections.Count > 0 + ? server.connections.Values.Sum(conn => (long)conn.MaxSendRate) / server.connections.Count + : 0; + public long GetAverageMaxReceiveRate() => + server.connections.Count > 0 + ? server.connections.Values.Sum(conn => (long)conn.MaxReceiveRate) / server.connections.Count + : 0; + long GetTotalSendQueue() => + server.connections.Values.Sum(conn => conn.SendQueueCount); + long GetTotalReceiveQueue() => + server.connections.Values.Sum(conn => conn.ReceiveQueueCount); + long GetTotalSendBuffer() => + server.connections.Values.Sum(conn => conn.SendBufferCount); + long GetTotalReceiveBuffer() => + server.connections.Values.Sum(conn => conn.ReceiveBufferCount); + + // PrettyBytes function from DOTSNET + // pretty prints bytes as KB/MB/GB/etc. + // long to support > 2GB + // divides by floats to return "2.5MB" etc. + public static string PrettyBytes(long bytes) + { + // bytes + if (bytes < 1024) + return $"{bytes} B"; + // kilobytes + else if (bytes < 1024L * 1024L) + return $"{(bytes / 1024f):F2} KB"; + // megabytes + else if (bytes < 1024 * 1024L * 1024L) + return $"{(bytes / (1024f * 1024f)):F2} MB"; + // gigabytes + return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB"; + } + +// OnGUI allocates even if it does nothing. avoid in release. +#if UNITY_EDITOR || DEVELOPMENT_BUILD + void OnGUI() + { + if (!statisticsGUI) return; + + GUILayout.BeginArea(new Rect(5, 110, 300, 300)); + + if (ServerActive()) + { + GUILayout.BeginVertical("Box"); + GUILayout.Label("SERVER"); + GUILayout.Label($" connections: {server.connections.Count}"); + GUILayout.Label($" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s"); + GUILayout.Label($" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s"); + 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($" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s"); + GUILayout.Label($" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s"); + GUILayout.Label($" SendQueue: {client.connection.SendQueueCount}"); + GUILayout.Label($" ReceiveQueue: {client.connection.ReceiveQueueCount}"); + GUILayout.Label($" SendBuffer: {client.connection.SendBufferCount}"); + GUILayout.Label($" ReceiveBuffer: {client.connection.ReceiveBufferCount}"); + GUILayout.EndVertical(); + } + + GUILayout.EndArea(); + } +#endif + + void OnLogStatistics() + { + if (ServerActive()) + { + string log = "kcp SERVER @ time: " + NetworkTime.localTime + "\n"; + log += $" connections: {server.connections.Count}\n"; + log += $" MaxSendRate (avg): {PrettyBytes(GetAverageMaxSendRate())}/s\n"; + log += $" MaxRecvRate (avg): {PrettyBytes(GetAverageMaxReceiveRate())}/s\n"; + log += $" SendQueue: {GetTotalSendQueue()}\n"; + log += $" ReceiveQueue: {GetTotalReceiveQueue()}\n"; + log += $" SendBuffer: {GetTotalSendBuffer()}\n"; + log += $" ReceiveBuffer: {GetTotalReceiveBuffer()}\n\n"; + Debug.Log(log); + } + + if (ClientConnected()) + { + string log = "kcp CLIENT @ time: " + NetworkTime.localTime + "\n"; + log += $" MaxSendRate: {PrettyBytes(client.connection.MaxSendRate)}/s\n"; + log += $" MaxRecvRate: {PrettyBytes(client.connection.MaxReceiveRate)}/s\n"; + log += $" SendQueue: {client.connection.SendQueueCount}\n"; + log += $" ReceiveQueue: {client.connection.ReceiveQueueCount}\n"; + log += $" SendBuffer: {client.connection.SendBufferCount}\n"; + log += $" ReceiveBuffer: {client.connection.ReceiveBufferCount}\n\n"; + Debug.Log(log); + } + } + + public override string ToString() => "KCP"; + } +} +//#endif MIRROR <- commented out because MIRROR isn't defined on first import yet diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta index f7280c8..d23997a 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 6b0fecffa3f624585964b0d0eb21b18e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +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 index 1dceadf..e982825 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 71a1c8e8c022d4731a481c1808f37e5d -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +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 index c77582e..2713574 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE @@ -1,24 +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 +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 index 49dc767..3c55a17 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 9a3e8369060cf4e94ac117603de47aa6 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +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 index b8de4bc..67bac88 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION @@ -1,94 +1,94 @@ -V1.12 [2021-07-16] -- where-allocation removed. will be optional in the future. -- Tests: don't depend on Unity anymore -- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls - OnDisconnected to let the user now. -- fix: KcpServer.DualMode is now configurable in the constructor instead of - using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too. - -V1.11 rollback [2021-06-01] -- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime - resizing/allocations - -V1.10 [2021-05-28] -- feature: configurable Timeout -- allocations explained with comments (C# ReceiveFrom / IPEndPoint.GetHashCode) -- fix: #17 KcpConnection.ReceiveNextReliable now assigns message default so it - works in .net too -- fix: Segment pool is not static anymore. Each kcp instance now has it's own - Pool. fixes #18 concurrency issues - -V1.9 [2021-03-02] -- Tick() split into TickIncoming()/TickOutgoing() to use in Mirror's new update - functions. allows to minimize latency. - => original Tick() is still supported for convenience. simply processes both! - -V1.8 [2021-02-14] -- fix: Unity IPv6 errors on Nintendo Switch -- fix: KcpConnection now disconnects if data message was received without content. - previously it would call OnData with an empty ArraySegment, causing all kinds of - weird behaviour in Mirror/DOTSNET. Added tests too. -- fix: KcpConnection.SendData: don't allow sending empty messages anymore. disconnect - and log a warning to make it completely obvious. - -V1.7 [2021-01-13] -- fix: unreliable messages reset timeout now too -- perf: KcpConnection OnCheckEnabled callback changed to a simple 'paused' boolean. - This is faster than invoking a Func every time and allows us to fix #8 more - easily later by calling .Pause/.Unpause from OnEnable/OnDisable in MirrorTransport. -- fix #8: Unpause now resets timeout to fix a bug where Mirror would pause kcp, - change the scene which took >10s, then unpause and kcp would detect the lack of - any messages for >10s as timeout. Added test to make sure it never happens again. -- MirrorTransport: statistics logging for headless servers -- Mirror Transport: Send/Receive window size increased once more from 2048 to 4096. - -V1.6 [2021-01-10] -- Unreliable channel added! -- perf: KcpHeader byte added to every kcp message to indicate - Handshake/Data/Ping/Disconnect instead of scanning each message for Hello/Byte/Ping - content via SegmentEquals. It's a lot cleaner, should be faster and should avoid - edge cases where a message content would equal Hello/Ping/Bye sequence accidentally. -- Kcp.Input: offset moved to parameters for cases where it's needed -- Kcp.SetMtu from original Kcp.c - -V1.5 [2021-01-07] -- KcpConnection.MaxSend/ReceiveRate calculation based on the article -- MirrorTransport: large send/recv window size defaults to avoid high latencies caused - by packets not being processed fast enough -- MirrorTransport: show MaxSend/ReceiveRate in debug gui -- MirrorTransport: don't Log.Info to console in headless mode if debug log is disabled - -V1.4 [2020-11-27] -- fix: OnCheckEnabled added. KcpConnection message processing while loop can now - be interrupted immediately. fixes Mirror Transport scene changes which need to stop - processing any messages immediately after a scene message) -- perf: Mirror KcpTransport: FastResend enabled by default. turbo mode according to: - https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration -- perf: Mirror KcpTransport: CongestionControl disabled by default (turbo mode) - -V1.3 [2020-11-17] -- Log.Info/Warning/Error so logging doesn't depend on UnityEngine anymore -- fix: Server.Tick catches SocketException which happens if Android client is killed -- MirrorTransport: debugLog option added that can be checked in Unity Inspector -- Utils.Clamp so Kcp.cs doesn't depend on UnityEngine -- Utils.SegmentsEqual: use Linq SequenceEqual so it doesn't depend on UnityEngine -=> kcp2k can now be used in any C# project even without Unity - -V1.2 [2020-11-10] -- more tests added -- fix: raw receive buffers are now all of MTU size -- fix: raw receive detects error where buffer was too small for msgLength and - result in excess data being dropped silently -- KcpConnection.MaxMessageSize added for use in high level -- KcpConnection.MaxMessageSize increased from 1200 bytes to to maximum allowed - message size of 145KB for kcp (based on mtu, overhead, wnd_rcv) - -V1.1 [2020-10-30] -- high level cleanup, fixes, improvements - -V1.0 [2020-10-22] -- Kcp.cs now mirrors original Kcp.c behaviour - (this fixes dozens of bugs) - -V0.1 +V1.12 [2021-07-16] +- where-allocation removed. will be optional in the future. +- Tests: don't depend on Unity anymore +- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls + OnDisconnected to let the user now. +- fix: KcpServer.DualMode is now configurable in the constructor instead of + using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too. + +V1.11 rollback [2021-06-01] +- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime + resizing/allocations + +V1.10 [2021-05-28] +- feature: configurable Timeout +- allocations explained with comments (C# ReceiveFrom / IPEndPoint.GetHashCode) +- fix: #17 KcpConnection.ReceiveNextReliable now assigns message default so it + works in .net too +- fix: Segment pool is not static anymore. Each kcp instance now has it's own + Pool. fixes #18 concurrency issues + +V1.9 [2021-03-02] +- Tick() split into TickIncoming()/TickOutgoing() to use in Mirror's new update + functions. allows to minimize latency. + => original Tick() is still supported for convenience. simply processes both! + +V1.8 [2021-02-14] +- fix: Unity IPv6 errors on Nintendo Switch +- fix: KcpConnection now disconnects if data message was received without content. + previously it would call OnData with an empty ArraySegment, causing all kinds of + weird behaviour in Mirror/DOTSNET. Added tests too. +- fix: KcpConnection.SendData: don't allow sending empty messages anymore. disconnect + and log a warning to make it completely obvious. + +V1.7 [2021-01-13] +- fix: unreliable messages reset timeout now too +- perf: KcpConnection OnCheckEnabled callback changed to a simple 'paused' boolean. + This is faster than invoking a Func every time and allows us to fix #8 more + easily later by calling .Pause/.Unpause from OnEnable/OnDisable in MirrorTransport. +- fix #8: Unpause now resets timeout to fix a bug where Mirror would pause kcp, + change the scene which took >10s, then unpause and kcp would detect the lack of + any messages for >10s as timeout. Added test to make sure it never happens again. +- MirrorTransport: statistics logging for headless servers +- Mirror Transport: Send/Receive window size increased once more from 2048 to 4096. + +V1.6 [2021-01-10] +- Unreliable channel added! +- perf: KcpHeader byte added to every kcp message to indicate + Handshake/Data/Ping/Disconnect instead of scanning each message for Hello/Byte/Ping + content via SegmentEquals. It's a lot cleaner, should be faster and should avoid + edge cases where a message content would equal Hello/Ping/Bye sequence accidentally. +- Kcp.Input: offset moved to parameters for cases where it's needed +- Kcp.SetMtu from original Kcp.c + +V1.5 [2021-01-07] +- KcpConnection.MaxSend/ReceiveRate calculation based on the article +- MirrorTransport: large send/recv window size defaults to avoid high latencies caused + by packets not being processed fast enough +- MirrorTransport: show MaxSend/ReceiveRate in debug gui +- MirrorTransport: don't Log.Info to console in headless mode if debug log is disabled + +V1.4 [2020-11-27] +- fix: OnCheckEnabled added. KcpConnection message processing while loop can now + be interrupted immediately. fixes Mirror Transport scene changes which need to stop + processing any messages immediately after a scene message) +- perf: Mirror KcpTransport: FastResend enabled by default. turbo mode according to: + https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration +- perf: Mirror KcpTransport: CongestionControl disabled by default (turbo mode) + +V1.3 [2020-11-17] +- Log.Info/Warning/Error so logging doesn't depend on UnityEngine anymore +- fix: Server.Tick catches SocketException which happens if Android client is killed +- MirrorTransport: debugLog option added that can be checked in Unity Inspector +- Utils.Clamp so Kcp.cs doesn't depend on UnityEngine +- Utils.SegmentsEqual: use Linq SequenceEqual so it doesn't depend on UnityEngine +=> kcp2k can now be used in any C# project even without Unity + +V1.2 [2020-11-10] +- more tests added +- fix: raw receive buffers are now all of MTU size +- fix: raw receive detects error where buffer was too small for msgLength and + result in excess data being dropped silently +- KcpConnection.MaxMessageSize added for use in high level +- KcpConnection.MaxMessageSize increased from 1200 bytes to to maximum allowed + message size of 145KB for kcp (based on mtu, overhead, wnd_rcv) + +V1.1 [2020-10-30] +- high level cleanup, fixes, improvements + +V1.0 [2020-10-22] +- 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 index 2a07daa..d242f43 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: ed3f2cf1bbf1b4d53a6f2c103d311f71 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +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 index 1c11c3d..817b9ea 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 5a54d18b954cb4407a28b633fc32ea6d -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 5a54d18b954cb4407a28b633fc32ea6d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs index ccb19ba..1c55a16 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs @@ -1,10 +1,10 @@ -namespace kcp2k -{ - // channel type and header for raw messages - public enum KcpChannel : byte - { - // don't react on 0x00. might help to filter out random noise. - Reliable = 0x01, - Unreliable = 0x02 - } +namespace kcp2k +{ + // channel type and header for raw messages + public enum KcpChannel : byte + { + // don't react on 0x00. might help to filter out random noise. + Reliable = 0x01, + Unreliable = 0x02 + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs.meta index 2721025..76be5dc 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 9e852b2532fb248d19715cfebe371db3 -timeCreated: 1610081248 \ No newline at end of file +fileFormatVersion: 2 +guid: 9e852b2532fb248d19715cfebe371db3 +timeCreated: 1610081248 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs index 64a005a..aad5942 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs @@ -1,120 +1,120 @@ -// kcp client logic abstracted into a class. -// for use in Mirror, DOTSNET, testing, etc. -using System; - -namespace kcp2k -{ - public class KcpClient - { - // events - public Action OnConnected; - public Action> 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; - } - - // CreateConnection can be overwritten for where-allocation: - // https://github.com/vis2k/where-allocation - protected virtual KcpClientConnection CreateConnection() => - new KcpClientConnection(); - - public void Connect(string address, ushort port, bool noDelay, uint interval, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = KcpConnection.DEFAULT_TIMEOUT) - { - if (connected) - { - Log.Warning("KCP: client already connected!"); - return; - } - - // create connection - connection = CreateConnection(); - - // setup events - connection.OnAuthenticated = () => - { - Log.Info($"KCP: OnClientConnected"); - connected = true; - OnConnected.Invoke(); - }; - connection.OnData = (message) => - { - //Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})"); - OnData.Invoke(message); - }; - connection.OnDisconnected = () => - { - Log.Info($"KCP: OnClientDisconnected"); - connected = false; - connection = null; - OnDisconnected.Invoke(); - }; - - // connect - connection.Connect(address, port, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); - } - - public void Send(ArraySegment segment, KcpChannel channel) - { - if (connected) - { - connection.SendData(segment, channel); - } - else Log.Warning("KCP: can't send because client not connected!"); - } - - public void Disconnect() - { - // only if connected - // otherwise we end up in a deadlock because of an open Mirror bug: - // https://github.com/vis2k/Mirror/issues/2353 - if (connected) - { - // call Disconnect and let the connection handle it. - // DO NOT set it to null yet. it needs to be updated a few more - // times first. let the connection handle it! - connection?.Disconnect(); - } - } - - // process incoming messages. should be called before updating the world. - public void TickIncoming() - { - // recv on socket first, then process incoming - // (even if we didn't receive anything. need to tick ping etc.) - // (connection is null if not active) - connection?.RawReceive(); - connection?.TickIncoming(); - } - - // process outgoing messages. should be called after updating the world. - public void TickOutgoing() - { - // process outgoing - // (connection is null if not active) - connection?.TickOutgoing(); - } - - // process incoming and outgoing for convenience - // => ideally call ProcessIncoming() before updating the world and - // ProcessOutgoing() after updating the world for minimum latency - public void Tick() - { - TickIncoming(); - TickOutgoing(); - } - - // pause/unpause to safely support mirror scene handling and to - // immediately pause the receive while loop if needed. - public void Pause() => connection?.Pause(); - public void Unpause() => connection?.Unpause(); - } -} +// kcp client logic abstracted into a class. +// for use in Mirror, DOTSNET, testing, etc. +using System; + +namespace kcp2k +{ + public class KcpClient + { + // events + public Action OnConnected; + public Action> 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; + } + + // CreateConnection can be overwritten for where-allocation: + // https://github.com/vis2k/where-allocation + protected virtual KcpClientConnection CreateConnection() => + new KcpClientConnection(); + + public void Connect(string address, ushort port, bool noDelay, uint interval, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = KcpConnection.DEFAULT_TIMEOUT) + { + if (connected) + { + Log.Warning("KCP: client already connected!"); + return; + } + + // create connection + connection = CreateConnection(); + + // setup events + connection.OnAuthenticated = () => + { + Log.Info($"KCP: OnClientConnected"); + connected = true; + OnConnected.Invoke(); + }; + connection.OnData = (message) => + { + //Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})"); + OnData.Invoke(message); + }; + connection.OnDisconnected = () => + { + Log.Info($"KCP: OnClientDisconnected"); + connected = false; + connection = null; + OnDisconnected.Invoke(); + }; + + // connect + connection.Connect(address, port, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); + } + + public void Send(ArraySegment segment, KcpChannel channel) + { + if (connected) + { + connection.SendData(segment, channel); + } + else Log.Warning("KCP: can't send because client not connected!"); + } + + public void Disconnect() + { + // only if connected + // otherwise we end up in a deadlock because of an open Mirror bug: + // https://github.com/vis2k/Mirror/issues/2353 + if (connected) + { + // call Disconnect and let the connection handle it. + // DO NOT set it to null yet. it needs to be updated a few more + // times first. let the connection handle it! + connection?.Disconnect(); + } + } + + // process incoming messages. should be called before updating the world. + public void TickIncoming() + { + // recv on socket first, then process incoming + // (even if we didn't receive anything. need to tick ping etc.) + // (connection is null if not active) + connection?.RawReceive(); + connection?.TickIncoming(); + } + + // process outgoing messages. should be called after updating the world. + public void TickOutgoing() + { + // process outgoing + // (connection is null if not active) + connection?.TickOutgoing(); + } + + // process incoming and outgoing for convenience + // => ideally call ProcessIncoming() before updating the world and + // ProcessOutgoing() after updating the world for minimum latency + public void Tick() + { + TickIncoming(); + TickOutgoing(); + } + + // pause/unpause to safely support mirror scene handling and to + // immediately pause the receive while loop if needed. + public void Pause() => connection?.Pause(); + public void Unpause() => connection?.Unpause(); + } +} diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta index e55306b..f5b7894 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 6aa069a28ed24fedb533c102d9742b36 -timeCreated: 1603786960 \ No newline at end of file +fileFormatVersion: 2 +guid: 6aa069a28ed24fedb533c102d9742b36 +timeCreated: 1603786960 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs index 9bde4af..05d5730 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs @@ -1,109 +1,109 @@ -using System.Net; -using System.Net.Sockets; - -namespace kcp2k -{ - public class KcpClientConnection : KcpConnection - { - // IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even - // if MaxMessageSize is larger. kcp always sends in MTU - // segments and having a buffer smaller than MTU would - // silently drop excess data. - // => we need the MTU to fit channel + message! - readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF]; - - // helper function to resolve host to IPAddress - public static bool ResolveHostname(string hostname, out IPAddress[] addresses) - { - try - { - addresses = Dns.GetHostAddresses(hostname); - return addresses.Length >= 1; - } - catch (SocketException) - { - Log.Info($"Failed to resolve host: {hostname}"); - addresses = null; - return false; - } - } - - // EndPoint & Receive functions can be overwritten for where-allocation: - // https://github.com/vis2k/where-allocation - // NOTE: Client's SendTo doesn't allocate, don't need a virtual. - protected virtual void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) => - remoteEndPoint = new IPEndPoint(addresses[0], port); - - protected virtual int ReceiveFrom(byte[] buffer) => - socket.ReceiveFrom(buffer, ref remoteEndPoint); - - 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, int timeout = DEFAULT_TIMEOUT) - { - Log.Info($"KcpClient: connect to {host}:{port}"); - - // try resolve host name - if (ResolveHostname(host, out IPAddress[] addresses)) - { - // create remote endpoint - CreateRemoteEndPoint(addresses, port); - - // create socket - socket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); - socket.Connect(remoteEndPoint); - - // set up kcp - SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); - - // client should send handshake to server as very first message - SendHandshake(); - - RawReceive(); - } - // otherwise call OnDisconnected to let the user know. - else OnDisconnected(); - } - - - // call from transport update - public void RawReceive() - { - try - { - if (socket != null) - { - while (socket.Poll(0, SelectMode.SelectRead)) - { - int msgLength = ReceiveFrom(rawReceiveBuffer); - // IMPORTANT: detect if buffer was too small for the - // received msgLength. otherwise the excess - // data would be silently lost. - // (see ReceiveFrom documentation) - if (msgLength <= rawReceiveBuffer.Length) - { - //Log.Debug($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); - RawInput(rawReceiveBuffer, msgLength); - } - else - { - Log.Error($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); - Disconnect(); - } - } - } - } - // 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); - } - } -} +using System.Net; +using System.Net.Sockets; + +namespace kcp2k +{ + public class KcpClientConnection : KcpConnection + { + // IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even + // if MaxMessageSize is larger. kcp always sends in MTU + // segments and having a buffer smaller than MTU would + // silently drop excess data. + // => we need the MTU to fit channel + message! + readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF]; + + // helper function to resolve host to IPAddress + public static bool ResolveHostname(string hostname, out IPAddress[] addresses) + { + try + { + addresses = Dns.GetHostAddresses(hostname); + return addresses.Length >= 1; + } + catch (SocketException) + { + Log.Info($"Failed to resolve host: {hostname}"); + addresses = null; + return false; + } + } + + // EndPoint & Receive functions can be overwritten for where-allocation: + // https://github.com/vis2k/where-allocation + // NOTE: Client's SendTo doesn't allocate, don't need a virtual. + protected virtual void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) => + remoteEndPoint = new IPEndPoint(addresses[0], port); + + protected virtual int ReceiveFrom(byte[] buffer) => + socket.ReceiveFrom(buffer, ref remoteEndPoint); + + 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, int timeout = DEFAULT_TIMEOUT) + { + Log.Info($"KcpClient: connect to {host}:{port}"); + + // try resolve host name + if (ResolveHostname(host, out IPAddress[] addresses)) + { + // create remote endpoint + CreateRemoteEndPoint(addresses, port); + + // create socket + socket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + socket.Connect(remoteEndPoint); + + // set up kcp + SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); + + // client should send handshake to server as very first message + SendHandshake(); + + RawReceive(); + } + // otherwise call OnDisconnected to let the user know. + else OnDisconnected(); + } + + + // call from transport update + public void RawReceive() + { + try + { + if (socket != null) + { + while (socket.Poll(0, SelectMode.SelectRead)) + { + int msgLength = ReceiveFrom(rawReceiveBuffer); + // IMPORTANT: detect if buffer was too small for the + // received msgLength. otherwise the excess + // data would be silently lost. + // (see ReceiveFrom documentation) + if (msgLength <= rawReceiveBuffer.Length) + { + //Log.Debug($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); + RawInput(rawReceiveBuffer, msgLength); + } + else + { + Log.Error($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); + Disconnect(); + } + } + } + } + // 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 index 3369918..884c93a 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 96512e74aa8214a6faa8a412a7a07877 -timeCreated: 1602601237 \ No newline at end of file +fileFormatVersion: 2 +guid: 96512e74aa8214a6faa8a412a7a07877 +timeCreated: 1602601237 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs index ecfe562..67c8f3f 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs @@ -1,674 +1,674 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Net.Sockets; - -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; - - // Mirror needs a way to stop the kcp message processing while loop - // immediately after a scene change message. Mirror can't process any - // other messages during a scene change. - // (could be useful for others too) - bool paused; - - // If we don't receive anything these many milliseconds - // then consider us disconnected - public const int DEFAULT_TIMEOUT = 10000; - public int timeout = DEFAULT_TIMEOUT; - 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(); - - // we need to subtract the channel byte from every MaxMessageSize - // calculation. - // we also need to tell kcp to use MTU-1 to leave space for the byte. - const int CHANNEL_HEADER_SIZE = 1; - - // reliable channel (= kcp) MaxMessageSize so the outside knows largest - // allowed message to send the calculation in Send() is not obvious at - // all, so let's provide the helper here. - // - // kcp does fragmentation, so max message is way larger than MTU. - // - // -> runtime MTU changes are disabled: mss is always MTU_DEF-OVERHEAD - // -> Send() checks if fragment count < WND_RCV, so we use WND_RCV - 1. - // note that Send() checks WND_RCV instead of wnd_rcv which may or - // may not be a bug in original kcp. but since it uses the define, we - // can use that here too. - // -> we add 1 byte KcpHeader enum to each message, so -1 - // - // IMPORTANT: max message is MTU * WND_RCV, in other words it completely - // fills the receive window! due to head of line blocking, - // all other messages have to wait while a maxed size message - // is being delivered. - // => in other words, DO NOT use max size all the time like - // for batching. - // => sending UNRELIABLE max message size most of the time is - // best for performance (use that one for batching!) - public const int ReliableMaxMessageSize = (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * (Kcp.WND_RCV - 1) - 1; - - // unreliable max message size is simply MTU - channel header size - public const int UnreliableMaxMessageSize = Kcp.MTU_DEF - CHANNEL_HEADER_SIZE; - - // buffer to receive kcp's processed messages (avoids allocations). - // IMPORTANT: this is for KCP messages. so it needs to be of size: - // 1 byte header + MaxMessageSize content - byte[] kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize]; - - // send buffer for handing user messages to kcp for processing. - // (avoids allocations). - // IMPORTANT: needs to be of size: - // 1 byte header + MaxMessageSize content - byte[] kcpSendBuffer = new byte[1 + ReliableMaxMessageSize]; - - // raw send buffer is exactly MTU. - byte[] rawSendBuffer = new byte[Kcp.MTU_DEF]; - - // 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; - - // getters for queue and buffer counts, used for debug info - public int SendQueueCount => kcp.snd_queue.Count; - public int ReceiveQueueCount => kcp.rcv_queue.Count; - public int SendBufferCount => kcp.snd_buf.Count; - public int ReceiveBufferCount => kcp.rcv_buf.Count; - - // maximum send rate per second can be calculated from kcp parameters - // source: https://translate.google.com/translate?sl=auto&tl=en&u=https://wetest.qq.com/lab/view/391.html - // - // KCP can send/receive a maximum of WND*MTU per interval. - // multiple by 1000ms / interval to get the per-second rate. - // - // example: - // WND(32) * MTU(1400) = 43.75KB - // => 43.75KB * 1000 / INTERVAL(10) = 4375KB/s - // - // returns bytes/second! - public uint MaxSendRate => - kcp.snd_wnd * kcp.mtu * 1000 / kcp.interval; - - public uint MaxReceiveRate => - kcp.rcv_wnd * kcp.mtu * 1000 / kcp.interval; - - // SetupKcp creates and configures a new KCP instance. - // => useful to start from a fresh state every time the client connects - // => NoDelay, interval, wnd 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, int timeout = DEFAULT_TIMEOUT) - { - // set up kcp over reliable channel (that's what kcp is for) - kcp = new Kcp(0, RawSendReliable); - // 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); - - // IMPORTANT: high level needs to add 1 channel byte to each raw - // message. so while Kcp.MTU_DEF is perfect, we actually need to - // tell kcp to use MTU-1 so we can still put the header into the - // message afterwards. - kcp.SetMtu(Kcp.MTU_DEF - CHANNEL_HEADER_SIZE); - - this.timeout = timeout; - state = KcpState.Connected; - - refTime.Start(); - } - - 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) - { - Log.Warning($"KCP: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); - Disconnect(); - } - } - - void HandleDeadLink() - { - // kcp has 'dead_link' detection. might as well use it. - if (kcp.state == -1) - { - Log.Warning("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 - //Log.Debug("KCP: sending ping..."); - SendPing(); - lastPingTime = time; - } - } - - void HandleChoked() - { - // disconnect connections that can't process the load. - // see QueueSizeDisconnect comments. - // => include all of kcp's buffers and the unreliable queue! - int total = kcp.rcv_queue.Count + kcp.snd_queue.Count + - kcp.rcv_buf.Count + kcp.snd_buf.Count; - if (total >= QueueDisconnectThreshold) - { - Log.Warning($"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 reliable message type & content from kcp. - // -> to avoid buffering, unreliable messages call OnData directly. - bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) - { - int msgSize = kcp.PeekSize(); - if (msgSize > 0) - { - // only allow receiving up to buffer sized messages. - // otherwise we would get BlockCopy ArgumentException anyway. - if (msgSize <= kcpMessageBuffer.Length) - { - // receive from kcp - int received = kcp.Receive(kcpMessageBuffer, msgSize); - if (received >= 0) - { - // extract header & content without header - header = (KcpHeader)kcpMessageBuffer[0]; - message = new ArraySegment(kcpMessageBuffer, 1, msgSize - 1); - lastReceiveTime = (uint)refTime.ElapsedMilliseconds; - return true; - } - else - { - // if receive failed, close everything - Log.Warning($"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 - { - Log.Warning($"KCP: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); - Disconnect(); - } - } - - message = default; - header = KcpHeader.Disconnect; - return false; - } - - void TickIncoming_Connected(uint time) - { - // detect common events & ping - HandleTimeout(time); - HandleDeadLink(); - HandlePing(time); - HandleChoked(); - - // any reliable kcp message received? - if (ReceiveNextReliable(out KcpHeader header, out ArraySegment message)) - { - // message type FSM. no default so we never miss a case. - switch (header) - { - case KcpHeader.Handshake: - { - // we were waiting for a handshake. - // it proves that the other end speaks our protocol. - Log.Info("KCP: received handshake"); - state = KcpState.Authenticated; - OnAuthenticated?.Invoke(); - break; - } - case KcpHeader.Ping: - { - // ping keeps kcp from timing out. do nothing. - break; - } - case KcpHeader.Data: - case KcpHeader.Disconnect: - { - // everything else is not allowed during handshake! - Log.Warning($"KCP: received invalid header {header} while Connected. Disconnecting the connection."); - Disconnect(); - break; - } - } - } - } - - void TickIncoming_Authenticated(uint time) - { - // detect common events & ping - HandleTimeout(time); - HandleDeadLink(); - HandlePing(time); - HandleChoked(); - - // process all received messages - // - // Mirror scene changing requires transports to immediately stop - // processing any more messages after a scene message was - // received. and since we are in a while loop here, we need this - // extra check. - // - // note while that this is mainly for Mirror, but might be - // useful in other applications too. - // - // note that we check it BEFORE ever calling ReceiveNext. otherwise - // we would silently eat the received message and never process it. - while (!paused && - ReceiveNextReliable(out KcpHeader header, out ArraySegment message)) - { - // message type FSM. no default so we never miss a case. - switch (header) - { - case KcpHeader.Handshake: - { - // should never receive another handshake after auth - Log.Warning($"KCP: received invalid header {header} while Authenticated. Disconnecting the connection."); - Disconnect(); - break; - } - case KcpHeader.Data: - { - // call OnData IF the message contained actual data - if (message.Count > 0) - { - //Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}"); - OnData?.Invoke(message); - } - // empty data = attacker, or something went wrong - else - { - Log.Warning("KCP: received empty Data message while Authenticated. Disconnecting the connection."); - Disconnect(); - } - break; - } - case KcpHeader.Ping: - { - // ping keeps kcp from timing out. do nothing. - break; - } - case KcpHeader.Disconnect: - { - // disconnect might happen - Log.Info("KCP: received disconnect message"); - Disconnect(); - break; - } - } - } - } - - public void TickIncoming() - { - uint time = (uint)refTime.ElapsedMilliseconds; - - try - { - switch (state) - { - case KcpState.Connected: - { - TickIncoming_Connected(time); - break; - } - case KcpState.Authenticated: - { - TickIncoming_Authenticated(time); - break; - } - case KcpState.Disconnected: - { - // do nothing while disconnected - break; - } - } - } - catch (SocketException exception) - { - // this is ok, the connection was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); - Disconnect(); - } - catch (ObjectDisposedException exception) - { - // fine, socket was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); - Disconnect(); - } - catch (Exception ex) - { - // unexpected - Log.Error(ex.ToString()); - Disconnect(); - } - } - - public void TickOutgoing() - { - uint time = (uint)refTime.ElapsedMilliseconds; - - try - { - switch (state) - { - case KcpState.Connected: - case KcpState.Authenticated: - { - // update flushes out messages - kcp.Update(time); - break; - } - case KcpState.Disconnected: - { - // do nothing while disconnected - break; - } - } - } - catch (SocketException exception) - { - // this is ok, the connection was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); - Disconnect(); - } - catch (ObjectDisposedException exception) - { - // fine, socket was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); - Disconnect(); - } - catch (Exception ex) - { - // unexpected - Log.Error(ex.ToString()); - Disconnect(); - } - } - - public void RawInput(byte[] buffer, int msgLength) - { - // parse channel - if (msgLength > 0) - { - byte channel = buffer[0]; - switch (channel) - { - case (byte)KcpChannel.Reliable: - { - // input into kcp, but skip channel byte - int input = kcp.Input(buffer, 1, msgLength - 1); - if (input != 0) - { - Log.Warning($"Input failed with error={input} for buffer with length={msgLength - 1}"); - } - break; - } - case (byte)KcpChannel.Unreliable: - { - // ideally we would queue all unreliable messages and - // then process them in ReceiveNext() together with the - // reliable messages, but: - // -> queues/allocations/pools are slow and complex. - // -> DOTSNET 10k is actually slower if we use pooled - // unreliable messages for transform messages. - // - // DOTSNET 10k benchmark: - // reliable-only: 170 FPS - // unreliable queued: 130-150 FPS - // unreliable direct: 183 FPS(!) - // - // DOTSNET 50k benchmark: - // reliable-only: FAILS (queues keep growing) - // unreliable direct: 18-22 FPS(!) - // - // -> all unreliable messages are DATA messages anyway. - // -> let's skip the magic and call OnData directly if - // the current state allows it. - if (state == KcpState.Authenticated) - { - // only process messages while not paused for Mirror - // scene switching etc. - // -> if an unreliable message comes in while - // paused, simply drop it. it's unreliable! - if (!paused) - { - ArraySegment message = new ArraySegment(buffer, 1, msgLength - 1); - OnData?.Invoke(message); - } - - // set last receive time to avoid timeout. - // -> we do this in ANY case even if not enabled. - // a message is a message. - // -> we set last receive time for both reliable and - // unreliable messages. both count. - // otherwise a connection might time out even - // though unreliable were received, but no - // reliable was received. - lastReceiveTime = (uint)refTime.ElapsedMilliseconds; - } - else - { - // should never - Log.Warning($"KCP: received unreliable message in state {state}. Disconnecting the connection."); - Disconnect(); - } - break; - } - default: - { - // not a valid channel. random data or attacks. - Log.Info($"Disconnecting connection because of invalid channel header: {channel}"); - Disconnect(); - break; - } - } - } - } - - // raw send puts the data into the socket - protected abstract void RawSend(byte[] data, int length); - - // raw send called by kcp - void RawSendReliable(byte[] data, int length) - { - // copy channel header, data into raw send buffer, then send - rawSendBuffer[0] = (byte)KcpChannel.Reliable; - Buffer.BlockCopy(data, 0, rawSendBuffer, 1, length); - RawSend(rawSendBuffer, length + 1); - } - - void SendReliable(KcpHeader header, ArraySegment content) - { - // 1 byte header + content needs to fit into send buffer - if (1 + content.Count <= kcpSendBuffer.Length) // TODO - { - // copy header, content (if any) into send buffer - kcpSendBuffer[0] = (byte)header; - if (content.Count > 0) - Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count); - - // send to kcp for processing - int sent = kcp.Send(kcpSendBuffer, 0, 1 + content.Count); - if (sent < 0) - { - Log.Warning($"Send failed with error={sent} for content with length={content.Count}"); - } - } - // otherwise content is larger than MaxMessageSize. let user know! - else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize}"); - } - - void SendUnreliable(ArraySegment message) - { - // message size needs to be <= unreliable max size - if (message.Count <= UnreliableMaxMessageSize) - { - // copy channel header, data into raw send buffer, then send - rawSendBuffer[0] = (byte)KcpChannel.Unreliable; - Buffer.BlockCopy(message.Array, 0, rawSendBuffer, 1, message.Count); - RawSend(rawSendBuffer, message.Count + 1); - } - // otherwise content is larger than MaxMessageSize. let user know! - else Log.Error($"Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={UnreliableMaxMessageSize}"); - } - - // 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) - // => handshake info needs to be delivered, so it goes over reliable. - public void SendHandshake() - { - Log.Info("KcpConnection: sending Handshake to other end!"); - SendReliable(KcpHeader.Handshake, default); - } - - public void SendData(ArraySegment data, KcpChannel channel) - { - // sending empty segments is not allowed. - // nobody should ever try to send empty data. - // it means that something went wrong, e.g. in Mirror/DOTSNET. - // let's make it obvious so it's easy to debug. - if (data.Count == 0) - { - Log.Warning("KcpConnection: tried sending empty message. This should never happen. Disconnecting."); - Disconnect(); - return; - } - - switch (channel) - { - case KcpChannel.Reliable: - SendReliable(KcpHeader.Data, data); - break; - case KcpChannel.Unreliable: - SendUnreliable(data); - break; - } - } - - // ping goes through kcp to keep it from timing out, so it goes over the - // reliable channel. - void SendPing() => SendReliable(KcpHeader.Ping, default); - - // disconnect info needs to be delivered, so it goes over reliable - void SendDisconnect() => SendReliable(KcpHeader.Disconnect, default); - - 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 - { - SendDisconnect(); - 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 - Log.Info("KCP Connection: Disconnected."); - state = KcpState.Disconnected; - OnDisconnected?.Invoke(); - } - - // get remote endpoint - public EndPoint GetRemoteEndPoint() => remoteEndPoint; - - // pause/unpause to safely support mirror scene handling and to - // immediately pause the receive while loop if needed. - public void Pause() => paused = true; - public void Unpause() - { - // unpause - paused = false; - - // reset the timeout. - // we have likely been paused for > timeout seconds, but that - // doesn't mean we should disconnect. for example, Mirror pauses - // kcp during scene changes which could easily take > 10s timeout: - // see also: https://github.com/vis2k/kcp2k/issues/8 - // => Unpause completely resets the timeout instead of restoring the - // time difference when we started pausing. it's more simple and - // it's a good idea to start counting from 0 after we unpaused! - lastReceiveTime = (uint)refTime.ElapsedMilliseconds; - } - } -} +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +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; + + // Mirror needs a way to stop the kcp message processing while loop + // immediately after a scene change message. Mirror can't process any + // other messages during a scene change. + // (could be useful for others too) + bool paused; + + // If we don't receive anything these many milliseconds + // then consider us disconnected + public const int DEFAULT_TIMEOUT = 10000; + public int timeout = DEFAULT_TIMEOUT; + 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(); + + // we need to subtract the channel byte from every MaxMessageSize + // calculation. + // we also need to tell kcp to use MTU-1 to leave space for the byte. + const int CHANNEL_HEADER_SIZE = 1; + + // reliable channel (= kcp) MaxMessageSize so the outside knows largest + // allowed message to send the calculation in Send() is not obvious at + // all, so let's provide the helper here. + // + // kcp does fragmentation, so max message is way larger than MTU. + // + // -> runtime MTU changes are disabled: mss is always MTU_DEF-OVERHEAD + // -> Send() checks if fragment count < WND_RCV, so we use WND_RCV - 1. + // note that Send() checks WND_RCV instead of wnd_rcv which may or + // may not be a bug in original kcp. but since it uses the define, we + // can use that here too. + // -> we add 1 byte KcpHeader enum to each message, so -1 + // + // IMPORTANT: max message is MTU * WND_RCV, in other words it completely + // fills the receive window! due to head of line blocking, + // all other messages have to wait while a maxed size message + // is being delivered. + // => in other words, DO NOT use max size all the time like + // for batching. + // => sending UNRELIABLE max message size most of the time is + // best for performance (use that one for batching!) + public const int ReliableMaxMessageSize = (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * (Kcp.WND_RCV - 1) - 1; + + // unreliable max message size is simply MTU - channel header size + public const int UnreliableMaxMessageSize = Kcp.MTU_DEF - CHANNEL_HEADER_SIZE; + + // buffer to receive kcp's processed messages (avoids allocations). + // IMPORTANT: this is for KCP messages. so it needs to be of size: + // 1 byte header + MaxMessageSize content + byte[] kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize]; + + // send buffer for handing user messages to kcp for processing. + // (avoids allocations). + // IMPORTANT: needs to be of size: + // 1 byte header + MaxMessageSize content + byte[] kcpSendBuffer = new byte[1 + ReliableMaxMessageSize]; + + // raw send buffer is exactly MTU. + byte[] rawSendBuffer = new byte[Kcp.MTU_DEF]; + + // 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; + + // getters for queue and buffer counts, used for debug info + public int SendQueueCount => kcp.snd_queue.Count; + public int ReceiveQueueCount => kcp.rcv_queue.Count; + public int SendBufferCount => kcp.snd_buf.Count; + public int ReceiveBufferCount => kcp.rcv_buf.Count; + + // maximum send rate per second can be calculated from kcp parameters + // source: https://translate.google.com/translate?sl=auto&tl=en&u=https://wetest.qq.com/lab/view/391.html + // + // KCP can send/receive a maximum of WND*MTU per interval. + // multiple by 1000ms / interval to get the per-second rate. + // + // example: + // WND(32) * MTU(1400) = 43.75KB + // => 43.75KB * 1000 / INTERVAL(10) = 4375KB/s + // + // returns bytes/second! + public uint MaxSendRate => + kcp.snd_wnd * kcp.mtu * 1000 / kcp.interval; + + public uint MaxReceiveRate => + kcp.rcv_wnd * kcp.mtu * 1000 / kcp.interval; + + // SetupKcp creates and configures a new KCP instance. + // => useful to start from a fresh state every time the client connects + // => NoDelay, interval, wnd 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, int timeout = DEFAULT_TIMEOUT) + { + // set up kcp over reliable channel (that's what kcp is for) + kcp = new Kcp(0, RawSendReliable); + // 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); + + // IMPORTANT: high level needs to add 1 channel byte to each raw + // message. so while Kcp.MTU_DEF is perfect, we actually need to + // tell kcp to use MTU-1 so we can still put the header into the + // message afterwards. + kcp.SetMtu(Kcp.MTU_DEF - CHANNEL_HEADER_SIZE); + + this.timeout = timeout; + state = KcpState.Connected; + + refTime.Start(); + } + + 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) + { + Log.Warning($"KCP: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); + Disconnect(); + } + } + + void HandleDeadLink() + { + // kcp has 'dead_link' detection. might as well use it. + if (kcp.state == -1) + { + Log.Warning("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 + //Log.Debug("KCP: sending ping..."); + SendPing(); + lastPingTime = time; + } + } + + void HandleChoked() + { + // disconnect connections that can't process the load. + // see QueueSizeDisconnect comments. + // => include all of kcp's buffers and the unreliable queue! + int total = kcp.rcv_queue.Count + kcp.snd_queue.Count + + kcp.rcv_buf.Count + kcp.snd_buf.Count; + if (total >= QueueDisconnectThreshold) + { + Log.Warning($"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 reliable message type & content from kcp. + // -> to avoid buffering, unreliable messages call OnData directly. + bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) + { + int msgSize = kcp.PeekSize(); + if (msgSize > 0) + { + // only allow receiving up to buffer sized messages. + // otherwise we would get BlockCopy ArgumentException anyway. + if (msgSize <= kcpMessageBuffer.Length) + { + // receive from kcp + int received = kcp.Receive(kcpMessageBuffer, msgSize); + if (received >= 0) + { + // extract header & content without header + header = (KcpHeader)kcpMessageBuffer[0]; + message = new ArraySegment(kcpMessageBuffer, 1, msgSize - 1); + lastReceiveTime = (uint)refTime.ElapsedMilliseconds; + return true; + } + else + { + // if receive failed, close everything + Log.Warning($"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 + { + Log.Warning($"KCP: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); + Disconnect(); + } + } + + message = default; + header = KcpHeader.Disconnect; + return false; + } + + void TickIncoming_Connected(uint time) + { + // detect common events & ping + HandleTimeout(time); + HandleDeadLink(); + HandlePing(time); + HandleChoked(); + + // any reliable kcp message received? + if (ReceiveNextReliable(out KcpHeader header, out ArraySegment message)) + { + // message type FSM. no default so we never miss a case. + switch (header) + { + case KcpHeader.Handshake: + { + // we were waiting for a handshake. + // it proves that the other end speaks our protocol. + Log.Info("KCP: received handshake"); + state = KcpState.Authenticated; + OnAuthenticated?.Invoke(); + break; + } + case KcpHeader.Ping: + { + // ping keeps kcp from timing out. do nothing. + break; + } + case KcpHeader.Data: + case KcpHeader.Disconnect: + { + // everything else is not allowed during handshake! + Log.Warning($"KCP: received invalid header {header} while Connected. Disconnecting the connection."); + Disconnect(); + break; + } + } + } + } + + void TickIncoming_Authenticated(uint time) + { + // detect common events & ping + HandleTimeout(time); + HandleDeadLink(); + HandlePing(time); + HandleChoked(); + + // process all received messages + // + // Mirror scene changing requires transports to immediately stop + // processing any more messages after a scene message was + // received. and since we are in a while loop here, we need this + // extra check. + // + // note while that this is mainly for Mirror, but might be + // useful in other applications too. + // + // note that we check it BEFORE ever calling ReceiveNext. otherwise + // we would silently eat the received message and never process it. + while (!paused && + ReceiveNextReliable(out KcpHeader header, out ArraySegment message)) + { + // message type FSM. no default so we never miss a case. + switch (header) + { + case KcpHeader.Handshake: + { + // should never receive another handshake after auth + Log.Warning($"KCP: received invalid header {header} while Authenticated. Disconnecting the connection."); + Disconnect(); + break; + } + case KcpHeader.Data: + { + // call OnData IF the message contained actual data + if (message.Count > 0) + { + //Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}"); + OnData?.Invoke(message); + } + // empty data = attacker, or something went wrong + else + { + Log.Warning("KCP: received empty Data message while Authenticated. Disconnecting the connection."); + Disconnect(); + } + break; + } + case KcpHeader.Ping: + { + // ping keeps kcp from timing out. do nothing. + break; + } + case KcpHeader.Disconnect: + { + // disconnect might happen + Log.Info("KCP: received disconnect message"); + Disconnect(); + break; + } + } + } + } + + public void TickIncoming() + { + uint time = (uint)refTime.ElapsedMilliseconds; + + try + { + switch (state) + { + case KcpState.Connected: + { + TickIncoming_Connected(time); + break; + } + case KcpState.Authenticated: + { + TickIncoming_Authenticated(time); + break; + } + case KcpState.Disconnected: + { + // do nothing while disconnected + break; + } + } + } + catch (SocketException exception) + { + // this is ok, the connection was closed + Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + Disconnect(); + } + catch (ObjectDisposedException exception) + { + // fine, socket was closed + Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + Disconnect(); + } + catch (Exception ex) + { + // unexpected + Log.Error(ex.ToString()); + Disconnect(); + } + } + + public void TickOutgoing() + { + uint time = (uint)refTime.ElapsedMilliseconds; + + try + { + switch (state) + { + case KcpState.Connected: + case KcpState.Authenticated: + { + // update flushes out messages + kcp.Update(time); + break; + } + case KcpState.Disconnected: + { + // do nothing while disconnected + break; + } + } + } + catch (SocketException exception) + { + // this is ok, the connection was closed + Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + Disconnect(); + } + catch (ObjectDisposedException exception) + { + // fine, socket was closed + Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + Disconnect(); + } + catch (Exception ex) + { + // unexpected + Log.Error(ex.ToString()); + Disconnect(); + } + } + + public void RawInput(byte[] buffer, int msgLength) + { + // parse channel + if (msgLength > 0) + { + byte channel = buffer[0]; + switch (channel) + { + case (byte)KcpChannel.Reliable: + { + // input into kcp, but skip channel byte + int input = kcp.Input(buffer, 1, msgLength - 1); + if (input != 0) + { + Log.Warning($"Input failed with error={input} for buffer with length={msgLength - 1}"); + } + break; + } + case (byte)KcpChannel.Unreliable: + { + // ideally we would queue all unreliable messages and + // then process them in ReceiveNext() together with the + // reliable messages, but: + // -> queues/allocations/pools are slow and complex. + // -> DOTSNET 10k is actually slower if we use pooled + // unreliable messages for transform messages. + // + // DOTSNET 10k benchmark: + // reliable-only: 170 FPS + // unreliable queued: 130-150 FPS + // unreliable direct: 183 FPS(!) + // + // DOTSNET 50k benchmark: + // reliable-only: FAILS (queues keep growing) + // unreliable direct: 18-22 FPS(!) + // + // -> all unreliable messages are DATA messages anyway. + // -> let's skip the magic and call OnData directly if + // the current state allows it. + if (state == KcpState.Authenticated) + { + // only process messages while not paused for Mirror + // scene switching etc. + // -> if an unreliable message comes in while + // paused, simply drop it. it's unreliable! + if (!paused) + { + ArraySegment message = new ArraySegment(buffer, 1, msgLength - 1); + OnData?.Invoke(message); + } + + // set last receive time to avoid timeout. + // -> we do this in ANY case even if not enabled. + // a message is a message. + // -> we set last receive time for both reliable and + // unreliable messages. both count. + // otherwise a connection might time out even + // though unreliable were received, but no + // reliable was received. + lastReceiveTime = (uint)refTime.ElapsedMilliseconds; + } + else + { + // should never + Log.Warning($"KCP: received unreliable message in state {state}. Disconnecting the connection."); + Disconnect(); + } + break; + } + default: + { + // not a valid channel. random data or attacks. + Log.Info($"Disconnecting connection because of invalid channel header: {channel}"); + Disconnect(); + break; + } + } + } + } + + // raw send puts the data into the socket + protected abstract void RawSend(byte[] data, int length); + + // raw send called by kcp + void RawSendReliable(byte[] data, int length) + { + // copy channel header, data into raw send buffer, then send + rawSendBuffer[0] = (byte)KcpChannel.Reliable; + Buffer.BlockCopy(data, 0, rawSendBuffer, 1, length); + RawSend(rawSendBuffer, length + 1); + } + + void SendReliable(KcpHeader header, ArraySegment content) + { + // 1 byte header + content needs to fit into send buffer + if (1 + content.Count <= kcpSendBuffer.Length) // TODO + { + // copy header, content (if any) into send buffer + kcpSendBuffer[0] = (byte)header; + if (content.Count > 0) + Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count); + + // send to kcp for processing + int sent = kcp.Send(kcpSendBuffer, 0, 1 + content.Count); + if (sent < 0) + { + Log.Warning($"Send failed with error={sent} for content with length={content.Count}"); + } + } + // otherwise content is larger than MaxMessageSize. let user know! + else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize}"); + } + + void SendUnreliable(ArraySegment message) + { + // message size needs to be <= unreliable max size + if (message.Count <= UnreliableMaxMessageSize) + { + // copy channel header, data into raw send buffer, then send + rawSendBuffer[0] = (byte)KcpChannel.Unreliable; + Buffer.BlockCopy(message.Array, 0, rawSendBuffer, 1, message.Count); + RawSend(rawSendBuffer, message.Count + 1); + } + // otherwise content is larger than MaxMessageSize. let user know! + else Log.Error($"Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={UnreliableMaxMessageSize}"); + } + + // 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) + // => handshake info needs to be delivered, so it goes over reliable. + public void SendHandshake() + { + Log.Info("KcpConnection: sending Handshake to other end!"); + SendReliable(KcpHeader.Handshake, default); + } + + public void SendData(ArraySegment data, KcpChannel channel) + { + // sending empty segments is not allowed. + // nobody should ever try to send empty data. + // it means that something went wrong, e.g. in Mirror/DOTSNET. + // let's make it obvious so it's easy to debug. + if (data.Count == 0) + { + Log.Warning("KcpConnection: tried sending empty message. This should never happen. Disconnecting."); + Disconnect(); + return; + } + + switch (channel) + { + case KcpChannel.Reliable: + SendReliable(KcpHeader.Data, data); + break; + case KcpChannel.Unreliable: + SendUnreliable(data); + break; + } + } + + // ping goes through kcp to keep it from timing out, so it goes over the + // reliable channel. + void SendPing() => SendReliable(KcpHeader.Ping, default); + + // disconnect info needs to be delivered, so it goes over reliable + void SendDisconnect() => SendReliable(KcpHeader.Disconnect, default); + + 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 + { + SendDisconnect(); + 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 + Log.Info("KCP Connection: Disconnected."); + state = KcpState.Disconnected; + OnDisconnected?.Invoke(); + } + + // get remote endpoint + public EndPoint GetRemoteEndPoint() => remoteEndPoint; + + // pause/unpause to safely support mirror scene handling and to + // immediately pause the receive while loop if needed. + public void Pause() => paused = true; + public void Unpause() + { + // unpause + paused = false; + + // reset the timeout. + // we have likely been paused for > timeout seconds, but that + // doesn't mean we should disconnect. for example, Mirror pauses + // kcp during scene changes which could easily take > 10s timeout: + // see also: https://github.com/vis2k/kcp2k/issues/8 + // => Unpause completely resets the timeout instead of restoring the + // time difference when we started pausing. it's more simple and + // it's a good idea to start counting from 0 after we unpaused! + lastReceiveTime = (uint)refTime.ElapsedMilliseconds; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta index fa5dcff..6d1f393 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 3915c7c62b72d4dc2a9e4e76c94fc484 -timeCreated: 1602600432 \ No newline at end of file +fileFormatVersion: 2 +guid: 3915c7c62b72d4dc2a9e4e76c94fc484 +timeCreated: 1602600432 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs index bc4b047..6f471fb 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs @@ -1,19 +1,19 @@ -namespace kcp2k -{ - // header for messages processed by kcp. - // this is NOT for the raw receive messages(!) because handshake/disconnect - // need to be sent reliably. it's not enough to have those in rawreceive - // because those messages might get lost without being resent! - public enum KcpHeader : byte - { - // don't react on 0x00. might help to filter out random noise. - Handshake = 0x01, - // ping goes over reliable & KcpHeader for now. could go over reliable - // too. there is no real difference except that this is easier because - // we already have a KcpHeader for reliable messages. - // ping is only used to keep it alive, so latency doesn't matter. - Ping = 0x02, - Data = 0x03, - Disconnect = 0x04 - } +namespace kcp2k +{ + // header for messages processed by kcp. + // this is NOT for the raw receive messages(!) because handshake/disconnect + // need to be sent reliably. it's not enough to have those in rawreceive + // because those messages might get lost without being resent! + public enum KcpHeader : byte + { + // don't react on 0x00. might help to filter out random noise. + Handshake = 0x01, + // ping goes over reliable & KcpHeader for now. could go over reliable + // too. there is no real difference except that this is easier because + // we already have a KcpHeader for reliable messages. + // ping is only used to keep it alive, so latency doesn't matter. + Ping = 0x02, + Data = 0x03, + Disconnect = 0x04 + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs.meta index 9e81c94..997ed09 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 91b5edac31224a49bd76f960ae018942 -timeCreated: 1610081248 \ No newline at end of file +fileFormatVersion: 2 +guid: 91b5edac31224a49bd76f960ae018942 +timeCreated: 1610081248 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs index c9847df..56b4910 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs @@ -1,337 +1,337 @@ -// 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; - -namespace kcp2k -{ - public class KcpServer - { - // events - public Action OnConnected; - public Action> OnData; - public Action OnDisconnected; - - // configuration - // DualMode uses both IPv6 and IPv4. not all platforms support it. - // (Nintendo Switch, etc.) - public bool DualMode; - // 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; - // timeout in milliseconds - public int Timeout; - - // state - protected Socket socket; - EndPoint newClientEP; - - // IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even - // if MaxMessageSize is larger. kcp always sends in MTU - // segments and having a buffer smaller than MTU would - // silently drop excess data. - // => we need the mtu to fit channel + message! - readonly byte[] rawReceiveBuffer = 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 DualMode, - bool NoDelay, - uint Interval, - int FastResend = 0, - bool CongestionWindow = true, - uint SendWindowSize = Kcp.WND_SND, - uint ReceiveWindowSize = Kcp.WND_RCV, - int Timeout = KcpConnection.DEFAULT_TIMEOUT) - { - this.OnConnected = OnConnected; - this.OnData = OnData; - this.OnDisconnected = OnDisconnected; - this.DualMode = DualMode; - this.NoDelay = NoDelay; - this.Interval = Interval; - this.FastResend = FastResend; - this.CongestionWindow = CongestionWindow; - this.SendWindowSize = SendWindowSize; - this.ReceiveWindowSize = ReceiveWindowSize; - this.Timeout = Timeout; - - // create newClientEP either IPv4 or IPv6 - newClientEP = DualMode - ? new IPEndPoint(IPAddress.IPv6Any, 0) - : new IPEndPoint(IPAddress.Any, 0); - } - - public bool IsActive() => socket != null; - - public void Start(ushort port) - { - // only start once - if (socket != null) - { - Log.Warning("KCP: server already started!"); - } - - // listen - if (DualMode) - { - // IPv6 socket with DualMode - socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); - socket.DualMode = true; - socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port)); - } - else - { - // IPv4 socket - socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - socket.Bind(new IPEndPoint(IPAddress.Any, port)); - } - } - - public void Send(int connectionId, ArraySegment segment, KcpChannel channel) - { - if (connections.TryGetValue(connectionId, out KcpServerConnection connection)) - { - connection.SendData(segment, channel); - } - } - - 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 ""; - } - - // EndPoint & Receive functions can be overwritten for where-allocation: - // https://github.com/vis2k/where-allocation - protected virtual int ReceiveFrom(byte[] buffer, out int connectionHash) - { - // NOTE: ReceiveFrom allocates. - // we pass our IPEndPoint to ReceiveFrom. - // receive from calls newClientEP.Create(socketAddr). - // IPEndPoint.Create always returns a new IPEndPoint. - // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761 - int read = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP); - - // calculate connectionHash from endpoint - // NOTE: IPEndPoint.GetHashCode() allocates. - // it calls m_Address.GetHashCode(). - // m_Address is an IPAddress. - // GetHashCode() allocates for IPv6: - // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699 - // - // => using only newClientEP.Port wouldn't work, because - // different connections can have the same port. - connectionHash = newClientEP.GetHashCode(); - return read; - } - - protected virtual KcpServerConnection CreateConnection() => - new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); - - // process incoming messages. should be called before updating the world. - HashSet connectionsToRemove = new HashSet(); - public void TickIncoming() - { - while (socket != null && socket.Poll(0, SelectMode.SelectRead)) - { - try - { - // receive - int msgLength = ReceiveFrom(rawReceiveBuffer, out int connectionId); - //Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); - - // IMPORTANT: detect if buffer was too small for the received - // msgLength. otherwise the excess data would be - // silently lost. - // (see ReceiveFrom documentation) - if (msgLength <= rawReceiveBuffer.Length) - { - // is this a new connection? - if (!connections.TryGetValue(connectionId, out KcpServerConnection connection)) - { - // create a new KcpConnection based on last received - // EndPoint. can be overwritten for where-allocation. - connection = CreateConnection(); - - // 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); - Log.Info($"KCP: server added connection({connectionId})"); - - // 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 - //Log.Info($"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 - Log.Info($"KCP: OnServerDisconnected({connectionId})"); - OnDisconnected.Invoke(connectionId); - }; - - // finally, call mirror OnConnected event - Log.Info($"KCP: OnServerConnected({connectionId})"); - OnConnected.Invoke(connectionId); - }; - - // now input the message & process received ones - // connected event was set up. - // tick will process the first message and adds the - // connection if it was the handshake. - connection.RawInput(rawReceiveBuffer, msgLength); - connection.TickIncoming(); - - // 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(rawReceiveBuffer, msgLength); - } - } - else - { - Log.Error($"KCP Server: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting connectionId={connectionId}."); - Disconnect(connectionId); - } - } - // this is fine, the socket might have been closed in the other end - catch (SocketException) {} - } - - // process inputs for all server connections - // (even if we didn't receive anything. need to tick ping etc.) - foreach (KcpServerConnection connection in connections.Values) - { - connection.TickIncoming(); - } - - // 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(); - } - - // process outgoing messages. should be called after updating the world. - public void TickOutgoing() - { - // flush all server connections - foreach (KcpServerConnection connection in connections.Values) - { - connection.TickOutgoing(); - } - } - - // process incoming and outgoing for convenience. - // => ideally call ProcessIncoming() before updating the world and - // ProcessOutgoing() after updating the world for minimum latency - public void Tick() - { - TickIncoming(); - TickOutgoing(); - } - - public void Stop() - { - socket?.Close(); - socket = null; - } - - // pause/unpause to safely support mirror scene handling and to - // immediately pause the receive while loop if needed. - public void Pause() - { - foreach (KcpServerConnection connection in connections.Values) - connection.Pause(); - } - - public void Unpause() - { - foreach (KcpServerConnection connection in connections.Values) - connection.Unpause(); - } - } -} +// 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; + +namespace kcp2k +{ + public class KcpServer + { + // events + public Action OnConnected; + public Action> OnData; + public Action OnDisconnected; + + // configuration + // DualMode uses both IPv6 and IPv4. not all platforms support it. + // (Nintendo Switch, etc.) + public bool DualMode; + // 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; + // timeout in milliseconds + public int Timeout; + + // state + protected Socket socket; + EndPoint newClientEP; + + // IMPORTANT: raw receive buffer always needs to be of 'MTU' size, even + // if MaxMessageSize is larger. kcp always sends in MTU + // segments and having a buffer smaller than MTU would + // silently drop excess data. + // => we need the mtu to fit channel + message! + readonly byte[] rawReceiveBuffer = 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 DualMode, + bool NoDelay, + uint Interval, + int FastResend = 0, + bool CongestionWindow = true, + uint SendWindowSize = Kcp.WND_SND, + uint ReceiveWindowSize = Kcp.WND_RCV, + int Timeout = KcpConnection.DEFAULT_TIMEOUT) + { + this.OnConnected = OnConnected; + this.OnData = OnData; + this.OnDisconnected = OnDisconnected; + this.DualMode = DualMode; + this.NoDelay = NoDelay; + this.Interval = Interval; + this.FastResend = FastResend; + this.CongestionWindow = CongestionWindow; + this.SendWindowSize = SendWindowSize; + this.ReceiveWindowSize = ReceiveWindowSize; + this.Timeout = Timeout; + + // create newClientEP either IPv4 or IPv6 + newClientEP = DualMode + ? new IPEndPoint(IPAddress.IPv6Any, 0) + : new IPEndPoint(IPAddress.Any, 0); + } + + public bool IsActive() => socket != null; + + public void Start(ushort port) + { + // only start once + if (socket != null) + { + Log.Warning("KCP: server already started!"); + } + + // listen + if (DualMode) + { + // IPv6 socket with DualMode + socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); + socket.DualMode = true; + socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port)); + } + else + { + // IPv4 socket + socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + socket.Bind(new IPEndPoint(IPAddress.Any, port)); + } + } + + public void Send(int connectionId, ArraySegment segment, KcpChannel channel) + { + if (connections.TryGetValue(connectionId, out KcpServerConnection connection)) + { + connection.SendData(segment, channel); + } + } + + 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 ""; + } + + // EndPoint & Receive functions can be overwritten for where-allocation: + // https://github.com/vis2k/where-allocation + protected virtual int ReceiveFrom(byte[] buffer, out int connectionHash) + { + // NOTE: ReceiveFrom allocates. + // we pass our IPEndPoint to ReceiveFrom. + // receive from calls newClientEP.Create(socketAddr). + // IPEndPoint.Create always returns a new IPEndPoint. + // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761 + int read = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref newClientEP); + + // calculate connectionHash from endpoint + // NOTE: IPEndPoint.GetHashCode() allocates. + // it calls m_Address.GetHashCode(). + // m_Address is an IPAddress. + // GetHashCode() allocates for IPv6: + // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699 + // + // => using only newClientEP.Port wouldn't work, because + // different connections can have the same port. + connectionHash = newClientEP.GetHashCode(); + return read; + } + + protected virtual KcpServerConnection CreateConnection() => + new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); + + // process incoming messages. should be called before updating the world. + HashSet connectionsToRemove = new HashSet(); + public void TickIncoming() + { + while (socket != null && socket.Poll(0, SelectMode.SelectRead)) + { + try + { + // receive + int msgLength = ReceiveFrom(rawReceiveBuffer, out int connectionId); + //Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); + + // IMPORTANT: detect if buffer was too small for the received + // msgLength. otherwise the excess data would be + // silently lost. + // (see ReceiveFrom documentation) + if (msgLength <= rawReceiveBuffer.Length) + { + // is this a new connection? + if (!connections.TryGetValue(connectionId, out KcpServerConnection connection)) + { + // create a new KcpConnection based on last received + // EndPoint. can be overwritten for where-allocation. + connection = CreateConnection(); + + // 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); + Log.Info($"KCP: server added connection({connectionId})"); + + // 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 + //Log.Info($"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 + Log.Info($"KCP: OnServerDisconnected({connectionId})"); + OnDisconnected.Invoke(connectionId); + }; + + // finally, call mirror OnConnected event + Log.Info($"KCP: OnServerConnected({connectionId})"); + OnConnected.Invoke(connectionId); + }; + + // now input the message & process received ones + // connected event was set up. + // tick will process the first message and adds the + // connection if it was the handshake. + connection.RawInput(rawReceiveBuffer, msgLength); + connection.TickIncoming(); + + // 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(rawReceiveBuffer, msgLength); + } + } + else + { + Log.Error($"KCP Server: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting connectionId={connectionId}."); + Disconnect(connectionId); + } + } + // this is fine, the socket might have been closed in the other end + catch (SocketException) {} + } + + // process inputs for all server connections + // (even if we didn't receive anything. need to tick ping etc.) + foreach (KcpServerConnection connection in connections.Values) + { + connection.TickIncoming(); + } + + // 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(); + } + + // process outgoing messages. should be called after updating the world. + public void TickOutgoing() + { + // flush all server connections + foreach (KcpServerConnection connection in connections.Values) + { + connection.TickOutgoing(); + } + } + + // process incoming and outgoing for convenience. + // => ideally call ProcessIncoming() before updating the world and + // ProcessOutgoing() after updating the world for minimum latency + public void Tick() + { + TickIncoming(); + TickOutgoing(); + } + + public void Stop() + { + socket?.Close(); + socket = null; + } + + // pause/unpause to safely support mirror scene handling and to + // immediately pause the receive while loop if needed. + public void Pause() + { + foreach (KcpServerConnection connection in connections.Values) + connection.Pause(); + } + + public void Unpause() + { + foreach (KcpServerConnection connection in connections.Values) + connection.Unpause(); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta index ef720d4..b5c5c15 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 9759159c6589494a9037f5e130a867ed -timeCreated: 1603787747 \ No newline at end of file +fileFormatVersion: 2 +guid: 9759159c6589494a9037f5e130a867ed +timeCreated: 1603787747 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs index 450a4da..17e2298 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs @@ -1,22 +1,22 @@ -using System.Net; -using System.Net.Sockets; - -namespace kcp2k -{ - public class KcpServerConnection : KcpConnection - { - // Constructor & Send functions can be overwritten for where-allocation: - // https://github.com/vis2k/where-allocation - 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, int timeout = DEFAULT_TIMEOUT) - { - this.socket = socket; - this.remoteEndPoint = remoteEndPoint; - SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); - } - - protected override void RawSend(byte[] data, int length) - { - socket.SendTo(data, 0, length, SocketFlags.None, remoteEndPoint); - } - } -} +using System.Net; +using System.Net.Sockets; + +namespace kcp2k +{ + public class KcpServerConnection : KcpConnection + { + // Constructor & Send functions can be overwritten for where-allocation: + // https://github.com/vis2k/where-allocation + 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, int timeout = DEFAULT_TIMEOUT) + { + this.socket = socket; + this.remoteEndPoint = remoteEndPoint; + SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); + } + + 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 index 10d9803..7f236c3 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 80a9b1ce9a6f14abeb32bfa9921d097b -timeCreated: 1602601483 \ No newline at end of file +fileFormatVersion: 2 +guid: 80a9b1ce9a6f14abeb32bfa9921d097b +timeCreated: 1602601483 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs index 939dae7..bf8c04a 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs @@ -1,14 +1,14 @@ -// A simple logger class that uses Console.WriteLine by default. -// Can also do Logger.LogMethod = Debug.Log for Unity etc. -// (this way we don't have to depend on UnityEngine) -using System; - -namespace kcp2k -{ - public static class Log - { - public static Action Info = Console.WriteLine; - public static Action Warning = Console.WriteLine; - public static Action Error = Console.Error.WriteLine; - } -} +// A simple logger class that uses Console.WriteLine by default. +// Can also do Logger.LogMethod = Debug.Log for Unity etc. +// (this way we don't have to depend on UnityEngine) +using System; + +namespace kcp2k +{ + public static class Log + { + public static Action Info = Console.WriteLine; + public static Action Warning = Console.WriteLine; + public static Action Error = Console.Error.WriteLine; + } +} diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs.meta index 333bee5..5690787 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 7b5e1de98d6d84c3793a61cf7d8da9a4 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 7b5e1de98d6d84c3793a61cf7d8da9a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc.meta index 4cbc909..18c0b27 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 0b320ff06046474eae7bce7240ea478c -timeCreated: 1626430641 \ No newline at end of file +fileFormatVersion: 2 +guid: 0b320ff06046474eae7bce7240ea478c +timeCreated: 1626430641 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs index b3e1b27..67b74f1 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs @@ -1,24 +1,24 @@ -// where-allocation version of KcpClientConnection. -// may not be wanted on all platforms, so it's an extra optional class. -using System.Net; -using WhereAllocation; - -namespace kcp2k -{ - public class KcpClientConnectionNonAlloc : KcpClientConnection - { - IPEndPointNonAlloc reusableEP; - - protected override void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) - { - // create reusableEP with same address family as remoteEndPoint. - // otherwise ReceiveFrom_NonAlloc couldn't use it. - reusableEP = new IPEndPointNonAlloc(addresses[0], port); - base.CreateRemoteEndPoint(addresses, port); - } - - // where-allocation nonalloc recv - protected override int ReceiveFrom(byte[] buffer) => - socket.ReceiveFrom_NonAlloc(buffer, reusableEP); - } +// where-allocation version of KcpClientConnection. +// may not be wanted on all platforms, so it's an extra optional class. +using System.Net; +using WhereAllocation; + +namespace kcp2k +{ + public class KcpClientConnectionNonAlloc : KcpClientConnection + { + IPEndPointNonAlloc reusableEP; + + protected override void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) + { + // create reusableEP with same address family as remoteEndPoint. + // otherwise ReceiveFrom_NonAlloc couldn't use it. + reusableEP = new IPEndPointNonAlloc(addresses[0], port); + base.CreateRemoteEndPoint(addresses, port); + } + + // where-allocation nonalloc recv + protected override int ReceiveFrom(byte[] buffer) => + socket.ReceiveFrom_NonAlloc(buffer, reusableEP); + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta index 9d4a42e..c696952 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 4c1b235bbe054706bef6d092f361006e -timeCreated: 1626430539 \ No newline at end of file +fileFormatVersion: 2 +guid: 4c1b235bbe054706bef6d092f361006e +timeCreated: 1626430539 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs index acd8e6b..e7430cc 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs @@ -1,17 +1,17 @@ -// where-allocation version of KcpClientConnectionNonAlloc. -// may not be wanted on all platforms, so it's an extra optional class. -using System; - -namespace kcp2k -{ - public class KcpClientNonAlloc : KcpClient - { - public KcpClientNonAlloc(Action OnConnected, Action> OnData, Action OnDisconnected) - : base(OnConnected, OnData, OnDisconnected) - { - } - - protected override KcpClientConnection CreateConnection() => - new KcpClientConnectionNonAlloc(); - } +// where-allocation version of KcpClientConnectionNonAlloc. +// may not be wanted on all platforms, so it's an extra optional class. +using System; + +namespace kcp2k +{ + public class KcpClientNonAlloc : KcpClient + { + public KcpClientNonAlloc(Action OnConnected, Action> OnData, Action OnDisconnected) + : base(OnConnected, OnData, OnDisconnected) + { + } + + protected override KcpClientConnection CreateConnection() => + new KcpClientConnectionNonAlloc(); + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta index 266dafb..0a71970 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 2cf0ccf7d551480bb5af08fcbe169f84 -timeCreated: 1626435264 \ No newline at end of file +fileFormatVersion: 2 +guid: 2cf0ccf7d551480bb5af08fcbe169f84 +timeCreated: 1626435264 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs index fe2e154..8c982eb 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs @@ -1,25 +1,25 @@ -// where-allocation version of KcpServerConnection. -// may not be wanted on all platforms, so it's an extra optional class. -using System.Net; -using System.Net.Sockets; -using WhereAllocation; - -namespace kcp2k -{ - public class KcpServerConnectionNonAlloc : KcpServerConnection - { - IPEndPointNonAlloc reusableSendEndPoint; - - public KcpServerConnectionNonAlloc(Socket socket, EndPoint remoteEndpoint, IPEndPointNonAlloc reusableSendEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT) - : base(socket, remoteEndpoint, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout) - { - this.reusableSendEndPoint = reusableSendEndPoint; - } - - protected override void RawSend(byte[] data, int length) - { - // where-allocation nonalloc send - socket.SendTo_NonAlloc(data, 0, length, SocketFlags.None, reusableSendEndPoint); - } - } +// where-allocation version of KcpServerConnection. +// may not be wanted on all platforms, so it's an extra optional class. +using System.Net; +using System.Net.Sockets; +using WhereAllocation; + +namespace kcp2k +{ + public class KcpServerConnectionNonAlloc : KcpServerConnection + { + IPEndPointNonAlloc reusableSendEndPoint; + + public KcpServerConnectionNonAlloc(Socket socket, EndPoint remoteEndpoint, IPEndPointNonAlloc reusableSendEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT) + : base(socket, remoteEndpoint, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout) + { + this.reusableSendEndPoint = reusableSendEndPoint; + } + + protected override void RawSend(byte[] data, int length) + { + // where-allocation nonalloc send + socket.SendTo_NonAlloc(data, 0, length, SocketFlags.None, reusableSendEndPoint); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta index 383fe02..bca8984 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 4e1b74cc224b4c83a0f6c8d8da9090ab -timeCreated: 1626430608 \ No newline at end of file +fileFormatVersion: 2 +guid: 4e1b74cc224b4c83a0f6c8d8da9090ab +timeCreated: 1626430608 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs index ec571b5..fc6ab2b 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs @@ -1,51 +1,51 @@ -// where-allocation version of KcpServer. -// may not be wanted on all platforms, so it's an extra optional class. -using System; -using System.Net; -using System.Net.Sockets; -using WhereAllocation; - -namespace kcp2k -{ - public class KcpServerNonAlloc : KcpServer - { - IPEndPointNonAlloc reusableClientEP; - - public KcpServerNonAlloc(Action OnConnected, Action> OnData, Action OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = Kcp.WND_SND, uint ReceiveWindowSize = Kcp.WND_RCV, int Timeout = KcpConnection.DEFAULT_TIMEOUT) - : base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout) - { - // create reusableClientEP either IPv4 or IPv6 - reusableClientEP = DualMode - ? new IPEndPointNonAlloc(IPAddress.IPv6Any, 0) - : new IPEndPointNonAlloc(IPAddress.Any, 0); - } - - protected override int ReceiveFrom(byte[] buffer, out int connectionHash) - { - // where-allocation nonalloc ReceiveFrom. - int read = socket.ReceiveFrom_NonAlloc(buffer, 0, buffer.Length, SocketFlags.None, reusableClientEP); - SocketAddress remoteAddress = reusableClientEP.temp; - - // where-allocation nonalloc GetHashCode - connectionHash = remoteAddress.GetHashCode(); - return read; - } - - protected override KcpServerConnection CreateConnection() - { - // IPEndPointNonAlloc is reused all the time. - // we can't store that as the connection's endpoint. - // we need a new copy! - IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint(); - - // for allocation free sending, we also need another - // IPEndPointNonAlloc... - IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port); - - // create a new KcpConnection NonAlloc version - // -> where-allocation IPEndPointNonAlloc is reused. - // need to create a new one from the temp address. - return new KcpServerConnectionNonAlloc(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); - } - } +// where-allocation version of KcpServer. +// may not be wanted on all platforms, so it's an extra optional class. +using System; +using System.Net; +using System.Net.Sockets; +using WhereAllocation; + +namespace kcp2k +{ + public class KcpServerNonAlloc : KcpServer + { + IPEndPointNonAlloc reusableClientEP; + + public KcpServerNonAlloc(Action OnConnected, Action> OnData, Action OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = Kcp.WND_SND, uint ReceiveWindowSize = Kcp.WND_RCV, int Timeout = KcpConnection.DEFAULT_TIMEOUT) + : base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout) + { + // create reusableClientEP either IPv4 or IPv6 + reusableClientEP = DualMode + ? new IPEndPointNonAlloc(IPAddress.IPv6Any, 0) + : new IPEndPointNonAlloc(IPAddress.Any, 0); + } + + protected override int ReceiveFrom(byte[] buffer, out int connectionHash) + { + // where-allocation nonalloc ReceiveFrom. + int read = socket.ReceiveFrom_NonAlloc(buffer, 0, buffer.Length, SocketFlags.None, reusableClientEP); + SocketAddress remoteAddress = reusableClientEP.temp; + + // where-allocation nonalloc GetHashCode + connectionHash = remoteAddress.GetHashCode(); + return read; + } + + protected override KcpServerConnection CreateConnection() + { + // IPEndPointNonAlloc is reused all the time. + // we can't store that as the connection's endpoint. + // we need a new copy! + IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint(); + + // for allocation free sending, we also need another + // IPEndPointNonAlloc... + IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port); + + // create a new KcpConnection NonAlloc version + // -> where-allocation IPEndPointNonAlloc is reused. + // need to create a new one from the temp address. + return new KcpServerConnectionNonAlloc(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta index a878cc1..5fca3fc 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 54b8398dcd544c8a93bcad846214cc40 -timeCreated: 1626432191 \ No newline at end of file +fileFormatVersion: 2 +guid: 54b8398dcd544c8a93bcad846214cc40 +timeCreated: 1626432191 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta index a7d6e11..b7f4d30 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 5cafb8851a0084f3e94a580c207b3923 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +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 index 5fe5547..df1ee7e 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using System.Runtime.CompilerServices; - +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 index 6b442a9..abcd22c 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: aec6a15ac7bd43129317ea1f01f19782 -timeCreated: 1602665988 \ No newline at end of file +fileFormatVersion: 2 +guid: aec6a15ac7bd43129317ea1f01f19782 +timeCreated: 1602665988 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs index 253757a..58a10a4 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs @@ -1,1042 +1,1042 @@ -// Kcp based on https://github.com/skywind3000/kcp -// Kept as close to original as possible. -using System; -using System.Collections.Generic; - -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 := MTU - OVERHEAD - internal uint snd_una; // unacknowledged. e.g. snd_una is 9 it means 8 has been confirmed, 9 and 10 have been sent - internal uint snd_nxt; - internal uint rcv_nxt; - internal uint ssthresh; // slow start threshold - internal int rx_rttval; // average deviation of rtt, used to measure the jitter of rtt - internal int rx_srtt; // smoothed round trip time (a weighted average of rtt) - 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; - - // segment pool to avoid allocations in C#. - // this is not part of the original C code. - readonly Pool SegmentPool = new Pool( - // create new segment - () => new Segment(), - // reset segment before reuse - (segment) => segment.Reset(), - // initial capacity - 32 - ); - - // 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. - Segment SegmentNew() => SegmentPool.Take(); - - // ikcp_segment_delete - // we keep the original function and add our pooling to it. - // this way we'll never miss it anywhere. - void SegmentDelete(Segment seg) => SegmentPool.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) - { - // fragment count - 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'. - - // calculate amount of fragments necessary for 'len' - if (len <= mss) count = 1; - else count = (int)((len + mss - 1) / mss); - - // original kcp uses WND_RCV const even though rcv_wnd is the - // runtime variable. may or may not be correct, see also: - // 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 = Utils.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 (e.g. UDP packet) - // => original kcp uses offset=0, we made it a parameter so that high - // level can skip the channel byte more easily - public int Input(byte[] data, int offset, int size) - { - uint prev_una = snd_una; - uint maxack = 0; - uint latest_ts = 0; - int flag = 0; - - if (data == null || size < OVERHEAD) return -1; - - 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; - - // enough data left to decode segment (aka OVERHEAD bytes)? - if (size < OVERHEAD) break; - - // decode segment - 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); - - // subtract the segment bytes from size - size -= OVERHEAD; - - // enough remaining to read 'len' bytes of the actual payload? - 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 allocates 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 congestion window: - if (!nocwnd) cwnd_ = Math.Min(cwnd, cwnd_); - - // move data from snd_queue to snd_buf - // sliding window, controlled by snd_nxt && sna_una+cwnd - // - // ELI5: 'snd_nxt' is what we want to send. - // 'snd_una' is what hasn't been acked yet. - // copy up to 'cwnd_' difference between them (sliding window) - 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 (e.g. 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 - // configuration: https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration - // nodelay : Whether nodelay mode is enabled, 0 is not enabled; 1 enabled. - // interval :Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms. - // resend :Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission) - // nc :Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”. - // Normal Mode: ikcp_nodelay(kcp, 0, 40, 0, 0); - // Turbo Mode: ikcp_nodelay(kcp, 1, 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); - } - } - } -} +// Kcp based on https://github.com/skywind3000/kcp +// Kept as close to original as possible. +using System; +using System.Collections.Generic; + +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 := MTU - OVERHEAD + internal uint snd_una; // unacknowledged. e.g. snd_una is 9 it means 8 has been confirmed, 9 and 10 have been sent + internal uint snd_nxt; + internal uint rcv_nxt; + internal uint ssthresh; // slow start threshold + internal int rx_rttval; // average deviation of rtt, used to measure the jitter of rtt + internal int rx_srtt; // smoothed round trip time (a weighted average of rtt) + 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; + + // segment pool to avoid allocations in C#. + // this is not part of the original C code. + readonly Pool SegmentPool = new Pool( + // create new segment + () => new Segment(), + // reset segment before reuse + (segment) => segment.Reset(), + // initial capacity + 32 + ); + + // 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. + Segment SegmentNew() => SegmentPool.Take(); + + // ikcp_segment_delete + // we keep the original function and add our pooling to it. + // this way we'll never miss it anywhere. + void SegmentDelete(Segment seg) => SegmentPool.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) + { + // fragment count + 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'. + + // calculate amount of fragments necessary for 'len' + if (len <= mss) count = 1; + else count = (int)((len + mss - 1) / mss); + + // original kcp uses WND_RCV const even though rcv_wnd is the + // runtime variable. may or may not be correct, see also: + // 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 = Utils.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 (e.g. UDP packet) + // => original kcp uses offset=0, we made it a parameter so that high + // level can skip the channel byte more easily + public int Input(byte[] data, int offset, int size) + { + uint prev_una = snd_una; + uint maxack = 0; + uint latest_ts = 0; + int flag = 0; + + if (data == null || size < OVERHEAD) return -1; + + 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; + + // enough data left to decode segment (aka OVERHEAD bytes)? + if (size < OVERHEAD) break; + + // decode segment + 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); + + // subtract the segment bytes from size + size -= OVERHEAD; + + // enough remaining to read 'len' bytes of the actual payload? + 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 allocates 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 congestion window: + if (!nocwnd) cwnd_ = Math.Min(cwnd, cwnd_); + + // move data from snd_queue to snd_buf + // sliding window, controlled by snd_nxt && sna_una+cwnd + // + // ELI5: 'snd_nxt' is what we want to send. + // 'snd_una' is what hasn't been acked yet. + // copy up to 'cwnd_' difference between them (sliding window) + 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 (e.g. 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 + // configuration: https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration + // nodelay : Whether nodelay mode is enabled, 0 is not enabled; 1 enabled. + // interval :Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms. + // resend :Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission) + // nc :Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”. + // Normal Mode: ikcp_nodelay(kcp, 0, 40, 0, 0); + // Turbo Mode: ikcp_nodelay(kcp, 1, 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 index 935b423..cdeb2ca 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: a59b1cae10a334faf807432ab472f212 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +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/Pool.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs index 81b5289..fcadb3a 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs @@ -1,46 +1,46 @@ -// Pool to avoid allocations (from libuv2k & Mirror) -using System; -using System.Collections.Generic; - -namespace kcp2k -{ - public class Pool - { - // Mirror is single threaded, no need for concurrent collections - readonly Stack objects = new Stack(); - - // some types might need additional parameters in their constructor, so - // we use a Func generator - readonly Func objectGenerator; - - // some types might need additional cleanup for returned objects - readonly Action objectResetter; - - public Pool(Func objectGenerator, Action objectResetter, int initialCapacity) - { - this.objectGenerator = objectGenerator; - this.objectResetter = objectResetter; - - // allocate an initial pool so we have fewer (if any) - // allocations in the first few frames (or seconds). - for (int i = 0; i < initialCapacity; ++i) - objects.Push(objectGenerator()); - } - - // take an element from the pool, or create a new one if empty - public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator(); - - // return an element to the pool - public void Return(T item) - { - objectResetter(item); - objects.Push(item); - } - - // clear the pool - public void Clear() => objects.Clear(); - - // count to see how many objects are in the pool. useful for tests. - public int Count => objects.Count; - } -} +// Pool to avoid allocations (from libuv2k & Mirror) +using System; +using System.Collections.Generic; + +namespace kcp2k +{ + public class Pool + { + // Mirror is single threaded, no need for concurrent collections + readonly Stack objects = new Stack(); + + // some types might need additional parameters in their constructor, so + // we use a Func generator + readonly Func objectGenerator; + + // some types might need additional cleanup for returned objects + readonly Action objectResetter; + + public Pool(Func objectGenerator, Action objectResetter, int initialCapacity) + { + this.objectGenerator = objectGenerator; + this.objectResetter = objectResetter; + + // allocate an initial pool so we have fewer (if any) + // allocations in the first few frames (or seconds). + for (int i = 0; i < initialCapacity; ++i) + objects.Push(objectGenerator()); + } + + // take an element from the pool, or create a new one if empty + public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator(); + + // return an element to the pool + public void Return(T item) + { + objectResetter(item); + objects.Push(item); + } + + // clear the pool + public void Clear() => objects.Clear(); + + // count to see how many objects are in the pool. useful for tests. + public int Count => objects.Count; + } +} diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs.meta index 5eba0e0..1967989 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 35c07818fc4784bb4ba472c8e5029002 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 35c07818fc4784bb4ba472c8e5029002 +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 index b5c9dcf..6c6ac94 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs @@ -1,62 +1,62 @@ -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 size that the receive can currently receive - 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 an 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. - // -> MTU as initial capacity to avoid most runtime resizing/allocations - internal MemoryStream data = new MemoryStream(Kcp.MTU_DEF); - - // 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); - } - } -} +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 size that the receive can currently receive + 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 an 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. + // -> MTU as initial capacity to avoid most runtime resizing/allocations + internal MemoryStream data = new MemoryStream(Kcp.MTU_DEF); + + // 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 index d14dc1a..ea792bf 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: fc58706a05dd3442c8fde858d5266855 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +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 index 45dc1a6..b347507 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs @@ -1,76 +1,76 @@ -using System.Runtime.CompilerServices; - -namespace kcp2k -{ - public static partial class Utils - { - // Clamp so we don't have to depend on UnityEngine - public static int Clamp(int value, int min, int max) - { - if (value < min) return min; - if (value > max) return max; - return value; - } - - // 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); - } - } -} +using System.Runtime.CompilerServices; + +namespace kcp2k +{ + public static partial class Utils + { + // Clamp so we don't have to depend on UnityEngine + public static int Clamp(int value, int min, int max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + + // 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 index 86118bc..a13cdee 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: ef959eb716205bd48b050f010a9a35ae -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: ef959eb716205bd48b050f010a9a35ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef index 9a90c82..f129ea4 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef @@ -1,15 +1,15 @@ -{ - "name": "kcp2k", - "references": [ - "GUID:63c380d6dae6946209ed0832388a657c" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": true, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false +{ + "name": "kcp2k", + "references": [ + "GUID:63c380d6dae6946209ed0832388a657c" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef.meta index 1d70e80..2baba0c 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 6806a62c384838046a3c66c44f06d75f -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 6806a62c384838046a3c66c44f06d75f +AssemblyDefinitionImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation.meta index 5c72cf0..0a2f112 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: e9de45e025f26411bbb52d1aefc8d5a5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: e9de45e025f26411bbb52d1aefc8d5a5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE index 0330370..1cf30f8 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2021 Mirror Networking (vis2k, FakeByte) - -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. +MIT License + +Copyright (c) 2021 Mirror Networking (vis2k, FakeByte) + +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. diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE.meta index 4fadbdf..2f81b34 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: a857d4e863bbf4a7dba70bc2cd1b5949 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a857d4e863bbf4a7dba70bc2cd1b5949 +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts.meta index 6878ad8..d100a95 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 6b7f3f8e8fa16475bbe48a8e9fbe800b -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 6b7f3f8e8fa16475bbe48a8e9fbe800b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs index 246a5d1..8d5f3be 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs @@ -1,3 +1,3 @@ -using System.Runtime.CompilerServices; - +using System.Runtime.CompilerServices; + [assembly: InternalsVisibleTo("where-allocations.Tests")] \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta index 1edb254..b50842c 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta @@ -1,3 +1,3 @@ -fileFormatVersion: 2 -guid: 158a96a7489b450485a8b06a13328871 -timeCreated: 1622356221 \ No newline at end of file +fileFormatVersion: 2 +guid: 158a96a7489b450485a8b06a13328871 +timeCreated: 1622356221 diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs index fcf18f6..ca35cac 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs @@ -1,58 +1,58 @@ -using System.Net; -using System.Net.Sockets; - -namespace WhereAllocation -{ - public static class Extensions - { - // always pass the same IPEndPointNonAlloc instead of allocating a new - // one each time. - // - // use IPEndPointNonAlloc.temp to get the latest SocketAdddress written - // by ReceiveFrom_Internal! - // - // IMPORTANT: .temp will be overwritten in next call! - // hash or manually copy it if you need to store it, e.g. - // when adding a new connection. - public static int ReceiveFrom_NonAlloc( - this Socket socket, - byte[] buffer, - int offset, - int size, - SocketFlags socketFlags, - IPEndPointNonAlloc remoteEndPoint) - { - // call ReceiveFrom with IPEndPointNonAlloc. - // need to wrap this in ReceiveFrom_NonAlloc because it's not - // obvious that IPEndPointNonAlloc.Create does NOT create a new - // IPEndPoint. it saves the result in IPEndPointNonAlloc.temp! - EndPoint casted = remoteEndPoint; - return socket.ReceiveFrom(buffer, offset, size, socketFlags, ref casted); - } - - // same as above, different parameters - public static int ReceiveFrom_NonAlloc(this Socket socket, byte[] buffer, IPEndPointNonAlloc remoteEndPoint) - { - EndPoint casted = remoteEndPoint; - return socket.ReceiveFrom(buffer, ref casted); - } - - // SendTo allocates too: - // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L2240 - // -> the allocation is in EndPoint.Serialize() - // NOTE: technically this function isn't necessary. - // could just pass IPEndPointNonAlloc. - // still good for strong typing. - public static int SendTo_NonAlloc( - this Socket socket, - byte[] buffer, - int offset, - int size, - SocketFlags socketFlags, - IPEndPointNonAlloc remoteEndPoint) - { - EndPoint casted = remoteEndPoint; - return socket.SendTo(buffer, offset, size, socketFlags, casted); - } - } +using System.Net; +using System.Net.Sockets; + +namespace WhereAllocation +{ + public static class Extensions + { + // always pass the same IPEndPointNonAlloc instead of allocating a new + // one each time. + // + // use IPEndPointNonAlloc.temp to get the latest SocketAdddress written + // by ReceiveFrom_Internal! + // + // IMPORTANT: .temp will be overwritten in next call! + // hash or manually copy it if you need to store it, e.g. + // when adding a new connection. + public static int ReceiveFrom_NonAlloc( + this Socket socket, + byte[] buffer, + int offset, + int size, + SocketFlags socketFlags, + IPEndPointNonAlloc remoteEndPoint) + { + // call ReceiveFrom with IPEndPointNonAlloc. + // need to wrap this in ReceiveFrom_NonAlloc because it's not + // obvious that IPEndPointNonAlloc.Create does NOT create a new + // IPEndPoint. it saves the result in IPEndPointNonAlloc.temp! + EndPoint casted = remoteEndPoint; + return socket.ReceiveFrom(buffer, offset, size, socketFlags, ref casted); + } + + // same as above, different parameters + public static int ReceiveFrom_NonAlloc(this Socket socket, byte[] buffer, IPEndPointNonAlloc remoteEndPoint) + { + EndPoint casted = remoteEndPoint; + return socket.ReceiveFrom(buffer, ref casted); + } + + // SendTo allocates too: + // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L2240 + // -> the allocation is in EndPoint.Serialize() + // NOTE: technically this function isn't necessary. + // could just pass IPEndPointNonAlloc. + // still good for strong typing. + public static int SendTo_NonAlloc( + this Socket socket, + byte[] buffer, + int offset, + int size, + SocketFlags socketFlags, + IPEndPointNonAlloc remoteEndPoint) + { + EndPoint casted = remoteEndPoint; + return socket.SendTo(buffer, offset, size, socketFlags, casted); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta index c4fa54d..9efaa33 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 9e801942544d44d65808fb250623fe25 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 9e801942544d44d65808fb250623fe25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs index 65eb453..44dbfe3 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs @@ -1,208 +1,208 @@ -using System; -using System.Net; -using System.Net.Sockets; - -namespace WhereAllocation -{ - public class IPEndPointNonAlloc : IPEndPoint - { - // Two steps to remove allocations in ReceiveFrom_Internal: - // - // 1.) remoteEndPoint.Serialize(): - // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1733 - // -> creates an EndPoint for ReceiveFrom_Internal to write into - // -> it's never read from: - // ReceiveFrom_Internal passes it to native: - // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1885 - // native recv populates 'sockaddr* from' with the remote address: - // https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom - // -> can NOT be null. bricks both Unity and Unity Hub otherwise. - // -> it seems as if Serialize() is only called to avoid allocating - // a 'new SocketAddress' in ReceiveFrom. it's up to the EndPoint. - // - // 2.) EndPoint.Create(SocketAddress): - // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761 - // -> SocketAddress is the remote's address that we want to return - // -> to avoid 'new EndPoint(SocketAddress), it seems up to the user - // to decide how to create a new EndPoint via .Create - // -> SocketAddress is the object that was returned by Serialize() - // - // in other words, all we need is an extra SocketAddress field that we - // can pass to ReceiveFrom_Internal to write the result into. - // => callers can then get the result from the extra field! - // => no allocations - // - // IMPORTANT: remember that IPEndPointNonAlloc is always the same object - // and never changes. only the helper field is changed. - public SocketAddress temp; - - // constructors simply create the field once by calling the base method. - // (our overwritten method would create anything new) - public IPEndPointNonAlloc(long address, int port) : base(address, port) - { - temp = base.Serialize(); - } - public IPEndPointNonAlloc(IPAddress address, int port) : base(address, port) - { - temp = base.Serialize(); - } - - // Serialize simply returns it - public override SocketAddress Serialize() => temp; - - // Create doesn't need to create anything. - // SocketAddress object is already the one we returned in Serialize(). - // ReceiveFrom_Internal simply wrote into it. - public override EndPoint Create(SocketAddress socketAddress) - { - // original IPEndPoint.Create validates: - if (socketAddress.Family != AddressFamily) - throw new ArgumentException($"Unsupported socketAddress.AddressFamily: {socketAddress.Family}. Expected: {AddressFamily}"); - if (socketAddress.Size < 8) - throw new ArgumentException($"Unsupported socketAddress.Size: {socketAddress.Size}. Expected: <8"); - - // double check to guarantee that ReceiveFrom actually did write - // into our 'temp' field. just in case that's ever changed. - if (socketAddress != temp) - { - // well this is fun. - // in the latest mono from the above github links, - // the result of Serialize() is passed as 'ref' so ReceiveFrom - // does in fact write into it. - // - // in Unity 2019 LTS's mono version, it does create a new one - // each time. this is from ILSpy Receive_From: - // - // SocketPal.CheckDualModeReceiveSupport(this); - // ValidateBlockingMode(); - // if (NetEventSource.IsEnabled) - // { - // NetEventSource.Info(this, $"SRC{LocalEndPoint} size:{size} remoteEP:{remoteEP}", "ReceiveFrom"); - // } - // EndPoint remoteEP2 = remoteEP; - // System.Net.Internals.SocketAddress socketAddress = SnapshotAndSerialize(ref remoteEP2); - // System.Net.Internals.SocketAddress socketAddress2 = IPEndPointExtensions.Serialize(remoteEP2); - // int bytesTransferred; - // SocketError socketError = SocketPal.ReceiveFrom(_handle, buffer, offset, size, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred); - // SocketException ex = null; - // if (socketError != 0) - // { - // ex = new SocketException((int)socketError); - // UpdateStatusAfterSocketError(ex); - // if (NetEventSource.IsEnabled) - // { - // NetEventSource.Error(this, ex, "ReceiveFrom"); - // } - // if (ex.SocketErrorCode != SocketError.MessageSize) - // { - // throw ex; - // } - // } - // if (!socketAddress2.Equals(socketAddress)) - // { - // try - // { - // remoteEP = remoteEP2.Create(socketAddress); - // } - // catch - // { - // } - // if (_rightEndPoint == null) - // { - // _rightEndPoint = remoteEP2; - // } - // } - // if (ex != null) - // { - // throw ex; - // } - // if (NetEventSource.IsEnabled) - // { - // NetEventSource.DumpBuffer(this, buffer, offset, size, "ReceiveFrom"); - // NetEventSource.Exit(this, bytesTransferred, "ReceiveFrom"); - // } - // return bytesTransferred; - // - - // so until they upgrade their mono version, we are stuck with - // some allocations. - // - // for now, let's pass the newly created on to our temp so at - // least we reuse it next time. - temp = socketAddress; - - // SocketAddress.GetHashCode() depends on SocketAddress.m_changed. - // ReceiveFrom only sets the buffer, it does not seem to set m_changed. - // we need to reset m_changed for two reasons: - // * if m_changed is false, GetHashCode() returns the cahced m_hash - // which is '0'. that would be a problem. - // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L262 - // * if we have a cached m_hash, but ReceiveFrom modified the buffer - // then the GetHashCode() should change too. so we need to reset - // either way. - // - // the only way to do that is by _actually_ modifying the buffer: - // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L99 - // so let's do that. - // -> unchecked in case it's byte.Max - unchecked - { - temp[0] += 1; - temp[0] -= 1; - } - - // make sure this worked. - // at least throw an Exception to make it obvious if the trick does - // not work anymore, in case ReceiveFrom is ever changed. - if (temp.GetHashCode() == 0) - throw new Exception($"SocketAddress GetHashCode() is 0 after ReceiveFrom. Does the m_changed trick not work anymore?"); - - // in the future, enable this again: - //throw new Exception($"Socket.ReceiveFrom(): passed SocketAddress={socketAddress} but expected {temp}. This should never happen. Did ReceiveFrom() change?"); - } - - // ReceiveFrom sets seed_endpoint to the result of Create(): - // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1764 - // so let's return ourselves at least. - // (seed_endpoint only seems to matter for BeginSend etc.) - return this; - } - - // we need to overwrite GetHashCode() for two reasons. - // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPEndPoint.cs#L160 - // * it uses m_Address. but our true SocketAddress is in m_temp. - // m_Address might not be set at all. - // * m_Address.GetHashCode() allocates: - // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699 - public override int GetHashCode() => temp.GetHashCode(); - - // helper function to create an ACTUAL new IPEndPoint from this. - // server needs it to store new connections as unique IPEndPoints. - public IPEndPoint DeepCopyIPEndPoint() - { - // we need to create a new IPEndPoint from 'temp' SocketAddress. - // there is no 'new IPEndPoint(SocketAddress) constructor. - // so we need to be a bit creative... - - // allocate a placeholder IPAddress to copy - // our SocketAddress into. - // -> needs to be the same address family. - IPAddress ipAddress; - if (temp.Family == AddressFamily.InterNetworkV6) - ipAddress = IPAddress.IPv6Any; - else if (temp.Family == AddressFamily.InterNetwork) - ipAddress = IPAddress.Any; - else - throw new Exception($"Unexpected SocketAddress family: {temp.Family}"); - - // allocate a placeholder IPEndPoint - // with the needed size form IPAddress. - // (the real class. not NonAlloc) - IPEndPoint placeholder = new IPEndPoint(ipAddress, 0); - - // the real IPEndPoint's .Create function can create a new IPEndPoint - // copy from a SocketAddress. - return (IPEndPoint)placeholder.Create(temp); - } - } +using System; +using System.Net; +using System.Net.Sockets; + +namespace WhereAllocation +{ + public class IPEndPointNonAlloc : IPEndPoint + { + // Two steps to remove allocations in ReceiveFrom_Internal: + // + // 1.) remoteEndPoint.Serialize(): + // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1733 + // -> creates an EndPoint for ReceiveFrom_Internal to write into + // -> it's never read from: + // ReceiveFrom_Internal passes it to native: + // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1885 + // native recv populates 'sockaddr* from' with the remote address: + // https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom + // -> can NOT be null. bricks both Unity and Unity Hub otherwise. + // -> it seems as if Serialize() is only called to avoid allocating + // a 'new SocketAddress' in ReceiveFrom. it's up to the EndPoint. + // + // 2.) EndPoint.Create(SocketAddress): + // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761 + // -> SocketAddress is the remote's address that we want to return + // -> to avoid 'new EndPoint(SocketAddress), it seems up to the user + // to decide how to create a new EndPoint via .Create + // -> SocketAddress is the object that was returned by Serialize() + // + // in other words, all we need is an extra SocketAddress field that we + // can pass to ReceiveFrom_Internal to write the result into. + // => callers can then get the result from the extra field! + // => no allocations + // + // IMPORTANT: remember that IPEndPointNonAlloc is always the same object + // and never changes. only the helper field is changed. + public SocketAddress temp; + + // constructors simply create the field once by calling the base method. + // (our overwritten method would create anything new) + public IPEndPointNonAlloc(long address, int port) : base(address, port) + { + temp = base.Serialize(); + } + public IPEndPointNonAlloc(IPAddress address, int port) : base(address, port) + { + temp = base.Serialize(); + } + + // Serialize simply returns it + public override SocketAddress Serialize() => temp; + + // Create doesn't need to create anything. + // SocketAddress object is already the one we returned in Serialize(). + // ReceiveFrom_Internal simply wrote into it. + public override EndPoint Create(SocketAddress socketAddress) + { + // original IPEndPoint.Create validates: + if (socketAddress.Family != AddressFamily) + throw new ArgumentException($"Unsupported socketAddress.AddressFamily: {socketAddress.Family}. Expected: {AddressFamily}"); + if (socketAddress.Size < 8) + throw new ArgumentException($"Unsupported socketAddress.Size: {socketAddress.Size}. Expected: <8"); + + // double check to guarantee that ReceiveFrom actually did write + // into our 'temp' field. just in case that's ever changed. + if (socketAddress != temp) + { + // well this is fun. + // in the latest mono from the above github links, + // the result of Serialize() is passed as 'ref' so ReceiveFrom + // does in fact write into it. + // + // in Unity 2019 LTS's mono version, it does create a new one + // each time. this is from ILSpy Receive_From: + // + // SocketPal.CheckDualModeReceiveSupport(this); + // ValidateBlockingMode(); + // if (NetEventSource.IsEnabled) + // { + // NetEventSource.Info(this, $"SRC{LocalEndPoint} size:{size} remoteEP:{remoteEP}", "ReceiveFrom"); + // } + // EndPoint remoteEP2 = remoteEP; + // System.Net.Internals.SocketAddress socketAddress = SnapshotAndSerialize(ref remoteEP2); + // System.Net.Internals.SocketAddress socketAddress2 = IPEndPointExtensions.Serialize(remoteEP2); + // int bytesTransferred; + // SocketError socketError = SocketPal.ReceiveFrom(_handle, buffer, offset, size, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred); + // SocketException ex = null; + // if (socketError != 0) + // { + // ex = new SocketException((int)socketError); + // UpdateStatusAfterSocketError(ex); + // if (NetEventSource.IsEnabled) + // { + // NetEventSource.Error(this, ex, "ReceiveFrom"); + // } + // if (ex.SocketErrorCode != SocketError.MessageSize) + // { + // throw ex; + // } + // } + // if (!socketAddress2.Equals(socketAddress)) + // { + // try + // { + // remoteEP = remoteEP2.Create(socketAddress); + // } + // catch + // { + // } + // if (_rightEndPoint == null) + // { + // _rightEndPoint = remoteEP2; + // } + // } + // if (ex != null) + // { + // throw ex; + // } + // if (NetEventSource.IsEnabled) + // { + // NetEventSource.DumpBuffer(this, buffer, offset, size, "ReceiveFrom"); + // NetEventSource.Exit(this, bytesTransferred, "ReceiveFrom"); + // } + // return bytesTransferred; + // + + // so until they upgrade their mono version, we are stuck with + // some allocations. + // + // for now, let's pass the newly created on to our temp so at + // least we reuse it next time. + temp = socketAddress; + + // SocketAddress.GetHashCode() depends on SocketAddress.m_changed. + // ReceiveFrom only sets the buffer, it does not seem to set m_changed. + // we need to reset m_changed for two reasons: + // * if m_changed is false, GetHashCode() returns the cahced m_hash + // which is '0'. that would be a problem. + // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L262 + // * if we have a cached m_hash, but ReceiveFrom modified the buffer + // then the GetHashCode() should change too. so we need to reset + // either way. + // + // the only way to do that is by _actually_ modifying the buffer: + // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L99 + // so let's do that. + // -> unchecked in case it's byte.Max + unchecked + { + temp[0] += 1; + temp[0] -= 1; + } + + // make sure this worked. + // at least throw an Exception to make it obvious if the trick does + // not work anymore, in case ReceiveFrom is ever changed. + if (temp.GetHashCode() == 0) + throw new Exception($"SocketAddress GetHashCode() is 0 after ReceiveFrom. Does the m_changed trick not work anymore?"); + + // in the future, enable this again: + //throw new Exception($"Socket.ReceiveFrom(): passed SocketAddress={socketAddress} but expected {temp}. This should never happen. Did ReceiveFrom() change?"); + } + + // ReceiveFrom sets seed_endpoint to the result of Create(): + // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1764 + // so let's return ourselves at least. + // (seed_endpoint only seems to matter for BeginSend etc.) + return this; + } + + // we need to overwrite GetHashCode() for two reasons. + // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPEndPoint.cs#L160 + // * it uses m_Address. but our true SocketAddress is in m_temp. + // m_Address might not be set at all. + // * m_Address.GetHashCode() allocates: + // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699 + public override int GetHashCode() => temp.GetHashCode(); + + // helper function to create an ACTUAL new IPEndPoint from this. + // server needs it to store new connections as unique IPEndPoints. + public IPEndPoint DeepCopyIPEndPoint() + { + // we need to create a new IPEndPoint from 'temp' SocketAddress. + // there is no 'new IPEndPoint(SocketAddress) constructor. + // so we need to be a bit creative... + + // allocate a placeholder IPAddress to copy + // our SocketAddress into. + // -> needs to be the same address family. + IPAddress ipAddress; + if (temp.Family == AddressFamily.InterNetworkV6) + ipAddress = IPAddress.IPv6Any; + else if (temp.Family == AddressFamily.InterNetwork) + ipAddress = IPAddress.Any; + else + throw new Exception($"Unexpected SocketAddress family: {temp.Family}"); + + // allocate a placeholder IPEndPoint + // with the needed size form IPAddress. + // (the real class. not NonAlloc) + IPEndPoint placeholder = new IPEndPoint(ipAddress, 0); + + // the real IPEndPoint's .Create function can create a new IPEndPoint + // copy from a SocketAddress. + return (IPEndPoint)placeholder.Create(temp); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta index ef424ba..48c05ce 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: af0279d15e39b484792394f1d3cad4d9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: af0279d15e39b484792394f1d3cad4d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef index a185c2b..ea09da3 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef @@ -1,13 +1,13 @@ -{ - "name": "where-allocations", - "references": [], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false +{ + "name": "where-allocations", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta index ce96c63..a2b980d 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 63c380d6dae6946209ed0832388a657c -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 63c380d6dae6946209ed0832388a657c +AssemblyDefinitionImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION index 8341d28..6fb4990 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION @@ -1,2 +1,2 @@ -V0.1 [2021-06-01] +V0.1 [2021-06-01] - initial release \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION.meta b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION.meta index 67ab688..6ea7700 100644 --- a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION.meta +++ b/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: f1256cadc037546ccb66071784fce137 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f1256cadc037546ccb66071784fce137 +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/LRM.meta b/Assets/Mirror/Runtime/Transport/LRM.meta index 9fd900d..e19232a 100644 --- a/Assets/Mirror/Runtime/Transport/LRM.meta +++ b/Assets/Mirror/Runtime/Transport/LRM.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 6bc259d9cf1aa444b824d3aa82a0ef28 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 6bc259d9cf1aa444b824d3aa82a0ef28 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs b/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs index 507c85f..e6f7d0f 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs @@ -1,56 +1,56 @@ -// -// Source: https://stackoverflow.com/questions/255341/getting-multiple-keys-of-specified-value-of-a-generic-dictionary#255630 -// -using System; -using System.Collections.Generic; - -namespace LightReflectiveMirror -{ - class BiDictionary - { - IDictionary firstToSecond = new Dictionary(); - IDictionary secondToFirst = new Dictionary(); - - public void Add(TFirst first, TSecond second) - { - if (firstToSecond.ContainsKey(first) || - secondToFirst.ContainsKey(second)) - { - throw new ArgumentException("Duplicate first or second"); - } - firstToSecond.Add(first, second); - secondToFirst.Add(second, first); - } - - public bool TryGetByFirst(TFirst first, out TSecond second) - { - return firstToSecond.TryGetValue(first, out second); - } - - public void Remove(TFirst first) - { - secondToFirst.Remove(firstToSecond[first]); - firstToSecond.Remove(first); - } - - public ICollection GetAllKeys() - { - return secondToFirst.Values; - } - - public bool TryGetBySecond(TSecond second, out TFirst first) - { - return secondToFirst.TryGetValue(second, out first); - } - - public TSecond GetByFirst(TFirst first) - { - return firstToSecond[first]; - } - - public TFirst GetBySecond(TSecond second) - { - return secondToFirst[second]; - } - } +// +// Source: https://stackoverflow.com/questions/255341/getting-multiple-keys-of-specified-value-of-a-generic-dictionary#255630 +// +using System; +using System.Collections.Generic; + +namespace LightReflectiveMirror +{ + class BiDictionary + { + IDictionary firstToSecond = new Dictionary(); + IDictionary secondToFirst = new Dictionary(); + + public void Add(TFirst first, TSecond second) + { + if (firstToSecond.ContainsKey(first) || + secondToFirst.ContainsKey(second)) + { + throw new ArgumentException("Duplicate first or second"); + } + firstToSecond.Add(first, second); + secondToFirst.Add(second, first); + } + + public bool TryGetByFirst(TFirst first, out TSecond second) + { + return firstToSecond.TryGetValue(first, out second); + } + + public void Remove(TFirst first) + { + secondToFirst.Remove(firstToSecond[first]); + firstToSecond.Remove(first); + } + + public ICollection GetAllKeys() + { + return secondToFirst.Values; + } + + public bool TryGetBySecond(TSecond second, out TFirst first) + { + return secondToFirst.TryGetValue(second, out first); + } + + public TSecond GetByFirst(TFirst first) + { + return firstToSecond[first]; + } + + public TFirst GetBySecond(TSecond second) + { + return secondToFirst[second]; + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs.meta index 6de220d..2d1915e 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/BiDictionary.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 8914d2a366f983644878d8499a097f9d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 8914d2a366f983644878d8499a097f9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/Editor.meta b/Assets/Mirror/Runtime/Transport/LRM/Editor.meta index 7ab3d9c..eace576 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/Editor.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/Editor.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: c9c76ba5b4c19024dabfcd3d608ae0d7 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: c9c76ba5b4c19024dabfcd3d608ae0d7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs b/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs index 225c75d..1dee152 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs @@ -1,311 +1,311 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -using System.Net; -using System.Reflection; -using System.Linq; -using Mirror; -using Mirror.SimpleWeb; -using System; -using kcp2k; - -namespace LightReflectiveMirror -{ -#if UNITY_EDITOR - [CustomEditor(typeof(LightReflectiveMirrorTransport))] - public class LRMInspector : Editor - { - int serverPort = 7777; - string serverIP; - float invalidServerIP = 0; - bool usingLLB = false; - LRMDirectConnectModule directModule; - string[] tabs = new string[] { "LRM Settings", "NAT Punch", "Load Balancer", "Other" }; - int currentTab = 0; - Type[] supportedTransports = new Type[3] { typeof(KcpTransport), typeof(SimpleWebTransport), typeof(TelepathyTransport) }; - - public override void OnInspectorGUI() - { - var lrm = (LightReflectiveMirrorTransport)target; - directModule = lrm.GetComponent(); - - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - GUILayout.Label(Resources.Load("LRM"), GUILayout.Height(50), GUILayout.Width(100)); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - if (string.IsNullOrEmpty(lrm.loadBalancerAddress)) - { - // First setup screen, ask if they are using LLB or just a single LRM node. - EditorGUILayout.HelpBox("Thank you for using LRM!\nTo get started, please select which setup you are using.", MessageType.None); - - if (GUILayout.Button("Load Balancer Setup")) - { - usingLLB = true; - lrm.loadBalancerAddress = "127.0.0.1"; - serverPort = 7070; - } - - if (GUILayout.Button("Single LRM Node Setup")) - { - lrm.loadBalancerAddress = "127.0.0.1"; - lrm.useLoadBalancer = false; - usingLLB = false; - serverIP = "172.105.109.117"; - } - } - else if (usingLLB) - { - // They said they are using LLB, configure it! - EditorGUILayout.HelpBox("The Load Balancer is another server that is different than the LRM node. Please enter the IP address or domain name of your Load Balancer server, along with the port.", MessageType.None); - EditorGUILayout.HelpBox("Acceptable Examples: 127.0.0.1, mydomain.com", MessageType.Info); - if (Time.realtimeSinceStartup - invalidServerIP < 5) - EditorGUILayout.HelpBox("Invalid Server Address!", MessageType.Error); - - serverIP = EditorGUILayout.TextField("Server Address", serverIP); - serverPort = Mathf.Clamp(EditorGUILayout.IntField("Server Port", serverPort), ushort.MinValue, ushort.MaxValue); - - if (GUILayout.Button("Continue")) - { - if (IPAddress.TryParse(serverIP, out IPAddress serverAddr)) - { - lrm.loadBalancerAddress = serverAddr.ToString(); - lrm.loadBalancerPort = (ushort)serverPort; - lrm.serverIP = "127.0.0.1"; - lrm.useLoadBalancer = true; - usingLLB = false; - serverIP = ""; - } - else - { - try - { - if (Dns.GetHostEntry(serverIP).AddressList.Length > 0) - { - lrm.loadBalancerAddress = serverIP; - lrm.loadBalancerPort = (ushort)serverPort; - lrm.serverIP = "127.0.0.1"; - usingLLB = false; - serverIP = ""; - } - else - invalidServerIP = Time.realtimeSinceStartup; - } - catch - { - invalidServerIP = Time.realtimeSinceStartup; - } - } - } - } - else if (lrm.clientToServerTransport == null) - { - // next up, the actual transport. We are going to loop over all the transport types here and let them select one. - EditorGUILayout.HelpBox("We need to use the same transport used on the server. Please select the same transport used on your LRM Node(s)", MessageType.None); - - foreach (var transport in supportedTransports) - { - if (GUILayout.Button(transport.Name)) - { - var newTransportGO = new GameObject("LRM - Connector"); - newTransportGO.transform.SetParent(lrm.transform); - var newTransport = newTransportGO.AddComponent(transport); - lrm.clientToServerTransport = (Transport)newTransport; - } - } - } - else if (string.IsNullOrEmpty(lrm.serverIP)) - { - // Empty server IP, this is pretty important! Lets show the UI to require it. - EditorGUILayout.HelpBox("For a single LRM node setup, we need the IP address or domain name of your LRM server.", MessageType.None); - EditorGUILayout.HelpBox("Acceptable Examples: 172.105.109.117, mydomain.com", MessageType.Info); - - if (Time.realtimeSinceStartup - invalidServerIP < 5) - EditorGUILayout.HelpBox("Invalid Server Address!", MessageType.Error); - - serverIP = EditorGUILayout.TextField("Server Address", serverIP); - serverPort = Mathf.Clamp(EditorGUILayout.IntField("Server Port", serverPort), ushort.MinValue, ushort.MaxValue); - - if (GUILayout.Button("Continue")) - { - if (IPAddress.TryParse(serverIP, out IPAddress serverAddr)) - { - lrm.serverIP = serverAddr.ToString(); - lrm.SetTransportPort((ushort)serverPort); - } - else - { - try - { - if (Dns.GetHostEntry(serverIP).AddressList.Length > 0) - { - lrm.serverIP = serverIP; - lrm.SetTransportPort((ushort)serverPort); - } - else - invalidServerIP = Time.realtimeSinceStartup; - } - catch - { - invalidServerIP = Time.realtimeSinceStartup; - } - } - } - } - else if (lrm.NATPunchtroughPort < 0) - { - // NAT Punchthrough configuration. - EditorGUILayout.HelpBox("Do you wish to use NAT Punchthrough? This can reduce load by up to 80% on your LRM nodes, but exposes players IP's to other players.", MessageType.None); - - if (GUILayout.Button("Use NAT Punchthrough")) - { - lrm.NATPunchtroughPort = 1; - lrm.useNATPunch = true; - lrm.gameObject.AddComponent(); - } - - if (GUILayout.Button("Do NOT use NAT Punchthrough")) - lrm.NATPunchtroughPort = 1; - - } - else if (directModule != null && directModule.directConnectTransport == null) - { - // NAT Punchthrough setup. - EditorGUILayout.HelpBox("To use direct connecting, we need a transport to communicate with the other clients. Please select a transport to use.", MessageType.None); - - foreach (var transport in supportedTransports) - { - if (lrm.useNATPunch && transport != typeof(KcpTransport)) - continue; - - if (GUILayout.Button(transport.Name)) - { - var newTransportGO = new GameObject("LRM - Direct Connect"); - newTransportGO.transform.SetParent(lrm.transform); - var newTransport = newTransportGO.AddComponent(transport); - directModule.directConnectTransport = (Transport)newTransport; - } - } - } - else - { - // They have completed the "setup guide" Show them the main UI - - // Remove unused transports... - foreach (var transport in lrm.GetComponentsInChildren()) - { - if (!(transport is LightReflectiveMirrorTransport)) - { - if (transport != lrm.clientToServerTransport && (directModule == null ? true : directModule.directConnectTransport != transport)) - { - if (transport.gameObject == lrm.gameObject) - DestroyImmediate(transport); - else - DestroyImmediate(transport.gameObject); - } - } - } - - currentTab = GUILayout.Toolbar(currentTab, tabs); - EditorGUILayout.Space(); - - EditorGUILayout.BeginVertical("Window"); - switch (currentTab) - { - case 0: - using (var change = new EditorGUI.ChangeCheckScope()) - { - - - // They are in the LRM Settings tab. - if (lrm.useLoadBalancer) - { - EditorGUILayout.HelpBox("While using a Load Balancer, you don't set the LRM node IP or port.", MessageType.Info); - GUI.enabled = false; - } - lrm.serverIP = EditorGUILayout.TextField("LRM Node IP", lrm.serverIP); - lrm.serverPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("LRM Node Port", lrm.serverPort), ushort.MinValue, ushort.MaxValue); - lrm.endpointServerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Endpoint Port", lrm.endpointServerPort), ushort.MinValue, ushort.MaxValue); - - if (lrm.useLoadBalancer) - { - GUI.enabled = true; - } - - lrm.authenticationKey = EditorGUILayout.TextField("LRM Auth Key", lrm.authenticationKey); - lrm.heartBeatInterval = EditorGUILayout.Slider("Heartbeat Time", lrm.heartBeatInterval, 0.1f, 5f); - lrm.connectOnAwake = EditorGUILayout.Toggle("Connect on Awake", lrm.connectOnAwake); - lrm.clientToServerTransport = (Transport)EditorGUILayout.ObjectField("LRM Transport", lrm.clientToServerTransport, typeof(Transport), true); - if (change.changed) - { - EditorUtility.SetDirty(lrm); - } - } - serializedObject.ApplyModifiedProperties(); - break; - case 1: - // NAT punch tab. - if (directModule == null) - { - EditorGUILayout.HelpBox("NAT Punchthrough disabled, missing Direct Connect.", MessageType.Info); - if (GUILayout.Button("Add Direct Connect")) - lrm.gameObject.AddComponent(); - } - else - { - if (!(directModule.directConnectTransport is KcpTransport)) - { - EditorGUILayout.HelpBox("NAT Punch only supports KCP currently.", MessageType.Info); - GUI.enabled = false; - lrm.useNATPunch = false; - } - - lrm.useNATPunch = EditorGUILayout.Toggle("Use NAT Punch", lrm.useNATPunch); - GUI.enabled = true; - directModule.directConnectTransport = (Transport)EditorGUILayout.ObjectField("Direct Transport", directModule.directConnectTransport, typeof(Transport), true); - } - serializedObject.ApplyModifiedProperties(); - break; - case 2: - // Load balancer tab - lrm.useLoadBalancer = EditorGUILayout.Toggle("Use Load Balancer", lrm.useLoadBalancer); - if (!lrm.useLoadBalancer) - GUI.enabled = false; - lrm.loadBalancerAddress = EditorGUILayout.TextField("Load Balancer Address", lrm.loadBalancerAddress); - lrm.loadBalancerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Load Balancer Port", lrm.loadBalancerPort), ushort.MinValue, ushort.MaxValue); - lrm.region = (LRMRegions)EditorGUILayout.EnumPopup("Node Region", lrm.region); - if (!lrm.useLoadBalancer) - GUI.enabled = true; - serializedObject.ApplyModifiedProperties(); - break; - case 3: - // Other tab... - - GUI.enabled = false; - EditorGUILayout.TextField("Server Status", lrm.serverStatus); - EditorGUILayout.TextField("Server ID", string.IsNullOrEmpty(lrm.serverId) ? "Not Hosting." : lrm.serverId); - GUI.enabled = true; - - EditorGUILayout.Space(); - - lrm.serverName = EditorGUILayout.TextField("Server Name", lrm.serverName); - lrm.extraServerData = EditorGUILayout.TextField("Extra Server Data", lrm.extraServerData); - lrm.maxServerPlayers = EditorGUILayout.IntField("Max Server Players", lrm.maxServerPlayers); - lrm.isPublicServer = EditorGUILayout.Toggle("Is Public Server", lrm.isPublicServer); - - EditorGUILayout.Space(); - EditorGUILayout.Space(); - EditorGUILayout.PropertyField(serializedObject.FindProperty("connectedToRelay")); - EditorGUILayout.PropertyField(serializedObject.FindProperty("disconnectedFromRelay")); - EditorGUILayout.PropertyField(serializedObject.FindProperty("serverListUpdated")); - serializedObject.ApplyModifiedProperties(); - break; - } - EditorGUILayout.EndVertical(); - } - } - } -#endif +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using System.Net; +using System.Reflection; +using System.Linq; +using Mirror; +using Mirror.SimpleWeb; +using System; +using kcp2k; + +namespace LightReflectiveMirror +{ +#if UNITY_EDITOR + [CustomEditor(typeof(LightReflectiveMirrorTransport))] + public class LRMInspector : Editor + { + int serverPort = 7777; + string serverIP; + float invalidServerIP = 0; + bool usingLLB = false; + LRMDirectConnectModule directModule; + string[] tabs = new string[] { "LRM Settings", "NAT Punch", "Load Balancer", "Other" }; + int currentTab = 0; + Type[] supportedTransports = new Type[3] { typeof(KcpTransport), typeof(SimpleWebTransport), typeof(TelepathyTransport) }; + + public override void OnInspectorGUI() + { + var lrm = (LightReflectiveMirrorTransport)target; + directModule = lrm.GetComponent(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(Resources.Load("LRM"), GUILayout.Height(50), GUILayout.Width(100)); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + if (string.IsNullOrEmpty(lrm.loadBalancerAddress)) + { + // First setup screen, ask if they are using LLB or just a single LRM node. + EditorGUILayout.HelpBox("Thank you for using LRM!\nTo get started, please select which setup you are using.", MessageType.None); + + if (GUILayout.Button("Load Balancer Setup")) + { + usingLLB = true; + lrm.loadBalancerAddress = "127.0.0.1"; + serverPort = 7070; + } + + if (GUILayout.Button("Single LRM Node Setup")) + { + lrm.loadBalancerAddress = "127.0.0.1"; + lrm.useLoadBalancer = false; + usingLLB = false; + serverIP = "172.105.109.117"; + } + } + else if (usingLLB) + { + // They said they are using LLB, configure it! + EditorGUILayout.HelpBox("The Load Balancer is another server that is different than the LRM node. Please enter the IP address or domain name of your Load Balancer server, along with the port.", MessageType.None); + EditorGUILayout.HelpBox("Acceptable Examples: 127.0.0.1, mydomain.com", MessageType.Info); + if (Time.realtimeSinceStartup - invalidServerIP < 5) + EditorGUILayout.HelpBox("Invalid Server Address!", MessageType.Error); + + serverIP = EditorGUILayout.TextField("Server Address", serverIP); + serverPort = Mathf.Clamp(EditorGUILayout.IntField("Server Port", serverPort), ushort.MinValue, ushort.MaxValue); + + if (GUILayout.Button("Continue")) + { + if (IPAddress.TryParse(serverIP, out IPAddress serverAddr)) + { + lrm.loadBalancerAddress = serverAddr.ToString(); + lrm.loadBalancerPort = (ushort)serverPort; + lrm.serverIP = "127.0.0.1"; + lrm.useLoadBalancer = true; + usingLLB = false; + serverIP = ""; + } + else + { + try + { + if (Dns.GetHostEntry(serverIP).AddressList.Length > 0) + { + lrm.loadBalancerAddress = serverIP; + lrm.loadBalancerPort = (ushort)serverPort; + lrm.serverIP = "127.0.0.1"; + usingLLB = false; + serverIP = ""; + } + else + invalidServerIP = Time.realtimeSinceStartup; + } + catch + { + invalidServerIP = Time.realtimeSinceStartup; + } + } + } + } + else if (lrm.clientToServerTransport == null) + { + // next up, the actual transport. We are going to loop over all the transport types here and let them select one. + EditorGUILayout.HelpBox("We need to use the same transport used on the server. Please select the same transport used on your LRM Node(s)", MessageType.None); + + foreach (var transport in supportedTransports) + { + if (GUILayout.Button(transport.Name)) + { + var newTransportGO = new GameObject("LRM - Connector"); + newTransportGO.transform.SetParent(lrm.transform); + var newTransport = newTransportGO.AddComponent(transport); + lrm.clientToServerTransport = (Transport)newTransport; + } + } + } + else if (string.IsNullOrEmpty(lrm.serverIP)) + { + // Empty server IP, this is pretty important! Lets show the UI to require it. + EditorGUILayout.HelpBox("For a single LRM node setup, we need the IP address or domain name of your LRM server.", MessageType.None); + EditorGUILayout.HelpBox("Acceptable Examples: 172.105.109.117, mydomain.com", MessageType.Info); + + if (Time.realtimeSinceStartup - invalidServerIP < 5) + EditorGUILayout.HelpBox("Invalid Server Address!", MessageType.Error); + + serverIP = EditorGUILayout.TextField("Server Address", serverIP); + serverPort = Mathf.Clamp(EditorGUILayout.IntField("Server Port", serverPort), ushort.MinValue, ushort.MaxValue); + + if (GUILayout.Button("Continue")) + { + if (IPAddress.TryParse(serverIP, out IPAddress serverAddr)) + { + lrm.serverIP = serverAddr.ToString(); + lrm.SetTransportPort((ushort)serverPort); + } + else + { + try + { + if (Dns.GetHostEntry(serverIP).AddressList.Length > 0) + { + lrm.serverIP = serverIP; + lrm.SetTransportPort((ushort)serverPort); + } + else + invalidServerIP = Time.realtimeSinceStartup; + } + catch + { + invalidServerIP = Time.realtimeSinceStartup; + } + } + } + } + else if (lrm.NATPunchtroughPort < 0) + { + // NAT Punchthrough configuration. + EditorGUILayout.HelpBox("Do you wish to use NAT Punchthrough? This can reduce load by up to 80% on your LRM nodes, but exposes players IP's to other players.", MessageType.None); + + if (GUILayout.Button("Use NAT Punchthrough")) + { + lrm.NATPunchtroughPort = 1; + lrm.useNATPunch = true; + lrm.gameObject.AddComponent(); + } + + if (GUILayout.Button("Do NOT use NAT Punchthrough")) + lrm.NATPunchtroughPort = 1; + + } + else if (directModule != null && directModule.directConnectTransport == null) + { + // NAT Punchthrough setup. + EditorGUILayout.HelpBox("To use direct connecting, we need a transport to communicate with the other clients. Please select a transport to use.", MessageType.None); + + foreach (var transport in supportedTransports) + { + if (lrm.useNATPunch && transport != typeof(KcpTransport)) + continue; + + if (GUILayout.Button(transport.Name)) + { + var newTransportGO = new GameObject("LRM - Direct Connect"); + newTransportGO.transform.SetParent(lrm.transform); + var newTransport = newTransportGO.AddComponent(transport); + directModule.directConnectTransport = (Transport)newTransport; + } + } + } + else + { + // They have completed the "setup guide" Show them the main UI + + // Remove unused transports... + foreach (var transport in lrm.GetComponentsInChildren()) + { + if (!(transport is LightReflectiveMirrorTransport)) + { + if (transport != lrm.clientToServerTransport && (directModule == null ? true : directModule.directConnectTransport != transport)) + { + if (transport.gameObject == lrm.gameObject) + DestroyImmediate(transport); + else + DestroyImmediate(transport.gameObject); + } + } + } + + currentTab = GUILayout.Toolbar(currentTab, tabs); + EditorGUILayout.Space(); + + EditorGUILayout.BeginVertical("Window"); + switch (currentTab) + { + case 0: + using (var change = new EditorGUI.ChangeCheckScope()) + { + + + // They are in the LRM Settings tab. + if (lrm.useLoadBalancer) + { + EditorGUILayout.HelpBox("While using a Load Balancer, you don't set the LRM node IP or port.", MessageType.Info); + GUI.enabled = false; + } + lrm.serverIP = EditorGUILayout.TextField("LRM Node IP", lrm.serverIP); + lrm.serverPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("LRM Node Port", lrm.serverPort), ushort.MinValue, ushort.MaxValue); + lrm.endpointServerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Endpoint Port", lrm.endpointServerPort), ushort.MinValue, ushort.MaxValue); + + if (lrm.useLoadBalancer) + { + GUI.enabled = true; + } + + lrm.authenticationKey = EditorGUILayout.TextField("LRM Auth Key", lrm.authenticationKey); + lrm.heartBeatInterval = EditorGUILayout.Slider("Heartbeat Time", lrm.heartBeatInterval, 0.1f, 5f); + lrm.connectOnAwake = EditorGUILayout.Toggle("Connect on Awake", lrm.connectOnAwake); + lrm.clientToServerTransport = (Transport)EditorGUILayout.ObjectField("LRM Transport", lrm.clientToServerTransport, typeof(Transport), true); + if (change.changed) + { + EditorUtility.SetDirty(lrm); + } + } + serializedObject.ApplyModifiedProperties(); + break; + case 1: + // NAT punch tab. + if (directModule == null) + { + EditorGUILayout.HelpBox("NAT Punchthrough disabled, missing Direct Connect.", MessageType.Info); + if (GUILayout.Button("Add Direct Connect")) + lrm.gameObject.AddComponent(); + } + else + { + if (!(directModule.directConnectTransport is KcpTransport)) + { + EditorGUILayout.HelpBox("NAT Punch only supports KCP currently.", MessageType.Info); + GUI.enabled = false; + lrm.useNATPunch = false; + } + + lrm.useNATPunch = EditorGUILayout.Toggle("Use NAT Punch", lrm.useNATPunch); + GUI.enabled = true; + directModule.directConnectTransport = (Transport)EditorGUILayout.ObjectField("Direct Transport", directModule.directConnectTransport, typeof(Transport), true); + } + serializedObject.ApplyModifiedProperties(); + break; + case 2: + // Load balancer tab + lrm.useLoadBalancer = EditorGUILayout.Toggle("Use Load Balancer", lrm.useLoadBalancer); + if (!lrm.useLoadBalancer) + GUI.enabled = false; + lrm.loadBalancerAddress = EditorGUILayout.TextField("Load Balancer Address", lrm.loadBalancerAddress); + lrm.loadBalancerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Load Balancer Port", lrm.loadBalancerPort), ushort.MinValue, ushort.MaxValue); + lrm.region = (LRMRegions)EditorGUILayout.EnumPopup("Node Region", lrm.region); + if (!lrm.useLoadBalancer) + GUI.enabled = true; + serializedObject.ApplyModifiedProperties(); + break; + case 3: + // Other tab... + + GUI.enabled = false; + EditorGUILayout.TextField("Server Status", lrm.serverStatus); + EditorGUILayout.TextField("Server ID", string.IsNullOrEmpty(lrm.serverId) ? "Not Hosting." : lrm.serverId); + GUI.enabled = true; + + EditorGUILayout.Space(); + + lrm.serverName = EditorGUILayout.TextField("Server Name", lrm.serverName); + lrm.extraServerData = EditorGUILayout.TextField("Extra Server Data", lrm.extraServerData); + lrm.maxServerPlayers = EditorGUILayout.IntField("Max Server Players", lrm.maxServerPlayers); + lrm.isPublicServer = EditorGUILayout.Toggle("Is Public Server", lrm.isPublicServer); + + EditorGUILayout.Space(); + EditorGUILayout.Space(); + EditorGUILayout.PropertyField(serializedObject.FindProperty("connectedToRelay")); + EditorGUILayout.PropertyField(serializedObject.FindProperty("disconnectedFromRelay")); + EditorGUILayout.PropertyField(serializedObject.FindProperty("serverListUpdated")); + serializedObject.ApplyModifiedProperties(); + break; + } + EditorGUILayout.EndVertical(); + } + } + } +#endif } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs.meta index f1fe928..f96441d 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/Editor/LRMInspector.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: ebe9be4a049785a41ac7c27b7b2c82d1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: ebe9be4a049785a41ac7c27b7b2c82d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs b/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs index e4a08d0..c10ad67 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs @@ -1,230 +1,230 @@ -// -------------------------------------------------------------------------------------------------------------------- -/// -/// -/// Copyright (c) 2017, John Earnshaw, reblGreen Software Limited -/// -/// -/// -/// All rights reserved. -/// Redistribution and use in source and binary forms, with or without modification, are -/// permitted provided that the following conditions are met: -/// 1. Redistributions of source code must retain the above copyright notice, this list of -/// conditions and the following disclaimer. -/// 2. Redistributions in binary form must reproduce the above copyright notice, this list -/// of conditions and the following disclaimer in the documentation and/or other materials -/// provided with the distribution. -/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -/// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -/// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE -/// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -/// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -/// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -/// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -/// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -/// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -/// -// -------------------------------------------------------------------------------------------------------------------- -using System; -using UnityEngine; -#if UNITY_EDITOR -using UnityEditor; -#endif - -[AttributeUsage(AttributeTargets.Field, Inherited = true)] -public class HelpAttribute : PropertyAttribute -{ - public readonly string text; - - // MessageType exists in UnityEditor namespace and can throw an exception when used outside the editor. - // We spoof MessageType at the bottom of this script to ensure that errors are not thrown when - // MessageType is unavailable. - public readonly MessageType type; - - - /// - /// Adds a HelpBox to the Unity property inspector above this field. - /// - /// The help text to be displayed in the HelpBox. - /// The icon to be displayed in the HelpBox. - public HelpAttribute(string text, MessageType type = MessageType.Info) - { - this.text = text; - this.type = type; - } -} - -#if UNITY_EDITOR -[CustomPropertyDrawer(typeof(HelpAttribute))] -public class HelpDrawer : PropertyDrawer -{ - // Used for top and bottom padding between the text and the HelpBox border. - const int paddingHeight = 8; - - // Used to add some margin between the the HelpBox and the property. - const int marginHeight = 2; - - // Global field to store the original (base) property height. - float baseHeight = 0; - - // Custom added height for drawing text area which has the MultilineAttribute. - float addedHeight = 0; - - /// - /// A wrapper which returns the PropertyDrawer.attribute field as a HelpAttribute. - /// - HelpAttribute helpAttribute { get { return (HelpAttribute)attribute; } } - - /// - /// A helper property to check for RangeAttribute. - /// - RangeAttribute rangeAttribute - { - get - { - var attributes = fieldInfo.GetCustomAttributes(typeof(RangeAttribute), true); - return attributes != null && attributes.Length > 0 ? (RangeAttribute)attributes[0] : null; - } - } - - /// - /// A helper property to check for MultiLineAttribute. - /// - MultilineAttribute multilineAttribute - { - get - { - var attributes = fieldInfo.GetCustomAttributes(typeof(MultilineAttribute), true); - return attributes != null && attributes.Length > 0 ? (MultilineAttribute)attributes[0] : null; - } - } - - - public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) - { - // We store the original property height for later use... - baseHeight = base.GetPropertyHeight(prop, label); - - // This stops icon shrinking if text content doesn't fill out the container enough. - float minHeight = paddingHeight * 5; - - // Calculate the height of the HelpBox using the GUIStyle on the current skin and the inspector - // window's currentViewWidth. - var content = new GUIContent(helpAttribute.text); - var style = GUI.skin.GetStyle("helpbox"); - - var height = style.CalcHeight(content, EditorGUIUtility.currentViewWidth); - - // We add tiny padding here to make sure the text is not overflowing the HelpBox from the top - // and bottom. - height += marginHeight * 2; - - // Since we draw a custom text area with the label above if our property contains the - // MultilineAttribute, we need to add some extra height to compensate. This is stored in a - // seperate global field so we can use it again later. - if (multilineAttribute != null && prop.propertyType == SerializedPropertyType.String) - { - addedHeight = 48f; - } - - // If the calculated HelpBox is less than our minimum height we use this to calculate the returned - // height instead. - return height > minHeight ? height + baseHeight + addedHeight : minHeight + baseHeight + addedHeight; - } - - - public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label) - { - // We get a local reference to the MultilineAttribute as we use it twice in this method and it - // saves calling the logic twice for minimal optimization, etc... - var multiline = multilineAttribute; - - EditorGUI.BeginProperty(position, label, prop); - - // Copy the position out so we can calculate the position of our HelpBox without affecting the - // original position. - var helpPos = position; - - helpPos.height -= baseHeight + marginHeight; - - - if (multiline != null) - { - helpPos.height -= addedHeight; - } - - // Renders the HelpBox in the Unity inspector UI. - EditorGUI.HelpBox(helpPos, helpAttribute.text, helpAttribute.type); - - position.y += helpPos.height + marginHeight; - position.height = baseHeight; - - - // If we have a RangeAttribute on our field, we need to handle the PropertyDrawer differently to - // keep the same style as Unity's default. - var range = rangeAttribute; - - if (range != null) - { - if (prop.propertyType == SerializedPropertyType.Float) - { - EditorGUI.Slider(position, prop, range.min, range.max, label); - } - else if (prop.propertyType == SerializedPropertyType.Integer) - { - EditorGUI.IntSlider(position, prop, (int)range.min, (int)range.max, label); - } - else - { - // Not numeric so draw standard property field as punishment for adding RangeAttribute to - // a property which can not have a range :P - EditorGUI.PropertyField(position, prop, label); - } - } - else if (multiline != null) - { - // Here's where we handle the PropertyDrawer differently if we have a MultiLineAttribute, to try - // and keep some kind of multiline text area. This is not identical to Unity's default but is - // better than nothing... - if (prop.propertyType == SerializedPropertyType.String) - { - var style = GUI.skin.label; - var size = style.CalcHeight(label, EditorGUIUtility.currentViewWidth); - - EditorGUI.LabelField(position, label); - - position.y += size; - position.height += addedHeight - size; - - // Fixed text dissappearing thanks to: http://answers.unity3d.com/questions/244043/textarea-does-not-work-text-dissapears-solution-is.html - prop.stringValue = EditorGUI.TextArea(position, prop.stringValue); - } - else - { - // Again with a MultilineAttribute on a non-text field deserves for the standard property field - // to be drawn as punishment :P - EditorGUI.PropertyField(position, prop, label); - } - } - else - { - // If we get to here it means we're drawing the default property field below the HelpBox. More custom - // and built in PropertyDrawers could be implemented to enable HelpBox but it could easily make for - // hefty else/if block which would need refactoring! - EditorGUI.PropertyField(position, prop, label); - } - - EditorGUI.EndProperty(); - } -} -#else - // Replicate MessageType Enum if we are not in editor as this enum exists in UnityEditor namespace. - // This should stop errors being logged the same as Shawn Featherly's commit in the Github repo but I - // feel is cleaner than having the conditional directive in the middle of the HelpAttribute constructor. - public enum MessageType - { - None, - Info, - Warning, - Error, - } -#endif +// -------------------------------------------------------------------------------------------------------------------- +/// +/// +/// Copyright (c) 2017, John Earnshaw, reblGreen Software Limited +/// +/// +/// +/// All rights reserved. +/// Redistribution and use in source and binary forms, with or without modification, are +/// permitted provided that the following conditions are met: +/// 1. Redistributions of source code must retain the above copyright notice, this list of +/// conditions and the following disclaimer. +/// 2. Redistributions in binary form must reproduce the above copyright notice, this list +/// of conditions and the following disclaimer in the documentation and/or other materials +/// provided with the distribution. +/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +/// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +/// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE +/// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +/// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +/// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +/// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +/// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +/// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/// +// -------------------------------------------------------------------------------------------------------------------- +using System; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +[AttributeUsage(AttributeTargets.Field, Inherited = true)] +public class HelpAttribute : PropertyAttribute +{ + public readonly string text; + + // MessageType exists in UnityEditor namespace and can throw an exception when used outside the editor. + // We spoof MessageType at the bottom of this script to ensure that errors are not thrown when + // MessageType is unavailable. + public readonly MessageType type; + + + /// + /// Adds a HelpBox to the Unity property inspector above this field. + /// + /// The help text to be displayed in the HelpBox. + /// The icon to be displayed in the HelpBox. + public HelpAttribute(string text, MessageType type = MessageType.Info) + { + this.text = text; + this.type = type; + } +} + +#if UNITY_EDITOR +[CustomPropertyDrawer(typeof(HelpAttribute))] +public class HelpDrawer : PropertyDrawer +{ + // Used for top and bottom padding between the text and the HelpBox border. + const int paddingHeight = 8; + + // Used to add some margin between the the HelpBox and the property. + const int marginHeight = 2; + + // Global field to store the original (base) property height. + float baseHeight = 0; + + // Custom added height for drawing text area which has the MultilineAttribute. + float addedHeight = 0; + + /// + /// A wrapper which returns the PropertyDrawer.attribute field as a HelpAttribute. + /// + HelpAttribute helpAttribute { get { return (HelpAttribute)attribute; } } + + /// + /// A helper property to check for RangeAttribute. + /// + RangeAttribute rangeAttribute + { + get + { + var attributes = fieldInfo.GetCustomAttributes(typeof(RangeAttribute), true); + return attributes != null && attributes.Length > 0 ? (RangeAttribute)attributes[0] : null; + } + } + + /// + /// A helper property to check for MultiLineAttribute. + /// + MultilineAttribute multilineAttribute + { + get + { + var attributes = fieldInfo.GetCustomAttributes(typeof(MultilineAttribute), true); + return attributes != null && attributes.Length > 0 ? (MultilineAttribute)attributes[0] : null; + } + } + + + public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) + { + // We store the original property height for later use... + baseHeight = base.GetPropertyHeight(prop, label); + + // This stops icon shrinking if text content doesn't fill out the container enough. + float minHeight = paddingHeight * 5; + + // Calculate the height of the HelpBox using the GUIStyle on the current skin and the inspector + // window's currentViewWidth. + var content = new GUIContent(helpAttribute.text); + var style = GUI.skin.GetStyle("helpbox"); + + var height = style.CalcHeight(content, EditorGUIUtility.currentViewWidth); + + // We add tiny padding here to make sure the text is not overflowing the HelpBox from the top + // and bottom. + height += marginHeight * 2; + + // Since we draw a custom text area with the label above if our property contains the + // MultilineAttribute, we need to add some extra height to compensate. This is stored in a + // seperate global field so we can use it again later. + if (multilineAttribute != null && prop.propertyType == SerializedPropertyType.String) + { + addedHeight = 48f; + } + + // If the calculated HelpBox is less than our minimum height we use this to calculate the returned + // height instead. + return height > minHeight ? height + baseHeight + addedHeight : minHeight + baseHeight + addedHeight; + } + + + public override void OnGUI(Rect position, SerializedProperty prop, GUIContent label) + { + // We get a local reference to the MultilineAttribute as we use it twice in this method and it + // saves calling the logic twice for minimal optimization, etc... + var multiline = multilineAttribute; + + EditorGUI.BeginProperty(position, label, prop); + + // Copy the position out so we can calculate the position of our HelpBox without affecting the + // original position. + var helpPos = position; + + helpPos.height -= baseHeight + marginHeight; + + + if (multiline != null) + { + helpPos.height -= addedHeight; + } + + // Renders the HelpBox in the Unity inspector UI. + EditorGUI.HelpBox(helpPos, helpAttribute.text, helpAttribute.type); + + position.y += helpPos.height + marginHeight; + position.height = baseHeight; + + + // If we have a RangeAttribute on our field, we need to handle the PropertyDrawer differently to + // keep the same style as Unity's default. + var range = rangeAttribute; + + if (range != null) + { + if (prop.propertyType == SerializedPropertyType.Float) + { + EditorGUI.Slider(position, prop, range.min, range.max, label); + } + else if (prop.propertyType == SerializedPropertyType.Integer) + { + EditorGUI.IntSlider(position, prop, (int)range.min, (int)range.max, label); + } + else + { + // Not numeric so draw standard property field as punishment for adding RangeAttribute to + // a property which can not have a range :P + EditorGUI.PropertyField(position, prop, label); + } + } + else if (multiline != null) + { + // Here's where we handle the PropertyDrawer differently if we have a MultiLineAttribute, to try + // and keep some kind of multiline text area. This is not identical to Unity's default but is + // better than nothing... + if (prop.propertyType == SerializedPropertyType.String) + { + var style = GUI.skin.label; + var size = style.CalcHeight(label, EditorGUIUtility.currentViewWidth); + + EditorGUI.LabelField(position, label); + + position.y += size; + position.height += addedHeight - size; + + // Fixed text dissappearing thanks to: http://answers.unity3d.com/questions/244043/textarea-does-not-work-text-dissapears-solution-is.html + prop.stringValue = EditorGUI.TextArea(position, prop.stringValue); + } + else + { + // Again with a MultilineAttribute on a non-text field deserves for the standard property field + // to be drawn as punishment :P + EditorGUI.PropertyField(position, prop, label); + } + } + else + { + // If we get to here it means we're drawing the default property field below the HelpBox. More custom + // and built in PropertyDrawers could be implemented to enable HelpBox but it could easily make for + // hefty else/if block which would need refactoring! + EditorGUI.PropertyField(position, prop, label); + } + + EditorGUI.EndProperty(); + } +} +#else + // Replicate MessageType Enum if we are not in editor as this enum exists in UnityEditor namespace. + // This should stop errors being logged the same as Shawn Featherly's commit in the Github repo but I + // feel is cleaner than having the conditional directive in the middle of the HelpAttribute constructor. + public enum MessageType + { + None, + Info, + Warning, + Error, + } +#endif diff --git a/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs.meta index f76e303..a54acf1 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/HelpAttribute.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 6a2112e9804bfdd4d8f1ad9bc5e4d168 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 6a2112e9804bfdd4d8f1ad9bc5e4d168 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef b/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef index 9d06bf1..a05dfd6 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef +++ b/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef @@ -1,18 +1,18 @@ -{ - "name": "LRM", - "references": [ - "GUID:30817c1a0e6d646d99c048fc403f5979", - "GUID:3b5390adca4e2bb4791cb930316d6f3e", - "GUID:725ee7191c021de4dbf9269590ded755", - "GUID:6806a62c384838046a3c66c44f06d75f" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": true, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false +{ + "name": "LRM", + "references": [ + "GUID:30817c1a0e6d646d99c048fc403f5979", + "GUID:3b5390adca4e2bb4791cb930316d6f3e", + "GUID:725ee7191c021de4dbf9269590ded755", + "GUID:6806a62c384838046a3c66c44f06d75f" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef.meta b/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef.meta index bed53c3..3db1617 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRM.asmdef.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 3e90199726a18bc488368938e8df23bf -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 3e90199726a18bc488368938e8df23bf +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs index 35d0a5b..8c56908 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs @@ -1,174 +1,174 @@ -// This is an optional module for adding direct connect support - -using Mirror; -using System; -using System.Collections.Generic; -using UnityEngine; -using LightReflectiveMirror; - -[RequireComponent(typeof(LightReflectiveMirrorTransport))] -public class LRMDirectConnectModule : MonoBehaviour -{ - [HideInInspector] - public Transport directConnectTransport; - public bool showDebugLogs; - private LightReflectiveMirrorTransport lightMirrorTransport; - - void Awake() - { - lightMirrorTransport = GetComponent(); - - if (directConnectTransport == null) - { - Debug.Log("Direct Connect Transport is null!"); - return; - } - - if (directConnectTransport is LightReflectiveMirrorTransport) - { - Debug.Log("Direct Connect Transport Cannot be the relay, silly. :P"); - return; - } - - directConnectTransport.OnServerConnected = (OnServerConnected); - directConnectTransport.OnServerDataReceived = (OnServerDataReceived); - directConnectTransport.OnServerDisconnected = (OnServerDisconnected); - directConnectTransport.OnServerError = (OnServerError); - directConnectTransport.OnClientConnected = (OnClientConnected); - directConnectTransport.OnClientDataReceived = (OnClientDataReceived); - directConnectTransport.OnClientDisconnected = (OnClientDisconnected); - directConnectTransport.OnClientError = (OnClientError); - } - - public void StartServer(int port) - { - if(port > 0) - SetTransportPort(port); - - directConnectTransport.ServerStart(); - if (showDebugLogs) - Debug.Log("Direct Connect Server Created!"); - } - - public void StopServer() - { - directConnectTransport.ServerStop(); - } - - public void JoinServer(string ip, int port) - { - if (SupportsNATPunch()) - SetTransportPort(port); - - directConnectTransport.ClientConnect(ip); - } - - public void SetTransportPort(int port) - { - if (directConnectTransport is kcp2k.KcpTransport kcpTransport) - kcpTransport.Port = (ushort)port; - else - { - throw new Exception("DIRECT CONNECT MODULE ONLY SUPPORTS KCP AT THE MOMENT."); - } - } - - public int GetTransportPort() - { - if (directConnectTransport is kcp2k.KcpTransport kcpTransport) - return kcpTransport.Port; - else - { - throw new Exception("DIRECT CONNECT MODULE ONLY SUPPORTS KCP AT THE MOMENT."); - } - } - - public bool SupportsNATPunch() - { - return directConnectTransport is kcp2k.KcpTransport; - } - - public bool KickClient(int clientID) - { - if (showDebugLogs) - Debug.Log("Kicked direct connect client."); -#if MIRROR_37_0_OR_NEWER - directConnectTransport.ServerDisconnect(clientID); - return true; -#else - return directConnectTransport.ServerDisconnect(clientID); -#endif - } - - public void ClientDisconnect() - { - directConnectTransport.ClientDisconnect(); - } - - public void ServerSend(int clientID, ArraySegment data, int channel) - { -#if MIRROR_40_0_OR_NEWER - directConnectTransport.ServerSend(clientID, data, channel); -#else - directConnectTransport.ServerSend(clientID, channel, data); -#endif - } - - public void ClientSend(ArraySegment data, int channel) - { -#if MIRROR_40_0_OR_NEWER - directConnectTransport.ClientSend(data, channel); -#else - directConnectTransport.ClientSend(channel, data); -#endif - } - -#region Transport Callbacks - void OnServerConnected(int clientID) - { - if (showDebugLogs) - Debug.Log("Direct Connect Client Connected"); - lightMirrorTransport.DirectAddClient(clientID); - } - - void OnServerDataReceived(int clientID, ArraySegment data, int channel) - { - lightMirrorTransport.DirectReceiveData(data, channel, clientID); - } - - void OnServerDisconnected(int clientID) - { - lightMirrorTransport.DirectRemoveClient(clientID); - } - - void OnServerError(int client, Exception error) - { - if (showDebugLogs) - Debug.Log("Direct Server Error: " + error); - } - - void OnClientConnected() - { - if (showDebugLogs) - Debug.Log("Direct Connect Client Joined"); - - lightMirrorTransport.DirectClientConnected(); - } - - void OnClientDisconnected() - { - lightMirrorTransport.DirectDisconnected(); - } - - void OnClientDataReceived(ArraySegment data, int channel) - { - lightMirrorTransport.DirectReceiveData(data, channel); - } - - void OnClientError(Exception error) - { - if (showDebugLogs) - Debug.Log("Direct Client Error: " + error); - } -#endregion +// This is an optional module for adding direct connect support + +using Mirror; +using System; +using System.Collections.Generic; +using UnityEngine; +using LightReflectiveMirror; + +[RequireComponent(typeof(LightReflectiveMirrorTransport))] +public class LRMDirectConnectModule : MonoBehaviour +{ + [HideInInspector] + public Transport directConnectTransport; + public bool showDebugLogs; + private LightReflectiveMirrorTransport lightMirrorTransport; + + void Awake() + { + lightMirrorTransport = GetComponent(); + + if (directConnectTransport == null) + { + Debug.Log("Direct Connect Transport is null!"); + return; + } + + if (directConnectTransport is LightReflectiveMirrorTransport) + { + Debug.Log("Direct Connect Transport Cannot be the relay, silly. :P"); + return; + } + + directConnectTransport.OnServerConnected = (OnServerConnected); + directConnectTransport.OnServerDataReceived = (OnServerDataReceived); + directConnectTransport.OnServerDisconnected = (OnServerDisconnected); + directConnectTransport.OnServerError = (OnServerError); + directConnectTransport.OnClientConnected = (OnClientConnected); + directConnectTransport.OnClientDataReceived = (OnClientDataReceived); + directConnectTransport.OnClientDisconnected = (OnClientDisconnected); + directConnectTransport.OnClientError = (OnClientError); + } + + public void StartServer(int port) + { + if(port > 0) + SetTransportPort(port); + + directConnectTransport.ServerStart(); + if (showDebugLogs) + Debug.Log("Direct Connect Server Created!"); + } + + public void StopServer() + { + directConnectTransport.ServerStop(); + } + + public void JoinServer(string ip, int port) + { + if (SupportsNATPunch()) + SetTransportPort(port); + + directConnectTransport.ClientConnect(ip); + } + + public void SetTransportPort(int port) + { + if (directConnectTransport is kcp2k.KcpTransport kcpTransport) + kcpTransport.Port = (ushort)port; + else + { + throw new Exception("DIRECT CONNECT MODULE ONLY SUPPORTS KCP AT THE MOMENT."); + } + } + + public int GetTransportPort() + { + if (directConnectTransport is kcp2k.KcpTransport kcpTransport) + return kcpTransport.Port; + else + { + throw new Exception("DIRECT CONNECT MODULE ONLY SUPPORTS KCP AT THE MOMENT."); + } + } + + public bool SupportsNATPunch() + { + return directConnectTransport is kcp2k.KcpTransport; + } + + public bool KickClient(int clientID) + { + if (showDebugLogs) + Debug.Log("Kicked direct connect client."); +#if MIRROR_37_0_OR_NEWER + directConnectTransport.ServerDisconnect(clientID); + return true; +#else + return directConnectTransport.ServerDisconnect(clientID); +#endif + } + + public void ClientDisconnect() + { + directConnectTransport.ClientDisconnect(); + } + + public void ServerSend(int clientID, ArraySegment data, int channel) + { +#if MIRROR_40_0_OR_NEWER + directConnectTransport.ServerSend(clientID, data, channel); +#else + directConnectTransport.ServerSend(clientID, channel, data); +#endif + } + + public void ClientSend(ArraySegment data, int channel) + { +#if MIRROR_40_0_OR_NEWER + directConnectTransport.ClientSend(data, channel); +#else + directConnectTransport.ClientSend(channel, data); +#endif + } + +#region Transport Callbacks + void OnServerConnected(int clientID) + { + if (showDebugLogs) + Debug.Log("Direct Connect Client Connected"); + lightMirrorTransport.DirectAddClient(clientID); + } + + void OnServerDataReceived(int clientID, ArraySegment data, int channel) + { + lightMirrorTransport.DirectReceiveData(data, channel, clientID); + } + + void OnServerDisconnected(int clientID) + { + lightMirrorTransport.DirectRemoveClient(clientID); + } + + void OnServerError(int client, Exception error) + { + if (showDebugLogs) + Debug.Log("Direct Server Error: " + error); + } + + void OnClientConnected() + { + if (showDebugLogs) + Debug.Log("Direct Connect Client Joined"); + + lightMirrorTransport.DirectClientConnected(); + } + + void OnClientDisconnected() + { + lightMirrorTransport.DirectDisconnected(); + } + + void OnClientDataReceived(ArraySegment data, int channel) + { + lightMirrorTransport.DirectReceiveData(data, channel); + } + + void OnClientError(Exception error) + { + if (showDebugLogs) + Debug.Log("Direct Client Error: " + error); + } +#endregion } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs.meta index ead67c2..619b35c 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMDirectConnectModule.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 9c4cbff877abc42448dd829920c6c233 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 9c4cbff877abc42448dd829920c6c233 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs index 321a42a..083d2d9 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs @@ -1,198 +1,198 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Text; -using UnityEngine; - -namespace LightReflectiveMirror -{ - public static class LRMTools - { - public static void WriteByte(this byte[] data, ref int position, byte value) - { - data[position] = value; - position += 1; - } - - public static byte ReadByte(this byte[] data, ref int position) - { - byte value = data[position]; - position += 1; - return value; - } - - public static void WriteBool(this byte[] data, ref int position, bool value) - { - unsafe - { - fixed (byte* dataPtr = &data[position]) - { - bool* valuePtr = (bool*)dataPtr; - *valuePtr = value; - position += 1; - } - } - } - - public static bool ReadBool(this byte[] data, ref int position) - { - bool value = BitConverter.ToBoolean(data, position); - position += 1; - return value; - } - - public static void WriteString(this byte[] data, ref int position, string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - data.WriteInt(ref position, 0); - } - else - { - data.WriteInt(ref position, value.Length); - for (int i = 0; i < value.Length; i++) - data.WriteChar(ref position, value[i]); - } - } - - public static string ReadString(this byte[] data, ref int position) - { - string value = default; - - int stringSize = data.ReadInt(ref position); - - for (int i = 0; i < stringSize; i++) - value += data.ReadChar(ref position); - - return value; - } - - public static void WriteBytes(this byte[] data, ref int position, byte[] value) - { - data.WriteInt(ref position, value.Length); - for (int i = 0; i < value.Length; i++) - data.WriteByte(ref position, value[i]); - } - - public static byte[] ReadBytes(this byte[] data, ref int position) - { - int byteSize = data.ReadInt(ref position); - - byte[] value = new byte[byteSize]; - - for (int i = 0; i < byteSize; i++) - value[i] = data.ReadByte(ref position); - - return value; - } - - public static void WriteChar(this byte[] data, ref int position, char value) - { - unsafe - { - fixed (byte* dataPtr = &data[position]) - { - char* valuePtr = (char*)dataPtr; - *valuePtr = value; - position += 2; - } - } - } - - public static char ReadChar(this byte[] data, ref int position) - { - char value = BitConverter.ToChar(data, position); - position += 2; - return value; - } - - public static void WriteInt(this byte[] data, ref int position, int value) - { - unsafe - { - fixed (byte* dataPtr = &data[position]) - { - int* valuePtr = (int*)dataPtr; - *valuePtr = value; - position += 4; - } - } - } - - public static int ReadInt(this byte[] data, ref int position) - { - int value = BitConverter.ToInt32(data, position); - position += 4; - return value; - } - } - - internal static class CompressorExtensions - { - /// - /// Decompresses the string. - /// - /// The compressed text. - /// - public static string Decompress(this string compressedText) - { - byte[] gZipBuffer = Convert.FromBase64String(compressedText); - using (var memoryStream = new MemoryStream()) - { - int dataLength = BitConverter.ToInt32(gZipBuffer, 0); - memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4); - - var buffer = new byte[dataLength]; - - memoryStream.Position = 0; - using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) - { - gZipStream.Read(buffer, 0, buffer.Length); - } - - return Encoding.UTF8.GetString(buffer); - } - } - } - - internal static class JsonUtilityHelper - { - public static bool IsJsonArray(string json) - { - return json.StartsWith("[") && json.EndsWith("]"); - } - - public static T[] FromJson(string json) - { - if (!IsJsonArray(json)) - { - throw new System.FormatException("The input json string is not a Json Array"); - } - json = "{\"Items\":" + json + "}"; - JsonWrapper wrapper = JsonUtility.FromJson>(json); - return wrapper.Items; - } - - public static string ToJson(T[] array) - { - JsonWrapper wrapper = new JsonWrapper(); - wrapper.Items = array; - return JsonUtility.ToJson(wrapper); - } - - public static string ToJson(T[] array, bool prettyPrint) - { - JsonWrapper wrapper = new JsonWrapper(); - wrapper.Items = array; - return JsonUtility.ToJson(wrapper, prettyPrint); - } - - [Serializable] - private class JsonWrapper - { - public T[] Items; - } - } +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; +using UnityEngine; + +namespace LightReflectiveMirror +{ + public static class LRMTools + { + public static void WriteByte(this byte[] data, ref int position, byte value) + { + data[position] = value; + position += 1; + } + + public static byte ReadByte(this byte[] data, ref int position) + { + byte value = data[position]; + position += 1; + return value; + } + + public static void WriteBool(this byte[] data, ref int position, bool value) + { + unsafe + { + fixed (byte* dataPtr = &data[position]) + { + bool* valuePtr = (bool*)dataPtr; + *valuePtr = value; + position += 1; + } + } + } + + public static bool ReadBool(this byte[] data, ref int position) + { + bool value = BitConverter.ToBoolean(data, position); + position += 1; + return value; + } + + public static void WriteString(this byte[] data, ref int position, string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + data.WriteInt(ref position, 0); + } + else + { + data.WriteInt(ref position, value.Length); + for (int i = 0; i < value.Length; i++) + data.WriteChar(ref position, value[i]); + } + } + + public static string ReadString(this byte[] data, ref int position) + { + string value = default; + + int stringSize = data.ReadInt(ref position); + + for (int i = 0; i < stringSize; i++) + value += data.ReadChar(ref position); + + return value; + } + + public static void WriteBytes(this byte[] data, ref int position, byte[] value) + { + data.WriteInt(ref position, value.Length); + for (int i = 0; i < value.Length; i++) + data.WriteByte(ref position, value[i]); + } + + public static byte[] ReadBytes(this byte[] data, ref int position) + { + int byteSize = data.ReadInt(ref position); + + byte[] value = new byte[byteSize]; + + for (int i = 0; i < byteSize; i++) + value[i] = data.ReadByte(ref position); + + return value; + } + + public static void WriteChar(this byte[] data, ref int position, char value) + { + unsafe + { + fixed (byte* dataPtr = &data[position]) + { + char* valuePtr = (char*)dataPtr; + *valuePtr = value; + position += 2; + } + } + } + + public static char ReadChar(this byte[] data, ref int position) + { + char value = BitConverter.ToChar(data, position); + position += 2; + return value; + } + + public static void WriteInt(this byte[] data, ref int position, int value) + { + unsafe + { + fixed (byte* dataPtr = &data[position]) + { + int* valuePtr = (int*)dataPtr; + *valuePtr = value; + position += 4; + } + } + } + + public static int ReadInt(this byte[] data, ref int position) + { + int value = BitConverter.ToInt32(data, position); + position += 4; + return value; + } + } + + internal static class CompressorExtensions + { + /// + /// Decompresses the string. + /// + /// The compressed text. + /// + public static string Decompress(this string compressedText) + { + byte[] gZipBuffer = Convert.FromBase64String(compressedText); + using (var memoryStream = new MemoryStream()) + { + int dataLength = BitConverter.ToInt32(gZipBuffer, 0); + memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4); + + var buffer = new byte[dataLength]; + + memoryStream.Position = 0; + using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) + { + gZipStream.Read(buffer, 0, buffer.Length); + } + + return Encoding.UTF8.GetString(buffer); + } + } + } + + internal static class JsonUtilityHelper + { + public static bool IsJsonArray(string json) + { + return json.StartsWith("[") && json.EndsWith("]"); + } + + public static T[] FromJson(string json) + { + if (!IsJsonArray(json)) + { + throw new System.FormatException("The input json string is not a Json Array"); + } + json = "{\"Items\":" + json + "}"; + JsonWrapper wrapper = JsonUtility.FromJson>(json); + return wrapper.Items; + } + + public static string ToJson(T[] array) + { + JsonWrapper wrapper = new JsonWrapper(); + wrapper.Items = array; + return JsonUtility.ToJson(wrapper); + } + + public static string ToJson(T[] array, bool prettyPrint) + { + JsonWrapper wrapper = new JsonWrapper(); + wrapper.Items = array; + return JsonUtility.ToJson(wrapper, prettyPrint); + } + + [Serializable] + private class JsonWrapper + { + public T[] Items; + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs.meta index ed1b6d1..034971e 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTools.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 603a99af3f33b8c4482f9908752ea561 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 603a99af3f33b8c4482f9908752ea561 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport.meta index 7a41af8..1e2ff86 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 9e2be9cb6d4c6d64ab1812a9dca8bc57 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 9e2be9cb6d4c6d64ab1812a9dca8bc57 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs index b9a962f..17ebbb3 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs @@ -1,74 +1,74 @@ -using Mirror; -using System; - -namespace LightReflectiveMirror -{ - public partial class LightReflectiveMirrorTransport : Transport - { - public void DirectAddClient(int clientID) - { - if (!_isServer) - return; - - _connectedDirectClients.Add(clientID, _currentMemberId); - OnServerConnected?.Invoke(_currentMemberId); - _currentMemberId++; - } - - public void DirectRemoveClient(int clientID) - { - if (!_isServer) - return; - - OnServerDisconnected?.Invoke(_connectedDirectClients.GetByFirst(clientID)); - _connectedDirectClients.Remove(clientID); - } - - public void DirectReceiveData(ArraySegment data, int channel, int clientID = -1) - { - if (_isServer) - OnServerDataReceived?.Invoke(_connectedDirectClients.GetByFirst(clientID), data, channel); - - if (_isClient) - OnClientDataReceived?.Invoke(data, channel); - } - - public void DirectClientConnected() - { - _directConnected = true; - OnClientConnected?.Invoke(); - } - - public void DirectDisconnected() - { - if (_directConnected) - { - _isClient = false; - _directConnected = false; - OnClientDisconnected?.Invoke(); - } - else - { - int pos = 0; - _directConnected = false; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); - _clientSendBuffer.WriteString(ref pos, _cachedHostID); - _clientSendBuffer.WriteBool(ref pos, false); // Direct failed, use relay - - _isClient = true; - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new System.ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - - if (_clientProxy != null) - { - _clientProxy.Dispose(); - _clientProxy = null; - } - } - } +using Mirror; +using System; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + public void DirectAddClient(int clientID) + { + if (!_isServer) + return; + + _connectedDirectClients.Add(clientID, _currentMemberId); + OnServerConnected?.Invoke(_currentMemberId); + _currentMemberId++; + } + + public void DirectRemoveClient(int clientID) + { + if (!_isServer) + return; + + OnServerDisconnected?.Invoke(_connectedDirectClients.GetByFirst(clientID)); + _connectedDirectClients.Remove(clientID); + } + + public void DirectReceiveData(ArraySegment data, int channel, int clientID = -1) + { + if (_isServer) + OnServerDataReceived?.Invoke(_connectedDirectClients.GetByFirst(clientID), data, channel); + + if (_isClient) + OnClientDataReceived?.Invoke(data, channel); + } + + public void DirectClientConnected() + { + _directConnected = true; + OnClientConnected?.Invoke(); + } + + public void DirectDisconnected() + { + if (_directConnected) + { + _isClient = false; + _directConnected = false; + OnClientDisconnected?.Invoke(); + } + else + { + int pos = 0; + _directConnected = false; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); + _clientSendBuffer.WriteString(ref pos, _cachedHostID); + _clientSendBuffer.WriteBool(ref pos, false); // Direct failed, use relay + + _isClient = true; + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new System.ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + + if (_clientProxy != null) + { + _clientProxy.Dispose(); + _clientProxy = null; + } + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs.meta index 6d489b6..1992bcd 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportDirectConnect.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 1e38dc8d43e06744484cf2962c017606 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 1e38dc8d43e06744484cf2962c017606 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs index de95188..6713992 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs @@ -1,67 +1,67 @@ -using Mirror; -using System; -using System.Collections; -using System.Net; -using UnityEngine; - -namespace LightReflectiveMirror -{ - public partial class LightReflectiveMirrorTransport : Transport - { - IEnumerator NATPunch(IPEndPoint remoteAddress) - { - for (int i = 0; i < 10; i++) - { - _NATPuncher.Send(_punchData, 1, remoteAddress); - yield return new WaitForSeconds(0.25f); - } - } - - void RecvData(IAsyncResult result) - { - IPEndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); - var data = _NATPuncher.EndReceive(result, ref newClientEP); - _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); - - if (!newClientEP.Address.Equals(_relayPuncherIP.Address)) - { - if (_isServer) - { - if (_serverProxies.TryGetByFirst(newClientEP, out SocketProxy foundProxy)) - { - if (data.Length > 2) - foundProxy.RelayData(data, data.Length); - } - else - { - _serverProxies.Add(newClientEP, new SocketProxy(_NATIP.Port + 1, newClientEP)); - _serverProxies.GetByFirst(newClientEP).dataReceived += ServerProcessProxyData; - } - } - - if (_isClient) - { - if (_clientProxy == null) - { - _clientProxy = new SocketProxy(_NATIP.Port - 1); - _clientProxy.dataReceived += ClientProcessProxyData; - } - else - { - _clientProxy.ClientRelayData(data, data.Length); - } - } - } - } - - void ServerProcessProxyData(IPEndPoint remoteEndpoint, byte[] data) - { - _NATPuncher.Send(data, data.Length, remoteEndpoint); - } - - void ClientProcessProxyData(IPEndPoint _, byte[] data) - { - _NATPuncher.Send(data, data.Length, _directConnectEndpoint); - } - } +using Mirror; +using System; +using System.Collections; +using System.Net; +using UnityEngine; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + IEnumerator NATPunch(IPEndPoint remoteAddress) + { + for (int i = 0; i < 10; i++) + { + _NATPuncher.Send(_punchData, 1, remoteAddress); + yield return new WaitForSeconds(0.25f); + } + } + + void RecvData(IAsyncResult result) + { + IPEndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); + var data = _NATPuncher.EndReceive(result, ref newClientEP); + _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); + + if (!newClientEP.Address.Equals(_relayPuncherIP.Address)) + { + if (_isServer) + { + if (_serverProxies.TryGetByFirst(newClientEP, out SocketProxy foundProxy)) + { + if (data.Length > 2) + foundProxy.RelayData(data, data.Length); + } + else + { + _serverProxies.Add(newClientEP, new SocketProxy(_NATIP.Port + 1, newClientEP)); + _serverProxies.GetByFirst(newClientEP).dataReceived += ServerProcessProxyData; + } + } + + if (_isClient) + { + if (_clientProxy == null) + { + _clientProxy = new SocketProxy(_NATIP.Port - 1); + _clientProxy.dataReceived += ClientProcessProxyData; + } + else + { + _clientProxy.ClientRelayData(data, data.Length); + } + } + } + } + + void ServerProcessProxyData(IPEndPoint remoteEndpoint, byte[] data) + { + _NATPuncher.Send(data, data.Length, remoteEndpoint); + } + + void ClientProcessProxyData(IPEndPoint _, byte[] data) + { + _NATPuncher.Send(data, data.Length, _directConnectEndpoint); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs.meta index acc1631..7ca9140 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportNATPuncher.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 3d493f36a877ab042875f198b110ebb3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 3d493f36a877ab042875f198b110ebb3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs index 305701f..b8a7ab3 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs @@ -1,325 +1,325 @@ -using Mirror; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using UnityEngine; - -namespace LightReflectiveMirror -{ - public partial class LightReflectiveMirrorTransport : Transport - { - public override bool ServerActive() => _isServer; - public override bool Available() => _connectedToRelay; - public override void ClientConnect(Uri uri) => ClientConnect(uri.Host); - public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId); - public override bool ClientConnected() => _isClient; - - public override void ServerLateUpdate() - { - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ServerLateUpdate(); - } - - public override string ServerGetClientAddress(int connectionId) - { - if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) - return relayId.ToString(); - - if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) - return "DIRECT-" + directId; - - // Shouldn't ever get here. - return "?"; - } - - public override void ClientEarlyUpdate() - { - clientToServerTransport.ClientEarlyUpdate(); - - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ClientEarlyUpdate(); - } - - public override void ClientLateUpdate() - { - clientToServerTransport.ClientLateUpdate(); - - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ClientLateUpdate(); - } - - public override void ServerEarlyUpdate() - { - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ServerEarlyUpdate(); - } - - public override void ClientConnect(string address) - { - if (!Available()) - { - Debug.Log("Not connected to relay!"); - OnClientDisconnected?.Invoke(); - return; - } - - if (_isClient || _isServer) - throw new Exception("Cannot connect while hosting/already connected!"); - - _cachedHostID = address; - - var room = GetServerForID(address); - - if (!useLoadBalancer) - { - int pos = 0; - _directConnected = false; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); - _clientSendBuffer.WriteString(ref pos, address); - _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null); - - if (_directConnectModule == null) - { - _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); - } - else - { - if (GetLocalIp() == null) - _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); - else - _clientSendBuffer.WriteString(ref pos, GetLocalIp()); - } - - _isClient = true; -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new System.ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); -#endif - - } - else - { - StartCoroutine(JoinOtherRelayAndMatch(room, address)); - } - } - - public override void ClientDisconnect() - { - _isClient = false; - - // make sure we are even connected to a relay - if (Available()) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - - if (_directConnectModule != null) - _directConnectModule.ClientDisconnect(); - } - -#if MIRROR_40_0_OR_NEWER - public override void ClientSend(ArraySegment segment, int channelId) -#else - public override void ClientSend(int channelId, ArraySegment segment) - -#endif - { - if (_directConnected) - { - _directConnectModule.ClientSend(segment, channelId); - } - else - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); - _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); - _clientSendBuffer.WriteInt(ref pos, 0); -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), channelId); -#else - clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - } - -#if !MIRROR_37_0_OR_NEWER - - public override bool ServerDisconnect(int connectionId) - { - if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer); - _clientSendBuffer.WriteInt(ref pos, relayId); - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); - return true; - } - - if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) - return _directConnectModule.KickClient(directId); - - return false; - } - -#else - - public override void ServerDisconnect(int connectionId) - { - if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer); - _clientSendBuffer.WriteInt(ref pos, relayId); - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); - return; - } - - if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) - _directConnectModule.KickClient(directId); - } - -#endif - -#if MIRROR_40_0_OR_NEWER - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) -#else - public override void ServerSend(int connectionId, int channelId, ArraySegment segment) -#endif - { - if (_directConnectModule != null && _connectedDirectClients.TryGetBySecond(connectionId, out int directId)) - { - _directConnectModule.ServerSend(directId, segment, channelId); - } - else - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); - _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); - _clientSendBuffer.WriteInt(ref pos, _connectedRelayClients.GetBySecond(connectionId)); -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), channelId); -#else - clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - } - - public override void ServerStart() - { - if (!Available()) - { - Debug.Log("Not connected to relay! Server failed to start."); - return; - } - - if (_isClient || _isServer) - { - Debug.Log("Cannot host while already hosting or connected!"); - return; - } - - _isServer = true; - _connectedRelayClients = new BiDictionary(); - _currentMemberId = 1; - _connectedDirectClients = new BiDictionary(); - - var keys = new List(_serverProxies.GetAllKeys()); - - for (int i = 0; i < keys.Count; i++) - { - _serverProxies.GetByFirst(keys[i]).Dispose(); - _serverProxies.Remove(keys[i]); - } - - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.CreateRoom); - _clientSendBuffer.WriteInt(ref pos, maxServerPlayers); - _clientSendBuffer.WriteString(ref pos, serverName); - _clientSendBuffer.WriteBool(ref pos, isPublicServer); - _clientSendBuffer.WriteString(ref pos, extraServerData); - // If we have direct connect module, and our local IP isnt null, tell server. Only time local IP is null is on cellular networks, such as IOS and Android. - _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null ? GetLocalIp() != null ? true : false : false); - - if (_directConnectModule != null && GetLocalIp() != null) - { - _clientSendBuffer.WriteString(ref pos, GetLocalIp()); - // Transport port will be NAT port + 1 for the proxy connections. - _directConnectModule.StartServer(useNATPunch ? _NATIP.Port + 1 : -1); - } - else - _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); - - if (useNATPunch) - { - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteInt(ref pos, 0); - } - else - { - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteInt(ref pos, _directConnectModule == null ? 1 : _directConnectModule.SupportsNATPunch() ? _directConnectModule.GetTransportPort() : 1); - } -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - - public override void ServerStop() - { - if (_isServer) - { - _isServer = false; - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - - if (_directConnectModule != null) - _directConnectModule.StopServer(); - - var keys = new List(_serverProxies.GetAllKeys()); - - for (int i = 0; i < keys.Count; i++) - { - _serverProxies.GetByFirst(keys[i]).Dispose(); - _serverProxies.Remove(keys[i]); - } - } - } - - public override Uri ServerUri() - { - UriBuilder builder = new UriBuilder - { - Scheme = "LRM", - Host = serverId.ToString() - }; - - return builder.Uri; - } - - public override void Shutdown() - { - _isAuthenticated = false; - _isClient = false; - _isServer = false; - _connectedToRelay = false; - clientToServerTransport.Shutdown(); - } - } +using Mirror; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using UnityEngine; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + public override bool ServerActive() => _isServer; + public override bool Available() => _connectedToRelay; + public override void ClientConnect(Uri uri) => ClientConnect(uri.Host); + public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId); + public override bool ClientConnected() => _isClient; + + public override void ServerLateUpdate() + { + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ServerLateUpdate(); + } + + public override string ServerGetClientAddress(int connectionId) + { + if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) + return relayId.ToString(); + + if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) + return "DIRECT-" + directId; + + // Shouldn't ever get here. + return "?"; + } + + public override void ClientEarlyUpdate() + { + clientToServerTransport.ClientEarlyUpdate(); + + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ClientEarlyUpdate(); + } + + public override void ClientLateUpdate() + { + clientToServerTransport.ClientLateUpdate(); + + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ClientLateUpdate(); + } + + public override void ServerEarlyUpdate() + { + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ServerEarlyUpdate(); + } + + public override void ClientConnect(string address) + { + if (!Available()) + { + Debug.Log("Not connected to relay!"); + OnClientDisconnected?.Invoke(); + return; + } + + if (_isClient || _isServer) + throw new Exception("Cannot connect while hosting/already connected!"); + + _cachedHostID = address; + + var room = GetServerForID(address); + + if (!useLoadBalancer) + { + int pos = 0; + _directConnected = false; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); + _clientSendBuffer.WriteString(ref pos, address); + _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null); + + if (_directConnectModule == null) + { + _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); + } + else + { + if (GetLocalIp() == null) + _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); + else + _clientSendBuffer.WriteString(ref pos, GetLocalIp()); + } + + _isClient = true; +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new System.ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); +#endif + + } + else + { + StartCoroutine(JoinOtherRelayAndMatch(room, address)); + } + } + + public override void ClientDisconnect() + { + _isClient = false; + + // make sure we are even connected to a relay + if (Available()) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + + if (_directConnectModule != null) + _directConnectModule.ClientDisconnect(); + } + +#if MIRROR_40_0_OR_NEWER + public override void ClientSend(ArraySegment segment, int channelId) +#else + public override void ClientSend(int channelId, ArraySegment segment) + +#endif + { + if (_directConnected) + { + _directConnectModule.ClientSend(segment, channelId); + } + else + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); + _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); + _clientSendBuffer.WriteInt(ref pos, 0); +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), channelId); +#else + clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + } + +#if !MIRROR_37_0_OR_NEWER + + public override bool ServerDisconnect(int connectionId) + { + if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer); + _clientSendBuffer.WriteInt(ref pos, relayId); + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); + return true; + } + + if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) + return _directConnectModule.KickClient(directId); + + return false; + } + +#else + + public override void ServerDisconnect(int connectionId) + { + if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer); + _clientSendBuffer.WriteInt(ref pos, relayId); + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); + return; + } + + if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) + _directConnectModule.KickClient(directId); + } + +#endif + +#if MIRROR_40_0_OR_NEWER + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) +#else + public override void ServerSend(int connectionId, int channelId, ArraySegment segment) +#endif + { + if (_directConnectModule != null && _connectedDirectClients.TryGetBySecond(connectionId, out int directId)) + { + _directConnectModule.ServerSend(directId, segment, channelId); + } + else + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); + _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); + _clientSendBuffer.WriteInt(ref pos, _connectedRelayClients.GetBySecond(connectionId)); +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), channelId); +#else + clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + } + + public override void ServerStart() + { + if (!Available()) + { + Debug.Log("Not connected to relay! Server failed to start."); + return; + } + + if (_isClient || _isServer) + { + Debug.Log("Cannot host while already hosting or connected!"); + return; + } + + _isServer = true; + _connectedRelayClients = new BiDictionary(); + _currentMemberId = 1; + _connectedDirectClients = new BiDictionary(); + + var keys = new List(_serverProxies.GetAllKeys()); + + for (int i = 0; i < keys.Count; i++) + { + _serverProxies.GetByFirst(keys[i]).Dispose(); + _serverProxies.Remove(keys[i]); + } + + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.CreateRoom); + _clientSendBuffer.WriteInt(ref pos, maxServerPlayers); + _clientSendBuffer.WriteString(ref pos, serverName); + _clientSendBuffer.WriteBool(ref pos, isPublicServer); + _clientSendBuffer.WriteString(ref pos, extraServerData); + // If we have direct connect module, and our local IP isnt null, tell server. Only time local IP is null is on cellular networks, such as IOS and Android. + _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null ? GetLocalIp() != null ? true : false : false); + + if (_directConnectModule != null && GetLocalIp() != null) + { + _clientSendBuffer.WriteString(ref pos, GetLocalIp()); + // Transport port will be NAT port + 1 for the proxy connections. + _directConnectModule.StartServer(useNATPunch ? _NATIP.Port + 1 : -1); + } + else + _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); + + if (useNATPunch) + { + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteInt(ref pos, 0); + } + else + { + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteInt(ref pos, _directConnectModule == null ? 1 : _directConnectModule.SupportsNATPunch() ? _directConnectModule.GetTransportPort() : 1); + } +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + + public override void ServerStop() + { + if (_isServer) + { + _isServer = false; + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + + if (_directConnectModule != null) + _directConnectModule.StopServer(); + + var keys = new List(_serverProxies.GetAllKeys()); + + for (int i = 0; i < keys.Count; i++) + { + _serverProxies.GetByFirst(keys[i]).Dispose(); + _serverProxies.Remove(keys[i]); + } + } + } + + public override Uri ServerUri() + { + UriBuilder builder = new UriBuilder + { + Scheme = "LRM", + Host = serverId.ToString() + }; + + return builder.Uri; + } + + public override void Shutdown() + { + _isAuthenticated = false; + _isClient = false; + _isServer = false; + _connectedToRelay = false; + clientToServerTransport.Shutdown(); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs.meta index 03f6af9..32f6bba 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportOverrides.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 16602a520793b6044894096e873bc28d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 16602a520793b6044894096e873bc28d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs index 7b93fa5..3442dd6 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs @@ -1,253 +1,227 @@ -using Mirror; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using UnityEngine; -using UnityEngine.Networking; - -namespace LightReflectiveMirror -{ - public partial class LightReflectiveMirrorTransport : Transport - { - public void RequestServerList(LRMRegions searchRegion = LRMRegions.Any) - { - if (_isAuthenticated && _connectedToRelay) - StartCoroutine(GetServerList(searchRegion)); - else - Debug.Log("You must be connected to Relay to request server list!"); - } - - IEnumerator RelayConnect() - { - - string url = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/join/"; - serverStatus = "Waiting for LLB..."; - using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) - { - // Request and wait for the desired page. - webRequest.SetRequestHeader("x-Region", ((int)region).ToString()); - webRequest.SetRequestHeader("Access-Control-Allow-Credentials", "true"); - webRequest.SetRequestHeader("Access-Control-Allow-Headers", "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time"); - webRequest.SetRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - webRequest.SetRequestHeader("Access-Control-Allow-Origin", "*"); - - yield return webRequest.SendWebRequest(); - // var result = webRequest.downloadHandler.text; - WebClient client = new WebClient(); - client.Proxy = null; - var result = client.DownloadString(url); - var parsedAddress = JsonUtility.FromJson(result); - Connect(parsedAddress.address, parsedAddress.port); - endpointServerPort = parsedAddress.endpointPort; - if (false) { -#if UNITY_2020_1_OR_NEWER - switch (webRequest.result) - { - case UnityWebRequest.Result.ConnectionError: - case UnityWebRequest.Result.DataProcessingError: - case UnityWebRequest.Result.ProtocolError: - Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); - break; - case UnityWebRequest.Result.Success: - // var parsedAddress = JsonUtility.FromJson(result); - // Connect(parsedAddress.address, parsedAddress.port); - // endpointServerPort = parsedAddress.endpointPort; - break; - } -#else - if (webRequest.isNetworkError || webRequest.isHttpError) - { - Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); - } - else - { - // join here - //var parsedAddress = JsonUtility.FromJson(result); - //Connect(parsedAddress.address, parsedAddress.port); - //endpointServerPort = parsedAddress.endpointPort; - } -#endif - } - } - } - - IEnumerator JoinOtherRelayAndMatch(Room? roomValue, string ID) - { - var room = new Room(); - - // using load balancer, we NEED the server's relay address - if (roomValue.HasValue) - room = roomValue.Value; - else - { - _serverListUpdated = false; - RequestServerList(); - - yield return new WaitUntil(() => _serverListUpdated); - - var foundRoom = GetServerForID(ID); - - if (foundRoom.HasValue) - { - room = foundRoom.Value; - } - else - { - Debug.LogWarning("LRM | Client tried to join a server that does not exist!"); - OnClientDisconnected?.Invoke(); - yield break; - } - } - - // Wait for disconnection - DisconnectFromRelay(); - - while (IsAuthenticated()) - yield return null; - - endpointServerPort = room.relayInfo.endpointPort; - Connect(room.relayInfo.address, room.relayInfo.port); - - while (!IsAuthenticated()) - yield return null; - - int pos = 0; - _directConnected = false; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); - _clientSendBuffer.WriteString(ref pos, room.serverId); - _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null); - - string local = GetLocalIp(); - - _clientSendBuffer.WriteString(ref pos, local ?? "0.0.0.0"); - - _isClient = true; - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new System.ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - - IEnumerator GetServerList(LRMRegions region) - { - if (!useLoadBalancer) - { - string uri = $"http://{serverIP}:{endpointServerPort}/api/compressed/servers"; - - using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) - { - // webRequest. - webRequest.SetRequestHeader("Access-Control-Allow-Credentials", "true"); - webRequest.SetRequestHeader("Access-Control-Allow-Headers", "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time"); - webRequest.SetRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - webRequest.SetRequestHeader("Access-Control-Allow-Origin", "*"); - // webRequest.SetRequestHeader("Proxy-Authorization", ""); - - // Request and wait for the desired page. - yield return webRequest.SendWebRequest(); - // var result = webRequest.downloadHandler.text; - - WebClient client = new WebClient(); - client.Proxy = null; - var result = client.DownloadString(uri); - // Debug.Log(result); - - Debug.Log(client.DownloadString(uri)); - relayServerList?.Clear(); - relayServerList = JsonUtilityHelper.FromJson(result.Decompress()).ToList(); - Debug.Log(relayServerList.Count); - serverListUpdated?.Invoke(); - Debug.Log(serverListUpdated==null); - /* if (false) - { -#if UNITY_2020_1_OR_NEWER - switch (webRequest.result) - { - case UnityWebRequest.Result.ConnectionError: - case UnityWebRequest.Result.DataProcessingError: - case UnityWebRequest.Result.ProtocolError: - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - break; - - case UnityWebRequest.Result.Success: - relayServerList?.Clear(); - relayServerList = JsonUtilityHelper.FromJson(result.Decompress()).ToList(); - serverListUpdated?.Invoke(); - break; - } -#else - if (webRequest.isNetworkError || webRequest.isHttpError) - { - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - } - else - { - relayServerList?.Clear(); - relayServerList = JsonUtilityHelper.FromJson(result.Decompress()).ToList(); - serverListUpdated?.Invoke(); - } -#endif - }*/ - } - } - else // get master list from load balancer - { - yield return StartCoroutine(RetrieveMasterServerListFromLoadBalancer(region)); - } - - } - - /// - /// Gets master list from the LB. - /// This can be optimized but for now it is it's - /// own separate method, so i can understand wtf is going on :D - /// - /// - IEnumerator RetrieveMasterServerListFromLoadBalancer(LRMRegions region) - { - string uri = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/masterlist/"; - - using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) - { - // webRequest.SetRequestHeader("Proxy-Authorization", ""); - webRequest.SetRequestHeader("x-Region", ((int)region).ToString()); - // Request and wait for the desired page. - yield return webRequest.SendWebRequest(); - var result = webRequest.downloadHandler.text; - -#if UNITY_2020_1_OR_NEWER - switch (webRequest.result) - { - case UnityWebRequest.Result.ConnectionError: - case UnityWebRequest.Result.DataProcessingError: - case UnityWebRequest.Result.ProtocolError: - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - break; - - case UnityWebRequest.Result.Success: - relayServerList?.Clear(); - relayServerList = JsonUtilityHelper.FromJson(result).ToList(); - serverListUpdated?.Invoke(); - _serverListUpdated = true; - break; - } -#else - if (webRequest.isNetworkError || webRequest.isHttpError) - { - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - } - else - { - relayServerList?.Clear(); - relayServerList = JsonUtilityHelper.FromJson(result).ToList(); - serverListUpdated?.Invoke(); - _serverListUpdated = true; - } -#endif - } - } - } -} +using Mirror; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Networking; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + public void RequestServerList(LRMRegions searchRegion = LRMRegions.Any) + { + if (_isAuthenticated && _connectedToRelay) + StartCoroutine(GetServerList(searchRegion)); + else + Debug.Log("You must be connected to Relay to request server list!"); + } + + IEnumerator RelayConnect() + { + string url = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/join/"; + serverStatus = "Waiting for LLB..."; + using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) + { + // Request and wait for the desired page. + webRequest.SetRequestHeader("x-Region", ((int)region).ToString()); + webRequest.SetRequestHeader("Access-Control-Allow-Credentials", "true"); + webRequest.SetRequestHeader("Access-Control-Allow-Headers", "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time"); + webRequest.SetRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + webRequest.SetRequestHeader("Access-Control-Allow-Origin", "*"); + + yield return webRequest.SendWebRequest(); + var result = webRequest.downloadHandler.text; + +#if UNITY_2020_1_OR_NEWER + switch (webRequest.result) + { + case UnityWebRequest.Result.ConnectionError: + case UnityWebRequest.Result.DataProcessingError: + case UnityWebRequest.Result.ProtocolError: + Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); + break; + case UnityWebRequest.Result.Success: + var parsedAddress = JsonUtility.FromJson(result); + Connect(parsedAddress.address, parsedAddress.port); + endpointServerPort = parsedAddress.endpointPort; + break; + } +#else + if (webRequest.isNetworkError || webRequest.isHttpError) + { + Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); + } + else + { + // join here + var parsedAddress = JsonUtility.FromJson(result); + Connect(parsedAddress.address, parsedAddress.port); + endpointServerPort = parsedAddress.endpointPort; + } +#endif + } + } + + IEnumerator JoinOtherRelayAndMatch(Room? roomValue, string ID) + { + var room = new Room(); + + // using load balancer, we NEED the server's relay address + if (roomValue.HasValue) + room = roomValue.Value; + else + { + _serverListUpdated = false; + RequestServerList(); + + yield return new WaitUntil(() => _serverListUpdated); + + var foundRoom = GetServerForID(ID); + + if (foundRoom.HasValue) + { + room = foundRoom.Value; + } + else + { + Debug.LogWarning("LRM | Client tried to join a server that does not exist!"); + OnClientDisconnected?.Invoke(); + yield break; + } + } + + // Wait for disconnection + DisconnectFromRelay(); + + while (IsAuthenticated()) + yield return null; + + endpointServerPort = room.relayInfo.endpointPort; + Connect(room.relayInfo.address, room.relayInfo.port); + + while (!IsAuthenticated()) + yield return null; + + int pos = 0; + _directConnected = false; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); + _clientSendBuffer.WriteString(ref pos, room.serverId); + _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null); + + string local = GetLocalIp(); + + _clientSendBuffer.WriteString(ref pos, local ?? "0.0.0.0"); + + _isClient = true; + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new System.ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + + IEnumerator GetServerList(LRMRegions region) + { + if (!useLoadBalancer) + { + string uri = $"http://{serverIP}:{endpointServerPort}/api/compressed/servers"; + + using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) + { + webRequest.SetRequestHeader("Access-Control-Allow-Credentials", "true"); + webRequest.SetRequestHeader("Access-Control-Allow-Headers", "Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time"); + webRequest.SetRequestHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + webRequest.SetRequestHeader("Access-Control-Allow-Origin", "*"); + + // Request and wait for the desired page. + yield return webRequest.SendWebRequest(); + var result = webRequest.downloadHandler.text; + +#if UNITY_2020_1_OR_NEWER + switch (webRequest.result) + { + case UnityWebRequest.Result.ConnectionError: + case UnityWebRequest.Result.DataProcessingError: + case UnityWebRequest.Result.ProtocolError: + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + break; + + case UnityWebRequest.Result.Success: + relayServerList?.Clear(); + relayServerList = JsonUtilityHelper.FromJson(result.Decompress()).ToList(); + serverListUpdated?.Invoke(); + break; + } +#else + if (webRequest.isNetworkError || webRequest.isHttpError) + { + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + } + else + { + relayServerList?.Clear(); + relayServerList = JsonUtilityHelper.FromJson(result.Decompress()).ToList(); + serverListUpdated?.Invoke(); + } +#endif + } + } + else // get master list from load balancer + { + yield return StartCoroutine(RetrieveMasterServerListFromLoadBalancer(region)); + } + + } + + /// + /// Gets master list from the LB. + /// This can be optimized but for now it is it's + /// own separate method, so i can understand wtf is going on :D + /// + /// + IEnumerator RetrieveMasterServerListFromLoadBalancer(LRMRegions region) + { + string uri = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/masterlist/"; + + using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) + { + webRequest.SetRequestHeader("x-Region", ((int)region).ToString()); + // Request and wait for the desired page. + yield return webRequest.SendWebRequest(); + var result = webRequest.downloadHandler.text; + +#if UNITY_2020_1_OR_NEWER + switch (webRequest.result) + { + case UnityWebRequest.Result.ConnectionError: + case UnityWebRequest.Result.DataProcessingError: + case UnityWebRequest.Result.ProtocolError: + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + break; + + case UnityWebRequest.Result.Success: + relayServerList?.Clear(); + relayServerList = JsonUtilityHelper.FromJson(result).ToList(); + serverListUpdated?.Invoke(); + _serverListUpdated = true; + break; + } +#else + if (webRequest.isNetworkError || webRequest.isHttpError) + { + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + } + else + { + relayServerList?.Clear(); + relayServerList = JsonUtilityHelper.FromJson(result).ToList(); + serverListUpdated?.Invoke(); + _serverListUpdated = true; + } +#endif + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs.meta index 877bd3d..554a8db 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportRequests.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 4ce47885d25567b4a8c0d6f3727de7f3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 4ce47885d25567b4a8c0d6f3727de7f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs index 3e9b207..38a30b2 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs @@ -1,74 +1,74 @@ -using Mirror; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using UnityEngine.Events; - -namespace LightReflectiveMirror -{ - public partial class LightReflectiveMirrorTransport : Transport - { - // Connection/auth variables - public Transport clientToServerTransport; - public string serverIP = null; - public ushort serverPort = 7777; - public ushort endpointServerPort = 8080; - public float heartBeatInterval = 3; - public bool connectOnAwake = true; - public string authenticationKey = "Secret Auth Key"; - - public UnityEvent disconnectedFromRelay; - public UnityEvent connectedToRelay; - - // NAT Puncher variables - public bool useNATPunch = false; - public int NATPunchtroughPort = -1; - private const int NAT_PUNCH_ATTEMPTS = 3; - - // LLB variables (LRM Load Balancer) - public bool useLoadBalancer = false; - public ushort loadBalancerPort = 7070; - public string loadBalancerAddress = null; - - // Server hosting variables - public string serverName = "My awesome server!"; - public string extraServerData = "Map 1"; - public int maxServerPlayers = 10; - public bool isPublicServer = true; - - private const string LOCALHOST = "127.0.0.1"; - - // Server list variables - public UnityEvent serverListUpdated; - public List relayServerList { private set; get; } = new List(); - - // Current Server Information - public string serverStatus = "Not Started."; - public string serverId = string.Empty; - - private LRMDirectConnectModule _directConnectModule; - - public LRMRegions region = LRMRegions.NorthAmerica; - private byte[] _clientSendBuffer; - private bool _connectedToRelay = false; - private bool _isClient = false; - private bool _isServer = false; - private bool _directConnected = false; - private bool _isAuthenticated = false; - private int _currentMemberId; - private bool _callbacksInitialized = false; - private string _cachedHostID; - private UdpClient _NATPuncher; - private IPEndPoint _NATIP; - private IPEndPoint _relayPuncherIP; - private byte[] _punchData = new byte[1] { 1 }; - private IPEndPoint _directConnectEndpoint; - private SocketProxy _clientProxy; - private BiDictionary _serverProxies = new BiDictionary(); - private BiDictionary _connectedRelayClients = new BiDictionary(); - private BiDictionary _connectedDirectClients = new BiDictionary(); - private bool _serverListUpdated = false; - } - - public enum LRMRegions { Any, NorthAmerica, SouthAmerica, Europe, Asia, Africa, Oceania } +using Mirror; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using UnityEngine.Events; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + // Connection/auth variables + public Transport clientToServerTransport; + public string serverIP = null; + public ushort serverPort = 7777; + public ushort endpointServerPort = 8080; + public float heartBeatInterval = 3; + public bool connectOnAwake = true; + public string authenticationKey = "Secret Auth Key"; + + public UnityEvent disconnectedFromRelay; + public UnityEvent connectedToRelay; + + // NAT Puncher variables + public bool useNATPunch = false; + public int NATPunchtroughPort = -1; + private const int NAT_PUNCH_ATTEMPTS = 3; + + // LLB variables (LRM Load Balancer) + public bool useLoadBalancer = false; + public ushort loadBalancerPort = 7070; + public string loadBalancerAddress = null; + + // Server hosting variables + public string serverName = "My awesome server!"; + public string extraServerData = "Map 1"; + public int maxServerPlayers = 10; + public bool isPublicServer = true; + + private const string LOCALHOST = "127.0.0.1"; + + // Server list variables + public UnityEvent serverListUpdated; + public List relayServerList { private set; get; } = new List(); + + // Current Server Information + public string serverStatus = "Not Started."; + public string serverId = string.Empty; + + private LRMDirectConnectModule _directConnectModule; + + public LRMRegions region = LRMRegions.NorthAmerica; + private byte[] _clientSendBuffer; + private bool _connectedToRelay = false; + private bool _isClient = false; + private bool _isServer = false; + private bool _directConnected = false; + private bool _isAuthenticated = false; + private int _currentMemberId; + private bool _callbacksInitialized = false; + private string _cachedHostID; + private UdpClient _NATPuncher; + private IPEndPoint _NATIP; + private IPEndPoint _relayPuncherIP; + private byte[] _punchData = new byte[1] { 1 }; + private IPEndPoint _directConnectEndpoint; + private SocketProxy _clientProxy; + private BiDictionary _serverProxies = new BiDictionary(); + private BiDictionary _connectedRelayClients = new BiDictionary(); + private BiDictionary _connectedDirectClients = new BiDictionary(); + private bool _serverListUpdated = false; + } + + public enum LRMRegions { Any, NorthAmerica, SouthAmerica, Europe, Asia, Africa, Oceania } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs.meta index 8b0abdb..5c51c48 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LRMTransportVariables.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: f823917eee8f54742b6b03e520f05730 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f823917eee8f54742b6b03e520f05730 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs index 5e9bab3..40967db 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs @@ -1,488 +1,488 @@ -using kcp2k; -using Mirror; -using Mirror.SimpleWeb; -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using UnityEngine; - -namespace LightReflectiveMirror -{ - [DefaultExecutionOrder(1001)] - public partial class LightReflectiveMirrorTransport : Transport - { - public bool IsAuthenticated() => _isAuthenticated; - - private void Awake() - { - if (Application.platform == RuntimePlatform.WebGLPlayer) - useNATPunch = false; - else - _directConnectModule = GetComponent(); - - if (clientToServerTransport is LightReflectiveMirrorTransport) - throw new Exception("Haha real funny... Use a different transport."); - - if (_directConnectModule != null) - { - if (useNATPunch && !_directConnectModule.SupportsNATPunch()) - { - Debug.LogWarning("LRM | NATPunch is turned on but the transport used does not support it. It will be disabled."); - useNATPunch = false; - } - } - - SetupCallbacks(); - - if (connectOnAwake) - ConnectToRelay(); - - InvokeRepeating(nameof(SendHeartbeat), heartBeatInterval, heartBeatInterval); - } - - private void SetupCallbacks() - { - if (_callbacksInitialized) - return; - - _callbacksInitialized = true; - clientToServerTransport.OnClientConnected = OnConnectedToRelay; - clientToServerTransport.OnClientDataReceived = DataReceived; - clientToServerTransport.OnClientDisconnected = Disconnected; - clientToServerTransport.OnClientError = (e) => Debug.LogException(e); - } - - private void Disconnected() - { - _connectedToRelay = false; - _isAuthenticated = false; - disconnectedFromRelay?.Invoke(); - serverStatus = "Disconnected from relay."; - } - - private void OnConnectedToRelay() - { - _connectedToRelay = true; - connectedToRelay?.Invoke(); - } - - public void ConnectToRelay() - { - if (!useLoadBalancer) - { - if (!_connectedToRelay) - { - Connect(serverIP, serverPort); - } - else - { - Debug.LogWarning("LRM | Already connected to relay!"); - } - } - else - { - if (!_connectedToRelay) - { - StartCoroutine(RelayConnect()); - } - else - { - Debug.LogWarning("LRM | Already connected to relay!"); - } - } - } - - /// - /// Connects to the desired relay - /// - /// - private void Connect(string serverIP, ushort port = 7777) - { - // need to implement custom port - if (clientToServerTransport is LightReflectiveMirrorTransport) - throw new Exception("LRM | Client to Server Transport cannot be LRM."); - - SetTransportPort(port); - - this.serverIP = serverIP; - serverStatus = "Connecting to relay..."; - _clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()]; - clientToServerTransport.ClientConnect(serverIP); - } - - public void DisconnectFromRelay() - { - if (IsAuthenticated()) - { - clientToServerTransport.ClientDisconnect(); - } - } - - private void SendHeartbeat() - { - if (_connectedToRelay) - { - // Send a blank message with just the opcode 200, which is heartbeat - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, 200); - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - - // If NAT Puncher is initialized, send heartbeat on that as well. - - try - { - if (_NATPuncher != null) - _NATPuncher.Send(new byte[] { 0 }, 1, _relayPuncherIP); - } - catch (Exception e) - { - print(e); - } - - // Check if any server-side proxies havent been used in 10 seconds, and timeout if so. - var keys = new List(_serverProxies.GetAllKeys()); - - for (int i = 0; i < keys.Count; i++) - { - if (DateTime.Now.Subtract(_serverProxies.GetByFirst(keys[i]).lastInteractionTime).TotalSeconds > 10) - { - _serverProxies.GetByFirst(keys[i]).Dispose(); - _serverProxies.Remove(keys[i]); - } - } - } - } - - private void DataReceived(ArraySegment segmentData, int channel) - { - try - { - var data = segmentData.Array; - int pos = segmentData.Offset; - // Read the opcode of the incoming data, this allows us to know what its used for. - OpCodes opcode = (OpCodes)data.ReadByte(ref pos); - - switch (opcode) - { - case OpCodes.Authenticated: - // Server authenticated us! That means we are fully ready to host and join servers. - serverStatus = "Authenticated! Good to go!"; - _isAuthenticated = true; - RequestServerList(); - break; - - case OpCodes.AuthenticationRequest: - // Server requested that we send an authentication request, lets send our auth key. - serverStatus = "Sent authentication to relay..."; - SendAuthKey(); - break; - - case OpCodes.GetData: - // Someone sent us a packet from their mirror over the relay - var recvData = data.ReadBytes(ref pos); - - // If we are the server and the client is registered, invoke the callback - if (_isServer) - { - if (_connectedRelayClients.TryGetByFirst(data.ReadInt(ref pos), out int clientID)) - OnServerDataReceived?.Invoke(clientID, new ArraySegment(recvData), channel); - } - - // If we are the client, invoke the callback - if (_isClient) - OnClientDataReceived?.Invoke(new ArraySegment(recvData), channel); - break; - - case OpCodes.ServerLeft: - // Called when we were kicked, or server was closed. - if (_isClient) - { - _isClient = false; - OnClientDisconnected?.Invoke(); - } - break; - - case OpCodes.PlayerDisconnected: - // Called when another player left the room. - if (_isServer) - { - // Get their client ID and invoke the mirror callback - int user = data.ReadInt(ref pos); - if (_connectedRelayClients.TryGetByFirst(user, out int clientID)) - { - OnServerDisconnected?.Invoke(clientID); - _connectedRelayClients.Remove(user); - } - } - break; - - case OpCodes.RoomCreated: - // We successfully created the room, the server also gave us the serverId of the room! - serverId = data.ReadString(ref pos); - break; - - case OpCodes.ServerJoined: - // Called when a player joins the room or when we joined a room. - int clientId = data.ReadInt(ref pos); - if (_isClient) - { - // We successfully joined a room, let mirror know. - OnClientConnected?.Invoke(); - } - if (_isServer) - { - // A client joined our room, let mirror know and setup their ID in the dictionary. - _connectedRelayClients.Add(clientId, _currentMemberId); - OnServerConnected?.Invoke(_currentMemberId); - _currentMemberId++; - } - break; - - case OpCodes.DirectConnectIP: - // Either a client is trying to join us via NAT Punch, or we are trying to join a host over NAT punch/Direct connect. - var ip = data.ReadString(ref pos); - int port = data.ReadInt(ref pos); - bool attemptNatPunch = data.ReadBool(ref pos); - - _directConnectEndpoint = new IPEndPoint(IPAddress.Parse(ip), port); - - // Both client and server will send data to each other to open the hole. - if (useNATPunch && attemptNatPunch) - { - StartCoroutine(NATPunch(_directConnectEndpoint)); - } - - if (!_isServer) - { - // We arent the server, so lets tell the direct connect module to attempt a connection and initializing our middle man socket. - if (_clientProxy == null && useNATPunch && attemptNatPunch) - { - _clientProxy = new SocketProxy(_NATIP.Port - 1); - _clientProxy.dataReceived += ClientProcessProxyData; - } - - if (useNATPunch && attemptNatPunch) - { - if (ip == LOCALHOST) - _directConnectModule.JoinServer(LOCALHOST, port + 1); - else - _directConnectModule.JoinServer(LOCALHOST, _NATIP.Port - 1); - } - else - _directConnectModule.JoinServer(ip, port); - } - - break; - - case OpCodes.RequestNATConnection: - // Called when the LRM node would like us to establish a NAT puncher connection. Its safe to ignore if NAT punch is disabled. - if (useNATPunch && GetLocalIp() != null && _directConnectModule != null) - { - byte[] initalData = new byte[150]; - int sendPos = 0; - - initalData.WriteBool(ref sendPos, true); - initalData.WriteString(ref sendPos, data.ReadString(ref pos)); - NATPunchtroughPort = data.ReadInt(ref pos); - - if (_NATPuncher == null) - { - _NATPuncher = new UdpClient { ExclusiveAddressUse = false }; - while (true) - { - try - { - _NATIP = new IPEndPoint(IPAddress.Parse(GetLocalIp()), UnityEngine.Random.Range(16000, 17000)); - _NATPuncher.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - _NATPuncher.Client.Bind(_NATIP); - break; - } - catch { } // Binding port is in use, keep trying :P - } - } - - if (!IPAddress.TryParse(serverIP, out IPAddress serverAddr)) - serverAddr = Dns.GetHostEntry(serverIP).AddressList[0]; - - _relayPuncherIP = new IPEndPoint(serverAddr, NATPunchtroughPort); - - for (int attempts = 0; attempts < NAT_PUNCH_ATTEMPTS; attempts++) - _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); - - _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); - } - break; - } - } - catch (Exception e) { print(e); } - } - - public void SetTransportPort(ushort port) - { - if (clientToServerTransport is KcpTransport kcp) - kcp.Port = port; - - if (clientToServerTransport is TelepathyTransport telepathy) - telepathy.port = port; - - if (clientToServerTransport is SimpleWebTransport swt) - swt.port = port; - } - - public void UpdateRoomName(string newServerName = "My Awesome Server!") - { - if (_isServer) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); - - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteString(ref pos, newServerName); - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, false); - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - } - - public void UpdateRoomData(string newServerData = "Extra Data!") - { - if (_isServer) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); - - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteString(ref pos, newServerData); - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, false); -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - } - - public void UpdateRoomVisibility(bool isPublic = true) - { - if (_isServer) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); - - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteBool(ref pos, isPublic); - _clientSendBuffer.WriteBool(ref pos, false); - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - } - - public void UpdateRoomPlayerCount(int maxPlayers = 16) - { - if (_isServer) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); - - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteInt(ref pos, maxPlayers); - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - } - - private Room? GetServerForID(string serverID) - { - for (int i = 0; i < relayServerList.Count; i++) - { - if (relayServerList[i].serverId == serverID) - return relayServerList[i]; - } - - return null; - } - - private void SendAuthKey() - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationResponse); - _clientSendBuffer.WriteString(ref pos, authenticationKey); - -#if MIRROR_40_0_OR_NEWER - clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); -#else - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); -#endif - } - - public enum OpCodes - { - Default = 0, RequestID = 1, JoinServer = 2, SendData = 3, GetID = 4, ServerJoined = 5, GetData = 6, CreateRoom = 7, ServerLeft = 8, PlayerDisconnected = 9, RoomCreated = 10, - LeaveRoom = 11, KickPlayer = 12, AuthenticationRequest = 13, AuthenticationResponse = 14, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19, RequestNATConnection = 20, - DirectConnectIP = 21 - } - - private static string GetLocalIp() - { - var host = Dns.GetHostEntry(Dns.GetHostName()); - foreach (var ip in host.AddressList) - { - if (ip.AddressFamily == AddressFamily.InterNetwork) - { - return ip.ToString(); - } - } - - return null; - } - } - - [Serializable] - public struct Room - { - public string serverName; - public int maxPlayers; - public string serverId; - public string serverData; - public int hostId; - public List clients; - public int currentPlayers; - public RelayAddress relayInfo; - } - - [Serializable] - public struct RelayAddress - { - public ushort port; - public ushort endpointPort; - public string address; - public LRMRegions serverRegion; - } +using kcp2k; +using Mirror; +using Mirror.SimpleWeb; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using UnityEngine; + +namespace LightReflectiveMirror +{ + [DefaultExecutionOrder(1001)] + public partial class LightReflectiveMirrorTransport : Transport + { + public bool IsAuthenticated() => _isAuthenticated; + + private void Awake() + { + if (Application.platform == RuntimePlatform.WebGLPlayer) + useNATPunch = false; + else + _directConnectModule = GetComponent(); + + if (clientToServerTransport is LightReflectiveMirrorTransport) + throw new Exception("Haha real funny... Use a different transport."); + + if (_directConnectModule != null) + { + if (useNATPunch && !_directConnectModule.SupportsNATPunch()) + { + Debug.LogWarning("LRM | NATPunch is turned on but the transport used does not support it. It will be disabled."); + useNATPunch = false; + } + } + + SetupCallbacks(); + + if (connectOnAwake) + ConnectToRelay(); + + InvokeRepeating(nameof(SendHeartbeat), heartBeatInterval, heartBeatInterval); + } + + private void SetupCallbacks() + { + if (_callbacksInitialized) + return; + + _callbacksInitialized = true; + clientToServerTransport.OnClientConnected = OnConnectedToRelay; + clientToServerTransport.OnClientDataReceived = DataReceived; + clientToServerTransport.OnClientDisconnected = Disconnected; + clientToServerTransport.OnClientError = (e) => Debug.LogException(e); + } + + private void Disconnected() + { + _connectedToRelay = false; + _isAuthenticated = false; + disconnectedFromRelay?.Invoke(); + serverStatus = "Disconnected from relay."; + } + + private void OnConnectedToRelay() + { + _connectedToRelay = true; + connectedToRelay?.Invoke(); + } + + public void ConnectToRelay() + { + if (!useLoadBalancer) + { + if (!_connectedToRelay) + { + Connect(serverIP, serverPort); + } + else + { + Debug.LogWarning("LRM | Already connected to relay!"); + } + } + else + { + if (!_connectedToRelay) + { + StartCoroutine(RelayConnect()); + } + else + { + Debug.LogWarning("LRM | Already connected to relay!"); + } + } + } + + /// + /// Connects to the desired relay + /// + /// + private void Connect(string serverIP, ushort port = 7777) + { + // need to implement custom port + if (clientToServerTransport is LightReflectiveMirrorTransport) + throw new Exception("LRM | Client to Server Transport cannot be LRM."); + + SetTransportPort(port); + + this.serverIP = serverIP; + serverStatus = "Connecting to relay..."; + _clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()]; + clientToServerTransport.ClientConnect(serverIP); + } + + public void DisconnectFromRelay() + { + if (IsAuthenticated()) + { + clientToServerTransport.ClientDisconnect(); + } + } + + private void SendHeartbeat() + { + if (_connectedToRelay) + { + // Send a blank message with just the opcode 200, which is heartbeat + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, 200); + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + + // If NAT Puncher is initialized, send heartbeat on that as well. + + try + { + if (_NATPuncher != null) + _NATPuncher.Send(new byte[] { 0 }, 1, _relayPuncherIP); + } + catch (Exception e) + { + print(e); + } + + // Check if any server-side proxies havent been used in 10 seconds, and timeout if so. + var keys = new List(_serverProxies.GetAllKeys()); + + for (int i = 0; i < keys.Count; i++) + { + if (DateTime.Now.Subtract(_serverProxies.GetByFirst(keys[i]).lastInteractionTime).TotalSeconds > 10) + { + _serverProxies.GetByFirst(keys[i]).Dispose(); + _serverProxies.Remove(keys[i]); + } + } + } + } + + private void DataReceived(ArraySegment segmentData, int channel) + { + try + { + var data = segmentData.Array; + int pos = segmentData.Offset; + // Read the opcode of the incoming data, this allows us to know what its used for. + OpCodes opcode = (OpCodes)data.ReadByte(ref pos); + + switch (opcode) + { + case OpCodes.Authenticated: + // Server authenticated us! That means we are fully ready to host and join servers. + serverStatus = "Authenticated! Good to go!"; + _isAuthenticated = true; + RequestServerList(); + break; + + case OpCodes.AuthenticationRequest: + // Server requested that we send an authentication request, lets send our auth key. + serverStatus = "Sent authentication to relay..."; + SendAuthKey(); + break; + + case OpCodes.GetData: + // Someone sent us a packet from their mirror over the relay + var recvData = data.ReadBytes(ref pos); + + // If we are the server and the client is registered, invoke the callback + if (_isServer) + { + if (_connectedRelayClients.TryGetByFirst(data.ReadInt(ref pos), out int clientID)) + OnServerDataReceived?.Invoke(clientID, new ArraySegment(recvData), channel); + } + + // If we are the client, invoke the callback + if (_isClient) + OnClientDataReceived?.Invoke(new ArraySegment(recvData), channel); + break; + + case OpCodes.ServerLeft: + // Called when we were kicked, or server was closed. + if (_isClient) + { + _isClient = false; + OnClientDisconnected?.Invoke(); + } + break; + + case OpCodes.PlayerDisconnected: + // Called when another player left the room. + if (_isServer) + { + // Get their client ID and invoke the mirror callback + int user = data.ReadInt(ref pos); + if (_connectedRelayClients.TryGetByFirst(user, out int clientID)) + { + OnServerDisconnected?.Invoke(clientID); + _connectedRelayClients.Remove(user); + } + } + break; + + case OpCodes.RoomCreated: + // We successfully created the room, the server also gave us the serverId of the room! + serverId = data.ReadString(ref pos); + break; + + case OpCodes.ServerJoined: + // Called when a player joins the room or when we joined a room. + int clientId = data.ReadInt(ref pos); + if (_isClient) + { + // We successfully joined a room, let mirror know. + OnClientConnected?.Invoke(); + } + if (_isServer) + { + // A client joined our room, let mirror know and setup their ID in the dictionary. + _connectedRelayClients.Add(clientId, _currentMemberId); + OnServerConnected?.Invoke(_currentMemberId); + _currentMemberId++; + } + break; + + case OpCodes.DirectConnectIP: + // Either a client is trying to join us via NAT Punch, or we are trying to join a host over NAT punch/Direct connect. + var ip = data.ReadString(ref pos); + int port = data.ReadInt(ref pos); + bool attemptNatPunch = data.ReadBool(ref pos); + + _directConnectEndpoint = new IPEndPoint(IPAddress.Parse(ip), port); + + // Both client and server will send data to each other to open the hole. + if (useNATPunch && attemptNatPunch) + { + StartCoroutine(NATPunch(_directConnectEndpoint)); + } + + if (!_isServer) + { + // We arent the server, so lets tell the direct connect module to attempt a connection and initializing our middle man socket. + if (_clientProxy == null && useNATPunch && attemptNatPunch) + { + _clientProxy = new SocketProxy(_NATIP.Port - 1); + _clientProxy.dataReceived += ClientProcessProxyData; + } + + if (useNATPunch && attemptNatPunch) + { + if (ip == LOCALHOST) + _directConnectModule.JoinServer(LOCALHOST, port + 1); + else + _directConnectModule.JoinServer(LOCALHOST, _NATIP.Port - 1); + } + else + _directConnectModule.JoinServer(ip, port); + } + + break; + + case OpCodes.RequestNATConnection: + // Called when the LRM node would like us to establish a NAT puncher connection. Its safe to ignore if NAT punch is disabled. + if (useNATPunch && GetLocalIp() != null && _directConnectModule != null) + { + byte[] initalData = new byte[150]; + int sendPos = 0; + + initalData.WriteBool(ref sendPos, true); + initalData.WriteString(ref sendPos, data.ReadString(ref pos)); + NATPunchtroughPort = data.ReadInt(ref pos); + + if (_NATPuncher == null) + { + _NATPuncher = new UdpClient { ExclusiveAddressUse = false }; + while (true) + { + try + { + _NATIP = new IPEndPoint(IPAddress.Parse(GetLocalIp()), UnityEngine.Random.Range(16000, 17000)); + _NATPuncher.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + _NATPuncher.Client.Bind(_NATIP); + break; + } + catch { } // Binding port is in use, keep trying :P + } + } + + if (!IPAddress.TryParse(serverIP, out IPAddress serverAddr)) + serverAddr = Dns.GetHostEntry(serverIP).AddressList[0]; + + _relayPuncherIP = new IPEndPoint(serverAddr, NATPunchtroughPort); + + for (int attempts = 0; attempts < NAT_PUNCH_ATTEMPTS; attempts++) + _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); + + _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); + } + break; + } + } + catch (Exception e) { print(e); } + } + + public void SetTransportPort(ushort port) + { + if (clientToServerTransport is KcpTransport kcp) + kcp.Port = port; + + if (clientToServerTransport is TelepathyTransport telepathy) + telepathy.port = port; + + if (clientToServerTransport is SimpleWebTransport swt) + swt.port = port; + } + + public void UpdateRoomName(string newServerName = "My Awesome Server!") + { + if (_isServer) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); + + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteString(ref pos, newServerName); + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, false); + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + } + + public void UpdateRoomData(string newServerData = "Extra Data!") + { + if (_isServer) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); + + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteString(ref pos, newServerData); + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, false); +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + } + + public void UpdateRoomVisibility(bool isPublic = true) + { + if (_isServer) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); + + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteBool(ref pos, isPublic); + _clientSendBuffer.WriteBool(ref pos, false); + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + } + + public void UpdateRoomPlayerCount(int maxPlayers = 16) + { + if (_isServer) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); + + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteInt(ref pos, maxPlayers); + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + } + + private Room? GetServerForID(string serverID) + { + for (int i = 0; i < relayServerList.Count; i++) + { + if (relayServerList[i].serverId == serverID) + return relayServerList[i]; + } + + return null; + } + + private void SendAuthKey() + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationResponse); + _clientSendBuffer.WriteString(ref pos, authenticationKey); + +#if MIRROR_40_0_OR_NEWER + clientToServerTransport.ClientSend(new ArraySegment(_clientSendBuffer, 0, pos), 0); +#else + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); +#endif + } + + public enum OpCodes + { + Default = 0, RequestID = 1, JoinServer = 2, SendData = 3, GetID = 4, ServerJoined = 5, GetData = 6, CreateRoom = 7, ServerLeft = 8, PlayerDisconnected = 9, RoomCreated = 10, + LeaveRoom = 11, KickPlayer = 12, AuthenticationRequest = 13, AuthenticationResponse = 14, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19, RequestNATConnection = 20, + DirectConnectIP = 21 + } + + private static string GetLocalIp() + { + var host = Dns.GetHostEntry(Dns.GetHostName()); + foreach (var ip in host.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + return ip.ToString(); + } + } + + return null; + } + } + + [Serializable] + public struct Room + { + public string serverName; + public int maxPlayers; + public string serverId; + public string serverData; + public int hostId; + public List clients; + public int currentPlayers; + public RelayAddress relayInfo; + } + + [Serializable] + public struct RelayAddress + { + public ushort port; + public ushort endpointPort; + public string address; + public LRMRegions serverRegion; + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs.meta index 87007ea..e84d878 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/LRMTransport/LightReflectiveMirrorTransport.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 7064b1b1d0671194baf55fa8d5f564d6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: f4000c50bdad7994f8425330b81e5d87, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 7064b1b1d0671194baf55fa8d5f564d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: f4000c50bdad7994f8425330b81e5d87, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/Resources.meta b/Assets/Mirror/Runtime/Transport/LRM/Resources.meta index a4dc52e..8b4ab8e 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/Resources.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/Resources.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 48c208983412ff24abf68dda3691fa07 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 48c208983412ff24abf68dda3691fa07 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/Resources/LRM.png.meta b/Assets/Mirror/Runtime/Transport/LRM/Resources/LRM.png.meta index d6c638f..d97e11f 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/Resources/LRM.png.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/Resources/LRM.png.meta @@ -1,92 +1,92 @@ -fileFormatVersion: 2 -guid: f4000c50bdad7994f8425330b81e5d87 -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 11 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: -1 - aniso: -1 - mipBias: -100 - wrapU: -1 - wrapV: -1 - wrapW: -1 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 0 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - applyGammaDecoding: 0 - platformSettings: - - serializedVersion: 3 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - spritePackingTag: - pSDRemoveMatte: 0 - pSDShowRemoveMatteOption: 0 - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f4000c50bdad7994f8425330b81e5d87 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs b/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs index 8017b66..c91da04 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs +++ b/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs @@ -1,68 +1,68 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using UnityEngine; - -namespace LightReflectiveMirror -{ - - // This class handles the proxying from punched socket to transport. - public class SocketProxy - { - public DateTime lastInteractionTime; - public Action dataReceived; - UdpClient _udpClient; - IPEndPoint _recvEndpoint = new IPEndPoint(IPAddress.Any, 0); - IPEndPoint _remoteEndpoint; - bool _clientInitialRecv = false; - - public SocketProxy(int port, IPEndPoint remoteEndpoint) - { - _udpClient = new UdpClient(); - _udpClient.Connect(new IPEndPoint(IPAddress.Loopback, port)); - _udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient); - lastInteractionTime = DateTime.Now; - // Clone it so when main socket recvies new data, it wont switcheroo on us. - _remoteEndpoint = new IPEndPoint(remoteEndpoint.Address, remoteEndpoint.Port); - } - - public SocketProxy(int port) - { - _udpClient = new UdpClient(port); - _udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient); - lastInteractionTime = DateTime.Now; - } - - public void RelayData(byte[] data, int length) - { - _udpClient.Send(data, length); - lastInteractionTime = DateTime.Now; - } - - public void ClientRelayData(byte[] data, int length) - { - if (_clientInitialRecv) - { - _udpClient.Send(data, length, _recvEndpoint); - lastInteractionTime = DateTime.Now; - } - } - - public void Dispose() - { - _udpClient.Dispose(); - } - - void RecvData(IAsyncResult result) - { - byte[] data = _udpClient.EndReceive(result, ref _recvEndpoint); - _udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient); - _clientInitialRecv = true; - lastInteractionTime = DateTime.Now; - dataReceived?.Invoke(_remoteEndpoint, data); - - } - } +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using UnityEngine; + +namespace LightReflectiveMirror +{ + + // This class handles the proxying from punched socket to transport. + public class SocketProxy + { + public DateTime lastInteractionTime; + public Action dataReceived; + UdpClient _udpClient; + IPEndPoint _recvEndpoint = new IPEndPoint(IPAddress.Any, 0); + IPEndPoint _remoteEndpoint; + bool _clientInitialRecv = false; + + public SocketProxy(int port, IPEndPoint remoteEndpoint) + { + _udpClient = new UdpClient(); + _udpClient.Connect(new IPEndPoint(IPAddress.Loopback, port)); + _udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient); + lastInteractionTime = DateTime.Now; + // Clone it so when main socket recvies new data, it wont switcheroo on us. + _remoteEndpoint = new IPEndPoint(remoteEndpoint.Address, remoteEndpoint.Port); + } + + public SocketProxy(int port) + { + _udpClient = new UdpClient(port); + _udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient); + lastInteractionTime = DateTime.Now; + } + + public void RelayData(byte[] data, int length) + { + _udpClient.Send(data, length); + lastInteractionTime = DateTime.Now; + } + + public void ClientRelayData(byte[] data, int length) + { + if (_clientInitialRecv) + { + _udpClient.Send(data, length, _recvEndpoint); + lastInteractionTime = DateTime.Now; + } + } + + public void Dispose() + { + _udpClient.Dispose(); + } + + void RecvData(IAsyncResult result) + { + byte[] data = _udpClient.EndReceive(result, ref _recvEndpoint); + _udpClient.BeginReceive(new AsyncCallback(RecvData), _udpClient); + _clientInitialRecv = true; + lastInteractionTime = DateTime.Now; + dataReceived?.Invoke(_remoteEndpoint, data); + + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs.meta b/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs.meta index 97b80f3..8192cf7 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/SocketProxy.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: f482ce0c4da51924ba033e827aca8f28 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f482ce0c4da51924ba033e827aca8f28 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LRM/package.json b/Assets/Mirror/Runtime/Transport/LRM/package.json index b510c41..c24b1b0 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/package.json +++ b/Assets/Mirror/Runtime/Transport/LRM/package.json @@ -1,9 +1,9 @@ -{ - "name": "com.derek-r-s.lrm", - "version": "0.10.120", - "displayName": "Light-Reflective-Mirror", - "description": "Light Reflective Mirror is a transport for Mirror Networking which relays network traffic through your own servers.", - "unity": "2019.4", - "dependencies": {}, - "keywords": [] +{ + "name": "com.derek-r-s.lrm", + "version": "0.10.120", + "displayName": "Light-Reflective-Mirror", + "description": "Light Reflective Mirror is a transport for Mirror Networking which relays network traffic through your own servers.", + "unity": "2019.4", + "dependencies": {}, + "keywords": [] } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/LRM/package.json.meta b/Assets/Mirror/Runtime/Transport/LRM/package.json.meta index 83a4cdf..675bfcf 100644 --- a/Assets/Mirror/Runtime/Transport/LRM/package.json.meta +++ b/Assets/Mirror/Runtime/Transport/LRM/package.json.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 09ee2ee51265449fea67160707630572 -PackageManifestImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 09ee2ee51265449fea67160707630572 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/LatencySimulation.cs b/Assets/Mirror/Runtime/Transport/LatencySimulation.cs index b4c0b74..319e184 100644 --- a/Assets/Mirror/Runtime/Transport/LatencySimulation.cs +++ b/Assets/Mirror/Runtime/Transport/LatencySimulation.cs @@ -1,280 +1,280 @@ -// wraps around a transport and adds latency/loss/scramble simulation. -// -// reliable: latency -// unreliable: latency, loss, scramble (unreliable isn't ordered so we scramble) -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace Mirror -{ - struct QueuedMessage - { - public int connectionId; - public byte[] bytes; - public float time; - } - - [HelpURL("https://mirror-networking.gitbook.io/docs/transports/latency-simulaton-transport")] - [DisallowMultipleComponent] - public class LatencySimulation : Transport - { - public Transport wrap; - - [Header("Common")] - [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] - [Range(0, 1)] public float latencySpikeMultiplier; - [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] - public float latencySpikeSpeedMultiplier = 1; - - [Header("Reliable Messages")] - [Tooltip("Reliable latency in seconds")] - public float reliableLatency; - // note: packet loss over reliable manifests itself in latency. - // don't need (and can't add) a loss option here. - // note: reliable is ordered by definition. no need to scramble. - - [Header("Unreliable Messages")] - [Tooltip("Packet loss in %")] - [Range(0, 1)] public float unreliableLoss; - [Tooltip("Unreliable latency in seconds")] - public float unreliableLatency; - [Tooltip("Scramble % of unreliable messages, just like over the real network. Mirror unreliable is unordered.")] - [Range(0, 1)] public float unreliableScramble; - - // message queues - // list so we can insert randomly (scramble) - List reliableClientToServer = new List(); - List reliableServerToClient = new List(); - List unreliableClientToServer = new List(); - List unreliableServerToClient = new List(); - - // random - // UnityEngine.Random.value is [0, 1] with both upper and lower bounds inclusive - // but we need the upper bound to be exclusive, so using System.Random instead. - // => NextDouble() is NEVER < 0 so loss=0 never drops! - // => NextDouble() is ALWAYS < 1 so loss=1 always drops! - System.Random random = new System.Random(); - - public void Awake() - { - if (wrap == null) - throw new Exception("PressureDrop requires an underlying transport to wrap around."); - } - - // forward enable/disable to the wrapped transport - void OnEnable() { wrap.enabled = true; } - void OnDisable() { wrap.enabled = false; } - - // noise function can be replaced if needed - protected virtual float Noise(float time) => Mathf.PerlinNoise(time, time); - - // helper function to simulate latency - float SimulateLatency(int channeldId) - { - // spike over perlin noise. - // no spikes isn't realistic. - // sin is too predictable / no realistic. - // perlin is still deterministic and random enough. - float spike = Noise(Time.time * latencySpikeSpeedMultiplier) * latencySpikeMultiplier; - - // base latency - switch (channeldId) - { - case Channels.Reliable: - return reliableLatency + spike; - case Channels.Unreliable: - return unreliableLatency + spike; - default: - return 0; - } - } - - // helper function to simulate a send with latency/loss/scramble - void SimulateSend(int connectionId, ArraySegment segment, int channelId, float latency, List reliableQueue, List unreliableQueue) - { - // segment is only valid after returning. copy it. - // (allocates for now. it's only for testing anyway.) - byte[] bytes = new byte[segment.Count]; - Buffer.BlockCopy(segment.Array, segment.Offset, bytes, 0, segment.Count); - - // enqueue message. send after latency interval. - QueuedMessage message = new QueuedMessage - { - connectionId = connectionId, - bytes = bytes, - time = Time.time + latency - }; - - switch (channelId) - { - case Channels.Reliable: - // simulate latency - reliableQueue.Add(message); - break; - case Channels.Unreliable: - // simulate packet loss - bool drop = random.NextDouble() < unreliableLoss; - if (!drop) - { - // simulate scramble (Random.Next is < max, so +1) - bool scramble = random.NextDouble() < unreliableScramble; - int last = unreliableQueue.Count; - int index = scramble ? random.Next(0, last + 1) : last; - - // simulate latency - unreliableQueue.Insert(index, message); - } - break; - default: - Debug.LogError($"{nameof(LatencySimulation)} unexpected channelId: {channelId}"); - break; - } - } - - public override bool Available() => wrap.Available(); - - public override void ClientConnect(string address) - { - wrap.OnClientConnected = OnClientConnected; - wrap.OnClientDataReceived = OnClientDataReceived; - wrap.OnClientError = OnClientError; - wrap.OnClientDisconnected = OnClientDisconnected; - wrap.ClientConnect(address); - } - - public override void ClientConnect(Uri uri) - { - wrap.OnClientConnected = OnClientConnected; - wrap.OnClientDataReceived = OnClientDataReceived; - wrap.OnClientError = OnClientError; - wrap.OnClientDisconnected = OnClientDisconnected; - wrap.ClientConnect(uri); - } - - public override bool ClientConnected() => wrap.ClientConnected(); - - public override void ClientDisconnect() - { - wrap.ClientDisconnect(); - reliableClientToServer.Clear(); - unreliableClientToServer.Clear(); - } - - public override void ClientSend(ArraySegment segment, int channelId) - { - float latency = SimulateLatency(channelId); - SimulateSend(0, segment, channelId, latency, reliableClientToServer, unreliableClientToServer); - } - - public override Uri ServerUri() => wrap.ServerUri(); - - public override bool ServerActive() => wrap.ServerActive(); - - public override string ServerGetClientAddress(int connectionId) => wrap.ServerGetClientAddress(connectionId); - - public override void ServerDisconnect(int connectionId) => wrap.ServerDisconnect(connectionId); - - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) - { - float latency = SimulateLatency(channelId); - SimulateSend(connectionId, segment, channelId, latency, reliableServerToClient, unreliableServerToClient); - } - - public override void ServerStart() - { - wrap.OnServerConnected = OnServerConnected; - wrap.OnServerDataReceived = OnServerDataReceived; - wrap.OnServerError = OnServerError; - wrap.OnServerDisconnected = OnServerDisconnected; - wrap.ServerStart(); - } - - public override void ServerStop() - { - wrap.ServerStop(); - reliableServerToClient.Clear(); - unreliableServerToClient.Clear(); - } - - public override void ClientEarlyUpdate() => wrap.ClientEarlyUpdate(); - public override void ServerEarlyUpdate() => wrap.ServerEarlyUpdate(); - public override void ClientLateUpdate() - { - // flush reliable messages after latency - while (reliableClientToServer.Count > 0) - { - // check the first message time - QueuedMessage message = reliableClientToServer[0]; - if (message.time <= Time.time) - { - // send and eat - wrap.ClientSend(new ArraySegment(message.bytes), Channels.Reliable); - reliableClientToServer.RemoveAt(0); - } - // not enough time elapsed yet - break; - } - - // flush unreliable messages after latency - while (unreliableClientToServer.Count > 0) - { - // check the first message time - QueuedMessage message = unreliableClientToServer[0]; - if (message.time <= Time.time) - { - // send and eat - wrap.ClientSend(new ArraySegment(message.bytes), Channels.Unreliable); - unreliableClientToServer.RemoveAt(0); - } - // not enough time elapsed yet - break; - } - - // update wrapped transport too - wrap.ClientLateUpdate(); - } - public override void ServerLateUpdate() - { - // flush reliable messages after latency - while (reliableServerToClient.Count > 0) - { - // check the first message time - QueuedMessage message = reliableServerToClient[0]; - if (message.time <= Time.time) - { - // send and eat - wrap.ServerSend(message.connectionId, new ArraySegment(message.bytes), Channels.Reliable); - reliableServerToClient.RemoveAt(0); - } - // not enough time elapsed yet - break; - } - - // flush unreliable messages after latency - while (unreliableServerToClient.Count > 0) - { - // check the first message time - QueuedMessage message = unreliableServerToClient[0]; - if (message.time <= Time.time) - { - // send and eat - wrap.ServerSend(message.connectionId, new ArraySegment(message.bytes), Channels.Unreliable); - unreliableServerToClient.RemoveAt(0); - } - // not enough time elapsed yet - break; - } - - // update wrapped transport too - wrap.ServerLateUpdate(); - } - - public override int GetBatchThreshold(int channelId) => wrap.GetBatchThreshold(channelId); - public override int GetMaxPacketSize(int channelId = 0) => wrap.GetMaxPacketSize(channelId); - - public override void Shutdown() => wrap.Shutdown(); - - public override string ToString() => $"{nameof(LatencySimulation)} {wrap}"; - } -} +// wraps around a transport and adds latency/loss/scramble simulation. +// +// reliable: latency +// unreliable: latency, loss, scramble (unreliable isn't ordered so we scramble) +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Mirror +{ + struct QueuedMessage + { + public int connectionId; + public byte[] bytes; + public float time; + } + + [HelpURL("https://mirror-networking.gitbook.io/docs/transports/latency-simulaton-transport")] + [DisallowMultipleComponent] + public class LatencySimulation : Transport + { + public Transport wrap; + + [Header("Common")] + [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] + [Range(0, 1)] public float latencySpikeMultiplier; + [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] + public float latencySpikeSpeedMultiplier = 1; + + [Header("Reliable Messages")] + [Tooltip("Reliable latency in seconds")] + public float reliableLatency; + // note: packet loss over reliable manifests itself in latency. + // don't need (and can't add) a loss option here. + // note: reliable is ordered by definition. no need to scramble. + + [Header("Unreliable Messages")] + [Tooltip("Packet loss in %")] + [Range(0, 1)] public float unreliableLoss; + [Tooltip("Unreliable latency in seconds")] + public float unreliableLatency; + [Tooltip("Scramble % of unreliable messages, just like over the real network. Mirror unreliable is unordered.")] + [Range(0, 1)] public float unreliableScramble; + + // message queues + // list so we can insert randomly (scramble) + List reliableClientToServer = new List(); + List reliableServerToClient = new List(); + List unreliableClientToServer = new List(); + List unreliableServerToClient = new List(); + + // random + // UnityEngine.Random.value is [0, 1] with both upper and lower bounds inclusive + // but we need the upper bound to be exclusive, so using System.Random instead. + // => NextDouble() is NEVER < 0 so loss=0 never drops! + // => NextDouble() is ALWAYS < 1 so loss=1 always drops! + System.Random random = new System.Random(); + + public void Awake() + { + if (wrap == null) + throw new Exception("PressureDrop requires an underlying transport to wrap around."); + } + + // forward enable/disable to the wrapped transport + void OnEnable() { wrap.enabled = true; } + void OnDisable() { wrap.enabled = false; } + + // noise function can be replaced if needed + protected virtual float Noise(float time) => Mathf.PerlinNoise(time, time); + + // helper function to simulate latency + float SimulateLatency(int channeldId) + { + // spike over perlin noise. + // no spikes isn't realistic. + // sin is too predictable / no realistic. + // perlin is still deterministic and random enough. + float spike = Noise(Time.time * latencySpikeSpeedMultiplier) * latencySpikeMultiplier; + + // base latency + switch (channeldId) + { + case Channels.Reliable: + return reliableLatency + spike; + case Channels.Unreliable: + return unreliableLatency + spike; + default: + return 0; + } + } + + // helper function to simulate a send with latency/loss/scramble + void SimulateSend(int connectionId, ArraySegment segment, int channelId, float latency, List reliableQueue, List unreliableQueue) + { + // segment is only valid after returning. copy it. + // (allocates for now. it's only for testing anyway.) + byte[] bytes = new byte[segment.Count]; + Buffer.BlockCopy(segment.Array, segment.Offset, bytes, 0, segment.Count); + + // enqueue message. send after latency interval. + QueuedMessage message = new QueuedMessage + { + connectionId = connectionId, + bytes = bytes, + time = Time.time + latency + }; + + switch (channelId) + { + case Channels.Reliable: + // simulate latency + reliableQueue.Add(message); + break; + case Channels.Unreliable: + // simulate packet loss + bool drop = random.NextDouble() < unreliableLoss; + if (!drop) + { + // simulate scramble (Random.Next is < max, so +1) + bool scramble = random.NextDouble() < unreliableScramble; + int last = unreliableQueue.Count; + int index = scramble ? random.Next(0, last + 1) : last; + + // simulate latency + unreliableQueue.Insert(index, message); + } + break; + default: + Debug.LogError($"{nameof(LatencySimulation)} unexpected channelId: {channelId}"); + break; + } + } + + public override bool Available() => wrap.Available(); + + public override void ClientConnect(string address) + { + wrap.OnClientConnected = OnClientConnected; + wrap.OnClientDataReceived = OnClientDataReceived; + wrap.OnClientError = OnClientError; + wrap.OnClientDisconnected = OnClientDisconnected; + wrap.ClientConnect(address); + } + + public override void ClientConnect(Uri uri) + { + wrap.OnClientConnected = OnClientConnected; + wrap.OnClientDataReceived = OnClientDataReceived; + wrap.OnClientError = OnClientError; + wrap.OnClientDisconnected = OnClientDisconnected; + wrap.ClientConnect(uri); + } + + public override bool ClientConnected() => wrap.ClientConnected(); + + public override void ClientDisconnect() + { + wrap.ClientDisconnect(); + reliableClientToServer.Clear(); + unreliableClientToServer.Clear(); + } + + public override void ClientSend(ArraySegment segment, int channelId) + { + float latency = SimulateLatency(channelId); + SimulateSend(0, segment, channelId, latency, reliableClientToServer, unreliableClientToServer); + } + + public override Uri ServerUri() => wrap.ServerUri(); + + public override bool ServerActive() => wrap.ServerActive(); + + public override string ServerGetClientAddress(int connectionId) => wrap.ServerGetClientAddress(connectionId); + + public override void ServerDisconnect(int connectionId) => wrap.ServerDisconnect(connectionId); + + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) + { + float latency = SimulateLatency(channelId); + SimulateSend(connectionId, segment, channelId, latency, reliableServerToClient, unreliableServerToClient); + } + + public override void ServerStart() + { + wrap.OnServerConnected = OnServerConnected; + wrap.OnServerDataReceived = OnServerDataReceived; + wrap.OnServerError = OnServerError; + wrap.OnServerDisconnected = OnServerDisconnected; + wrap.ServerStart(); + } + + public override void ServerStop() + { + wrap.ServerStop(); + reliableServerToClient.Clear(); + unreliableServerToClient.Clear(); + } + + public override void ClientEarlyUpdate() => wrap.ClientEarlyUpdate(); + public override void ServerEarlyUpdate() => wrap.ServerEarlyUpdate(); + public override void ClientLateUpdate() + { + // flush reliable messages after latency + while (reliableClientToServer.Count > 0) + { + // check the first message time + QueuedMessage message = reliableClientToServer[0]; + if (message.time <= Time.time) + { + // send and eat + wrap.ClientSend(new ArraySegment(message.bytes), Channels.Reliable); + reliableClientToServer.RemoveAt(0); + } + // not enough time elapsed yet + break; + } + + // flush unreliable messages after latency + while (unreliableClientToServer.Count > 0) + { + // check the first message time + QueuedMessage message = unreliableClientToServer[0]; + if (message.time <= Time.time) + { + // send and eat + wrap.ClientSend(new ArraySegment(message.bytes), Channels.Unreliable); + unreliableClientToServer.RemoveAt(0); + } + // not enough time elapsed yet + break; + } + + // update wrapped transport too + wrap.ClientLateUpdate(); + } + public override void ServerLateUpdate() + { + // flush reliable messages after latency + while (reliableServerToClient.Count > 0) + { + // check the first message time + QueuedMessage message = reliableServerToClient[0]; + if (message.time <= Time.time) + { + // send and eat + wrap.ServerSend(message.connectionId, new ArraySegment(message.bytes), Channels.Reliable); + reliableServerToClient.RemoveAt(0); + } + // not enough time elapsed yet + break; + } + + // flush unreliable messages after latency + while (unreliableServerToClient.Count > 0) + { + // check the first message time + QueuedMessage message = unreliableServerToClient[0]; + if (message.time <= Time.time) + { + // send and eat + wrap.ServerSend(message.connectionId, new ArraySegment(message.bytes), Channels.Unreliable); + unreliableServerToClient.RemoveAt(0); + } + // not enough time elapsed yet + break; + } + + // update wrapped transport too + wrap.ServerLateUpdate(); + } + + public override int GetBatchThreshold(int channelId) => wrap.GetBatchThreshold(channelId); + public override int GetMaxPacketSize(int channelId = 0) => wrap.GetMaxPacketSize(channelId); + + public override void Shutdown() => wrap.Shutdown(); + + public override string ToString() => $"{nameof(LatencySimulation)} {wrap}"; + } +} diff --git a/Assets/Mirror/Runtime/Transport/LatencySimulation.cs.meta b/Assets/Mirror/Runtime/Transport/LatencySimulation.cs.meta index eabbe4a..db475bf 100644 --- a/Assets/Mirror/Runtime/Transport/LatencySimulation.cs.meta +++ b/Assets/Mirror/Runtime/Transport/LatencySimulation.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 96b149f511061407fb54895c057b7736 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 96b149f511061407fb54895c057b7736 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs b/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs index a0a64ad..80314bf 100644 --- a/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs +++ b/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs @@ -1,54 +1,54 @@ -using System; -using UnityEngine; - -namespace Mirror -{ - /// - /// Allows Middleware to override some of the transport methods or let the inner transport handle them. - /// - [DisallowMultipleComponent] - public abstract class MiddlewareTransport : Transport - { - /// - /// Transport to call to after middleware - /// - public Transport inner; - - public override bool Available() => inner.Available(); - public override int GetMaxPacketSize(int channelId = 0) => inner.GetMaxPacketSize(channelId); - public override void Shutdown() => inner.Shutdown(); - - #region Client - public override void ClientConnect(string address) - { - inner.OnClientConnected = OnClientConnected; - inner.OnClientDataReceived = OnClientDataReceived; - inner.OnClientDisconnected = OnClientDisconnected; - inner.OnClientError = OnClientError; - inner.ClientConnect(address); - } - - public override bool ClientConnected() => inner.ClientConnected(); - public override void ClientDisconnect() => inner.ClientDisconnect(); - public override void ClientSend(ArraySegment segment, int channelId) => inner.ClientSend(segment, channelId); - #endregion - - #region Server - public override bool ServerActive() => inner.ServerActive(); - public override void ServerStart() - { - inner.OnServerConnected = OnServerConnected; - inner.OnServerDataReceived = OnServerDataReceived; - inner.OnServerDisconnected = OnServerDisconnected; - inner.OnServerError = OnServerError; - inner.ServerStart(); - } - - public override void ServerStop() => inner.ServerStop(); - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) => inner.ServerSend(connectionId, segment, channelId); - public override void ServerDisconnect(int connectionId) => inner.ServerDisconnect(connectionId); - public override string ServerGetClientAddress(int connectionId) => inner.ServerGetClientAddress(connectionId); - public override Uri ServerUri() => inner.ServerUri(); - #endregion - } -} +using System; +using UnityEngine; + +namespace Mirror +{ + /// + /// Allows Middleware to override some of the transport methods or let the inner transport handle them. + /// + [DisallowMultipleComponent] + public abstract class MiddlewareTransport : Transport + { + /// + /// Transport to call to after middleware + /// + public Transport inner; + + public override bool Available() => inner.Available(); + public override int GetMaxPacketSize(int channelId = 0) => inner.GetMaxPacketSize(channelId); + public override void Shutdown() => inner.Shutdown(); + + #region Client + public override void ClientConnect(string address) + { + inner.OnClientConnected = OnClientConnected; + inner.OnClientDataReceived = OnClientDataReceived; + inner.OnClientDisconnected = OnClientDisconnected; + inner.OnClientError = OnClientError; + inner.ClientConnect(address); + } + + public override bool ClientConnected() => inner.ClientConnected(); + public override void ClientDisconnect() => inner.ClientDisconnect(); + public override void ClientSend(ArraySegment segment, int channelId) => inner.ClientSend(segment, channelId); + #endregion + + #region Server + public override bool ServerActive() => inner.ServerActive(); + public override void ServerStart() + { + inner.OnServerConnected = OnServerConnected; + inner.OnServerDataReceived = OnServerDataReceived; + inner.OnServerDisconnected = OnServerDisconnected; + inner.OnServerError = OnServerError; + inner.ServerStart(); + } + + public override void ServerStop() => inner.ServerStop(); + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) => inner.ServerSend(connectionId, segment, channelId); + public override void ServerDisconnect(int connectionId) => inner.ServerDisconnect(connectionId); + public override string ServerGetClientAddress(int connectionId) => inner.ServerGetClientAddress(connectionId); + public override Uri ServerUri() => inner.ServerUri(); + #endregion + } +} diff --git a/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs.meta b/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs.meta index dce8378..79529ec 100644 --- a/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs.meta +++ b/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 46f20ede74658e147a1af57172710de2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 46f20ede74658e147a1af57172710de2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs b/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs index 0d0503d..fa05409 100644 --- a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs +++ b/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs @@ -1,306 +1,306 @@ -using System; -using System.Text; -using UnityEngine; - -namespace Mirror -{ - // a transport that can listen to multiple underlying transport at the same time - [DisallowMultipleComponent] - public class MultiplexTransport : Transport - { - public Transport[] transports; - - Transport available; - - public void Awake() - { - if (transports == null || transports.Length == 0) - { - Debug.LogError("Multiplex transport requires at least 1 underlying transport"); - } - } - - public override void ClientEarlyUpdate() - { - foreach (Transport transport in transports) - { - transport.ClientEarlyUpdate(); - } - } - - public override void ServerEarlyUpdate() - { - foreach (Transport transport in transports) - { - transport.ServerEarlyUpdate(); - } - } - - public override void ClientLateUpdate() - { - foreach (Transport transport in transports) - { - transport.ClientLateUpdate(); - } - } - - public override void ServerLateUpdate() - { - foreach (Transport transport in transports) - { - transport.ServerLateUpdate(); - } - } - - void OnEnable() - { - foreach (Transport transport in transports) - { - transport.enabled = true; - } - } - - void OnDisable() - { - foreach (Transport transport in transports) - { - transport.enabled = false; - } - } - - public override bool Available() - { - // available if any of the transports is available - foreach (Transport transport in transports) - { - if (transport.Available()) - { - return true; - } - } - return false; - } - - #region Client - - public override void ClientConnect(string address) - { - foreach (Transport transport in transports) - { - if (transport.Available()) - { - available = transport; - transport.OnClientConnected = OnClientConnected; - transport.OnClientDataReceived = OnClientDataReceived; - transport.OnClientError = OnClientError; - transport.OnClientDisconnected = OnClientDisconnected; - transport.ClientConnect(address); - return; - } - } - throw new ArgumentException("No transport suitable for this platform"); - } - - public override void ClientConnect(Uri uri) - { - foreach (Transport transport in transports) - { - if (transport.Available()) - { - try - { - available = transport; - transport.OnClientConnected = OnClientConnected; - transport.OnClientDataReceived = OnClientDataReceived; - transport.OnClientError = OnClientError; - transport.OnClientDisconnected = OnClientDisconnected; - transport.ClientConnect(uri); - return; - } - catch (ArgumentException) - { - // transport does not support the schema, just move on to the next one - } - } - } - throw new ArgumentException("No transport suitable for this platform"); - } - - public override bool ClientConnected() - { - return (object)available != null && available.ClientConnected(); - } - - public override void ClientDisconnect() - { - if ((object)available != null) - available.ClientDisconnect(); - } - - public override void ClientSend(ArraySegment segment, int channelId) - { - available.ClientSend(segment, channelId); - } - - #endregion - - #region Server - // connection ids get mapped to base transports - // if we have 3 transports, then - // transport 0 will produce connection ids [0, 3, 6, 9, ...] - // transport 1 will produce connection ids [1, 4, 7, 10, ...] - // transport 2 will produce connection ids [2, 5, 8, 11, ...] - int FromBaseId(int transportId, int connectionId) - { - return connectionId * transports.Length + transportId; - } - - int ToBaseId(int connectionId) - { - return connectionId / transports.Length; - } - - int ToTransportId(int connectionId) - { - return connectionId % transports.Length; - } - - void AddServerCallbacks() - { - // wire all the base transports to my events - for (int i = 0; i < transports.Length; i++) - { - // this is required for the handlers, if I use i directly - // then all the handlers will use the last i - int locali = i; - Transport transport = transports[i]; - - transport.OnServerConnected = (baseConnectionId => - { - OnServerConnected.Invoke(FromBaseId(locali, baseConnectionId)); - }); - - transport.OnServerDataReceived = (baseConnectionId, data, channel) => - { - OnServerDataReceived.Invoke(FromBaseId(locali, baseConnectionId), data, channel); - }; - - transport.OnServerError = (baseConnectionId, error) => - { - OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error); - }; - transport.OnServerDisconnected = baseConnectionId => - { - OnServerDisconnected.Invoke(FromBaseId(locali, baseConnectionId)); - }; - } - } - - // for now returns the first uri, - // should we return all available uris? - public override Uri ServerUri() - { - return transports[0].ServerUri(); - } - - - public override bool ServerActive() - { - // avoid Linq.All allocations - foreach (Transport transport in transports) - { - if (!transport.ServerActive()) - { - return false; - } - } - return true; - } - - public override string ServerGetClientAddress(int connectionId) - { - int baseConnectionId = ToBaseId(connectionId); - int transportId = ToTransportId(connectionId); - return transports[transportId].ServerGetClientAddress(baseConnectionId); - } - - public override void ServerDisconnect(int connectionId) - { - int baseConnectionId = ToBaseId(connectionId); - int transportId = ToTransportId(connectionId); - transports[transportId].ServerDisconnect(baseConnectionId); - } - - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) - { - int baseConnectionId = ToBaseId(connectionId); - int transportId = ToTransportId(connectionId); - - for (int i = 0; i < transports.Length; ++i) - { - if (i == transportId) - { - transports[i].ServerSend(baseConnectionId, segment, channelId); - } - } - } - - public override void ServerStart() - { - foreach (Transport transport in transports) - { - AddServerCallbacks(); - transport.ServerStart(); - } - } - - public override void ServerStop() - { - foreach (Transport transport in transports) - { - transport.ServerStop(); - } - } - #endregion - - public override int GetMaxPacketSize(int channelId = 0) - { - // finding the max packet size in a multiplex environment has to be - // done very carefully: - // * servers run multiple transports at the same time - // * different clients run different transports - // * there should only ever be ONE true max packet size for everyone, - // otherwise a spawn message might be sent to all tcp sockets, but - // be too big for some udp sockets. that would be a debugging - // nightmare and allow for possible exploits and players on - // different platforms seeing a different game state. - // => the safest solution is to use the smallest max size for all - // transports. that will never fail. - int mininumAllowedSize = int.MaxValue; - foreach (Transport transport in transports) - { - int size = transport.GetMaxPacketSize(channelId); - mininumAllowedSize = Mathf.Min(size, mininumAllowedSize); - } - return mininumAllowedSize; - } - - public override void Shutdown() - { - foreach (Transport transport in transports) - { - transport.Shutdown(); - } - } - - public override string ToString() - { - StringBuilder builder = new StringBuilder(); - foreach (Transport transport in transports) - { - builder.AppendLine(transport.ToString()); - } - return builder.ToString().Trim(); - } - } -} +using System; +using System.Text; +using UnityEngine; + +namespace Mirror +{ + // a transport that can listen to multiple underlying transport at the same time + [DisallowMultipleComponent] + public class MultiplexTransport : Transport + { + public Transport[] transports; + + Transport available; + + public void Awake() + { + if (transports == null || transports.Length == 0) + { + Debug.LogError("Multiplex transport requires at least 1 underlying transport"); + } + } + + public override void ClientEarlyUpdate() + { + foreach (Transport transport in transports) + { + transport.ClientEarlyUpdate(); + } + } + + public override void ServerEarlyUpdate() + { + foreach (Transport transport in transports) + { + transport.ServerEarlyUpdate(); + } + } + + public override void ClientLateUpdate() + { + foreach (Transport transport in transports) + { + transport.ClientLateUpdate(); + } + } + + public override void ServerLateUpdate() + { + foreach (Transport transport in transports) + { + transport.ServerLateUpdate(); + } + } + + void OnEnable() + { + foreach (Transport transport in transports) + { + transport.enabled = true; + } + } + + void OnDisable() + { + foreach (Transport transport in transports) + { + transport.enabled = false; + } + } + + public override bool Available() + { + // available if any of the transports is available + foreach (Transport transport in transports) + { + if (transport.Available()) + { + return true; + } + } + return false; + } + + #region Client + + public override void ClientConnect(string address) + { + foreach (Transport transport in transports) + { + if (transport.Available()) + { + available = transport; + transport.OnClientConnected = OnClientConnected; + transport.OnClientDataReceived = OnClientDataReceived; + transport.OnClientError = OnClientError; + transport.OnClientDisconnected = OnClientDisconnected; + transport.ClientConnect(address); + return; + } + } + throw new ArgumentException("No transport suitable for this platform"); + } + + public override void ClientConnect(Uri uri) + { + foreach (Transport transport in transports) + { + if (transport.Available()) + { + try + { + available = transport; + transport.OnClientConnected = OnClientConnected; + transport.OnClientDataReceived = OnClientDataReceived; + transport.OnClientError = OnClientError; + transport.OnClientDisconnected = OnClientDisconnected; + transport.ClientConnect(uri); + return; + } + catch (ArgumentException) + { + // transport does not support the schema, just move on to the next one + } + } + } + throw new ArgumentException("No transport suitable for this platform"); + } + + public override bool ClientConnected() + { + return (object)available != null && available.ClientConnected(); + } + + public override void ClientDisconnect() + { + if ((object)available != null) + available.ClientDisconnect(); + } + + public override void ClientSend(ArraySegment segment, int channelId) + { + available.ClientSend(segment, channelId); + } + + #endregion + + #region Server + // connection ids get mapped to base transports + // if we have 3 transports, then + // transport 0 will produce connection ids [0, 3, 6, 9, ...] + // transport 1 will produce connection ids [1, 4, 7, 10, ...] + // transport 2 will produce connection ids [2, 5, 8, 11, ...] + int FromBaseId(int transportId, int connectionId) + { + return connectionId * transports.Length + transportId; + } + + int ToBaseId(int connectionId) + { + return connectionId / transports.Length; + } + + int ToTransportId(int connectionId) + { + return connectionId % transports.Length; + } + + void AddServerCallbacks() + { + // wire all the base transports to my events + for (int i = 0; i < transports.Length; i++) + { + // this is required for the handlers, if I use i directly + // then all the handlers will use the last i + int locali = i; + Transport transport = transports[i]; + + transport.OnServerConnected = (baseConnectionId => + { + OnServerConnected.Invoke(FromBaseId(locali, baseConnectionId)); + }); + + transport.OnServerDataReceived = (baseConnectionId, data, channel) => + { + OnServerDataReceived.Invoke(FromBaseId(locali, baseConnectionId), data, channel); + }; + + transport.OnServerError = (baseConnectionId, error) => + { + OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error); + }; + transport.OnServerDisconnected = baseConnectionId => + { + OnServerDisconnected.Invoke(FromBaseId(locali, baseConnectionId)); + }; + } + } + + // for now returns the first uri, + // should we return all available uris? + public override Uri ServerUri() + { + return transports[0].ServerUri(); + } + + + public override bool ServerActive() + { + // avoid Linq.All allocations + foreach (Transport transport in transports) + { + if (!transport.ServerActive()) + { + return false; + } + } + return true; + } + + public override string ServerGetClientAddress(int connectionId) + { + int baseConnectionId = ToBaseId(connectionId); + int transportId = ToTransportId(connectionId); + return transports[transportId].ServerGetClientAddress(baseConnectionId); + } + + public override void ServerDisconnect(int connectionId) + { + int baseConnectionId = ToBaseId(connectionId); + int transportId = ToTransportId(connectionId); + transports[transportId].ServerDisconnect(baseConnectionId); + } + + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) + { + int baseConnectionId = ToBaseId(connectionId); + int transportId = ToTransportId(connectionId); + + for (int i = 0; i < transports.Length; ++i) + { + if (i == transportId) + { + transports[i].ServerSend(baseConnectionId, segment, channelId); + } + } + } + + public override void ServerStart() + { + foreach (Transport transport in transports) + { + AddServerCallbacks(); + transport.ServerStart(); + } + } + + public override void ServerStop() + { + foreach (Transport transport in transports) + { + transport.ServerStop(); + } + } + #endregion + + public override int GetMaxPacketSize(int channelId = 0) + { + // finding the max packet size in a multiplex environment has to be + // done very carefully: + // * servers run multiple transports at the same time + // * different clients run different transports + // * there should only ever be ONE true max packet size for everyone, + // otherwise a spawn message might be sent to all tcp sockets, but + // be too big for some udp sockets. that would be a debugging + // nightmare and allow for possible exploits and players on + // different platforms seeing a different game state. + // => the safest solution is to use the smallest max size for all + // transports. that will never fail. + int mininumAllowedSize = int.MaxValue; + foreach (Transport transport in transports) + { + int size = transport.GetMaxPacketSize(channelId); + mininumAllowedSize = Mathf.Min(size, mininumAllowedSize); + } + return mininumAllowedSize; + } + + public override void Shutdown() + { + foreach (Transport transport in transports) + { + transport.Shutdown(); + } + } + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + foreach (Transport transport in transports) + { + builder.AppendLine(transport.ToString()); + } + return builder.ToString().Trim(); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs.meta b/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs.meta index 6e97b28..debaa8d 100644 --- a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs.meta +++ b/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 929e3234c7db540b899f00183fc2b1fe -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 929e3234c7db540b899f00183fc2b1fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport.meta index 5baa80f..2d2545d 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: a3ba68af305d809418d6c6a804939290 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a3ba68af305d809418d6c6a804939290 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json new file mode 100644 index 0000000..58be06f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json @@ -0,0 +1,8 @@ +{ + "_readme_1": "Make a copy of this file and update the fields below. (readme fields should be deleted)", + "_readme_2": "Include the json file and cert with your server build ONLY (put them outside of asset folder)", + "_readme_path": "path is relative from cwd not this json file", + "_readme_password": "password can be empty or left out", + "path": "./certs/MirrorLocal.pfx", + "password": "" +} \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json.meta new file mode 100644 index 0000000..75090bc --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json.meta @@ -0,0 +1,2 @@ +guid: 2F50A381F3D112C3F81A7CCFAA732196 +fileFormatVersion: 2 diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs index 25269e2..6d52bb3 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("SimpleWebTransport.Tests.Runtime")] -[assembly: InternalsVisibleTo("SimpleWebTransport.Tests.Editor")] +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("SimpleWebTransport.Tests.Runtime")] +[assembly: InternalsVisibleTo("SimpleWebTransport.Tests.Editor")] diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs.meta index 028a307..04a5798 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: ee9e76201f7665244bd6ab8ea343a83f -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: ee9e76201f7665244bd6ab8ea343a83f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client.meta index e6e2943..cac3231 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 5faa957b8d9fc314ab7596ccf14750d9 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 5faa957b8d9fc314ab7596ccf14750d9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs index 5c9e030..64c9e3a 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs @@ -1,86 +1,86 @@ -using System; -using System.Collections.Concurrent; -using UnityEngine; - -namespace Mirror.SimpleWeb -{ - public enum ClientState - { - NotConnected = 0, - Connecting = 1, - Connected = 2, - Disconnecting = 3, - } - /// - /// Client used to control websockets - /// Base class used by WebSocketClientWebGl and WebSocketClientStandAlone - /// - public abstract class SimpleWebClient - { - public static SimpleWebClient Create(int maxMessageSize, int maxMessagesPerTick, TcpConfig tcpConfig) - { -#if UNITY_WEBGL && !UNITY_EDITOR - return new WebSocketClientWebGl(maxMessageSize, maxMessagesPerTick); -#else - return new WebSocketClientStandAlone(maxMessageSize, maxMessagesPerTick, tcpConfig); -#endif - } - - readonly int maxMessagesPerTick; - protected readonly int maxMessageSize; - protected readonly ConcurrentQueue receiveQueue = new ConcurrentQueue(); - protected readonly BufferPool bufferPool; - - protected ClientState state; - - protected SimpleWebClient(int maxMessageSize, int maxMessagesPerTick) - { - this.maxMessageSize = maxMessageSize; - this.maxMessagesPerTick = maxMessagesPerTick; - bufferPool = new BufferPool(5, 20, maxMessageSize); - } - - public ClientState ConnectionState => state; - - public event Action onConnect; - public event Action onDisconnect; - public event Action> onData; - public event Action onError; - - public void ProcessMessageQueue(MonoBehaviour behaviour) - { - int processedCount = 0; - // check enabled every time in case behaviour was disabled after data - while ( - behaviour.enabled && - processedCount < maxMessagesPerTick && - // Dequeue last - receiveQueue.TryDequeue(out Message next) - ) - { - processedCount++; - - switch (next.type) - { - case EventType.Connected: - onConnect?.Invoke(); - break; - case EventType.Data: - onData?.Invoke(next.data.ToSegment()); - next.data.Release(); - break; - case EventType.Disconnected: - onDisconnect?.Invoke(); - break; - case EventType.Error: - onError?.Invoke(next.exception); - break; - } - } - } - - public abstract void Connect(Uri serverAddress); - public abstract void Disconnect(); - public abstract void Send(ArraySegment segment); - } -} +using System; +using System.Collections.Concurrent; +using UnityEngine; + +namespace Mirror.SimpleWeb +{ + public enum ClientState + { + NotConnected = 0, + Connecting = 1, + Connected = 2, + Disconnecting = 3, + } + /// + /// Client used to control websockets + /// Base class used by WebSocketClientWebGl and WebSocketClientStandAlone + /// + public abstract class SimpleWebClient + { + public static SimpleWebClient Create(int maxMessageSize, int maxMessagesPerTick, TcpConfig tcpConfig) + { +#if UNITY_WEBGL && !UNITY_EDITOR + return new WebSocketClientWebGl(maxMessageSize, maxMessagesPerTick); +#else + return new WebSocketClientStandAlone(maxMessageSize, maxMessagesPerTick, tcpConfig); +#endif + } + + readonly int maxMessagesPerTick; + protected readonly int maxMessageSize; + protected readonly ConcurrentQueue receiveQueue = new ConcurrentQueue(); + protected readonly BufferPool bufferPool; + + protected ClientState state; + + protected SimpleWebClient(int maxMessageSize, int maxMessagesPerTick) + { + this.maxMessageSize = maxMessageSize; + this.maxMessagesPerTick = maxMessagesPerTick; + bufferPool = new BufferPool(5, 20, maxMessageSize); + } + + public ClientState ConnectionState => state; + + public event Action onConnect; + public event Action onDisconnect; + public event Action> onData; + public event Action onError; + + public void ProcessMessageQueue(MonoBehaviour behaviour) + { + int processedCount = 0; + // check enabled every time in case behaviour was disabled after data + while ( + behaviour.enabled && + processedCount < maxMessagesPerTick && + // Dequeue last + receiveQueue.TryDequeue(out Message next) + ) + { + processedCount++; + + switch (next.type) + { + case EventType.Connected: + onConnect?.Invoke(); + break; + case EventType.Data: + onData?.Invoke(next.data.ToSegment()); + next.data.Release(); + break; + case EventType.Disconnected: + onDisconnect?.Invoke(); + break; + case EventType.Error: + onError?.Invoke(next.exception); + break; + } + } + } + + public abstract void Connect(Uri serverAddress); + public abstract void Disconnect(); + public abstract void Send(ArraySegment segment); + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs.meta index 90c361b..be2f4f9 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 13131761a0bf5a64dadeccd700fe26e5 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 13131761a0bf5a64dadeccd700fe26e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone.meta index bf320c6..ce466a5 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: a9c19d05220a87c4cbbe4d1e422da0aa -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a9c19d05220a87c4cbbe4d1e422da0aa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs index 24c3896..5865fc4 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs @@ -1,77 +1,77 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; - -namespace Mirror.SimpleWeb -{ - /// - /// Handles Handshake to the server when it first connects - /// The client handshake does not need buffers to reduce allocations since it only happens once - /// - internal class ClientHandshake - { - public bool TryHandshake(Connection conn, Uri uri) - { - try - { - Stream stream = conn.stream; - - byte[] keyBuffer = new byte[16]; - using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) - { - rng.GetBytes(keyBuffer); - } - - string key = Convert.ToBase64String(keyBuffer); - string keySum = key + Constants.HandshakeGUID; - byte[] keySumBytes = Encoding.ASCII.GetBytes(keySum); - Log.Verbose($"Handshake Hashing {Encoding.ASCII.GetString(keySumBytes)}"); - - byte[] keySumHash = SHA1.Create().ComputeHash(keySumBytes); - - string expectedResponse = Convert.ToBase64String(keySumHash); - string handshake = - $"GET {uri.PathAndQuery} HTTP/1.1\r\n" + - $"Host: {uri.Host}:{uri.Port}\r\n" + - $"Upgrade: websocket\r\n" + - $"Connection: Upgrade\r\n" + - $"Sec-WebSocket-Key: {key}\r\n" + - $"Sec-WebSocket-Version: 13\r\n" + - "\r\n"; - byte[] encoded = Encoding.ASCII.GetBytes(handshake); - stream.Write(encoded, 0, encoded.Length); - - byte[] responseBuffer = new byte[1000]; - - int? lengthOrNull = ReadHelper.SafeReadTillMatch(stream, responseBuffer, 0, responseBuffer.Length, Constants.endOfHandshake); - - if (!lengthOrNull.HasValue) - { - Log.Error("Connected closed before handshake"); - return false; - } - - string responseString = Encoding.ASCII.GetString(responseBuffer, 0, lengthOrNull.Value); - - string acceptHeader = "Sec-WebSocket-Accept: "; - int startIndex = responseString.IndexOf(acceptHeader) + acceptHeader.Length; - int endIndex = responseString.IndexOf("\r\n", startIndex); - string responseKey = responseString.Substring(startIndex, endIndex - startIndex); - - if (responseKey != expectedResponse) - { - Log.Error($"Response key incorrect, Response:{responseKey} Expected:{expectedResponse}"); - return false; - } - - return true; - } - catch (Exception e) - { - Log.Exception(e); - return false; - } - } - } -} +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Mirror.SimpleWeb +{ + /// + /// Handles Handshake to the server when it first connects + /// The client handshake does not need buffers to reduce allocations since it only happens once + /// + internal class ClientHandshake + { + public bool TryHandshake(Connection conn, Uri uri) + { + try + { + Stream stream = conn.stream; + + byte[] keyBuffer = new byte[16]; + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(keyBuffer); + } + + string key = Convert.ToBase64String(keyBuffer); + string keySum = key + Constants.HandshakeGUID; + byte[] keySumBytes = Encoding.ASCII.GetBytes(keySum); + Log.Verbose($"Handshake Hashing {Encoding.ASCII.GetString(keySumBytes)}"); + + byte[] keySumHash = SHA1.Create().ComputeHash(keySumBytes); + + string expectedResponse = Convert.ToBase64String(keySumHash); + string handshake = + $"GET {uri.PathAndQuery} HTTP/1.1\r\n" + + $"Host: {uri.Host}:{uri.Port}\r\n" + + $"Upgrade: websocket\r\n" + + $"Connection: Upgrade\r\n" + + $"Sec-WebSocket-Key: {key}\r\n" + + $"Sec-WebSocket-Version: 13\r\n" + + "\r\n"; + byte[] encoded = Encoding.ASCII.GetBytes(handshake); + stream.Write(encoded, 0, encoded.Length); + + byte[] responseBuffer = new byte[1000]; + + int? lengthOrNull = ReadHelper.SafeReadTillMatch(stream, responseBuffer, 0, responseBuffer.Length, Constants.endOfHandshake); + + if (!lengthOrNull.HasValue) + { + Log.Error("Connected closed before handshake"); + return false; + } + + string responseString = Encoding.ASCII.GetString(responseBuffer, 0, lengthOrNull.Value); + + string acceptHeader = "Sec-WebSocket-Accept: "; + int startIndex = responseString.IndexOf(acceptHeader) + acceptHeader.Length; + int endIndex = responseString.IndexOf("\r\n", startIndex); + string responseKey = responseString.Substring(startIndex, endIndex - startIndex); + + if (responseKey != expectedResponse) + { + Log.Error($"Response key incorrect, Response:{responseKey} Expected:{expectedResponse}"); + return false; + } + + return true; + } + catch (Exception e) + { + Log.Exception(e); + return false; + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta index ad3d40d..c498186 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 3ffdcabc9e28f764a94fc4efc82d3e8b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 3ffdcabc9e28f764a94fc4efc82d3e8b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs index be93f6c..14be980 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs @@ -1,47 +1,47 @@ -using System; -using System.IO; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; - -namespace Mirror.SimpleWeb -{ - internal class ClientSslHelper - { - internal bool TryCreateStream(Connection conn, Uri uri) - { - NetworkStream stream = conn.client.GetStream(); - if (uri.Scheme != "wss") - { - conn.stream = stream; - return true; - } - - try - { - conn.stream = CreateStream(stream, uri); - return true; - } - catch (Exception e) - { - Log.Error($"Create SSLStream Failed: {e}", false); - return false; - } - } - - Stream CreateStream(NetworkStream stream, Uri uri) - { - SslStream sslStream = new SslStream(stream, true, ValidateServerCertificate); - sslStream.AuthenticateAsClient(uri.Host); - return sslStream; - } - - static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - { - // Do not allow this client to communicate with unauthenticated servers. - - // only accept if no errors - return sslPolicyErrors == SslPolicyErrors.None; - } - } -} +using System; +using System.IO; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; + +namespace Mirror.SimpleWeb +{ + internal class ClientSslHelper + { + internal bool TryCreateStream(Connection conn, Uri uri) + { + NetworkStream stream = conn.client.GetStream(); + if (uri.Scheme != "wss") + { + conn.stream = stream; + return true; + } + + try + { + conn.stream = CreateStream(stream, uri); + return true; + } + catch (Exception e) + { + Log.Error($"Create SSLStream Failed: {e}", false); + return false; + } + } + + Stream CreateStream(NetworkStream stream, Uri uri) + { + SslStream sslStream = new SslStream(stream, true, ValidateServerCertificate); + sslStream.AuthenticateAsClient(uri.Host); + return sslStream; + } + + static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + // Do not allow this client to communicate with unauthenticated servers. + + // only accept if no errors + return sslPolicyErrors == SslPolicyErrors.None; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta index d6be2bb..d79c5d6 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 46055a75559a79849a750f39a766db61 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 46055a75559a79849a750f39a766db61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs index 7eae284..d93ffc5 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs @@ -1,139 +1,139 @@ -using System; -using System.Net.Sockets; -using System.Threading; - -namespace Mirror.SimpleWeb -{ - public class WebSocketClientStandAlone : SimpleWebClient - { - readonly ClientSslHelper sslHelper; - readonly ClientHandshake handshake; - readonly TcpConfig tcpConfig; - Connection conn; - - - internal WebSocketClientStandAlone(int maxMessageSize, int maxMessagesPerTick, TcpConfig tcpConfig) : base(maxMessageSize, maxMessagesPerTick) - { -#if UNITY_WEBGL && !UNITY_EDITOR - throw new NotSupportedException(); -#else - sslHelper = new ClientSslHelper(); - handshake = new ClientHandshake(); - this.tcpConfig = tcpConfig; -#endif - } - - public override void Connect(Uri serverAddress) - { - state = ClientState.Connecting; - Thread receiveThread = new Thread(() => ConnectAndReceiveLoop(serverAddress)); - receiveThread.IsBackground = true; - receiveThread.Start(); - } - - void ConnectAndReceiveLoop(Uri serverAddress) - { - try - { - TcpClient client = new TcpClient(); - tcpConfig.ApplyTo(client); - - // create connection object here so dispose correctly disconnects on failed connect - conn = new Connection(client, AfterConnectionDisposed); - conn.receiveThread = Thread.CurrentThread; - - try - { - client.Connect(serverAddress.Host, serverAddress.Port); - } - catch (SocketException) - { - client.Dispose(); - throw; - } - - - bool success = sslHelper.TryCreateStream(conn, serverAddress); - if (!success) - { - Log.Warn("Failed to create Stream"); - conn.Dispose(); - return; - } - - success = handshake.TryHandshake(conn, serverAddress); - if (!success) - { - Log.Warn("Failed Handshake"); - conn.Dispose(); - return; - } - - Log.Info("HandShake Successful"); - - state = ClientState.Connected; - - receiveQueue.Enqueue(new Message(EventType.Connected)); - - Thread sendThread = new Thread(() => - { - SendLoop.Config sendConfig = new SendLoop.Config( - conn, - bufferSize: Constants.HeaderSize + Constants.MaskSize + maxMessageSize, - setMask: true); - - SendLoop.Loop(sendConfig); - }); - - conn.sendThread = sendThread; - sendThread.IsBackground = true; - sendThread.Start(); - - ReceiveLoop.Config config = new ReceiveLoop.Config(conn, - maxMessageSize, - false, - receiveQueue, - bufferPool); - ReceiveLoop.Loop(config); - } - catch (ThreadInterruptedException e) { Log.InfoException(e); } - catch (ThreadAbortException e) { Log.InfoException(e); } - catch (Exception e) { Log.Exception(e); } - finally - { - // close here in case connect fails - conn?.Dispose(); - } - } - - void AfterConnectionDisposed(Connection conn) - { - state = ClientState.NotConnected; - // make sure Disconnected event is only called once - receiveQueue.Enqueue(new Message(EventType.Disconnected)); - } - - public override void Disconnect() - { - state = ClientState.Disconnecting; - Log.Info("Disconnect Called"); - if (conn == null) - { - state = ClientState.NotConnected; - } - else - { - conn?.Dispose(); - } - } - - public override void Send(ArraySegment segment) - { - ArrayBuffer buffer = bufferPool.Take(segment.Count); - buffer.CopyFrom(segment); - - conn.sendQueue.Enqueue(buffer); - conn.sendPending.Set(); - } - } -} +using System; +using System.Net.Sockets; +using System.Threading; + +namespace Mirror.SimpleWeb +{ + public class WebSocketClientStandAlone : SimpleWebClient + { + readonly ClientSslHelper sslHelper; + readonly ClientHandshake handshake; + readonly TcpConfig tcpConfig; + Connection conn; + + + internal WebSocketClientStandAlone(int maxMessageSize, int maxMessagesPerTick, TcpConfig tcpConfig) : base(maxMessageSize, maxMessagesPerTick) + { +#if UNITY_WEBGL && !UNITY_EDITOR + throw new NotSupportedException(); +#else + sslHelper = new ClientSslHelper(); + handshake = new ClientHandshake(); + this.tcpConfig = tcpConfig; +#endif + } + + public override void Connect(Uri serverAddress) + { + state = ClientState.Connecting; + Thread receiveThread = new Thread(() => ConnectAndReceiveLoop(serverAddress)); + receiveThread.IsBackground = true; + receiveThread.Start(); + } + + void ConnectAndReceiveLoop(Uri serverAddress) + { + try + { + TcpClient client = new TcpClient(); + tcpConfig.ApplyTo(client); + + // create connection object here so dispose correctly disconnects on failed connect + conn = new Connection(client, AfterConnectionDisposed); + conn.receiveThread = Thread.CurrentThread; + + try + { + client.Connect(serverAddress.Host, serverAddress.Port); + } + catch (SocketException) + { + client.Dispose(); + throw; + } + + + bool success = sslHelper.TryCreateStream(conn, serverAddress); + if (!success) + { + Log.Warn("Failed to create Stream"); + conn.Dispose(); + return; + } + + success = handshake.TryHandshake(conn, serverAddress); + if (!success) + { + Log.Warn("Failed Handshake"); + conn.Dispose(); + return; + } + + Log.Info("HandShake Successful"); + + state = ClientState.Connected; + + receiveQueue.Enqueue(new Message(EventType.Connected)); + + Thread sendThread = new Thread(() => + { + SendLoop.Config sendConfig = new SendLoop.Config( + conn, + bufferSize: Constants.HeaderSize + Constants.MaskSize + maxMessageSize, + setMask: true); + + SendLoop.Loop(sendConfig); + }); + + conn.sendThread = sendThread; + sendThread.IsBackground = true; + sendThread.Start(); + + ReceiveLoop.Config config = new ReceiveLoop.Config(conn, + maxMessageSize, + false, + receiveQueue, + bufferPool); + ReceiveLoop.Loop(config); + } + catch (ThreadInterruptedException e) { Log.InfoException(e); } + catch (ThreadAbortException e) { Log.InfoException(e); } + catch (Exception e) { Log.Exception(e); } + finally + { + // close here in case connect fails + conn?.Dispose(); + } + } + + void AfterConnectionDisposed(Connection conn) + { + state = ClientState.NotConnected; + // make sure Disconnected event is only called once + receiveQueue.Enqueue(new Message(EventType.Disconnected)); + } + + public override void Disconnect() + { + state = ClientState.Disconnecting; + Log.Info("Disconnect Called"); + if (conn == null) + { + state = ClientState.NotConnected; + } + else + { + conn?.Dispose(); + } + } + + public override void Send(ArraySegment segment) + { + ArrayBuffer buffer = bufferPool.Take(segment.Count); + buffer.CopyFrom(segment); + + conn.sendQueue.Enqueue(buffer); + conn.sendPending.Set(); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta index 37229d3..5b9d94a 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 05a9c87dea309e241a9185e5aa0d72ab -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 05a9c87dea309e241a9185e5aa0d72ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl.meta index 2d81f7f..c4134f6 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 7142349d566213c4abc763afaf4d91a1 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 7142349d566213c4abc763afaf4d91a1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs index 6af4671..2ee75ab 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs @@ -1,34 +1,34 @@ -using System; -#if UNITY_WEBGL -using System.Runtime.InteropServices; -#endif - -namespace Mirror.SimpleWeb -{ - internal static class SimpleWebJSLib - { -#if UNITY_WEBGL - [DllImport("__Internal")] - internal static extern bool IsConnected(int index); - -#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments - [DllImport("__Internal")] -#pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments - internal static extern int Connect(string address, Action openCallback, Action closeCallBack, Action messageCallback, Action errorCallback); - - [DllImport("__Internal")] - internal static extern void Disconnect(int index); - - [DllImport("__Internal")] - internal static extern bool Send(int index, byte[] array, int offset, int length); -#else - internal static bool IsConnected(int index) => throw new NotSupportedException(); - - internal static int Connect(string address, Action openCallback, Action closeCallBack, Action messageCallback, Action errorCallback) => throw new NotSupportedException(); - - internal static void Disconnect(int index) => throw new NotSupportedException(); - - internal static bool Send(int index, byte[] array, int offset, int length) => throw new NotSupportedException(); -#endif - } -} +using System; +#if UNITY_WEBGL +using System.Runtime.InteropServices; +#endif + +namespace Mirror.SimpleWeb +{ + internal static class SimpleWebJSLib + { +#if UNITY_WEBGL + [DllImport("__Internal")] + internal static extern bool IsConnected(int index); + +#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments + [DllImport("__Internal")] +#pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments + internal static extern int Connect(string address, Action openCallback, Action closeCallBack, Action messageCallback, Action errorCallback); + + [DllImport("__Internal")] + internal static extern void Disconnect(int index); + + [DllImport("__Internal")] + internal static extern bool Send(int index, byte[] array, int offset, int length); +#else + internal static bool IsConnected(int index) => throw new NotSupportedException(); + + internal static int Connect(string address, Action openCallback, Action closeCallBack, Action messageCallback, Action errorCallback) => throw new NotSupportedException(); + + internal static void Disconnect(int index) => throw new NotSupportedException(); + + internal static bool Send(int index, byte[] array, int offset, int length) => throw new NotSupportedException(); +#endif + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta index 9dfa12e..016454c 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 97b96a0b65c104443977473323c2ff35 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 97b96a0b65c104443977473323c2ff35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs index 0c953ef..79b997b 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs @@ -1,99 +1,99 @@ -using System; -using System.Collections.Generic; -using AOT; - -namespace Mirror.SimpleWeb -{ - public class WebSocketClientWebGl : SimpleWebClient - { - static readonly Dictionary instances = new Dictionary(); - - /// - /// key for instances sent between c# and js - /// - int index; - - internal WebSocketClientWebGl(int maxMessageSize, int maxMessagesPerTick) : base(maxMessageSize, maxMessagesPerTick) - { -#if !UNITY_WEBGL || UNITY_EDITOR - throw new NotSupportedException(); -#endif - } - - public bool CheckJsConnected() => SimpleWebJSLib.IsConnected(index); - - public override void Connect(Uri serverAddress) - { - index = SimpleWebJSLib.Connect(serverAddress.ToString(), OpenCallback, CloseCallBack, MessageCallback, ErrorCallback); - instances.Add(index, this); - state = ClientState.Connecting; - } - - public override void Disconnect() - { - state = ClientState.Disconnecting; - // disconnect should cause closeCallback and OnDisconnect to be called - SimpleWebJSLib.Disconnect(index); - } - - public override void Send(ArraySegment segment) - { - if (segment.Count > maxMessageSize) - { - Log.Error($"Cant send message with length {segment.Count} because it is over the max size of {maxMessageSize}"); - return; - } - - SimpleWebJSLib.Send(index, segment.Array, 0, segment.Count); - } - - void onOpen() - { - receiveQueue.Enqueue(new Message(EventType.Connected)); - state = ClientState.Connected; - } - - void onClose() - { - // this code should be last in this class - - receiveQueue.Enqueue(new Message(EventType.Disconnected)); - state = ClientState.NotConnected; - instances.Remove(index); - } - - void onMessage(IntPtr bufferPtr, int count) - { - try - { - ArrayBuffer buffer = bufferPool.Take(count); - buffer.CopyFrom(bufferPtr, count); - - receiveQueue.Enqueue(new Message(buffer)); - } - catch (Exception e) - { - Log.Error($"onData {e.GetType()}: {e.Message}\n{e.StackTrace}"); - receiveQueue.Enqueue(new Message(e)); - } - } - - void onErr() - { - receiveQueue.Enqueue(new Message(new Exception("Javascript Websocket error"))); - Disconnect(); - } - - [MonoPInvokeCallback(typeof(Action))] - static void OpenCallback(int index) => instances[index].onOpen(); - - [MonoPInvokeCallback(typeof(Action))] - static void CloseCallBack(int index) => instances[index].onClose(); - - [MonoPInvokeCallback(typeof(Action))] - static void MessageCallback(int index, IntPtr bufferPtr, int count) => instances[index].onMessage(bufferPtr, count); - - [MonoPInvokeCallback(typeof(Action))] - static void ErrorCallback(int index) => instances[index].onErr(); - } -} +using System; +using System.Collections.Generic; +using AOT; + +namespace Mirror.SimpleWeb +{ + public class WebSocketClientWebGl : SimpleWebClient + { + static readonly Dictionary instances = new Dictionary(); + + /// + /// key for instances sent between c# and js + /// + int index; + + internal WebSocketClientWebGl(int maxMessageSize, int maxMessagesPerTick) : base(maxMessageSize, maxMessagesPerTick) + { +#if !UNITY_WEBGL || UNITY_EDITOR + throw new NotSupportedException(); +#endif + } + + public bool CheckJsConnected() => SimpleWebJSLib.IsConnected(index); + + public override void Connect(Uri serverAddress) + { + index = SimpleWebJSLib.Connect(serverAddress.ToString(), OpenCallback, CloseCallBack, MessageCallback, ErrorCallback); + instances.Add(index, this); + state = ClientState.Connecting; + } + + public override void Disconnect() + { + state = ClientState.Disconnecting; + // disconnect should cause closeCallback and OnDisconnect to be called + SimpleWebJSLib.Disconnect(index); + } + + public override void Send(ArraySegment segment) + { + if (segment.Count > maxMessageSize) + { + Log.Error($"Cant send message with length {segment.Count} because it is over the max size of {maxMessageSize}"); + return; + } + + SimpleWebJSLib.Send(index, segment.Array, 0, segment.Count); + } + + void onOpen() + { + receiveQueue.Enqueue(new Message(EventType.Connected)); + state = ClientState.Connected; + } + + void onClose() + { + // this code should be last in this class + + receiveQueue.Enqueue(new Message(EventType.Disconnected)); + state = ClientState.NotConnected; + instances.Remove(index); + } + + void onMessage(IntPtr bufferPtr, int count) + { + try + { + ArrayBuffer buffer = bufferPool.Take(count); + buffer.CopyFrom(bufferPtr, count); + + receiveQueue.Enqueue(new Message(buffer)); + } + catch (Exception e) + { + Log.Error($"onData {e.GetType()}: {e.Message}\n{e.StackTrace}"); + receiveQueue.Enqueue(new Message(e)); + } + } + + void onErr() + { + receiveQueue.Enqueue(new Message(new Exception("Javascript Websocket error"))); + Disconnect(); + } + + [MonoPInvokeCallback(typeof(Action))] + static void OpenCallback(int index) => instances[index].onOpen(); + + [MonoPInvokeCallback(typeof(Action))] + static void CloseCallBack(int index) => instances[index].onClose(); + + [MonoPInvokeCallback(typeof(Action))] + static void MessageCallback(int index, IntPtr bufferPtr, int count) => instances[index].onMessage(bufferPtr, count); + + [MonoPInvokeCallback(typeof(Action))] + static void ErrorCallback(int index) => instances[index].onErr(); + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta index 3827d3a..47f9d8b 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 015c5b1915fd1a64cbe36444d16b2f7d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 015c5b1915fd1a64cbe36444d16b2f7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin.meta index b516a8f..ff67d16 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 1999985791b91b9458059e88404885a7 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 1999985791b91b9458059e88404885a7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib index 13da1b7..2c72420 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib @@ -1,105 +1,105 @@ -// this will create a global object -const SimpleWeb = { - webSockets: [], - next: 1, - GetWebSocket: function (index) { - return SimpleWeb.webSockets[index] - }, - AddNextSocket: function (webSocket) { - var index = SimpleWeb.next; - SimpleWeb.next++; - SimpleWeb.webSockets[index] = webSocket; - return index; - }, - RemoveSocket: function (index) { - SimpleWeb.webSockets[index] = undefined; - }, -}; - -function IsConnected(index) { - var webSocket = SimpleWeb.GetWebSocket(index); - if (webSocket) { - return webSocket.readyState === webSocket.OPEN; - } - else { - return false; - } -} - -function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackPtr, errorCallbackPtr) { - const address = Pointer_stringify(addressPtr); - console.log("Connecting to " + address); - // Create webSocket connection. - webSocket = new WebSocket(address); - webSocket.binaryType = 'arraybuffer'; - const index = SimpleWeb.AddNextSocket(webSocket); - - // Connection opened - webSocket.addEventListener('open', function (event) { - console.log("Connected to " + address); - Runtime.dynCall('vi', openCallbackPtr, [index]); - }); - webSocket.addEventListener('close', function (event) { - console.log("Disconnected from " + address); - Runtime.dynCall('vi', closeCallBackPtr, [index]); - }); - - // Listen for messages - webSocket.addEventListener('message', function (event) { - if (event.data instanceof ArrayBuffer) { - // TODO dont alloc each time - var array = new Uint8Array(event.data); - var arrayLength = array.length; - - var bufferPtr = _malloc(arrayLength); - var dataBuffer = new Uint8Array(HEAPU8.buffer, bufferPtr, arrayLength); - dataBuffer.set(array); - - Runtime.dynCall('viii', messageCallbackPtr, [index, bufferPtr, arrayLength]); - _free(bufferPtr); - } - else { - console.error("message type not supported") - } - }); - - webSocket.addEventListener('error', function (event) { - console.error('Socket Error', event); - - Runtime.dynCall('vi', errorCallbackPtr, [index]); - }); - - return index; -} - -function Disconnect(index) { - var webSocket = SimpleWeb.GetWebSocket(index); - if (webSocket) { - webSocket.close(1000, "Disconnect Called by Mirror"); - } - - SimpleWeb.RemoveSocket(index); -} - -function Send(index, arrayPtr, offset, length) { - var webSocket = SimpleWeb.GetWebSocket(index); - if (webSocket) { - const start = arrayPtr + offset; - const end = start + length; - const data = HEAPU8.buffer.slice(start, end); - webSocket.send(data); - return true; - } - return false; -} - - -const SimpleWebLib = { - $SimpleWeb: SimpleWeb, - IsConnected, - Connect, - Disconnect, - Send -}; -autoAddDeps(SimpleWebLib, '$SimpleWeb'); +// this will create a global object +const SimpleWeb = { + webSockets: [], + next: 1, + GetWebSocket: function (index) { + return SimpleWeb.webSockets[index] + }, + AddNextSocket: function (webSocket) { + var index = SimpleWeb.next; + SimpleWeb.next++; + SimpleWeb.webSockets[index] = webSocket; + return index; + }, + RemoveSocket: function (index) { + SimpleWeb.webSockets[index] = undefined; + }, +}; + +function IsConnected(index) { + var webSocket = SimpleWeb.GetWebSocket(index); + if (webSocket) { + return webSocket.readyState === webSocket.OPEN; + } + else { + return false; + } +} + +function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackPtr, errorCallbackPtr) { + const address = Pointer_stringify(addressPtr); + console.log("Connecting to " + address); + // Create webSocket connection. + webSocket = new WebSocket(address); + webSocket.binaryType = 'arraybuffer'; + const index = SimpleWeb.AddNextSocket(webSocket); + + // Connection opened + webSocket.addEventListener('open', function (event) { + console.log("Connected to " + address); + Runtime.dynCall('vi', openCallbackPtr, [index]); + }); + webSocket.addEventListener('close', function (event) { + console.log("Disconnected from " + address); + Runtime.dynCall('vi', closeCallBackPtr, [index]); + }); + + // Listen for messages + webSocket.addEventListener('message', function (event) { + if (event.data instanceof ArrayBuffer) { + // TODO dont alloc each time + var array = new Uint8Array(event.data); + var arrayLength = array.length; + + var bufferPtr = _malloc(arrayLength); + var dataBuffer = new Uint8Array(HEAPU8.buffer, bufferPtr, arrayLength); + dataBuffer.set(array); + + Runtime.dynCall('viii', messageCallbackPtr, [index, bufferPtr, arrayLength]); + _free(bufferPtr); + } + else { + console.error("message type not supported") + } + }); + + webSocket.addEventListener('error', function (event) { + console.error('Socket Error', event); + + Runtime.dynCall('vi', errorCallbackPtr, [index]); + }); + + return index; +} + +function Disconnect(index) { + var webSocket = SimpleWeb.GetWebSocket(index); + if (webSocket) { + webSocket.close(1000, "Disconnect Called by Mirror"); + } + + SimpleWeb.RemoveSocket(index); +} + +function Send(index, arrayPtr, offset, length) { + var webSocket = SimpleWeb.GetWebSocket(index); + if (webSocket) { + const start = arrayPtr + offset; + const end = start + length; + const data = HEAPU8.buffer.slice(start, end); + webSocket.send(data); + return true; + } + return false; +} + + +const SimpleWebLib = { + $SimpleWeb: SimpleWeb, + IsConnected, + Connect, + Disconnect, + Send +}; +autoAddDeps(SimpleWebLib, '$SimpleWeb'); mergeInto(LibraryManager.library, SimpleWebLib); \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta index cc1319e..267973f 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta @@ -1,37 +1,37 @@ -fileFormatVersion: 2 -guid: 54452a8c6d2ca9b49a8c79f81b50305c -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 0 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - Facebook: WebGL - second: - enabled: 1 - settings: {} - - first: - WebGL: WebGL - second: - enabled: 1 - settings: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 54452a8c6d2ca9b49a8c79f81b50305c +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: '' + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Facebook: WebGL + second: + enabled: 1 + settings: {} + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common.meta index 078faaa..d7744d5 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 564d2cd3eee5b21419553c0528739d1b -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 564d2cd3eee5b21419553c0528739d1b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs index 315d371..6980df4 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs @@ -1,265 +1,265 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Mirror.SimpleWeb -{ - public interface IBufferOwner - { - void Return(ArrayBuffer buffer); - } - - public sealed class ArrayBuffer : IDisposable - { - readonly IBufferOwner owner; - - public readonly byte[] array; - - /// - /// number of bytes writen to buffer - /// - internal int count; - - /// - /// How many times release needs to be called before buffer is returned to pool - /// This allows the buffer to be used in multiple places at the same time - /// - public void SetReleasesRequired(int required) - { - releasesRequired = required; - } - - /// - /// How many times release needs to be called before buffer is returned to pool - /// This allows the buffer to be used in multiple places at the same time - /// - /// - /// This value is normally 0, but can be changed to require release to be called multiple times - /// - int releasesRequired; - - public ArrayBuffer(IBufferOwner owner, int size) - { - this.owner = owner; - array = new byte[size]; - } - - public void Release() - { - int newValue = Interlocked.Decrement(ref releasesRequired); - if (newValue <= 0) - { - count = 0; - owner.Return(this); - } - } - public void Dispose() - { - Release(); - } - - - public void CopyTo(byte[] target, int offset) - { - if (count > (target.Length + offset)) throw new ArgumentException($"{nameof(count)} was greater than {nameof(target)}.length", nameof(target)); - - Buffer.BlockCopy(array, 0, target, offset, count); - } - - public void CopyFrom(ArraySegment segment) - { - CopyFrom(segment.Array, segment.Offset, segment.Count); - } - - public void CopyFrom(byte[] source, int offset, int length) - { - if (length > array.Length) throw new ArgumentException($"{nameof(length)} was greater than {nameof(array)}.length", nameof(length)); - - count = length; - Buffer.BlockCopy(source, offset, array, 0, length); - } - - public void CopyFrom(IntPtr bufferPtr, int length) - { - if (length > array.Length) throw new ArgumentException($"{nameof(length)} was greater than {nameof(array)}.length", nameof(length)); - - count = length; - Marshal.Copy(bufferPtr, array, 0, length); - } - - public ArraySegment ToSegment() - { - return new ArraySegment(array, 0, count); - } - - [Conditional("UNITY_ASSERTIONS")] - internal void Validate(int arraySize) - { - if (array.Length != arraySize) - { - Log.Error("Buffer that was returned had an array of the wrong size"); - } - } - } - - internal class BufferBucket : IBufferOwner - { - public readonly int arraySize; - readonly ConcurrentQueue buffers; - - /// - /// keeps track of how many arrays are taken vs returned - /// - internal int _current = 0; - - public BufferBucket(int arraySize) - { - this.arraySize = arraySize; - buffers = new ConcurrentQueue(); - } - - public ArrayBuffer Take() - { - IncrementCreated(); - if (buffers.TryDequeue(out ArrayBuffer buffer)) - { - return buffer; - } - else - { - Log.Verbose($"BufferBucket({arraySize}) create new"); - return new ArrayBuffer(this, arraySize); - } - } - - public void Return(ArrayBuffer buffer) - { - DecrementCreated(); - buffer.Validate(arraySize); - buffers.Enqueue(buffer); - } - - [Conditional("DEBUG")] - void IncrementCreated() - { - int next = Interlocked.Increment(ref _current); - Log.Verbose($"BufferBucket({arraySize}) count:{next}"); - } - [Conditional("DEBUG")] - void DecrementCreated() - { - int next = Interlocked.Decrement(ref _current); - Log.Verbose($"BufferBucket({arraySize}) count:{next}"); - } - } - - /// - /// Collection of different sized buffers - /// - /// - /// - /// Problem:
- /// * Need to cached byte[] so that new ones aren't created each time
- /// * Arrays sent are multiple different sizes
- /// * Some message might be big so need buffers to cover that size
- /// * Most messages will be small compared to max message size
- ///
- ///
- /// - /// Solution:
- /// * Create multiple groups of buffers covering the range of allowed sizes
- /// * Split range exponentially (using math.log) so that there are more groups for small buffers
- ///
- ///
- public class BufferPool - { - internal readonly BufferBucket[] buckets; - readonly int bucketCount; - readonly int smallest; - readonly int largest; - - public BufferPool(int bucketCount, int smallest, int largest) - { - if (bucketCount < 2) throw new ArgumentException("Count must be at least 2"); - if (smallest < 1) throw new ArgumentException("Smallest must be at least 1"); - if (largest < smallest) throw new ArgumentException("Largest must be greater than smallest"); - - - this.bucketCount = bucketCount; - this.smallest = smallest; - this.largest = largest; - - - // split range over log scale (more buckets for smaller sizes) - - double minLog = Math.Log(this.smallest); - double maxLog = Math.Log(this.largest); - - double range = maxLog - minLog; - double each = range / (bucketCount - 1); - - buckets = new BufferBucket[bucketCount]; - - for (int i = 0; i < bucketCount; i++) - { - double size = smallest * Math.Pow(Math.E, each * i); - buckets[i] = new BufferBucket((int)Math.Ceiling(size)); - } - - - Validate(); - - // Example - // 5 count - // 20 smallest - // 16400 largest - - // 3.0 log 20 - // 9.7 log 16400 - - // 6.7 range 9.7 - 3 - // 1.675 each 6.7 / (5-1) - - // 20 e^ (3 + 1.675 * 0) - // 107 e^ (3 + 1.675 * 1) - // 572 e^ (3 + 1.675 * 2) - // 3056 e^ (3 + 1.675 * 3) - // 16,317 e^ (3 + 1.675 * 4) - - // perceision wont be lose when using doubles - } - - [Conditional("UNITY_ASSERTIONS")] - void Validate() - { - if (buckets[0].arraySize != smallest) - { - Log.Error($"BufferPool Failed to create bucket for smallest. bucket:{buckets[0].arraySize} smallest{smallest}"); - } - - int largestBucket = buckets[bucketCount - 1].arraySize; - // rounded using Ceiling, so allowed to be 1 more that largest - if (largestBucket != largest && largestBucket != largest + 1) - { - Log.Error($"BufferPool Failed to create bucket for largest. bucket:{largestBucket} smallest{largest}"); - } - } - - public ArrayBuffer Take(int size) - { - if (size > largest) { throw new ArgumentException($"Size ({size}) is greatest that largest ({largest})"); } - - for (int i = 0; i < bucketCount; i++) - { - if (size <= buckets[i].arraySize) - { - return buckets[i].Take(); - } - } - - throw new ArgumentException($"Size ({size}) is greatest that largest ({largest})"); - } - } -} +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Mirror.SimpleWeb +{ + public interface IBufferOwner + { + void Return(ArrayBuffer buffer); + } + + public sealed class ArrayBuffer : IDisposable + { + readonly IBufferOwner owner; + + public readonly byte[] array; + + /// + /// number of bytes writen to buffer + /// + internal int count; + + /// + /// How many times release needs to be called before buffer is returned to pool + /// This allows the buffer to be used in multiple places at the same time + /// + public void SetReleasesRequired(int required) + { + releasesRequired = required; + } + + /// + /// How many times release needs to be called before buffer is returned to pool + /// This allows the buffer to be used in multiple places at the same time + /// + /// + /// This value is normally 0, but can be changed to require release to be called multiple times + /// + int releasesRequired; + + public ArrayBuffer(IBufferOwner owner, int size) + { + this.owner = owner; + array = new byte[size]; + } + + public void Release() + { + int newValue = Interlocked.Decrement(ref releasesRequired); + if (newValue <= 0) + { + count = 0; + owner.Return(this); + } + } + public void Dispose() + { + Release(); + } + + + public void CopyTo(byte[] target, int offset) + { + if (count > (target.Length + offset)) throw new ArgumentException($"{nameof(count)} was greater than {nameof(target)}.length", nameof(target)); + + Buffer.BlockCopy(array, 0, target, offset, count); + } + + public void CopyFrom(ArraySegment segment) + { + CopyFrom(segment.Array, segment.Offset, segment.Count); + } + + public void CopyFrom(byte[] source, int offset, int length) + { + if (length > array.Length) throw new ArgumentException($"{nameof(length)} was greater than {nameof(array)}.length", nameof(length)); + + count = length; + Buffer.BlockCopy(source, offset, array, 0, length); + } + + public void CopyFrom(IntPtr bufferPtr, int length) + { + if (length > array.Length) throw new ArgumentException($"{nameof(length)} was greater than {nameof(array)}.length", nameof(length)); + + count = length; + Marshal.Copy(bufferPtr, array, 0, length); + } + + public ArraySegment ToSegment() + { + return new ArraySegment(array, 0, count); + } + + [Conditional("UNITY_ASSERTIONS")] + internal void Validate(int arraySize) + { + if (array.Length != arraySize) + { + Log.Error("Buffer that was returned had an array of the wrong size"); + } + } + } + + internal class BufferBucket : IBufferOwner + { + public readonly int arraySize; + readonly ConcurrentQueue buffers; + + /// + /// keeps track of how many arrays are taken vs returned + /// + internal int _current = 0; + + public BufferBucket(int arraySize) + { + this.arraySize = arraySize; + buffers = new ConcurrentQueue(); + } + + public ArrayBuffer Take() + { + IncrementCreated(); + if (buffers.TryDequeue(out ArrayBuffer buffer)) + { + return buffer; + } + else + { + Log.Verbose($"BufferBucket({arraySize}) create new"); + return new ArrayBuffer(this, arraySize); + } + } + + public void Return(ArrayBuffer buffer) + { + DecrementCreated(); + buffer.Validate(arraySize); + buffers.Enqueue(buffer); + } + + [Conditional("DEBUG")] + void IncrementCreated() + { + int next = Interlocked.Increment(ref _current); + Log.Verbose($"BufferBucket({arraySize}) count:{next}"); + } + [Conditional("DEBUG")] + void DecrementCreated() + { + int next = Interlocked.Decrement(ref _current); + Log.Verbose($"BufferBucket({arraySize}) count:{next}"); + } + } + + /// + /// Collection of different sized buffers + /// + /// + /// + /// Problem:
+ /// * Need to cached byte[] so that new ones aren't created each time
+ /// * Arrays sent are multiple different sizes
+ /// * Some message might be big so need buffers to cover that size
+ /// * Most messages will be small compared to max message size
+ ///
+ ///
+ /// + /// Solution:
+ /// * Create multiple groups of buffers covering the range of allowed sizes
+ /// * Split range exponentially (using math.log) so that there are more groups for small buffers
+ ///
+ ///
+ public class BufferPool + { + internal readonly BufferBucket[] buckets; + readonly int bucketCount; + readonly int smallest; + readonly int largest; + + public BufferPool(int bucketCount, int smallest, int largest) + { + if (bucketCount < 2) throw new ArgumentException("Count must be at least 2"); + if (smallest < 1) throw new ArgumentException("Smallest must be at least 1"); + if (largest < smallest) throw new ArgumentException("Largest must be greater than smallest"); + + + this.bucketCount = bucketCount; + this.smallest = smallest; + this.largest = largest; + + + // split range over log scale (more buckets for smaller sizes) + + double minLog = Math.Log(this.smallest); + double maxLog = Math.Log(this.largest); + + double range = maxLog - minLog; + double each = range / (bucketCount - 1); + + buckets = new BufferBucket[bucketCount]; + + for (int i = 0; i < bucketCount; i++) + { + double size = smallest * Math.Pow(Math.E, each * i); + buckets[i] = new BufferBucket((int)Math.Ceiling(size)); + } + + + Validate(); + + // Example + // 5 count + // 20 smallest + // 16400 largest + + // 3.0 log 20 + // 9.7 log 16400 + + // 6.7 range 9.7 - 3 + // 1.675 each 6.7 / (5-1) + + // 20 e^ (3 + 1.675 * 0) + // 107 e^ (3 + 1.675 * 1) + // 572 e^ (3 + 1.675 * 2) + // 3056 e^ (3 + 1.675 * 3) + // 16,317 e^ (3 + 1.675 * 4) + + // perceision wont be lose when using doubles + } + + [Conditional("UNITY_ASSERTIONS")] + void Validate() + { + if (buckets[0].arraySize != smallest) + { + Log.Error($"BufferPool Failed to create bucket for smallest. bucket:{buckets[0].arraySize} smallest{smallest}"); + } + + int largestBucket = buckets[bucketCount - 1].arraySize; + // rounded using Ceiling, so allowed to be 1 more that largest + if (largestBucket != largest && largestBucket != largest + 1) + { + Log.Error($"BufferPool Failed to create bucket for largest. bucket:{largestBucket} smallest{largest}"); + } + } + + public ArrayBuffer Take(int size) + { + if (size > largest) { throw new ArgumentException($"Size ({size}) is greatest that largest ({largest})"); } + + for (int i = 0; i < bucketCount; i++) + { + if (size <= buckets[i].arraySize) + { + return buckets[i].Take(); + } + } + + throw new ArgumentException($"Size ({size}) is greatest that largest ({largest})"); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs.meta index 0b1070f..5a59c46 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 94ae50f3ec35667469b861b12cd72f92 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 94ae50f3ec35667469b861b12cd72f92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs index f16dd7c..8b0b42c 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs @@ -1,90 +1,90 @@ -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Net.Sockets; -using System.Threading; - -namespace Mirror.SimpleWeb -{ - internal sealed class Connection : IDisposable - { - public const int IdNotSet = -1; - - readonly object disposedLock = new object(); - - public TcpClient client; - - public int connId = IdNotSet; - public Stream stream; - public Thread receiveThread; - public Thread sendThread; - - public ManualResetEventSlim sendPending = new ManualResetEventSlim(false); - public ConcurrentQueue sendQueue = new ConcurrentQueue(); - - public Action onDispose; - - volatile bool hasDisposed; - - public Connection(TcpClient client, Action onDispose) - { - this.client = client ?? throw new ArgumentNullException(nameof(client)); - this.onDispose = onDispose; - } - - - /// - /// disposes client and stops threads - /// - public void Dispose() - { - Log.Verbose($"Dispose {ToString()}"); - - // check hasDisposed first to stop ThreadInterruptedException on lock - if (hasDisposed) { return; } - - Log.Info($"Connection Close: {ToString()}"); - - - lock (disposedLock) - { - // check hasDisposed again inside lock to make sure no other object has called this - if (hasDisposed) { return; } - hasDisposed = true; - - // stop threads first so they don't try to use disposed objects - receiveThread.Interrupt(); - sendThread?.Interrupt(); - - try - { - // stream - stream?.Dispose(); - stream = null; - client.Dispose(); - client = null; - } - catch (Exception e) - { - Log.Exception(e); - } - - sendPending.Dispose(); - - // release all buffers in send queue - while (sendQueue.TryDequeue(out ArrayBuffer buffer)) - { - buffer.Release(); - } - - onDispose.Invoke(this); - } - } - - public override string ToString() - { - System.Net.EndPoint endpoint = client?.Client?.RemoteEndPoint; - return $"[Conn:{connId}, endPoint:{endpoint}]"; - } - } -} +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Net.Sockets; +using System.Threading; + +namespace Mirror.SimpleWeb +{ + internal sealed class Connection : IDisposable + { + public const int IdNotSet = -1; + + readonly object disposedLock = new object(); + + public TcpClient client; + + public int connId = IdNotSet; + public Stream stream; + public Thread receiveThread; + public Thread sendThread; + + public ManualResetEventSlim sendPending = new ManualResetEventSlim(false); + public ConcurrentQueue sendQueue = new ConcurrentQueue(); + + public Action onDispose; + + volatile bool hasDisposed; + + public Connection(TcpClient client, Action onDispose) + { + this.client = client ?? throw new ArgumentNullException(nameof(client)); + this.onDispose = onDispose; + } + + + /// + /// disposes client and stops threads + /// + public void Dispose() + { + Log.Verbose($"Dispose {ToString()}"); + + // check hasDisposed first to stop ThreadInterruptedException on lock + if (hasDisposed) { return; } + + Log.Info($"Connection Close: {ToString()}"); + + + lock (disposedLock) + { + // check hasDisposed again inside lock to make sure no other object has called this + if (hasDisposed) { return; } + hasDisposed = true; + + // stop threads first so they don't try to use disposed objects + receiveThread.Interrupt(); + sendThread?.Interrupt(); + + try + { + // stream + stream?.Dispose(); + stream = null; + client.Dispose(); + client = null; + } + catch (Exception e) + { + Log.Exception(e); + } + + sendPending.Dispose(); + + // release all buffers in send queue + while (sendQueue.TryDequeue(out ArrayBuffer buffer)) + { + buffer.Release(); + } + + onDispose.Invoke(this); + } + } + + public override string ToString() + { + System.Net.EndPoint endpoint = client?.Client?.RemoteEndPoint; + return $"[Conn:{connId}, endPoint:{endpoint}]"; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs.meta index d48a835..44fd092 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: a13073c2b49d39943888df45174851bd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a13073c2b49d39943888df45174851bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs index cc94cf3..9ac3bda 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs @@ -1,72 +1,72 @@ -using System.Text; - -namespace Mirror.SimpleWeb -{ - /// - /// Constant values that should never change - /// - /// Some values are from https://tools.ietf.org/html/rfc6455 - /// - /// - internal static class Constants - { - /// - /// Header is at most 4 bytes - /// - /// If message is less than 125 then header is 2 bytes, else header is 4 bytes - /// - /// - public const int HeaderSize = 4; - - /// - /// Smallest size of header - /// - /// If message is less than 125 then header is 2 bytes, else header is 4 bytes - /// - /// - public const int HeaderMinSize = 2; - - /// - /// bytes for short length - /// - public const int ShortLength = 2; - - /// - /// Message mask is always 4 bytes - /// - public const int MaskSize = 4; - - /// - /// Max size of a message for length to be 1 byte long - /// - /// payload length between 0-125 - /// - /// - public const int BytePayloadLength = 125; - - /// - /// if payload length is 126 when next 2 bytes will be the length - /// - public const int UshortPayloadLength = 126; - - /// - /// if payload length is 127 when next 8 bytes will be the length - /// - public const int UlongPayloadLength = 127; - - - /// - /// Guid used for WebSocket Protocol - /// - public const string HandshakeGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - public static readonly int HandshakeGUIDLength = HandshakeGUID.Length; - - public static readonly byte[] HandshakeGUIDBytes = Encoding.ASCII.GetBytes(HandshakeGUID); - - /// - /// Handshake messages will end with \r\n\r\n - /// - public static readonly byte[] endOfHandshake = new byte[4] { (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }; - } -} +using System.Text; + +namespace Mirror.SimpleWeb +{ + /// + /// Constant values that should never change + /// + /// Some values are from https://tools.ietf.org/html/rfc6455 + /// + /// + internal static class Constants + { + /// + /// Header is at most 4 bytes + /// + /// If message is less than 125 then header is 2 bytes, else header is 4 bytes + /// + /// + public const int HeaderSize = 4; + + /// + /// Smallest size of header + /// + /// If message is less than 125 then header is 2 bytes, else header is 4 bytes + /// + /// + public const int HeaderMinSize = 2; + + /// + /// bytes for short length + /// + public const int ShortLength = 2; + + /// + /// Message mask is always 4 bytes + /// + public const int MaskSize = 4; + + /// + /// Max size of a message for length to be 1 byte long + /// + /// payload length between 0-125 + /// + /// + public const int BytePayloadLength = 125; + + /// + /// if payload length is 126 when next 2 bytes will be the length + /// + public const int UshortPayloadLength = 126; + + /// + /// if payload length is 127 when next 8 bytes will be the length + /// + public const int UlongPayloadLength = 127; + + + /// + /// Guid used for WebSocket Protocol + /// + public const string HandshakeGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + public static readonly int HandshakeGUIDLength = HandshakeGUID.Length; + + public static readonly byte[] HandshakeGUIDBytes = Encoding.ASCII.GetBytes(HandshakeGUID); + + /// + /// Handshake messages will end with \r\n\r\n + /// + public static readonly byte[] endOfHandshake = new byte[4] { (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }; + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs.meta index ece602e..647042b 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 85d110a089d6ad348abf2d073ebce7cd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 85d110a089d6ad348abf2d073ebce7cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs index 3a9d185..4098fdd 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs @@ -1,10 +1,10 @@ -namespace Mirror.SimpleWeb -{ - public enum EventType - { - Connected, - Data, - Disconnected, - Error - } -} +namespace Mirror.SimpleWeb +{ + public enum EventType + { + Connected, + Data, + Disconnected, + Error + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs.meta index a91403a..3329456 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 2d9cd7d2b5229ab42a12e82ae17d0347 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 2d9cd7d2b5229ab42a12e82ae17d0347 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs index a807b47..c7e05b5 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs @@ -1,115 +1,115 @@ -using System; -using UnityEngine; -using Conditional = System.Diagnostics.ConditionalAttribute; - -namespace Mirror.SimpleWeb -{ - public static class Log - { - // used for Conditional - const string SIMPLEWEB_LOG_ENABLED = nameof(SIMPLEWEB_LOG_ENABLED); - const string DEBUG = nameof(DEBUG); - - public enum Levels - { - none = 0, - error = 1, - warn = 2, - info = 3, - verbose = 4, - } - - public static Levels level = Levels.none; - - public static string BufferToString(byte[] buffer, int offset = 0, int? length = null) - { - return BitConverter.ToString(buffer, offset, length ?? buffer.Length); - } - - [Conditional(SIMPLEWEB_LOG_ENABLED)] - public static void DumpBuffer(string label, byte[] buffer, int offset, int length) - { - if (level < Levels.verbose) - return; - - Debug.Log($"VERBOSE: {label}: {BufferToString(buffer, offset, length)}"); - } - - [Conditional(SIMPLEWEB_LOG_ENABLED)] - public static void DumpBuffer(string label, ArrayBuffer arrayBuffer) - { - if (level < Levels.verbose) - return; - - Debug.Log($"VERBOSE: {label}: {BufferToString(arrayBuffer.array, 0, arrayBuffer.count)}"); - } - - [Conditional(SIMPLEWEB_LOG_ENABLED)] - public static void Verbose(string msg, bool showColor = true) - { - if (level < Levels.verbose) - return; - - if (showColor) - Debug.Log($"VERBOSE: {msg}"); - else - Debug.Log($"VERBOSE: {msg}"); - } - - [Conditional(SIMPLEWEB_LOG_ENABLED)] - public static void Info(string msg, bool showColor = true) - { - if (level < Levels.info) - return; - - if (showColor) - Debug.Log($"INFO: {msg}"); - else - Debug.Log($"INFO: {msg}"); - } - - /// - /// An expected Exception was caught, useful for debugging but not important - /// - /// - /// - [Conditional(SIMPLEWEB_LOG_ENABLED)] - public static void InfoException(Exception e) - { - if (level < Levels.info) - return; - - Debug.Log($"INFO_EXCEPTION: {e.GetType().Name} Message: {e.Message}"); - } - - [Conditional(SIMPLEWEB_LOG_ENABLED), Conditional(DEBUG)] - public static void Warn(string msg, bool showColor = true) - { - if (level < Levels.warn) - return; - - if (showColor) - Debug.LogWarning($"WARN: {msg}"); - else - Debug.LogWarning($"WARN: {msg}"); - } - - [Conditional(SIMPLEWEB_LOG_ENABLED), Conditional(DEBUG)] - public static void Error(string msg, bool showColor = true) - { - if (level < Levels.error) - return; - - if (showColor) - Debug.LogError($"ERROR: {msg}"); - else - Debug.LogError($"ERROR: {msg}"); - } - - public static void Exception(Exception e) - { - // always log Exceptions - Debug.LogError($"EXCEPTION: {e.GetType().Name} Message: {e.Message}"); - } - } -} +using System; +using UnityEngine; +using Conditional = System.Diagnostics.ConditionalAttribute; + +namespace Mirror.SimpleWeb +{ + public static class Log + { + // used for Conditional + const string SIMPLEWEB_LOG_ENABLED = nameof(SIMPLEWEB_LOG_ENABLED); + const string DEBUG = nameof(DEBUG); + + public enum Levels + { + none = 0, + error = 1, + warn = 2, + info = 3, + verbose = 4, + } + + public static Levels level = Levels.none; + + public static string BufferToString(byte[] buffer, int offset = 0, int? length = null) + { + return BitConverter.ToString(buffer, offset, length ?? buffer.Length); + } + + [Conditional(SIMPLEWEB_LOG_ENABLED)] + public static void DumpBuffer(string label, byte[] buffer, int offset, int length) + { + if (level < Levels.verbose) + return; + + Debug.Log($"VERBOSE: {label}: {BufferToString(buffer, offset, length)}"); + } + + [Conditional(SIMPLEWEB_LOG_ENABLED)] + public static void DumpBuffer(string label, ArrayBuffer arrayBuffer) + { + if (level < Levels.verbose) + return; + + Debug.Log($"VERBOSE: {label}: {BufferToString(arrayBuffer.array, 0, arrayBuffer.count)}"); + } + + [Conditional(SIMPLEWEB_LOG_ENABLED)] + public static void Verbose(string msg, bool showColor = true) + { + if (level < Levels.verbose) + return; + + if (showColor) + Debug.Log($"VERBOSE: {msg}"); + else + Debug.Log($"VERBOSE: {msg}"); + } + + [Conditional(SIMPLEWEB_LOG_ENABLED)] + public static void Info(string msg, bool showColor = true) + { + if (level < Levels.info) + return; + + if (showColor) + Debug.Log($"INFO: {msg}"); + else + Debug.Log($"INFO: {msg}"); + } + + /// + /// An expected Exception was caught, useful for debugging but not important + /// + /// + /// + [Conditional(SIMPLEWEB_LOG_ENABLED)] + public static void InfoException(Exception e) + { + if (level < Levels.info) + return; + + Debug.Log($"INFO_EXCEPTION: {e.GetType().Name} Message: {e.Message}"); + } + + [Conditional(SIMPLEWEB_LOG_ENABLED), Conditional(DEBUG)] + public static void Warn(string msg, bool showColor = true) + { + if (level < Levels.warn) + return; + + if (showColor) + Debug.LogWarning($"WARN: {msg}"); + else + Debug.LogWarning($"WARN: {msg}"); + } + + [Conditional(SIMPLEWEB_LOG_ENABLED), Conditional(DEBUG)] + public static void Error(string msg, bool showColor = true) + { + if (level < Levels.error) + return; + + if (showColor) + Debug.LogError($"ERROR: {msg}"); + else + Debug.LogError($"ERROR: {msg}"); + } + + public static void Exception(Exception e) + { + // always log Exceptions + Debug.LogError($"EXCEPTION: {e.GetType().Name} Message: {e.Message}"); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs.meta index beb2883..b45838b 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 3cf1521098e04f74fbea0fe2aa0439f8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 3cf1521098e04f74fbea0fe2aa0439f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs index 29b4849..d90cd78 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs @@ -1,49 +1,49 @@ -using System; - -namespace Mirror.SimpleWeb -{ - public struct Message - { - public readonly int connId; - public readonly EventType type; - public readonly ArrayBuffer data; - public readonly Exception exception; - - public Message(EventType type) : this() - { - this.type = type; - } - - public Message(ArrayBuffer data) : this() - { - type = EventType.Data; - this.data = data; - } - - public Message(Exception exception) : this() - { - type = EventType.Error; - this.exception = exception; - } - - public Message(int connId, EventType type) : this() - { - this.connId = connId; - this.type = type; - } - - public Message(int connId, ArrayBuffer data) : this() - { - this.connId = connId; - type = EventType.Data; - this.data = data; - } - - public Message(int connId, Exception exception) : this() - { - this.connId = connId; - type = EventType.Error; - this.exception = exception; - } - } -} +using System; + +namespace Mirror.SimpleWeb +{ + public struct Message + { + public readonly int connId; + public readonly EventType type; + public readonly ArrayBuffer data; + public readonly Exception exception; + + public Message(EventType type) : this() + { + this.type = type; + } + + public Message(ArrayBuffer data) : this() + { + type = EventType.Data; + this.data = data; + } + + public Message(Exception exception) : this() + { + type = EventType.Error; + this.exception = exception; + } + + public Message(int connId, EventType type) : this() + { + this.connId = connId; + this.type = type; + } + + public Message(int connId, ArrayBuffer data) : this() + { + this.connId = connId; + type = EventType.Data; + this.data = data; + } + + public Message(int connId, Exception exception) : this() + { + this.connId = connId; + type = EventType.Error; + this.exception = exception; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs.meta index 3286a2c..333ee3f 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: f5d05d71b09d2714b96ffe80bc3d2a77 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f5d05d71b09d2714b96ffe80bc3d2a77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs index 1bf98f0..d070b9a 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs @@ -1,140 +1,140 @@ -using System.IO; -using System.Runtime.CompilerServices; - -namespace Mirror.SimpleWeb -{ - public static class MessageProcessor - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static byte FirstLengthByte(byte[] buffer) => (byte)(buffer[1] & 0b0111_1111); - - public static bool NeedToReadShortLength(byte[] buffer) - { - byte lenByte = FirstLengthByte(buffer); - - return lenByte >= Constants.UshortPayloadLength; - } - - public static int GetOpcode(byte[] buffer) - { - return buffer[0] & 0b0000_1111; - } - - public static int GetPayloadLength(byte[] buffer) - { - byte lenByte = FirstLengthByte(buffer); - return GetMessageLength(buffer, 0, lenByte); - } - - public static void ValidateHeader(byte[] buffer, int maxLength, bool expectMask) - { - bool finished = (buffer[0] & 0b1000_0000) != 0; // has full message been sent - bool hasMask = (buffer[1] & 0b1000_0000) != 0; // true from clients, false from server, "All messages from the client to the server have this bit set" - - int opcode = buffer[0] & 0b0000_1111; // expecting 1 - text message - byte lenByte = FirstLengthByte(buffer); - - ThrowIfNotFinished(finished); - ThrowIfMaskNotExpected(hasMask, expectMask); - ThrowIfBadOpCode(opcode); - - int msglen = GetMessageLength(buffer, 0, lenByte); - - ThrowIfLengthZero(msglen); - ThrowIfMsgLengthTooLong(msglen, maxLength); - } - - public static void ToggleMask(byte[] src, int sourceOffset, int messageLength, byte[] maskBuffer, int maskOffset) - { - ToggleMask(src, sourceOffset, src, sourceOffset, messageLength, maskBuffer, maskOffset); - } - - public static void ToggleMask(byte[] src, int sourceOffset, ArrayBuffer dst, int messageLength, byte[] maskBuffer, int maskOffset) - { - ToggleMask(src, sourceOffset, dst.array, 0, messageLength, maskBuffer, maskOffset); - dst.count = messageLength; - } - - public static void ToggleMask(byte[] src, int srcOffset, byte[] dst, int dstOffset, int messageLength, byte[] maskBuffer, int maskOffset) - { - for (int i = 0; i < messageLength; i++) - { - byte maskByte = maskBuffer[maskOffset + i % Constants.MaskSize]; - dst[dstOffset + i] = (byte)(src[srcOffset + i] ^ maskByte); - } - } - - /// - static int GetMessageLength(byte[] buffer, int offset, byte lenByte) - { - if (lenByte == Constants.UshortPayloadLength) - { - // header is 4 bytes long - ushort value = 0; - value |= (ushort)(buffer[offset + 2] << 8); - value |= buffer[offset + 3]; - - return value; - } - else if (lenByte == Constants.UlongPayloadLength) - { - throw new InvalidDataException("Max length is longer than allowed in a single message"); - } - else // is less than 126 - { - // header is 2 bytes long - return lenByte; - } - } - - /// - static void ThrowIfNotFinished(bool finished) - { - if (!finished) - { - throw new InvalidDataException("Full message should have been sent, if the full message wasn't sent it wasn't sent from this trasnport"); - } - } - - /// - static void ThrowIfMaskNotExpected(bool hasMask, bool expectMask) - { - if (hasMask != expectMask) - { - throw new InvalidDataException($"Message expected mask to be {expectMask} but was {hasMask}"); - } - } - - /// - static void ThrowIfBadOpCode(int opcode) - { - // 2 = binary - // 8 = close - if (opcode != 2 && opcode != 8) - { - throw new InvalidDataException("Expected opcode to be binary or close"); - } - } - - /// - static void ThrowIfLengthZero(int msglen) - { - if (msglen == 0) - { - throw new InvalidDataException("Message length was zero"); - } - } - - /// - /// need to check this so that data from previous buffer isn't used - /// - /// - static void ThrowIfMsgLengthTooLong(int msglen, int maxLength) - { - if (msglen > maxLength) - { - throw new InvalidDataException("Message length is greater than max length"); - } - } - } -} +using System.IO; +using System.Runtime.CompilerServices; + +namespace Mirror.SimpleWeb +{ + public static class MessageProcessor + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static byte FirstLengthByte(byte[] buffer) => (byte)(buffer[1] & 0b0111_1111); + + public static bool NeedToReadShortLength(byte[] buffer) + { + byte lenByte = FirstLengthByte(buffer); + + return lenByte >= Constants.UshortPayloadLength; + } + + public static int GetOpcode(byte[] buffer) + { + return buffer[0] & 0b0000_1111; + } + + public static int GetPayloadLength(byte[] buffer) + { + byte lenByte = FirstLengthByte(buffer); + return GetMessageLength(buffer, 0, lenByte); + } + + public static void ValidateHeader(byte[] buffer, int maxLength, bool expectMask) + { + bool finished = (buffer[0] & 0b1000_0000) != 0; // has full message been sent + bool hasMask = (buffer[1] & 0b1000_0000) != 0; // true from clients, false from server, "All messages from the client to the server have this bit set" + + int opcode = buffer[0] & 0b0000_1111; // expecting 1 - text message + byte lenByte = FirstLengthByte(buffer); + + ThrowIfNotFinished(finished); + ThrowIfMaskNotExpected(hasMask, expectMask); + ThrowIfBadOpCode(opcode); + + int msglen = GetMessageLength(buffer, 0, lenByte); + + ThrowIfLengthZero(msglen); + ThrowIfMsgLengthTooLong(msglen, maxLength); + } + + public static void ToggleMask(byte[] src, int sourceOffset, int messageLength, byte[] maskBuffer, int maskOffset) + { + ToggleMask(src, sourceOffset, src, sourceOffset, messageLength, maskBuffer, maskOffset); + } + + public static void ToggleMask(byte[] src, int sourceOffset, ArrayBuffer dst, int messageLength, byte[] maskBuffer, int maskOffset) + { + ToggleMask(src, sourceOffset, dst.array, 0, messageLength, maskBuffer, maskOffset); + dst.count = messageLength; + } + + public static void ToggleMask(byte[] src, int srcOffset, byte[] dst, int dstOffset, int messageLength, byte[] maskBuffer, int maskOffset) + { + for (int i = 0; i < messageLength; i++) + { + byte maskByte = maskBuffer[maskOffset + i % Constants.MaskSize]; + dst[dstOffset + i] = (byte)(src[srcOffset + i] ^ maskByte); + } + } + + /// + static int GetMessageLength(byte[] buffer, int offset, byte lenByte) + { + if (lenByte == Constants.UshortPayloadLength) + { + // header is 4 bytes long + ushort value = 0; + value |= (ushort)(buffer[offset + 2] << 8); + value |= buffer[offset + 3]; + + return value; + } + else if (lenByte == Constants.UlongPayloadLength) + { + throw new InvalidDataException("Max length is longer than allowed in a single message"); + } + else // is less than 126 + { + // header is 2 bytes long + return lenByte; + } + } + + /// + static void ThrowIfNotFinished(bool finished) + { + if (!finished) + { + throw new InvalidDataException("Full message should have been sent, if the full message wasn't sent it wasn't sent from this trasnport"); + } + } + + /// + static void ThrowIfMaskNotExpected(bool hasMask, bool expectMask) + { + if (hasMask != expectMask) + { + throw new InvalidDataException($"Message expected mask to be {expectMask} but was {hasMask}"); + } + } + + /// + static void ThrowIfBadOpCode(int opcode) + { + // 2 = binary + // 8 = close + if (opcode != 2 && opcode != 8) + { + throw new InvalidDataException("Expected opcode to be binary or close"); + } + } + + /// + static void ThrowIfLengthZero(int msglen) + { + if (msglen == 0) + { + throw new InvalidDataException("Message length was zero"); + } + } + + /// + /// need to check this so that data from previous buffer isn't used + /// + /// + static void ThrowIfMsgLengthTooLong(int msglen, int maxLength) + { + if (msglen > maxLength) + { + throw new InvalidDataException("Message length is greater than max length"); + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs.meta index 7e3a7c4..48c3916 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 4c1f218a2b16ca846aaf23260078e549 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 4c1f218a2b16ca846aaf23260078e549 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs index 66f36c9..1f749c1 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs @@ -1,132 +1,132 @@ -using System; -using System.IO; -using System.Runtime.Serialization; - -namespace Mirror.SimpleWeb -{ - public static class ReadHelper - { - /// - /// Reads exactly length from stream - /// - /// outOffset + length - /// - public static int Read(Stream stream, byte[] outBuffer, int outOffset, int length) - { - int received = 0; - try - { - while (received < length) - { - int read = stream.Read(outBuffer, outOffset + received, length - received); - if (read == 0) - { - throw new ReadHelperException("returned 0"); - } - received += read; - } - } - catch (AggregateException ae) - { - // if interrupt is called we don't care about Exceptions - Utils.CheckForInterupt(); - - // rethrow - ae.Handle(e => false); - } - - if (received != length) - { - throw new ReadHelperException("returned not equal to length"); - } - - return outOffset + received; - } - - /// - /// Reads and returns results. This should never throw an exception - /// - public static bool TryRead(Stream stream, byte[] outBuffer, int outOffset, int length) - { - try - { - Read(stream, outBuffer, outOffset, length); - return true; - } - catch (ReadHelperException) - { - return false; - } - catch (IOException) - { - return false; - } - catch (Exception e) - { - Log.Exception(e); - return false; - } - } - - public static int? SafeReadTillMatch(Stream stream, byte[] outBuffer, int outOffset, int maxLength, byte[] endOfHeader) - { - try - { - int read = 0; - int endIndex = 0; - int endLength = endOfHeader.Length; - while (true) - { - int next = stream.ReadByte(); - if (next == -1) // closed - return null; - - if (read >= maxLength) - { - Log.Error("SafeReadTillMatch exceeded maxLength"); - return null; - } - - outBuffer[outOffset + read] = (byte)next; - read++; - - // if n is match, check n+1 next - if (endOfHeader[endIndex] == next) - { - endIndex++; - // when all is match return with read length - if (endIndex >= endLength) - { - return read; - } - } - // if n not match reset to 0 - else - { - endIndex = 0; - } - } - } - catch (IOException e) - { - Log.InfoException(e); - return null; - } - catch (Exception e) - { - Log.Exception(e); - return null; - } - } - } - - [Serializable] - public class ReadHelperException : Exception - { - public ReadHelperException(string message) : base(message) {} - - protected ReadHelperException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } -} +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace Mirror.SimpleWeb +{ + public static class ReadHelper + { + /// + /// Reads exactly length from stream + /// + /// outOffset + length + /// + public static int Read(Stream stream, byte[] outBuffer, int outOffset, int length) + { + int received = 0; + try + { + while (received < length) + { + int read = stream.Read(outBuffer, outOffset + received, length - received); + if (read == 0) + { + throw new ReadHelperException("returned 0"); + } + received += read; + } + } + catch (AggregateException ae) + { + // if interrupt is called we don't care about Exceptions + Utils.CheckForInterupt(); + + // rethrow + ae.Handle(e => false); + } + + if (received != length) + { + throw new ReadHelperException("returned not equal to length"); + } + + return outOffset + received; + } + + /// + /// Reads and returns results. This should never throw an exception + /// + public static bool TryRead(Stream stream, byte[] outBuffer, int outOffset, int length) + { + try + { + Read(stream, outBuffer, outOffset, length); + return true; + } + catch (ReadHelperException) + { + return false; + } + catch (IOException) + { + return false; + } + catch (Exception e) + { + Log.Exception(e); + return false; + } + } + + public static int? SafeReadTillMatch(Stream stream, byte[] outBuffer, int outOffset, int maxLength, byte[] endOfHeader) + { + try + { + int read = 0; + int endIndex = 0; + int endLength = endOfHeader.Length; + while (true) + { + int next = stream.ReadByte(); + if (next == -1) // closed + return null; + + if (read >= maxLength) + { + Log.Error("SafeReadTillMatch exceeded maxLength"); + return null; + } + + outBuffer[outOffset + read] = (byte)next; + read++; + + // if n is match, check n+1 next + if (endOfHeader[endIndex] == next) + { + endIndex++; + // when all is match return with read length + if (endIndex >= endLength) + { + return read; + } + } + // if n not match reset to 0 + else + { + endIndex = 0; + } + } + } + catch (IOException e) + { + Log.InfoException(e); + return null; + } + catch (Exception e) + { + Log.Exception(e); + return null; + } + } + } + + [Serializable] + public class ReadHelperException : Exception + { + public ReadHelperException(string message) : base(message) {} + + protected ReadHelperException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs.meta index 77d09c1..5cf2cdb 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 9f4fa5d324e708c46a55810a97de75bc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 9f4fa5d324e708c46a55810a97de75bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs index 16126ab..0d8625c 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs @@ -1,199 +1,199 @@ -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using UnityEngine.Profiling; - -namespace Mirror.SimpleWeb -{ - internal static class ReceiveLoop - { - public struct Config - { - public readonly Connection conn; - public readonly int maxMessageSize; - public readonly bool expectMask; - public readonly ConcurrentQueue queue; - public readonly BufferPool bufferPool; - - public Config(Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue queue, BufferPool bufferPool) - { - this.conn = conn ?? throw new ArgumentNullException(nameof(conn)); - this.maxMessageSize = maxMessageSize; - this.expectMask = expectMask; - this.queue = queue ?? throw new ArgumentNullException(nameof(queue)); - this.bufferPool = bufferPool ?? throw new ArgumentNullException(nameof(bufferPool)); - } - - public void Deconstruct(out Connection conn, out int maxMessageSize, out bool expectMask, out ConcurrentQueue queue, out BufferPool bufferPool) - { - conn = this.conn; - maxMessageSize = this.maxMessageSize; - expectMask = this.expectMask; - queue = this.queue; - bufferPool = this.bufferPool; - } - } - - public static void Loop(Config config) - { - (Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue queue, BufferPool _) = config; - - Profiler.BeginThreadProfiling("SimpleWeb", $"ReceiveLoop {conn.connId}"); - - byte[] readBuffer = new byte[Constants.HeaderSize + (expectMask ? Constants.MaskSize : 0) + maxMessageSize]; - try - { - try - { - TcpClient client = conn.client; - - while (client.Connected) - { - ReadOneMessage(config, readBuffer); - } - - Log.Info($"{conn} Not Connected"); - } - catch (Exception) - { - // if interrupted we don't care about other exceptions - Utils.CheckForInterupt(); - throw; - } - } - catch (ThreadInterruptedException e) { Log.InfoException(e); } - catch (ThreadAbortException e) { Log.InfoException(e); } - catch (ObjectDisposedException e) { Log.InfoException(e); } - catch (ReadHelperException e) - { - // log as info only - Log.InfoException(e); - } - catch (SocketException e) - { - // this could happen if wss client closes stream - Log.Warn($"ReceiveLoop SocketException\n{e.Message}", false); - queue.Enqueue(new Message(conn.connId, e)); - } - catch (IOException e) - { - // this could happen if client disconnects - Log.Warn($"ReceiveLoop IOException\n{e.Message}", false); - queue.Enqueue(new Message(conn.connId, e)); - } - catch (InvalidDataException e) - { - Log.Error($"Invalid data from {conn}: {e.Message}"); - queue.Enqueue(new Message(conn.connId, e)); - } - catch (Exception e) - { - Log.Exception(e); - queue.Enqueue(new Message(conn.connId, e)); - } - finally - { - Profiler.EndThreadProfiling(); - - conn.Dispose(); - } - } - - static void ReadOneMessage(Config config, byte[] buffer) - { - (Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue queue, BufferPool bufferPool) = config; - Stream stream = conn.stream; - - int offset = 0; - // read 2 - offset = ReadHelper.Read(stream, buffer, offset, Constants.HeaderMinSize); - // log after first blocking call - Log.Verbose($"Message From {conn}"); - - if (MessageProcessor.NeedToReadShortLength(buffer)) - { - offset = ReadHelper.Read(stream, buffer, offset, Constants.ShortLength); - } - - MessageProcessor.ValidateHeader(buffer, maxMessageSize, expectMask); - - if (expectMask) - { - offset = ReadHelper.Read(stream, buffer, offset, Constants.MaskSize); - } - - int opcode = MessageProcessor.GetOpcode(buffer); - int payloadLength = MessageProcessor.GetPayloadLength(buffer); - - Log.Verbose($"Header ln:{payloadLength} op:{opcode} mask:{expectMask}"); - Log.DumpBuffer($"Raw Header", buffer, 0, offset); - - int msgOffset = offset; - offset = ReadHelper.Read(stream, buffer, offset, payloadLength); - - switch (opcode) - { - case 2: - HandleArrayMessage(config, buffer, msgOffset, payloadLength); - break; - case 8: - HandleCloseMessage(config, buffer, msgOffset, payloadLength); - break; - } - } - - static void HandleArrayMessage(Config config, byte[] buffer, int msgOffset, int payloadLength) - { - (Connection conn, int _, bool expectMask, ConcurrentQueue queue, BufferPool bufferPool) = config; - - ArrayBuffer arrayBuffer = bufferPool.Take(payloadLength); - - if (expectMask) - { - int maskOffset = msgOffset - Constants.MaskSize; - // write the result of toggle directly into arrayBuffer to avoid 2nd copy call - MessageProcessor.ToggleMask(buffer, msgOffset, arrayBuffer, payloadLength, buffer, maskOffset); - } - else - { - arrayBuffer.CopyFrom(buffer, msgOffset, payloadLength); - } - - // dump after mask off - Log.DumpBuffer($"Message", arrayBuffer); - - queue.Enqueue(new Message(conn.connId, arrayBuffer)); - } - - static void HandleCloseMessage(Config config, byte[] buffer, int msgOffset, int payloadLength) - { - (Connection conn, int _, bool expectMask, ConcurrentQueue _, BufferPool _) = config; - - if (expectMask) - { - int maskOffset = msgOffset - Constants.MaskSize; - MessageProcessor.ToggleMask(buffer, msgOffset, payloadLength, buffer, maskOffset); - } - - // dump after mask off - Log.DumpBuffer($"Message", buffer, msgOffset, payloadLength); - - Log.Info($"Close: {GetCloseCode(buffer, msgOffset)} message:{GetCloseMessage(buffer, msgOffset, payloadLength)}"); - - conn.Dispose(); - } - - static string GetCloseMessage(byte[] buffer, int msgOffset, int payloadLength) - { - return Encoding.UTF8.GetString(buffer, msgOffset + 2, payloadLength - 2); - } - - static int GetCloseCode(byte[] buffer, int msgOffset) - { - return buffer[msgOffset + 0] << 8 | buffer[msgOffset + 1]; - } - } -} +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using UnityEngine.Profiling; + +namespace Mirror.SimpleWeb +{ + internal static class ReceiveLoop + { + public struct Config + { + public readonly Connection conn; + public readonly int maxMessageSize; + public readonly bool expectMask; + public readonly ConcurrentQueue queue; + public readonly BufferPool bufferPool; + + public Config(Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue queue, BufferPool bufferPool) + { + this.conn = conn ?? throw new ArgumentNullException(nameof(conn)); + this.maxMessageSize = maxMessageSize; + this.expectMask = expectMask; + this.queue = queue ?? throw new ArgumentNullException(nameof(queue)); + this.bufferPool = bufferPool ?? throw new ArgumentNullException(nameof(bufferPool)); + } + + public void Deconstruct(out Connection conn, out int maxMessageSize, out bool expectMask, out ConcurrentQueue queue, out BufferPool bufferPool) + { + conn = this.conn; + maxMessageSize = this.maxMessageSize; + expectMask = this.expectMask; + queue = this.queue; + bufferPool = this.bufferPool; + } + } + + public static void Loop(Config config) + { + (Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue queue, BufferPool _) = config; + + Profiler.BeginThreadProfiling("SimpleWeb", $"ReceiveLoop {conn.connId}"); + + byte[] readBuffer = new byte[Constants.HeaderSize + (expectMask ? Constants.MaskSize : 0) + maxMessageSize]; + try + { + try + { + TcpClient client = conn.client; + + while (client.Connected) + { + ReadOneMessage(config, readBuffer); + } + + Log.Info($"{conn} Not Connected"); + } + catch (Exception) + { + // if interrupted we don't care about other exceptions + Utils.CheckForInterupt(); + throw; + } + } + catch (ThreadInterruptedException e) { Log.InfoException(e); } + catch (ThreadAbortException e) { Log.InfoException(e); } + catch (ObjectDisposedException e) { Log.InfoException(e); } + catch (ReadHelperException e) + { + // log as info only + Log.InfoException(e); + } + catch (SocketException e) + { + // this could happen if wss client closes stream + Log.Warn($"ReceiveLoop SocketException\n{e.Message}", false); + queue.Enqueue(new Message(conn.connId, e)); + } + catch (IOException e) + { + // this could happen if client disconnects + Log.Warn($"ReceiveLoop IOException\n{e.Message}", false); + queue.Enqueue(new Message(conn.connId, e)); + } + catch (InvalidDataException e) + { + Log.Error($"Invalid data from {conn}: {e.Message}"); + queue.Enqueue(new Message(conn.connId, e)); + } + catch (Exception e) + { + Log.Exception(e); + queue.Enqueue(new Message(conn.connId, e)); + } + finally + { + Profiler.EndThreadProfiling(); + + conn.Dispose(); + } + } + + static void ReadOneMessage(Config config, byte[] buffer) + { + (Connection conn, int maxMessageSize, bool expectMask, ConcurrentQueue queue, BufferPool bufferPool) = config; + Stream stream = conn.stream; + + int offset = 0; + // read 2 + offset = ReadHelper.Read(stream, buffer, offset, Constants.HeaderMinSize); + // log after first blocking call + Log.Verbose($"Message From {conn}"); + + if (MessageProcessor.NeedToReadShortLength(buffer)) + { + offset = ReadHelper.Read(stream, buffer, offset, Constants.ShortLength); + } + + MessageProcessor.ValidateHeader(buffer, maxMessageSize, expectMask); + + if (expectMask) + { + offset = ReadHelper.Read(stream, buffer, offset, Constants.MaskSize); + } + + int opcode = MessageProcessor.GetOpcode(buffer); + int payloadLength = MessageProcessor.GetPayloadLength(buffer); + + Log.Verbose($"Header ln:{payloadLength} op:{opcode} mask:{expectMask}"); + Log.DumpBuffer($"Raw Header", buffer, 0, offset); + + int msgOffset = offset; + offset = ReadHelper.Read(stream, buffer, offset, payloadLength); + + switch (opcode) + { + case 2: + HandleArrayMessage(config, buffer, msgOffset, payloadLength); + break; + case 8: + HandleCloseMessage(config, buffer, msgOffset, payloadLength); + break; + } + } + + static void HandleArrayMessage(Config config, byte[] buffer, int msgOffset, int payloadLength) + { + (Connection conn, int _, bool expectMask, ConcurrentQueue queue, BufferPool bufferPool) = config; + + ArrayBuffer arrayBuffer = bufferPool.Take(payloadLength); + + if (expectMask) + { + int maskOffset = msgOffset - Constants.MaskSize; + // write the result of toggle directly into arrayBuffer to avoid 2nd copy call + MessageProcessor.ToggleMask(buffer, msgOffset, arrayBuffer, payloadLength, buffer, maskOffset); + } + else + { + arrayBuffer.CopyFrom(buffer, msgOffset, payloadLength); + } + + // dump after mask off + Log.DumpBuffer($"Message", arrayBuffer); + + queue.Enqueue(new Message(conn.connId, arrayBuffer)); + } + + static void HandleCloseMessage(Config config, byte[] buffer, int msgOffset, int payloadLength) + { + (Connection conn, int _, bool expectMask, ConcurrentQueue _, BufferPool _) = config; + + if (expectMask) + { + int maskOffset = msgOffset - Constants.MaskSize; + MessageProcessor.ToggleMask(buffer, msgOffset, payloadLength, buffer, maskOffset); + } + + // dump after mask off + Log.DumpBuffer($"Message", buffer, msgOffset, payloadLength); + + Log.Info($"Close: {GetCloseCode(buffer, msgOffset)} message:{GetCloseMessage(buffer, msgOffset, payloadLength)}"); + + conn.Dispose(); + } + + static string GetCloseMessage(byte[] buffer, int msgOffset, int payloadLength) + { + return Encoding.UTF8.GetString(buffer, msgOffset + 2, payloadLength - 2); + } + + static int GetCloseCode(byte[] buffer, int msgOffset) + { + return buffer[msgOffset + 0] << 8 | buffer[msgOffset + 1]; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs.meta index 47c6ff5..494ee04 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: a26c2815f58431c4a98c158c8b655ffd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a26c2815f58431c4a98c158c8b655ffd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs index 096b7ff..f5e1b15 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs @@ -1,207 +1,207 @@ -using System; -using System.IO; -using System.Net.Sockets; -using System.Security.Cryptography; -using System.Threading; -using UnityEngine.Profiling; - -namespace Mirror.SimpleWeb -{ - public static class SendLoopConfig - { - public static volatile bool batchSend = false; - public static volatile bool sleepBeforeSend = false; - } - internal static class SendLoop - { - public struct Config - { - public readonly Connection conn; - public readonly int bufferSize; - public readonly bool setMask; - - public Config(Connection conn, int bufferSize, bool setMask) - { - this.conn = conn ?? throw new ArgumentNullException(nameof(conn)); - this.bufferSize = bufferSize; - this.setMask = setMask; - } - - public void Deconstruct(out Connection conn, out int bufferSize, out bool setMask) - { - conn = this.conn; - bufferSize = this.bufferSize; - setMask = this.setMask; - } - } - - public static void Loop(Config config) - { - (Connection conn, int bufferSize, bool setMask) = config; - - Profiler.BeginThreadProfiling("SimpleWeb", $"SendLoop {conn.connId}"); - - // create write buffer for this thread - byte[] writeBuffer = new byte[bufferSize]; - MaskHelper maskHelper = setMask ? new MaskHelper() : null; - try - { - TcpClient client = conn.client; - Stream stream = conn.stream; - - // null check in case disconnect while send thread is starting - if (client == null) - return; - - while (client.Connected) - { - // wait for message - conn.sendPending.Wait(); - // wait for 1ms for mirror to send other messages - if (SendLoopConfig.sleepBeforeSend) - { - Thread.Sleep(1); - } - conn.sendPending.Reset(); - - if (SendLoopConfig.batchSend) - { - int offset = 0; - while (conn.sendQueue.TryDequeue(out ArrayBuffer msg)) - { - // check if connected before sending message - if (!client.Connected) { Log.Info($"SendLoop {conn} not connected"); return; } - - int maxLength = msg.count + Constants.HeaderSize + Constants.MaskSize; - - // if next writer could overflow, write to stream and clear buffer - if (offset + maxLength > bufferSize) - { - stream.Write(writeBuffer, 0, offset); - offset = 0; - } - - offset = SendMessage(writeBuffer, offset, msg, setMask, maskHelper); - msg.Release(); - } - - // after no message in queue, send remaining messages - // don't need to check offset > 0 because last message in queue will always be sent here - - stream.Write(writeBuffer, 0, offset); - } - else - { - while (conn.sendQueue.TryDequeue(out ArrayBuffer msg)) - { - // check if connected before sending message - if (!client.Connected) { Log.Info($"SendLoop {conn} not connected"); return; } - - int length = SendMessage(writeBuffer, 0, msg, setMask, maskHelper); - stream.Write(writeBuffer, 0, length); - msg.Release(); - } - } - } - - Log.Info($"{conn} Not Connected"); - } - catch (ThreadInterruptedException e) { Log.InfoException(e); } - catch (ThreadAbortException e) { Log.InfoException(e); } - catch (Exception e) - { - Log.Exception(e); - } - finally - { - Profiler.EndThreadProfiling(); - conn.Dispose(); - maskHelper?.Dispose(); - } - } - - /// new offset in buffer - static int SendMessage(byte[] buffer, int startOffset, ArrayBuffer msg, bool setMask, MaskHelper maskHelper) - { - int msgLength = msg.count; - int offset = WriteHeader(buffer, startOffset, msgLength, setMask); - - if (setMask) - { - offset = maskHelper.WriteMask(buffer, offset); - } - - msg.CopyTo(buffer, offset); - offset += msgLength; - - // dump before mask on - Log.DumpBuffer("Send", buffer, startOffset, offset); - - if (setMask) - { - int messageOffset = offset - msgLength; - MessageProcessor.ToggleMask(buffer, messageOffset, msgLength, buffer, messageOffset - Constants.MaskSize); - } - - return offset; - } - - static int WriteHeader(byte[] buffer, int startOffset, int msgLength, bool setMask) - { - int sendLength = 0; - const byte finished = 128; - const byte byteOpCode = 2; - - buffer[startOffset + 0] = finished | byteOpCode; - sendLength++; - - if (msgLength <= Constants.BytePayloadLength) - { - buffer[startOffset + 1] = (byte)msgLength; - sendLength++; - } - else if (msgLength <= ushort.MaxValue) - { - buffer[startOffset + 1] = 126; - buffer[startOffset + 2] = (byte)(msgLength >> 8); - buffer[startOffset + 3] = (byte)msgLength; - sendLength += 3; - } - else - { - throw new InvalidDataException($"Trying to send a message larger than {ushort.MaxValue} bytes"); - } - - if (setMask) - { - buffer[startOffset + 1] |= 0b1000_0000; - } - - return sendLength + startOffset; - } - - sealed class MaskHelper : IDisposable - { - readonly byte[] maskBuffer; - readonly RNGCryptoServiceProvider random; - - public MaskHelper() - { - maskBuffer = new byte[4]; - random = new RNGCryptoServiceProvider(); - } - public void Dispose() - { - random.Dispose(); - } - - public int WriteMask(byte[] buffer, int offset) - { - random.GetBytes(maskBuffer); - Buffer.BlockCopy(maskBuffer, 0, buffer, offset, 4); - - return offset + 4; - } - } - } -} +using System; +using System.IO; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Threading; +using UnityEngine.Profiling; + +namespace Mirror.SimpleWeb +{ + public static class SendLoopConfig + { + public static volatile bool batchSend = false; + public static volatile bool sleepBeforeSend = false; + } + internal static class SendLoop + { + public struct Config + { + public readonly Connection conn; + public readonly int bufferSize; + public readonly bool setMask; + + public Config(Connection conn, int bufferSize, bool setMask) + { + this.conn = conn ?? throw new ArgumentNullException(nameof(conn)); + this.bufferSize = bufferSize; + this.setMask = setMask; + } + + public void Deconstruct(out Connection conn, out int bufferSize, out bool setMask) + { + conn = this.conn; + bufferSize = this.bufferSize; + setMask = this.setMask; + } + } + + public static void Loop(Config config) + { + (Connection conn, int bufferSize, bool setMask) = config; + + Profiler.BeginThreadProfiling("SimpleWeb", $"SendLoop {conn.connId}"); + + // create write buffer for this thread + byte[] writeBuffer = new byte[bufferSize]; + MaskHelper maskHelper = setMask ? new MaskHelper() : null; + try + { + TcpClient client = conn.client; + Stream stream = conn.stream; + + // null check in case disconnect while send thread is starting + if (client == null) + return; + + while (client.Connected) + { + // wait for message + conn.sendPending.Wait(); + // wait for 1ms for mirror to send other messages + if (SendLoopConfig.sleepBeforeSend) + { + Thread.Sleep(1); + } + conn.sendPending.Reset(); + + if (SendLoopConfig.batchSend) + { + int offset = 0; + while (conn.sendQueue.TryDequeue(out ArrayBuffer msg)) + { + // check if connected before sending message + if (!client.Connected) { Log.Info($"SendLoop {conn} not connected"); return; } + + int maxLength = msg.count + Constants.HeaderSize + Constants.MaskSize; + + // if next writer could overflow, write to stream and clear buffer + if (offset + maxLength > bufferSize) + { + stream.Write(writeBuffer, 0, offset); + offset = 0; + } + + offset = SendMessage(writeBuffer, offset, msg, setMask, maskHelper); + msg.Release(); + } + + // after no message in queue, send remaining messages + // don't need to check offset > 0 because last message in queue will always be sent here + + stream.Write(writeBuffer, 0, offset); + } + else + { + while (conn.sendQueue.TryDequeue(out ArrayBuffer msg)) + { + // check if connected before sending message + if (!client.Connected) { Log.Info($"SendLoop {conn} not connected"); return; } + + int length = SendMessage(writeBuffer, 0, msg, setMask, maskHelper); + stream.Write(writeBuffer, 0, length); + msg.Release(); + } + } + } + + Log.Info($"{conn} Not Connected"); + } + catch (ThreadInterruptedException e) { Log.InfoException(e); } + catch (ThreadAbortException e) { Log.InfoException(e); } + catch (Exception e) + { + Log.Exception(e); + } + finally + { + Profiler.EndThreadProfiling(); + conn.Dispose(); + maskHelper?.Dispose(); + } + } + + /// new offset in buffer + static int SendMessage(byte[] buffer, int startOffset, ArrayBuffer msg, bool setMask, MaskHelper maskHelper) + { + int msgLength = msg.count; + int offset = WriteHeader(buffer, startOffset, msgLength, setMask); + + if (setMask) + { + offset = maskHelper.WriteMask(buffer, offset); + } + + msg.CopyTo(buffer, offset); + offset += msgLength; + + // dump before mask on + Log.DumpBuffer("Send", buffer, startOffset, offset); + + if (setMask) + { + int messageOffset = offset - msgLength; + MessageProcessor.ToggleMask(buffer, messageOffset, msgLength, buffer, messageOffset - Constants.MaskSize); + } + + return offset; + } + + static int WriteHeader(byte[] buffer, int startOffset, int msgLength, bool setMask) + { + int sendLength = 0; + const byte finished = 128; + const byte byteOpCode = 2; + + buffer[startOffset + 0] = finished | byteOpCode; + sendLength++; + + if (msgLength <= Constants.BytePayloadLength) + { + buffer[startOffset + 1] = (byte)msgLength; + sendLength++; + } + else if (msgLength <= ushort.MaxValue) + { + buffer[startOffset + 1] = 126; + buffer[startOffset + 2] = (byte)(msgLength >> 8); + buffer[startOffset + 3] = (byte)msgLength; + sendLength += 3; + } + else + { + throw new InvalidDataException($"Trying to send a message larger than {ushort.MaxValue} bytes"); + } + + if (setMask) + { + buffer[startOffset + 1] |= 0b1000_0000; + } + + return sendLength + startOffset; + } + + sealed class MaskHelper : IDisposable + { + readonly byte[] maskBuffer; + readonly RNGCryptoServiceProvider random; + + public MaskHelper() + { + maskBuffer = new byte[4]; + random = new RNGCryptoServiceProvider(); + } + public void Dispose() + { + random.Dispose(); + } + + public int WriteMask(byte[] buffer, int offset) + { + random.GetBytes(maskBuffer); + Buffer.BlockCopy(maskBuffer, 0, buffer, offset, 4); + + return offset + 4; + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs.meta index 09dfd1e..9e29fd7 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: f87dd81736d9c824db67f808ac71841d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f87dd81736d9c824db67f808ac71841d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs index 8cb4779..e1a615c 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs @@ -1,25 +1,25 @@ -using System.Net.Sockets; - -namespace Mirror.SimpleWeb -{ - public struct TcpConfig - { - public readonly bool noDelay; - public readonly int sendTimeout; - public readonly int receiveTimeout; - - public TcpConfig(bool noDelay, int sendTimeout, int receiveTimeout) - { - this.noDelay = noDelay; - this.sendTimeout = sendTimeout; - this.receiveTimeout = receiveTimeout; - } - - public void ApplyTo(TcpClient client) - { - client.SendTimeout = sendTimeout; - client.ReceiveTimeout = receiveTimeout; - client.NoDelay = noDelay; - } - } -} +using System.Net.Sockets; + +namespace Mirror.SimpleWeb +{ + public struct TcpConfig + { + public readonly bool noDelay; + public readonly int sendTimeout; + public readonly int receiveTimeout; + + public TcpConfig(bool noDelay, int sendTimeout, int receiveTimeout) + { + this.noDelay = noDelay; + this.sendTimeout = sendTimeout; + this.receiveTimeout = receiveTimeout; + } + + public void ApplyTo(TcpClient client) + { + client.SendTimeout = sendTimeout; + client.ReceiveTimeout = receiveTimeout; + client.NoDelay = noDelay; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs.meta index 62ba232..1984f60 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 81ac8d35f28fab14b9edda5cd9d4fc86 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 81ac8d35f28fab14b9edda5cd9d4fc86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs index b8a860c..2084090 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs @@ -1,13 +1,13 @@ -using System.Threading; - -namespace Mirror.SimpleWeb -{ - internal static class Utils - { - public static void CheckForInterupt() - { - // sleep in order to check for ThreadInterruptedException - Thread.Sleep(1); - } - } -} +using System.Threading; + +namespace Mirror.SimpleWeb +{ + internal static class Utils + { + public static void CheckForInterupt() + { + // sleep in order to check for ThreadInterruptedException + Thread.Sleep(1); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs.meta index 79a1583..c6d985c 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 4643ffb4cb0562847b1ae925d07e15b6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 4643ffb4cb0562847b1ae925d07e15b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt index 58aa246..87c286c 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt @@ -1,22 +1,22 @@ -SimpleWebTransport is a Transport that implements websocket for Webgl builds of -mirror. This transport can also work on standalone builds and has support for -encryption with websocket secure. - -How to use: - Replace your existing Transport with SimpleWebTransport on your NetworkManager - -Requirements: - Unity 2018.4 LTS - Mirror v18.0.0 - -Documentation: - https://mirror-networking.gitbook.io/docs/ - https://github.com/MirrorNetworking/SimpleWebTransport/blob/master/README.md - -Support: - Discord: https://discordapp.com/invite/N9QVxbM - Bug Reports: https://github.com/MirrorNetworking/SimpleWebTransport/issues - - -**To get most recent updates and fixes download from github** -https://github.com/MirrorNetworking/SimpleWebTransport/releases +SimpleWebTransport is a Transport that implements websocket for Webgl builds of +mirror. This transport can also work on standalone builds and has support for +encryption with websocket secure. + +How to use: + Replace your existing Transport with SimpleWebTransport on your NetworkManager + +Requirements: + Unity 2018.4 LTS + Mirror v18.0.0 + +Documentation: + https://mirror-networking.gitbook.io/docs/ + https://github.com/MirrorNetworking/SimpleWebTransport/blob/master/README.md + +Support: + Discord: https://discordapp.com/invite/N9QVxbM + Bug Reports: https://github.com/MirrorNetworking/SimpleWebTransport/issues + + +**To get most recent updates and fixes download from github** +https://github.com/MirrorNetworking/SimpleWebTransport/releases diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt.meta index b63fe39..048e66a 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 0e3971d5783109f4d9ce93c7a689d701 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 0e3971d5783109f4d9ce93c7a689d701 +TextScriptImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server.meta index 31f317f..719de64 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 0e599e92544d43344a9a9060052add28 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 0e599e92544d43344a9a9060052add28 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs index f186eb4..e29ef51 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs @@ -1,149 +1,149 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; - -namespace Mirror.SimpleWeb -{ - /// - /// Handles Handshakes from new clients on the server - /// The server handshake has buffers to reduce allocations when clients connect - /// - internal class ServerHandshake - { - const int GetSize = 3; - const int ResponseLength = 129; - const int KeyLength = 24; - const int MergedKeyLength = 60; - const string KeyHeaderString = "Sec-WebSocket-Key: "; - // this isn't an official max, just a reasonable size for a websocket handshake - readonly int maxHttpHeaderSize = 3000; - - readonly SHA1 sha1 = SHA1.Create(); - readonly BufferPool bufferPool; - - public ServerHandshake(BufferPool bufferPool, int handshakeMaxSize) - { - this.bufferPool = bufferPool; - this.maxHttpHeaderSize = handshakeMaxSize; - } - - ~ServerHandshake() - { - sha1.Dispose(); - } - - public bool TryHandshake(Connection conn) - { - Stream stream = conn.stream; - - using (ArrayBuffer getHeader = bufferPool.Take(GetSize)) - { - if (!ReadHelper.TryRead(stream, getHeader.array, 0, GetSize)) - return false; - getHeader.count = GetSize; - - - if (!IsGet(getHeader.array)) - { - Log.Warn($"First bytes from client was not 'GET' for handshake, instead was {Log.BufferToString(getHeader.array, 0, GetSize)}"); - return false; - } - } - - - string msg = ReadToEndForHandshake(stream); - - if (string.IsNullOrEmpty(msg)) - return false; - - try - { - AcceptHandshake(stream, msg); - return true; - } - catch (ArgumentException e) - { - Log.InfoException(e); - return false; - } - } - - string ReadToEndForHandshake(Stream stream) - { - using (ArrayBuffer readBuffer = bufferPool.Take(maxHttpHeaderSize)) - { - int? readCountOrFail = ReadHelper.SafeReadTillMatch(stream, readBuffer.array, 0, maxHttpHeaderSize, Constants.endOfHandshake); - if (!readCountOrFail.HasValue) - return null; - - int readCount = readCountOrFail.Value; - - string msg = Encoding.ASCII.GetString(readBuffer.array, 0, readCount); - Log.Verbose(msg); - - return msg; - } - } - - static bool IsGet(byte[] getHeader) - { - // just check bytes here instead of using Encoding.ASCII - return getHeader[0] == 71 && // G - getHeader[1] == 69 && // E - getHeader[2] == 84; // T - } - - void AcceptHandshake(Stream stream, string msg) - { - using ( - ArrayBuffer keyBuffer = bufferPool.Take(KeyLength), - responseBuffer = bufferPool.Take(ResponseLength)) - { - GetKey(msg, keyBuffer.array); - AppendGuid(keyBuffer.array); - byte[] keyHash = CreateHash(keyBuffer.array); - CreateResponse(keyHash, responseBuffer.array); - - stream.Write(responseBuffer.array, 0, ResponseLength); - } - } - - - static void GetKey(string msg, byte[] keyBuffer) - { - int start = msg.IndexOf(KeyHeaderString) + KeyHeaderString.Length; - - Log.Verbose($"Handshake Key: {msg.Substring(start, KeyLength)}"); - Encoding.ASCII.GetBytes(msg, start, KeyLength, keyBuffer, 0); - } - - static void AppendGuid(byte[] keyBuffer) - { - Buffer.BlockCopy(Constants.HandshakeGUIDBytes, 0, keyBuffer, KeyLength, Constants.HandshakeGUID.Length); - } - - byte[] CreateHash(byte[] keyBuffer) - { - Log.Verbose($"Handshake Hashing {Encoding.ASCII.GetString(keyBuffer, 0, MergedKeyLength)}"); - - return sha1.ComputeHash(keyBuffer, 0, MergedKeyLength); - } - - static void CreateResponse(byte[] keyHash, byte[] responseBuffer) - { - string keyHashString = Convert.ToBase64String(keyHash); - - // compiler should merge these strings into 1 string before format - string message = string.Format( - "HTTP/1.1 101 Switching Protocols\r\n" + - "Connection: Upgrade\r\n" + - "Upgrade: websocket\r\n" + - "Sec-WebSocket-Accept: {0}\r\n\r\n", - keyHashString); - - Log.Verbose($"Handshake Response length {message.Length}, IsExpected {message.Length == ResponseLength}"); - Encoding.ASCII.GetBytes(message, 0, ResponseLength, responseBuffer, 0); - } - } -} +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Mirror.SimpleWeb +{ + /// + /// Handles Handshakes from new clients on the server + /// The server handshake has buffers to reduce allocations when clients connect + /// + internal class ServerHandshake + { + const int GetSize = 3; + const int ResponseLength = 129; + const int KeyLength = 24; + const int MergedKeyLength = 60; + const string KeyHeaderString = "Sec-WebSocket-Key: "; + // this isn't an official max, just a reasonable size for a websocket handshake + readonly int maxHttpHeaderSize = 3000; + + readonly SHA1 sha1 = SHA1.Create(); + readonly BufferPool bufferPool; + + public ServerHandshake(BufferPool bufferPool, int handshakeMaxSize) + { + this.bufferPool = bufferPool; + this.maxHttpHeaderSize = handshakeMaxSize; + } + + ~ServerHandshake() + { + sha1.Dispose(); + } + + public bool TryHandshake(Connection conn) + { + Stream stream = conn.stream; + + using (ArrayBuffer getHeader = bufferPool.Take(GetSize)) + { + if (!ReadHelper.TryRead(stream, getHeader.array, 0, GetSize)) + return false; + getHeader.count = GetSize; + + + if (!IsGet(getHeader.array)) + { + Log.Warn($"First bytes from client was not 'GET' for handshake, instead was {Log.BufferToString(getHeader.array, 0, GetSize)}"); + return false; + } + } + + + string msg = ReadToEndForHandshake(stream); + + if (string.IsNullOrEmpty(msg)) + return false; + + try + { + AcceptHandshake(stream, msg); + return true; + } + catch (ArgumentException e) + { + Log.InfoException(e); + return false; + } + } + + string ReadToEndForHandshake(Stream stream) + { + using (ArrayBuffer readBuffer = bufferPool.Take(maxHttpHeaderSize)) + { + int? readCountOrFail = ReadHelper.SafeReadTillMatch(stream, readBuffer.array, 0, maxHttpHeaderSize, Constants.endOfHandshake); + if (!readCountOrFail.HasValue) + return null; + + int readCount = readCountOrFail.Value; + + string msg = Encoding.ASCII.GetString(readBuffer.array, 0, readCount); + Log.Verbose(msg); + + return msg; + } + } + + static bool IsGet(byte[] getHeader) + { + // just check bytes here instead of using Encoding.ASCII + return getHeader[0] == 71 && // G + getHeader[1] == 69 && // E + getHeader[2] == 84; // T + } + + void AcceptHandshake(Stream stream, string msg) + { + using ( + ArrayBuffer keyBuffer = bufferPool.Take(KeyLength), + responseBuffer = bufferPool.Take(ResponseLength)) + { + GetKey(msg, keyBuffer.array); + AppendGuid(keyBuffer.array); + byte[] keyHash = CreateHash(keyBuffer.array); + CreateResponse(keyHash, responseBuffer.array); + + stream.Write(responseBuffer.array, 0, ResponseLength); + } + } + + + static void GetKey(string msg, byte[] keyBuffer) + { + int start = msg.IndexOf(KeyHeaderString) + KeyHeaderString.Length; + + Log.Verbose($"Handshake Key: {msg.Substring(start, KeyLength)}"); + Encoding.ASCII.GetBytes(msg, start, KeyLength, keyBuffer, 0); + } + + static void AppendGuid(byte[] keyBuffer) + { + Buffer.BlockCopy(Constants.HandshakeGUIDBytes, 0, keyBuffer, KeyLength, Constants.HandshakeGUID.Length); + } + + byte[] CreateHash(byte[] keyBuffer) + { + Log.Verbose($"Handshake Hashing {Encoding.ASCII.GetString(keyBuffer, 0, MergedKeyLength)}"); + + return sha1.ComputeHash(keyBuffer, 0, MergedKeyLength); + } + + static void CreateResponse(byte[] keyHash, byte[] responseBuffer) + { + string keyHashString = Convert.ToBase64String(keyHash); + + // compiler should merge these strings into 1 string before format + string message = string.Format( + "HTTP/1.1 101 Switching Protocols\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: websocket\r\n" + + "Sec-WebSocket-Accept: {0}\r\n\r\n", + keyHashString); + + Log.Verbose($"Handshake Response length {message.Length}, IsExpected {message.Length == ResponseLength}"); + Encoding.ASCII.GetBytes(message, 0, ResponseLength, responseBuffer, 0); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs.meta index 6fa74da..49db835 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 6268509ac4fb48141b9944c03295da11 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 6268509ac4fb48141b9944c03295da11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs index de6c022..5a03a95 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs @@ -1,74 +1,74 @@ -using System; -using System.IO; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; - -namespace Mirror.SimpleWeb -{ - public struct SslConfig - { - public readonly bool enabled; - public readonly string certPath; - public readonly string certPassword; - public readonly SslProtocols sslProtocols; - - public SslConfig(bool enabled, string certPath, string certPassword, SslProtocols sslProtocols) - { - this.enabled = enabled; - this.certPath = certPath; - this.certPassword = certPassword; - this.sslProtocols = sslProtocols; - } - } - internal class ServerSslHelper - { - readonly SslConfig config; - readonly X509Certificate2 certificate; - - public ServerSslHelper(SslConfig sslConfig) - { - config = sslConfig; - if (config.enabled) - certificate = new X509Certificate2(config.certPath, config.certPassword); - } - - internal bool TryCreateStream(Connection conn) - { - NetworkStream stream = conn.client.GetStream(); - if (config.enabled) - { - try - { - conn.stream = CreateStream(stream); - return true; - } - catch (Exception e) - { - Log.Error($"Create SSLStream Failed: {e}", false); - return false; - } - } - else - { - conn.stream = stream; - return true; - } - } - - Stream CreateStream(NetworkStream stream) - { - SslStream sslStream = new SslStream(stream, true, acceptClient); - sslStream.AuthenticateAsServer(certificate, false, config.sslProtocols, false); - - return sslStream; - } - - bool acceptClient(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - { - // always accept client - return true; - } - } -} +using System; +using System.IO; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace Mirror.SimpleWeb +{ + public struct SslConfig + { + public readonly bool enabled; + public readonly string certPath; + public readonly string certPassword; + public readonly SslProtocols sslProtocols; + + public SslConfig(bool enabled, string certPath, string certPassword, SslProtocols sslProtocols) + { + this.enabled = enabled; + this.certPath = certPath; + this.certPassword = certPassword; + this.sslProtocols = sslProtocols; + } + } + internal class ServerSslHelper + { + readonly SslConfig config; + readonly X509Certificate2 certificate; + + public ServerSslHelper(SslConfig sslConfig) + { + config = sslConfig; + if (config.enabled) + certificate = new X509Certificate2(config.certPath, config.certPassword); + } + + internal bool TryCreateStream(Connection conn) + { + NetworkStream stream = conn.client.GetStream(); + if (config.enabled) + { + try + { + conn.stream = CreateStream(stream); + return true; + } + catch (Exception e) + { + Log.Error($"Create SSLStream Failed: {e}", false); + return false; + } + } + else + { + conn.stream = stream; + return true; + } + } + + Stream CreateStream(NetworkStream stream) + { + SslStream sslStream = new SslStream(stream, true, acceptClient); + sslStream.AuthenticateAsServer(certificate, false, config.sslProtocols, false); + + return sslStream; + } + + bool acceptClient(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + // always accept client + return true; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs.meta index e0d133c..86d33e0 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 11061fee528ebdd43817a275b1e4a317 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 11061fee528ebdd43817a275b1e4a317 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs index f1ed565..8450fad 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs @@ -1,105 +1,105 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace Mirror.SimpleWeb -{ - public class SimpleWebServer - { - readonly int maxMessagesPerTick; - - readonly WebSocketServer server; - readonly BufferPool bufferPool; - - public SimpleWebServer(int maxMessagesPerTick, TcpConfig tcpConfig, int maxMessageSize, int handshakeMaxSize, SslConfig sslConfig) - { - this.maxMessagesPerTick = maxMessagesPerTick; - // use max because bufferpool is used for both messages and handshake - int max = Math.Max(maxMessageSize, handshakeMaxSize); - bufferPool = new BufferPool(5, 20, max); - - server = new WebSocketServer(tcpConfig, maxMessageSize, handshakeMaxSize, sslConfig, bufferPool); - } - - public bool Active { get; private set; } - - public event Action onConnect; - public event Action onDisconnect; - public event Action> onData; - public event Action onError; - - public void Start(ushort port) - { - server.Listen(port); - Active = true; - } - - public void Stop() - { - server.Stop(); - Active = false; - } - - public void SendAll(List connectionIds, ArraySegment source) - { - ArrayBuffer buffer = bufferPool.Take(source.Count); - buffer.CopyFrom(source); - buffer.SetReleasesRequired(connectionIds.Count); - - // make copy of array before for each, data sent to each client is the same - foreach (int id in connectionIds) - { - server.Send(id, buffer); - } - } - public void SendOne(int connectionId, ArraySegment source) - { - ArrayBuffer buffer = bufferPool.Take(source.Count); - buffer.CopyFrom(source); - - server.Send(connectionId, buffer); - } - - public bool KickClient(int connectionId) - { - return server.CloseConnection(connectionId); - } - - public string GetClientAddress(int connectionId) - { - return server.GetClientAddress(connectionId); - } - - public void ProcessMessageQueue(MonoBehaviour behaviour) - { - int processedCount = 0; - // check enabled every time in case behaviour was disabled after data - while ( - behaviour.enabled && - processedCount < maxMessagesPerTick && - // Dequeue last - server.receiveQueue.TryDequeue(out Message next) - ) - { - processedCount++; - - switch (next.type) - { - case EventType.Connected: - onConnect?.Invoke(next.connId); - break; - case EventType.Data: - onData?.Invoke(next.connId, next.data.ToSegment()); - next.data.Release(); - break; - case EventType.Disconnected: - onDisconnect?.Invoke(next.connId); - break; - case EventType.Error: - onError?.Invoke(next.connId, next.exception); - break; - } - } - } - } -} +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Mirror.SimpleWeb +{ + public class SimpleWebServer + { + readonly int maxMessagesPerTick; + + readonly WebSocketServer server; + readonly BufferPool bufferPool; + + public SimpleWebServer(int maxMessagesPerTick, TcpConfig tcpConfig, int maxMessageSize, int handshakeMaxSize, SslConfig sslConfig) + { + this.maxMessagesPerTick = maxMessagesPerTick; + // use max because bufferpool is used for both messages and handshake + int max = Math.Max(maxMessageSize, handshakeMaxSize); + bufferPool = new BufferPool(5, 20, max); + + server = new WebSocketServer(tcpConfig, maxMessageSize, handshakeMaxSize, sslConfig, bufferPool); + } + + public bool Active { get; private set; } + + public event Action onConnect; + public event Action onDisconnect; + public event Action> onData; + public event Action onError; + + public void Start(ushort port) + { + server.Listen(port); + Active = true; + } + + public void Stop() + { + server.Stop(); + Active = false; + } + + public void SendAll(List connectionIds, ArraySegment source) + { + ArrayBuffer buffer = bufferPool.Take(source.Count); + buffer.CopyFrom(source); + buffer.SetReleasesRequired(connectionIds.Count); + + // make copy of array before for each, data sent to each client is the same + foreach (int id in connectionIds) + { + server.Send(id, buffer); + } + } + public void SendOne(int connectionId, ArraySegment source) + { + ArrayBuffer buffer = bufferPool.Take(source.Count); + buffer.CopyFrom(source); + + server.Send(connectionId, buffer); + } + + public bool KickClient(int connectionId) + { + return server.CloseConnection(connectionId); + } + + public string GetClientAddress(int connectionId) + { + return server.GetClientAddress(connectionId); + } + + public void ProcessMessageQueue(MonoBehaviour behaviour) + { + int processedCount = 0; + // check enabled every time in case behaviour was disabled after data + while ( + behaviour.enabled && + processedCount < maxMessagesPerTick && + // Dequeue last + server.receiveQueue.TryDequeue(out Message next) + ) + { + processedCount++; + + switch (next.type) + { + case EventType.Connected: + onConnect?.Invoke(next.connId); + break; + case EventType.Data: + onData?.Invoke(next.connId, next.data.ToSegment()); + next.data.Release(); + break; + case EventType.Disconnected: + onDisconnect?.Invoke(next.connId); + break; + case EventType.Error: + onError?.Invoke(next.connId, next.exception); + break; + } + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs.meta index c8c6f5a..435d6eb 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: bd51d7896f55a5e48b41a4b526562b0e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: bd51d7896f55a5e48b41a4b526562b0e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs index c924be4..9f06edf 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs @@ -1,230 +1,230 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Net.Sockets; -using System.Threading; - -namespace Mirror.SimpleWeb -{ - public class WebSocketServer - { - public readonly ConcurrentQueue receiveQueue = new ConcurrentQueue(); - - readonly TcpConfig tcpConfig; - readonly int maxMessageSize; - - TcpListener listener; - Thread acceptThread; - bool serverStopped; - readonly ServerHandshake handShake; - readonly ServerSslHelper sslHelper; - readonly BufferPool bufferPool; - readonly ConcurrentDictionary connections = new ConcurrentDictionary(); - - - int _idCounter = 0; - - public WebSocketServer(TcpConfig tcpConfig, int maxMessageSize, int handshakeMaxSize, SslConfig sslConfig, BufferPool bufferPool) - { - this.tcpConfig = tcpConfig; - this.maxMessageSize = maxMessageSize; - sslHelper = new ServerSslHelper(sslConfig); - this.bufferPool = bufferPool; - handShake = new ServerHandshake(this.bufferPool, handshakeMaxSize); - } - - public void Listen(int port) - { - listener = TcpListener.Create(port); - listener.Start(); - - Log.Info($"Server has started on port {port}"); - - acceptThread = new Thread(acceptLoop); - acceptThread.IsBackground = true; - acceptThread.Start(); - } - - public void Stop() - { - serverStopped = true; - - // Interrupt then stop so that Exception is handled correctly - acceptThread?.Interrupt(); - listener?.Stop(); - acceptThread = null; - - - Log.Info("Server stopped, Closing all connections..."); - // make copy so that foreach doesn't break if values are removed - Connection[] connectionsCopy = connections.Values.ToArray(); - foreach (Connection conn in connectionsCopy) - { - conn.Dispose(); - } - - connections.Clear(); - } - - void acceptLoop() - { - try - { - try - { - while (true) - { - TcpClient client = listener.AcceptTcpClient(); - tcpConfig.ApplyTo(client); - - - // TODO keep track of connections before they are in connections dictionary - // this might not be a problem as HandshakeAndReceiveLoop checks for stop - // and returns/disposes before sending message to queue - Connection conn = new Connection(client, AfterConnectionDisposed); - Log.Info($"A client connected {conn}"); - - // handshake needs its own thread as it needs to wait for message from client - Thread receiveThread = new Thread(() => HandshakeAndReceiveLoop(conn)); - - conn.receiveThread = receiveThread; - - receiveThread.IsBackground = true; - receiveThread.Start(); - } - } - catch (SocketException) - { - // check for Interrupted/Abort - Utils.CheckForInterupt(); - throw; - } - } - catch (ThreadInterruptedException e) { Log.InfoException(e); } - catch (ThreadAbortException e) { Log.InfoException(e); } - catch (Exception e) { Log.Exception(e); } - } - - void HandshakeAndReceiveLoop(Connection conn) - { - try - { - bool success = sslHelper.TryCreateStream(conn); - if (!success) - { - Log.Error($"Failed to create SSL Stream {conn}"); - conn.Dispose(); - return; - } - - success = handShake.TryHandshake(conn); - - if (success) - { - Log.Info($"Sent Handshake {conn}"); - } - else - { - Log.Error($"Handshake Failed {conn}"); - conn.Dispose(); - return; - } - - // check if Stop has been called since accepting this client - if (serverStopped) - { - Log.Info("Server stops after successful handshake"); - return; - } - - conn.connId = Interlocked.Increment(ref _idCounter); - connections.TryAdd(conn.connId, conn); - - receiveQueue.Enqueue(new Message(conn.connId, EventType.Connected)); - - Thread sendThread = new Thread(() => - { - SendLoop.Config sendConfig = new SendLoop.Config( - conn, - bufferSize: Constants.HeaderSize + maxMessageSize, - setMask: false); - - SendLoop.Loop(sendConfig); - }); - - conn.sendThread = sendThread; - sendThread.IsBackground = true; - sendThread.Name = $"SendLoop {conn.connId}"; - sendThread.Start(); - - ReceiveLoop.Config receiveConfig = new ReceiveLoop.Config( - conn, - maxMessageSize, - expectMask: true, - receiveQueue, - bufferPool); - - ReceiveLoop.Loop(receiveConfig); - } - catch (ThreadInterruptedException e) { Log.InfoException(e); } - catch (ThreadAbortException e) { Log.InfoException(e); } - catch (Exception e) { Log.Exception(e); } - finally - { - // close here in case connect fails - conn.Dispose(); - } - } - - void AfterConnectionDisposed(Connection conn) - { - if (conn.connId != Connection.IdNotSet) - { - receiveQueue.Enqueue(new Message(conn.connId, EventType.Disconnected)); - connections.TryRemove(conn.connId, out Connection _); - } - } - - public void Send(int id, ArrayBuffer buffer) - { - if (connections.TryGetValue(id, out Connection conn)) - { - conn.sendQueue.Enqueue(buffer); - conn.sendPending.Set(); - } - else - { - Log.Warn($"Cant send message to {id} because connection was not found in dictionary. Maybe it disconnected."); - } - } - - public bool CloseConnection(int id) - { - if (connections.TryGetValue(id, out Connection conn)) - { - Log.Info($"Kicking connection {id}"); - conn.Dispose(); - return true; - } - else - { - Log.Warn($"Failed to kick {id} because id not found"); - - return false; - } - } - - public string GetClientAddress(int id) - { - if (connections.TryGetValue(id, out Connection conn)) - { - return conn.client.Client.RemoteEndPoint.ToString(); - } - else - { - Log.Error($"Cant close connection to {id} because connection was not found in dictionary"); - return null; - } - } - } -} +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net.Sockets; +using System.Threading; + +namespace Mirror.SimpleWeb +{ + public class WebSocketServer + { + public readonly ConcurrentQueue receiveQueue = new ConcurrentQueue(); + + readonly TcpConfig tcpConfig; + readonly int maxMessageSize; + + TcpListener listener; + Thread acceptThread; + bool serverStopped; + readonly ServerHandshake handShake; + readonly ServerSslHelper sslHelper; + readonly BufferPool bufferPool; + readonly ConcurrentDictionary connections = new ConcurrentDictionary(); + + + int _idCounter = 0; + + public WebSocketServer(TcpConfig tcpConfig, int maxMessageSize, int handshakeMaxSize, SslConfig sslConfig, BufferPool bufferPool) + { + this.tcpConfig = tcpConfig; + this.maxMessageSize = maxMessageSize; + sslHelper = new ServerSslHelper(sslConfig); + this.bufferPool = bufferPool; + handShake = new ServerHandshake(this.bufferPool, handshakeMaxSize); + } + + public void Listen(int port) + { + listener = TcpListener.Create(port); + listener.Start(); + + Log.Info($"Server has started on port {port}"); + + acceptThread = new Thread(acceptLoop); + acceptThread.IsBackground = true; + acceptThread.Start(); + } + + public void Stop() + { + serverStopped = true; + + // Interrupt then stop so that Exception is handled correctly + acceptThread?.Interrupt(); + listener?.Stop(); + acceptThread = null; + + + Log.Info("Server stopped, Closing all connections..."); + // make copy so that foreach doesn't break if values are removed + Connection[] connectionsCopy = connections.Values.ToArray(); + foreach (Connection conn in connectionsCopy) + { + conn.Dispose(); + } + + connections.Clear(); + } + + void acceptLoop() + { + try + { + try + { + while (true) + { + TcpClient client = listener.AcceptTcpClient(); + tcpConfig.ApplyTo(client); + + + // TODO keep track of connections before they are in connections dictionary + // this might not be a problem as HandshakeAndReceiveLoop checks for stop + // and returns/disposes before sending message to queue + Connection conn = new Connection(client, AfterConnectionDisposed); + Log.Info($"A client connected {conn}"); + + // handshake needs its own thread as it needs to wait for message from client + Thread receiveThread = new Thread(() => HandshakeAndReceiveLoop(conn)); + + conn.receiveThread = receiveThread; + + receiveThread.IsBackground = true; + receiveThread.Start(); + } + } + catch (SocketException) + { + // check for Interrupted/Abort + Utils.CheckForInterupt(); + throw; + } + } + catch (ThreadInterruptedException e) { Log.InfoException(e); } + catch (ThreadAbortException e) { Log.InfoException(e); } + catch (Exception e) { Log.Exception(e); } + } + + void HandshakeAndReceiveLoop(Connection conn) + { + try + { + bool success = sslHelper.TryCreateStream(conn); + if (!success) + { + Log.Error($"Failed to create SSL Stream {conn}"); + conn.Dispose(); + return; + } + + success = handShake.TryHandshake(conn); + + if (success) + { + Log.Info($"Sent Handshake {conn}"); + } + else + { + Log.Error($"Handshake Failed {conn}"); + conn.Dispose(); + return; + } + + // check if Stop has been called since accepting this client + if (serverStopped) + { + Log.Info("Server stops after successful handshake"); + return; + } + + conn.connId = Interlocked.Increment(ref _idCounter); + connections.TryAdd(conn.connId, conn); + + receiveQueue.Enqueue(new Message(conn.connId, EventType.Connected)); + + Thread sendThread = new Thread(() => + { + SendLoop.Config sendConfig = new SendLoop.Config( + conn, + bufferSize: Constants.HeaderSize + maxMessageSize, + setMask: false); + + SendLoop.Loop(sendConfig); + }); + + conn.sendThread = sendThread; + sendThread.IsBackground = true; + sendThread.Name = $"SendLoop {conn.connId}"; + sendThread.Start(); + + ReceiveLoop.Config receiveConfig = new ReceiveLoop.Config( + conn, + maxMessageSize, + expectMask: true, + receiveQueue, + bufferPool); + + ReceiveLoop.Loop(receiveConfig); + } + catch (ThreadInterruptedException e) { Log.InfoException(e); } + catch (ThreadAbortException e) { Log.InfoException(e); } + catch (Exception e) { Log.Exception(e); } + finally + { + // close here in case connect fails + conn.Dispose(); + } + } + + void AfterConnectionDisposed(Connection conn) + { + if (conn.connId != Connection.IdNotSet) + { + receiveQueue.Enqueue(new Message(conn.connId, EventType.Disconnected)); + connections.TryRemove(conn.connId, out Connection _); + } + } + + public void Send(int id, ArrayBuffer buffer) + { + if (connections.TryGetValue(id, out Connection conn)) + { + conn.sendQueue.Enqueue(buffer); + conn.sendPending.Set(); + } + else + { + Log.Warn($"Cant send message to {id} because connection was not found in dictionary. Maybe it disconnected."); + } + } + + public bool CloseConnection(int id) + { + if (connections.TryGetValue(id, out Connection conn)) + { + Log.Info($"Kicking connection {id}"); + conn.Dispose(); + return true; + } + else + { + Log.Warn($"Failed to kick {id} because id not found"); + + return false; + } + } + + public string GetClientAddress(int id) + { + if (connections.TryGetValue(id, out Connection conn)) + { + return conn.client.Client.RemoteEndPoint.ToString(); + } + else + { + Log.Error($"Cant close connection to {id} because connection was not found in dictionary"); + return null; + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs.meta index 0a76a9f..03f58bf 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 5c434db044777d2439bae5a57d4e8ee7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 5c434db044777d2439bae5a57d4e8ee7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef index 3687c5d..922b31a 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef @@ -1,14 +1,14 @@ -{ - "name": "SimpleWebTransport", - "references": [ - "Mirror" - ], - "optionalUnityReferences": [], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [] +{ + "name": "SimpleWebTransport", + "references": [ + "Mirror" + ], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef.meta index 99755b6..97ded70 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 3b5390adca4e2bb4791cb930316d6f3e -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 3b5390adca4e2bb4791cb930316d6f3e +AssemblyDefinitionImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs index b884121..9b91214 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs @@ -1,293 +1,293 @@ -using System; -using System.Net; -using System.Security.Authentication; -using UnityEngine; -using UnityEngine.Serialization; - -namespace Mirror.SimpleWeb -{ - [DisallowMultipleComponent] - public class SimpleWebTransport : Transport - { - public const string NormalScheme = "ws"; - public const string SecureScheme = "wss"; - - [Tooltip("Port to use for server and client")] - public ushort port = 7778; - - - [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")] - public int maxMessageSize = 16 * 1024; - - [Tooltip("Max size for http header send as handshake for websockets")] - public int handshakeMaxSize = 3000; - - [Tooltip("disables nagle algorithm. lowers CPU% and latency but increases bandwidth")] - public bool noDelay = true; - - [Tooltip("Send would stall forever if the network is cut off during a send, so we need a timeout (in milliseconds)")] - public int sendTimeout = 5000; - - [Tooltip("How long without a message before disconnecting (in milliseconds)")] - public int receiveTimeout = 20000; - - [Tooltip("Caps the number of messages the server will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")] - public int serverMaxMessagesPerTick = 10000; - - [Tooltip("Caps the number of messages the client will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")] - public int clientMaxMessagesPerTick = 1000; - - [Header("Server settings")] - - [Tooltip("Groups messages in queue before calling Stream.Send")] - public bool batchSend = true; - - [Tooltip("Waits for 1ms before grouping and sending messages. " + - "This gives time for mirror to finish adding message to queue so that less groups need to be made. " + - "If WaitBeforeSend is true then BatchSend Will also be set to true")] - public bool waitBeforeSend = false; - - - [Header("Ssl Settings")] - [Tooltip("Sets connect scheme to wss. Useful when client needs to connect using wss when TLS is outside of transport, NOTE: if sslEnabled is true clientUseWss is also true")] - public bool clientUseWss; - - public bool sslEnabled; - [Tooltip("Path to json file that contains path to cert and its password\n\nUse Json file so that cert password is not included in client builds\n\nSee cert.example.Json")] - public string sslCertJson = "./cert.json"; - public SslProtocols sslProtocols = SslProtocols.Tls12; - - [Header("Debug")] - [Tooltip("Log functions uses ConditionalAttribute which will effect which log methods are allowed. DEBUG allows warn/error, SIMPLEWEB_LOG_ENABLED allows all")] - [FormerlySerializedAs("logLevels")] - [SerializeField] Log.Levels _logLevels = Log.Levels.none; - - /// - /// Gets _logLevels field - /// Sets _logLevels and Log.level fields - /// - public Log.Levels LogLevels - { - get => _logLevels; - set - { - _logLevels = value; - Log.level = _logLevels; - } - } - - void OnValidate() - { - if (maxMessageSize > ushort.MaxValue) - { - Debug.LogWarning($"max supported value for maxMessageSize is {ushort.MaxValue}"); - maxMessageSize = ushort.MaxValue; - } - - Log.level = _logLevels; - } - - SimpleWebClient client; - SimpleWebServer server; - - TcpConfig TcpConfig => new TcpConfig(noDelay, sendTimeout, receiveTimeout); - - public override bool Available() - { - return true; - } - public override int GetMaxPacketSize(int channelId = 0) - { - return maxMessageSize; - } - - void Awake() - { - Log.level = _logLevels; - } - public override void Shutdown() - { - client?.Disconnect(); - client = null; - server?.Stop(); - server = null; - } - - #region Client - string GetClientScheme() => (sslEnabled || clientUseWss) ? SecureScheme : NormalScheme; - string GetServerScheme() => sslEnabled ? SecureScheme : NormalScheme; - public override bool ClientConnected() - { - // not null and not NotConnected (we want to return true if connecting or disconnecting) - return client != null && client.ConnectionState != ClientState.NotConnected; - } - - public override void ClientConnect(string hostname) - { - // connecting or connected - if (ClientConnected()) - { - Debug.LogError("Already Connected"); - return; - } - - UriBuilder builder = new UriBuilder - { - Scheme = GetClientScheme(), - Host = hostname, - Port = port - }; - - - client = SimpleWebClient.Create(maxMessageSize, clientMaxMessagesPerTick, TcpConfig); - if (client == null) { return; } - - client.onConnect += OnClientConnected.Invoke; - client.onDisconnect += () => - { - OnClientDisconnected.Invoke(); - // clear client here after disconnect event has been sent - // there should be no more messages after disconnect - client = null; - }; - client.onData += (ArraySegment data) => OnClientDataReceived.Invoke(data, Channels.Reliable); - client.onError += (Exception e) => - { - OnClientError.Invoke(e); - ClientDisconnect(); - }; - - client.Connect(builder.Uri); - } - - public override void ClientDisconnect() - { - // don't set client null here of messages wont be processed - client?.Disconnect(); - } - - public override void ClientSend(ArraySegment segment, int channelId) - { - if (!ClientConnected()) - { - Debug.LogError("Not Connected"); - return; - } - - if (segment.Count > maxMessageSize) - { - Log.Error("Message greater than max size"); - return; - } - - if (segment.Count == 0) - { - Log.Error("Message count was zero"); - return; - } - - client.Send(segment); - } - - // messages should always be processed in early update - public override void ClientEarlyUpdate() - { - client?.ProcessMessageQueue(this); - } - #endregion - - #region Server - public override bool ServerActive() - { - return server != null && server.Active; - } - - public override void ServerStart() - { - if (ServerActive()) - { - Debug.LogError("SimpleWebServer Already Started"); - } - - SslConfig config = SslConfigLoader.Load(this); - server = new SimpleWebServer(serverMaxMessagesPerTick, TcpConfig, maxMessageSize, handshakeMaxSize, config); - - server.onConnect += OnServerConnected.Invoke; - server.onDisconnect += OnServerDisconnected.Invoke; - server.onData += (int connId, ArraySegment data) => OnServerDataReceived.Invoke(connId, data, Channels.Reliable); - server.onError += OnServerError.Invoke; - - SendLoopConfig.batchSend = batchSend || waitBeforeSend; - SendLoopConfig.sleepBeforeSend = waitBeforeSend; - - server.Start(port); - } - - public override void ServerStop() - { - if (!ServerActive()) - { - Debug.LogError("SimpleWebServer Not Active"); - } - - server.Stop(); - server = null; - } - - public override void ServerDisconnect(int connectionId) - { - if (!ServerActive()) - { - Debug.LogError("SimpleWebServer Not Active"); - } - - server.KickClient(connectionId); - } - - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) - { - if (!ServerActive()) - { - Debug.LogError("SimpleWebServer Not Active"); - return; - } - - if (segment.Count > maxMessageSize) - { - Log.Error("Message greater than max size"); - return; - } - - if (segment.Count == 0) - { - Log.Error("Message count was zero"); - return; - } - - server.SendOne(connectionId, segment); - } - - public override string ServerGetClientAddress(int connectionId) - { - return server.GetClientAddress(connectionId); - } - - public override Uri ServerUri() - { - UriBuilder builder = new UriBuilder - { - Scheme = GetServerScheme(), - Host = Dns.GetHostName(), - Port = port - }; - return builder.Uri; - } - - // messages should always be processed in early update - public override void ServerEarlyUpdate() - { - server?.ProcessMessageQueue(this); - } - #endregion - } -} +using System; +using System.Net; +using System.Security.Authentication; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Mirror.SimpleWeb +{ + [DisallowMultipleComponent] + public class SimpleWebTransport : Transport + { + public const string NormalScheme = "ws"; + public const string SecureScheme = "wss"; + + [Tooltip("Port to use for server and client")] + public ushort port = 7778; + + + [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")] + public int maxMessageSize = 16 * 1024; + + [Tooltip("Max size for http header send as handshake for websockets")] + public int handshakeMaxSize = 3000; + + [Tooltip("disables nagle algorithm. lowers CPU% and latency but increases bandwidth")] + public bool noDelay = true; + + [Tooltip("Send would stall forever if the network is cut off during a send, so we need a timeout (in milliseconds)")] + public int sendTimeout = 5000; + + [Tooltip("How long without a message before disconnecting (in milliseconds)")] + public int receiveTimeout = 20000; + + [Tooltip("Caps the number of messages the server will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")] + public int serverMaxMessagesPerTick = 10000; + + [Tooltip("Caps the number of messages the client will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")] + public int clientMaxMessagesPerTick = 1000; + + [Header("Server settings")] + + [Tooltip("Groups messages in queue before calling Stream.Send")] + public bool batchSend = true; + + [Tooltip("Waits for 1ms before grouping and sending messages. " + + "This gives time for mirror to finish adding message to queue so that less groups need to be made. " + + "If WaitBeforeSend is true then BatchSend Will also be set to true")] + public bool waitBeforeSend = false; + + + [Header("Ssl Settings")] + [Tooltip("Sets connect scheme to wss. Useful when client needs to connect using wss when TLS is outside of transport, NOTE: if sslEnabled is true clientUseWss is also true")] + public bool clientUseWss; + + public bool sslEnabled; + [Tooltip("Path to json file that contains path to cert and its password\n\nUse Json file so that cert password is not included in client builds\n\nSee cert.example.Json")] + public string sslCertJson = "./cert.json"; + public SslProtocols sslProtocols = SslProtocols.Tls12; + + [Header("Debug")] + [Tooltip("Log functions uses ConditionalAttribute which will effect which log methods are allowed. DEBUG allows warn/error, SIMPLEWEB_LOG_ENABLED allows all")] + [FormerlySerializedAs("logLevels")] + [SerializeField] Log.Levels _logLevels = Log.Levels.none; + + /// + /// Gets _logLevels field + /// Sets _logLevels and Log.level fields + /// + public Log.Levels LogLevels + { + get => _logLevels; + set + { + _logLevels = value; + Log.level = _logLevels; + } + } + + void OnValidate() + { + if (maxMessageSize > ushort.MaxValue) + { + Debug.LogWarning($"max supported value for maxMessageSize is {ushort.MaxValue}"); + maxMessageSize = ushort.MaxValue; + } + + Log.level = _logLevels; + } + + SimpleWebClient client; + SimpleWebServer server; + + TcpConfig TcpConfig => new TcpConfig(noDelay, sendTimeout, receiveTimeout); + + public override bool Available() + { + return true; + } + public override int GetMaxPacketSize(int channelId = 0) + { + return maxMessageSize; + } + + void Awake() + { + Log.level = _logLevels; + } + public override void Shutdown() + { + client?.Disconnect(); + client = null; + server?.Stop(); + server = null; + } + + #region Client + string GetClientScheme() => (sslEnabled || clientUseWss) ? SecureScheme : NormalScheme; + string GetServerScheme() => sslEnabled ? SecureScheme : NormalScheme; + public override bool ClientConnected() + { + // not null and not NotConnected (we want to return true if connecting or disconnecting) + return client != null && client.ConnectionState != ClientState.NotConnected; + } + + public override void ClientConnect(string hostname) + { + // connecting or connected + if (ClientConnected()) + { + Debug.LogError("Already Connected"); + return; + } + + UriBuilder builder = new UriBuilder + { + Scheme = GetClientScheme(), + Host = hostname, + Port = port + }; + + + client = SimpleWebClient.Create(maxMessageSize, clientMaxMessagesPerTick, TcpConfig); + if (client == null) { return; } + + client.onConnect += OnClientConnected.Invoke; + client.onDisconnect += () => + { + OnClientDisconnected.Invoke(); + // clear client here after disconnect event has been sent + // there should be no more messages after disconnect + client = null; + }; + client.onData += (ArraySegment data) => OnClientDataReceived.Invoke(data, Channels.Reliable); + client.onError += (Exception e) => + { + OnClientError.Invoke(e); + ClientDisconnect(); + }; + + client.Connect(builder.Uri); + } + + public override void ClientDisconnect() + { + // don't set client null here of messages wont be processed + client?.Disconnect(); + } + + public override void ClientSend(ArraySegment segment, int channelId) + { + if (!ClientConnected()) + { + Debug.LogError("Not Connected"); + return; + } + + if (segment.Count > maxMessageSize) + { + Log.Error("Message greater than max size"); + return; + } + + if (segment.Count == 0) + { + Log.Error("Message count was zero"); + return; + } + + client.Send(segment); + } + + // messages should always be processed in early update + public override void ClientEarlyUpdate() + { + client?.ProcessMessageQueue(this); + } + #endregion + + #region Server + public override bool ServerActive() + { + return server != null && server.Active; + } + + public override void ServerStart() + { + if (ServerActive()) + { + Debug.LogError("SimpleWebServer Already Started"); + } + + SslConfig config = SslConfigLoader.Load(this); + server = new SimpleWebServer(serverMaxMessagesPerTick, TcpConfig, maxMessageSize, handshakeMaxSize, config); + + server.onConnect += OnServerConnected.Invoke; + server.onDisconnect += OnServerDisconnected.Invoke; + server.onData += (int connId, ArraySegment data) => OnServerDataReceived.Invoke(connId, data, Channels.Reliable); + server.onError += OnServerError.Invoke; + + SendLoopConfig.batchSend = batchSend || waitBeforeSend; + SendLoopConfig.sleepBeforeSend = waitBeforeSend; + + server.Start(port); + } + + public override void ServerStop() + { + if (!ServerActive()) + { + Debug.LogError("SimpleWebServer Not Active"); + } + + server.Stop(); + server = null; + } + + public override void ServerDisconnect(int connectionId) + { + if (!ServerActive()) + { + Debug.LogError("SimpleWebServer Not Active"); + } + + server.KickClient(connectionId); + } + + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) + { + if (!ServerActive()) + { + Debug.LogError("SimpleWebServer Not Active"); + return; + } + + if (segment.Count > maxMessageSize) + { + Log.Error("Message greater than max size"); + return; + } + + if (segment.Count == 0) + { + Log.Error("Message count was zero"); + return; + } + + server.SendOne(connectionId, segment); + } + + public override string ServerGetClientAddress(int connectionId) + { + return server.GetClientAddress(connectionId); + } + + public override Uri ServerUri() + { + UriBuilder builder = new UriBuilder + { + Scheme = GetServerScheme(), + Host = Dns.GetHostName(), + Port = port + }; + return builder.Uri; + } + + // messages should always be processed in early update + public override void ServerEarlyUpdate() + { + server?.ProcessMessageQueue(this); + } + #endregion + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs.meta index 381a5c7..f60f869 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 0110f245bfcfc7d459681f7bd9ebc590 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 0110f245bfcfc7d459681f7bd9ebc590 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs index a6d661e..617b1f6 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs @@ -1,49 +1,49 @@ -using System.IO; -using UnityEngine; - -namespace Mirror.SimpleWeb -{ - internal class SslConfigLoader - { - internal struct Cert - { - public string path; - public string password; - } - internal static SslConfig Load(SimpleWebTransport transport) - { - // don't need to load anything if ssl is not enabled - if (!transport.sslEnabled) - return default; - - string certJsonPath = transport.sslCertJson; - - Cert cert = LoadCertJson(certJsonPath); - - return new SslConfig( - enabled: transport.sslEnabled, - sslProtocols: transport.sslProtocols, - certPath: cert.path, - certPassword: cert.password - ); - } - - internal static Cert LoadCertJson(string certJsonPath) - { - string json = File.ReadAllText(certJsonPath); - Cert cert = JsonUtility.FromJson(json); - - if (string.IsNullOrEmpty(cert.path)) - { - throw new InvalidDataException("Cert Json didn't not contain \"path\""); - } - if (string.IsNullOrEmpty(cert.password)) - { - // password can be empty - cert.password = string.Empty; - } - - return cert; - } - } -} +using System.IO; +using UnityEngine; + +namespace Mirror.SimpleWeb +{ + internal class SslConfigLoader + { + internal struct Cert + { + public string path; + public string password; + } + internal static SslConfig Load(SimpleWebTransport transport) + { + // don't need to load anything if ssl is not enabled + if (!transport.sslEnabled) + return default; + + string certJsonPath = transport.sslCertJson; + + Cert cert = LoadCertJson(certJsonPath); + + return new SslConfig( + enabled: transport.sslEnabled, + sslProtocols: transport.sslProtocols, + certPath: cert.path, + certPassword: cert.password + ); + } + + internal static Cert LoadCertJson(string certJsonPath) + { + string json = File.ReadAllText(certJsonPath); + Cert cert = JsonUtility.FromJson(json); + + if (string.IsNullOrEmpty(cert.path)) + { + throw new InvalidDataException("Cert Json didn't not contain \"path\""); + } + if (string.IsNullOrEmpty(cert.password)) + { + // password can be empty + cert.password = string.Empty; + } + + return cert; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs.meta b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs.meta index e653532..ad90bec 100644 --- a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs.meta +++ b/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: dfdb6b97a48a48b498e563e857342da1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: dfdb6b97a48a48b498e563e857342da1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy.meta b/Assets/Mirror/Runtime/Transport/Telepathy.meta index ede2d0e..3674a85 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 552b3d8382916438d81fe7f39e18db72 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 552b3d8382916438d81fe7f39e18db72 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy.meta index 345a638..1ed93d1 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: a1233408bc4b145fb8f6f5a8e95790e0 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a1233408bc4b145fb8f6f5a8e95790e0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs index 73e775c..dc59a14 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs @@ -1,362 +1,362 @@ -using System; -using System.Net.Sockets; -using System.Threading; - -namespace Telepathy -{ - // ClientState OBJECT that can be handed to the ReceiveThread safely. - // => allows us to create a NEW OBJECT every time we connect and start a - // receive thread. - // => perfectly protects us against data races. fixes all the flaky tests - // where .Connecting or .client would still be used by a dieing thread - // while attempting to use it for a new connection attempt etc. - // => creating a fresh client state each time is the best solution against - // data races here! - class ClientConnectionState : ConnectionState - { - public Thread receiveThread; - - // TcpClient.Connected doesn't check if socket != null, which - // results in NullReferenceExceptions if connection was closed. - // -> let's check it manually instead - public bool Connected => client != null && - client.Client != null && - client.Client.Connected; - - // TcpClient has no 'connecting' state to check. We need to keep track - // of it manually. - // -> checking 'thread.IsAlive && !Connected' is not enough because the - // thread is alive and connected is false for a short moment after - // disconnecting, so this would cause race conditions. - // -> we use a threadsafe bool wrapper so that ThreadFunction can remain - // static (it needs a common lock) - // => Connecting is true from first Connect() call in here, through the - // thread start, until TcpClient.Connect() returns. Simple and clear. - // => bools are atomic according to - // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/variables - // made volatile so the compiler does not reorder access to it - public volatile bool Connecting; - - // thread safe pipe for received messages - // => inside client connection state so that we can create a new state - // each time we connect - // (unlike server which has one receive pipe for all connections) - public readonly MagnificentReceivePipe receivePipe; - - // constructor always creates new TcpClient for client connection! - public ClientConnectionState(int MaxMessageSize) : base(new TcpClient(), MaxMessageSize) - { - // create receive pipe with max message size for pooling - receivePipe = new MagnificentReceivePipe(MaxMessageSize); - } - - // dispose all the state safely - public void Dispose() - { - // close client - client.Close(); - - // wait until thread finished. this is the only way to guarantee - // that we can call Connect() again immediately after Disconnect - // -> calling .Join would sometimes wait forever, e.g. when - // calling Disconnect while trying to connect to a dead end - receiveThread?.Interrupt(); - - // we interrupted the receive Thread, so we can't guarantee that - // connecting was reset. let's do it manually. - Connecting = false; - - // clear send pipe. no need to hold on to elements. - // (unlike receiveQueue, which is still needed to process the - // latest Disconnected message, etc.) - sendPipe.Clear(); - - // IMPORTANT: DO NOT CLEAR RECEIVE PIPE. - // we still want to process disconnect messages in Tick()! - - // let go of this client completely. the thread ended, no one uses - // it anymore and this way Connected is false again immediately. - client = null; - } - } - - public class Client : Common - { - // events to hook into - // => OnData uses ArraySegment for allocation free receives later - public Action OnConnected; - public Action> OnData; - public Action OnDisconnected; - - // disconnect if send queue gets too big. - // -> avoids ever growing queue memory if network is slower than input - // -> disconnecting is great for load balancing. better to disconnect - // one connection than risking every connection / the whole server - // -> huge queue would introduce multiple seconds of latency anyway - // - // Mirror/DOTSNET use MaxMessageSize batching, so for a 16kb max size: - // limit = 1,000 means 16 MB of memory/connection - // limit = 10,000 means 160 MB of memory/connection - public int SendQueueLimit = 10000; - public int ReceiveQueueLimit = 10000; - - // all client state wrapped into an object that is passed to ReceiveThread - // => we create a new one each time we connect to avoid data races with - // old dieing threads still using the previous object! - ClientConnectionState state; - - // Connected & Connecting - public bool Connected => state != null && state.Connected; - public bool Connecting => state != null && state.Connecting; - - // pipe count, useful for debugging / benchmarks - public int ReceivePipeCount => state != null ? state.receivePipe.TotalCount : 0; - - // constructor - public Client(int MaxMessageSize) : base(MaxMessageSize) {} - - // the thread function - // STATIC to avoid sharing state! - // => pass ClientState object. a new one is created for each new thread! - // => avoids data races where an old dieing thread might still modify - // the current thread's state :/ - static void ReceiveThreadFunction(ClientConnectionState state, string ip, int port, int MaxMessageSize, bool NoDelay, int SendTimeout, int ReceiveTimeout, int ReceiveQueueLimit) - - { - Thread sendThread = null; - - // absolutely must wrap with try/catch, otherwise thread - // exceptions are silent - try - { - // connect (blocking) - state.client.Connect(ip, port); - state.Connecting = false; // volatile! - - // set socket options after the socket was created in Connect() - // (not after the constructor because we clear the socket there) - state.client.NoDelay = NoDelay; - state.client.SendTimeout = SendTimeout; - state.client.ReceiveTimeout = ReceiveTimeout; - - // start send thread only after connected - // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! - sendThread = new Thread(() => { ThreadFunctions.SendLoop(0, state.client, state.sendPipe, state.sendPending); }); - sendThread.IsBackground = true; - sendThread.Start(); - - // run the receive loop - // (receive pipe is shared across all loops) - ThreadFunctions.ReceiveLoop(0, state.client, MaxMessageSize, state.receivePipe, ReceiveQueueLimit); - } - catch (SocketException exception) - { - // this happens if (for example) the ip address is correct - // but there is no server running on that ip/port - Log.Info("Client Recv: failed to connect to ip=" + ip + " port=" + port + " reason=" + exception); - - // add 'Disconnected' event to receive pipe so that the caller - // knows that the Connect failed. otherwise they will never know - state.receivePipe.Enqueue(0, EventType.Disconnected, default); - } - catch (ThreadInterruptedException) - { - // expected if Disconnect() aborts it - } - catch (ThreadAbortException) - { - // expected if Disconnect() aborts it - } - catch (ObjectDisposedException) - { - // expected if Disconnect() aborts it and disposed the client - // while ReceiveThread is in a blocking Connect() call - } - catch (Exception exception) - { - // something went wrong. probably important. - Log.Error("Client Recv Exception: " + exception); - } - - // sendthread might be waiting on ManualResetEvent, - // so let's make sure to end it if the connection - // closed. - // otherwise the send thread would only end if it's - // actually sending data while the connection is - // closed. - sendThread?.Interrupt(); - - // Connect might have failed. thread might have been closed. - // let's reset connecting state no matter what. - state.Connecting = false; - - // if we got here then we are done. ReceiveLoop cleans up already, - // but we may never get there if connect fails. so let's clean up - // here too. - state.client?.Close(); - } - - public void Connect(string ip, int port) - { - // not if already started - if (Connecting || Connected) - { - Log.Warning("Telepathy Client can not create connection because an existing connection is connecting or connected"); - return; - } - - // overwrite old thread's state object. create a new one to avoid - // data races where an old dieing thread might still modify the - // current state! fixes all the flaky tests! - state = new ClientConnectionState(MaxMessageSize); - - // We are connecting from now until Connect succeeds or fails - state.Connecting = true; - - // create a TcpClient with perfect IPv4, IPv6 and hostname resolving - // support. - // - // * TcpClient(hostname, port): works but would connect (and block) - // already - // * TcpClient(AddressFamily.InterNetworkV6): takes Ipv4 and IPv6 - // addresses but only connects to IPv6 servers (e.g. Telepathy). - // does NOT connect to IPv4 servers (e.g. Mirror Booster), even - // with DualMode enabled. - // * TcpClient(): creates IPv4 socket internally, which would force - // Connect() to only use IPv4 sockets. - // - // => the trick is to clear the internal IPv4 socket so that Connect - // resolves the hostname and creates either an IPv4 or an IPv6 - // socket as needed (see TcpClient source) - state.client.Client = null; // clear internal IPv4 socket until Connect() - - // client.Connect(ip, port) is blocking. let's call it in the thread - // and return immediately. - // -> this way the application doesn't hang for 30s if connect takes - // too long, which is especially good in games - // -> this way we don't async client.BeginConnect, which seems to - // fail sometimes if we connect too many clients too fast - state.receiveThread = new Thread(() => { - ReceiveThreadFunction(state, ip, port, MaxMessageSize, NoDelay, SendTimeout, ReceiveTimeout, ReceiveQueueLimit); - }); - state.receiveThread.IsBackground = true; - state.receiveThread.Start(); - } - - public void Disconnect() - { - // only if started - if (Connecting || Connected) - { - // dispose all the state safely - state.Dispose(); - - // IMPORTANT: DO NOT set state = null! - // we still want to process the pipe's disconnect message etc.! - } - } - - // send message to server using socket connection. - // arraysegment for allocation free sends later. - // -> the segment's array is only used until Send() returns! - public bool Send(ArraySegment message) - { - if (Connected) - { - // respect max message size to avoid allocation attacks. - if (message.Count <= MaxMessageSize) - { - // check send pipe limit - if (state.sendPipe.Count < SendQueueLimit) - { - // add to thread safe send pipe and return immediately. - // calling Send here would be blocking (sometimes for long - // times if other side lags or wire was disconnected) - state.sendPipe.Enqueue(message); - state.sendPending.Set(); // interrupt SendThread WaitOne() - return true; - } - // disconnect if send queue gets too big. - // -> avoids ever growing queue memory if network is slower - // than input - // -> avoids ever growing latency as well - // - // note: while SendThread always grabs the WHOLE send queue - // immediately, it's still possible that the sending - // blocks for so long that the send queue just gets - // way too big. have a limit - better safe than sorry. - else - { - // log the reason - Log.Warning($"Client.Send: sendPipe reached limit of {SendQueueLimit}. This can happen if we call send faster than the network can process messages. Disconnecting to avoid ever growing memory & latency."); - - // just close it. send thread will take care of the rest. - state.client.Close(); - return false; - } - } - Log.Error("Client.Send: message too big: " + message.Count + ". Limit: " + MaxMessageSize); - return false; - } - Log.Warning("Client.Send: not connected!"); - return false; - } - - // tick: processes up to 'limit' messages - // => limit parameter to avoid deadlocks / too long freezes if server or - // client is too slow to process network load - // => Mirror & DOTSNET need to have a process limit anyway. - // might as well do it here and make life easier. - // => returns amount of remaining messages to process, so the caller - // can call tick again as many times as needed (or up to a limit) - // - // Tick() may process multiple messages, but Mirror needs a way to stop - // processing immediately if a scene change messages arrives. Mirror - // can't process any other messages during a scene change. - // (could be useful for others too) - // => make sure to allocate the lambda only once in transports - public int Tick(int processLimit, Func checkEnabled = null) - { - // only if state was created yet (after connect()) - // note: we don't check 'only if connected' because we want to still - // process Disconnect messages afterwards too! - if (state == null) - return 0; - - // process up to 'processLimit' messages - for (int i = 0; i < processLimit; ++i) - { - // check enabled in case a Mirror scene message arrived - if (checkEnabled != null && !checkEnabled()) - break; - - // peek first. allows us to process the first queued entry while - // still keeping the pooled byte[] alive by not removing anything. - if (state.receivePipe.TryPeek(out int _, out EventType eventType, out ArraySegment message)) - { - switch (eventType) - { - case EventType.Connected: - OnConnected?.Invoke(); - break; - case EventType.Data: - OnData?.Invoke(message); - break; - case EventType.Disconnected: - OnDisconnected?.Invoke(); - break; - } - - // IMPORTANT: now dequeue and return it to pool AFTER we are - // done processing the event. - state.receivePipe.TryDequeue(); - } - // no more messages. stop the loop. - else break; - } - - // return what's left to process for next time - return state.receivePipe.TotalCount; - } - } -} +using System; +using System.Net.Sockets; +using System.Threading; + +namespace Telepathy +{ + // ClientState OBJECT that can be handed to the ReceiveThread safely. + // => allows us to create a NEW OBJECT every time we connect and start a + // receive thread. + // => perfectly protects us against data races. fixes all the flaky tests + // where .Connecting or .client would still be used by a dieing thread + // while attempting to use it for a new connection attempt etc. + // => creating a fresh client state each time is the best solution against + // data races here! + class ClientConnectionState : ConnectionState + { + public Thread receiveThread; + + // TcpClient.Connected doesn't check if socket != null, which + // results in NullReferenceExceptions if connection was closed. + // -> let's check it manually instead + public bool Connected => client != null && + client.Client != null && + client.Client.Connected; + + // TcpClient has no 'connecting' state to check. We need to keep track + // of it manually. + // -> checking 'thread.IsAlive && !Connected' is not enough because the + // thread is alive and connected is false for a short moment after + // disconnecting, so this would cause race conditions. + // -> we use a threadsafe bool wrapper so that ThreadFunction can remain + // static (it needs a common lock) + // => Connecting is true from first Connect() call in here, through the + // thread start, until TcpClient.Connect() returns. Simple and clear. + // => bools are atomic according to + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/variables + // made volatile so the compiler does not reorder access to it + public volatile bool Connecting; + + // thread safe pipe for received messages + // => inside client connection state so that we can create a new state + // each time we connect + // (unlike server which has one receive pipe for all connections) + public readonly MagnificentReceivePipe receivePipe; + + // constructor always creates new TcpClient for client connection! + public ClientConnectionState(int MaxMessageSize) : base(new TcpClient(), MaxMessageSize) + { + // create receive pipe with max message size for pooling + receivePipe = new MagnificentReceivePipe(MaxMessageSize); + } + + // dispose all the state safely + public void Dispose() + { + // close client + client.Close(); + + // wait until thread finished. this is the only way to guarantee + // that we can call Connect() again immediately after Disconnect + // -> calling .Join would sometimes wait forever, e.g. when + // calling Disconnect while trying to connect to a dead end + receiveThread?.Interrupt(); + + // we interrupted the receive Thread, so we can't guarantee that + // connecting was reset. let's do it manually. + Connecting = false; + + // clear send pipe. no need to hold on to elements. + // (unlike receiveQueue, which is still needed to process the + // latest Disconnected message, etc.) + sendPipe.Clear(); + + // IMPORTANT: DO NOT CLEAR RECEIVE PIPE. + // we still want to process disconnect messages in Tick()! + + // let go of this client completely. the thread ended, no one uses + // it anymore and this way Connected is false again immediately. + client = null; + } + } + + public class Client : Common + { + // events to hook into + // => OnData uses ArraySegment for allocation free receives later + public Action OnConnected; + public Action> OnData; + public Action OnDisconnected; + + // disconnect if send queue gets too big. + // -> avoids ever growing queue memory if network is slower than input + // -> disconnecting is great for load balancing. better to disconnect + // one connection than risking every connection / the whole server + // -> huge queue would introduce multiple seconds of latency anyway + // + // Mirror/DOTSNET use MaxMessageSize batching, so for a 16kb max size: + // limit = 1,000 means 16 MB of memory/connection + // limit = 10,000 means 160 MB of memory/connection + public int SendQueueLimit = 10000; + public int ReceiveQueueLimit = 10000; + + // all client state wrapped into an object that is passed to ReceiveThread + // => we create a new one each time we connect to avoid data races with + // old dieing threads still using the previous object! + ClientConnectionState state; + + // Connected & Connecting + public bool Connected => state != null && state.Connected; + public bool Connecting => state != null && state.Connecting; + + // pipe count, useful for debugging / benchmarks + public int ReceivePipeCount => state != null ? state.receivePipe.TotalCount : 0; + + // constructor + public Client(int MaxMessageSize) : base(MaxMessageSize) {} + + // the thread function + // STATIC to avoid sharing state! + // => pass ClientState object. a new one is created for each new thread! + // => avoids data races where an old dieing thread might still modify + // the current thread's state :/ + static void ReceiveThreadFunction(ClientConnectionState state, string ip, int port, int MaxMessageSize, bool NoDelay, int SendTimeout, int ReceiveTimeout, int ReceiveQueueLimit) + + { + Thread sendThread = null; + + // absolutely must wrap with try/catch, otherwise thread + // exceptions are silent + try + { + // connect (blocking) + state.client.Connect(ip, port); + state.Connecting = false; // volatile! + + // set socket options after the socket was created in Connect() + // (not after the constructor because we clear the socket there) + state.client.NoDelay = NoDelay; + state.client.SendTimeout = SendTimeout; + state.client.ReceiveTimeout = ReceiveTimeout; + + // start send thread only after connected + // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! + sendThread = new Thread(() => { ThreadFunctions.SendLoop(0, state.client, state.sendPipe, state.sendPending); }); + sendThread.IsBackground = true; + sendThread.Start(); + + // run the receive loop + // (receive pipe is shared across all loops) + ThreadFunctions.ReceiveLoop(0, state.client, MaxMessageSize, state.receivePipe, ReceiveQueueLimit); + } + catch (SocketException exception) + { + // this happens if (for example) the ip address is correct + // but there is no server running on that ip/port + Log.Info("Client Recv: failed to connect to ip=" + ip + " port=" + port + " reason=" + exception); + + // add 'Disconnected' event to receive pipe so that the caller + // knows that the Connect failed. otherwise they will never know + state.receivePipe.Enqueue(0, EventType.Disconnected, default); + } + catch (ThreadInterruptedException) + { + // expected if Disconnect() aborts it + } + catch (ThreadAbortException) + { + // expected if Disconnect() aborts it + } + catch (ObjectDisposedException) + { + // expected if Disconnect() aborts it and disposed the client + // while ReceiveThread is in a blocking Connect() call + } + catch (Exception exception) + { + // something went wrong. probably important. + Log.Error("Client Recv Exception: " + exception); + } + + // sendthread might be waiting on ManualResetEvent, + // so let's make sure to end it if the connection + // closed. + // otherwise the send thread would only end if it's + // actually sending data while the connection is + // closed. + sendThread?.Interrupt(); + + // Connect might have failed. thread might have been closed. + // let's reset connecting state no matter what. + state.Connecting = false; + + // if we got here then we are done. ReceiveLoop cleans up already, + // but we may never get there if connect fails. so let's clean up + // here too. + state.client?.Close(); + } + + public void Connect(string ip, int port) + { + // not if already started + if (Connecting || Connected) + { + Log.Warning("Telepathy Client can not create connection because an existing connection is connecting or connected"); + return; + } + + // overwrite old thread's state object. create a new one to avoid + // data races where an old dieing thread might still modify the + // current state! fixes all the flaky tests! + state = new ClientConnectionState(MaxMessageSize); + + // We are connecting from now until Connect succeeds or fails + state.Connecting = true; + + // create a TcpClient with perfect IPv4, IPv6 and hostname resolving + // support. + // + // * TcpClient(hostname, port): works but would connect (and block) + // already + // * TcpClient(AddressFamily.InterNetworkV6): takes Ipv4 and IPv6 + // addresses but only connects to IPv6 servers (e.g. Telepathy). + // does NOT connect to IPv4 servers (e.g. Mirror Booster), even + // with DualMode enabled. + // * TcpClient(): creates IPv4 socket internally, which would force + // Connect() to only use IPv4 sockets. + // + // => the trick is to clear the internal IPv4 socket so that Connect + // resolves the hostname and creates either an IPv4 or an IPv6 + // socket as needed (see TcpClient source) + state.client.Client = null; // clear internal IPv4 socket until Connect() + + // client.Connect(ip, port) is blocking. let's call it in the thread + // and return immediately. + // -> this way the application doesn't hang for 30s if connect takes + // too long, which is especially good in games + // -> this way we don't async client.BeginConnect, which seems to + // fail sometimes if we connect too many clients too fast + state.receiveThread = new Thread(() => { + ReceiveThreadFunction(state, ip, port, MaxMessageSize, NoDelay, SendTimeout, ReceiveTimeout, ReceiveQueueLimit); + }); + state.receiveThread.IsBackground = true; + state.receiveThread.Start(); + } + + public void Disconnect() + { + // only if started + if (Connecting || Connected) + { + // dispose all the state safely + state.Dispose(); + + // IMPORTANT: DO NOT set state = null! + // we still want to process the pipe's disconnect message etc.! + } + } + + // send message to server using socket connection. + // arraysegment for allocation free sends later. + // -> the segment's array is only used until Send() returns! + public bool Send(ArraySegment message) + { + if (Connected) + { + // respect max message size to avoid allocation attacks. + if (message.Count <= MaxMessageSize) + { + // check send pipe limit + if (state.sendPipe.Count < SendQueueLimit) + { + // add to thread safe send pipe and return immediately. + // calling Send here would be blocking (sometimes for long + // times if other side lags or wire was disconnected) + state.sendPipe.Enqueue(message); + state.sendPending.Set(); // interrupt SendThread WaitOne() + return true; + } + // disconnect if send queue gets too big. + // -> avoids ever growing queue memory if network is slower + // than input + // -> avoids ever growing latency as well + // + // note: while SendThread always grabs the WHOLE send queue + // immediately, it's still possible that the sending + // blocks for so long that the send queue just gets + // way too big. have a limit - better safe than sorry. + else + { + // log the reason + Log.Warning($"Client.Send: sendPipe reached limit of {SendQueueLimit}. This can happen if we call send faster than the network can process messages. Disconnecting to avoid ever growing memory & latency."); + + // just close it. send thread will take care of the rest. + state.client.Close(); + return false; + } + } + Log.Error("Client.Send: message too big: " + message.Count + ". Limit: " + MaxMessageSize); + return false; + } + Log.Warning("Client.Send: not connected!"); + return false; + } + + // tick: processes up to 'limit' messages + // => limit parameter to avoid deadlocks / too long freezes if server or + // client is too slow to process network load + // => Mirror & DOTSNET need to have a process limit anyway. + // might as well do it here and make life easier. + // => returns amount of remaining messages to process, so the caller + // can call tick again as many times as needed (or up to a limit) + // + // Tick() may process multiple messages, but Mirror needs a way to stop + // processing immediately if a scene change messages arrives. Mirror + // can't process any other messages during a scene change. + // (could be useful for others too) + // => make sure to allocate the lambda only once in transports + public int Tick(int processLimit, Func checkEnabled = null) + { + // only if state was created yet (after connect()) + // note: we don't check 'only if connected' because we want to still + // process Disconnect messages afterwards too! + if (state == null) + return 0; + + // process up to 'processLimit' messages + for (int i = 0; i < processLimit; ++i) + { + // check enabled in case a Mirror scene message arrived + if (checkEnabled != null && !checkEnabled()) + break; + + // peek first. allows us to process the first queued entry while + // still keeping the pooled byte[] alive by not removing anything. + if (state.receivePipe.TryPeek(out int _, out EventType eventType, out ArraySegment message)) + { + switch (eventType) + { + case EventType.Connected: + OnConnected?.Invoke(); + break; + case EventType.Data: + OnData?.Invoke(message); + break; + case EventType.Disconnected: + OnDisconnected?.Invoke(); + break; + } + + // IMPORTANT: now dequeue and return it to pool AFTER we are + // done processing the event. + state.receivePipe.TryDequeue(); + } + // no more messages. stop the loop. + else break; + } + + // return what's left to process for next time + return state.receivePipe.TotalCount; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs.meta index 1b6d222..dcee69d 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: a5b95294cc4ec4b15aacba57531c7985 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: a5b95294cc4ec4b15aacba57531c7985 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs index 15265f9..7b4df50 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs @@ -1,39 +1,39 @@ -// common code used by server and client -namespace Telepathy -{ - public abstract class Common - { - // IMPORTANT: DO NOT SHARE STATE ACROSS SEND/RECV LOOPS (DATA RACES) - // (except receive pipe which is used for all threads) - - // NoDelay disables nagle algorithm. lowers CPU% and latency but - // increases bandwidth - public bool NoDelay = true; - - // Prevent allocation attacks. Each packet is prefixed with a length - // header, so an attacker could send a fake packet with length=2GB, - // causing the server to allocate 2GB and run out of memory quickly. - // -> simply increase max packet size if you want to send around bigger - // files! - // -> 16KB per message should be more than enough. - public readonly int MaxMessageSize; - - // Send would stall forever if the network is cut off during a send, so - // we need a timeout (in milliseconds) - public int SendTimeout = 5000; - - // Default TCP receive time out can be huge (minutes). - // That's way too much for games, let's make it configurable. - // we need a timeout (in milliseconds) - // => '0' means disabled - // => disabled by default because some people might use Telepathy - // without Mirror and without sending pings, so timeouts are likely - public int ReceiveTimeout = 0; - - // constructor - protected Common(int MaxMessageSize) - { - this.MaxMessageSize = MaxMessageSize; - } - } -} +// common code used by server and client +namespace Telepathy +{ + public abstract class Common + { + // IMPORTANT: DO NOT SHARE STATE ACROSS SEND/RECV LOOPS (DATA RACES) + // (except receive pipe which is used for all threads) + + // NoDelay disables nagle algorithm. lowers CPU% and latency but + // increases bandwidth + public bool NoDelay = true; + + // Prevent allocation attacks. Each packet is prefixed with a length + // header, so an attacker could send a fake packet with length=2GB, + // causing the server to allocate 2GB and run out of memory quickly. + // -> simply increase max packet size if you want to send around bigger + // files! + // -> 16KB per message should be more than enough. + public readonly int MaxMessageSize; + + // Send would stall forever if the network is cut off during a send, so + // we need a timeout (in milliseconds) + public int SendTimeout = 5000; + + // Default TCP receive time out can be huge (minutes). + // That's way too much for games, let's make it configurable. + // we need a timeout (in milliseconds) + // => '0' means disabled + // => disabled by default because some people might use Telepathy + // without Mirror and without sending pings, so timeouts are likely + public int ReceiveTimeout = 0; + + // constructor + protected Common(int MaxMessageSize) + { + this.MaxMessageSize = MaxMessageSize; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs.meta index 5d8ab5b..5d6a173 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: c4d56322cf0e248a89103c002a505dab -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: c4d56322cf0e248a89103c002a505dab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs index cdfe3c0..37b297a 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs @@ -1,35 +1,35 @@ -// both server and client need a connection state object. -// -> server needs it to keep track of multiple connections -// -> client needs it to safely create a new connection state on every new -// connect in order to avoid data races where a dieing thread might still -// modify the current state. can't happen if we create a new state each time! -// (fixes all the flaky tests) -// -// ... besides, it also allows us to share code! -using System.Net.Sockets; -using System.Threading; - -namespace Telepathy -{ - public class ConnectionState - { - public TcpClient client; - - // thread safe pipe to send messages from main thread to send thread - public readonly MagnificentSendPipe sendPipe; - - // ManualResetEvent to wake up the send thread. better than Thread.Sleep - // -> call Set() if everything was sent - // -> call Reset() if there is something to send again - // -> call WaitOne() to block until Reset was called - public ManualResetEvent sendPending = new ManualResetEvent(false); - - public ConnectionState(TcpClient client, int MaxMessageSize) - { - this.client = client; - - // create send pipe with max message size for pooling - sendPipe = new MagnificentSendPipe(MaxMessageSize); - } - } +// both server and client need a connection state object. +// -> server needs it to keep track of multiple connections +// -> client needs it to safely create a new connection state on every new +// connect in order to avoid data races where a dieing thread might still +// modify the current state. can't happen if we create a new state each time! +// (fixes all the flaky tests) +// +// ... besides, it also allows us to share code! +using System.Net.Sockets; +using System.Threading; + +namespace Telepathy +{ + public class ConnectionState + { + public TcpClient client; + + // thread safe pipe to send messages from main thread to send thread + public readonly MagnificentSendPipe sendPipe; + + // ManualResetEvent to wake up the send thread. better than Thread.Sleep + // -> call Set() if everything was sent + // -> call Reset() if there is something to send again + // -> call WaitOne() to block until Reset was called + public ManualResetEvent sendPending = new ManualResetEvent(false); + + public ConnectionState(TcpClient client, int MaxMessageSize) + { + this.client = client; + + // create send pipe with max message size for pooling + sendPipe = new MagnificentSendPipe(MaxMessageSize); + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs.meta index 3dcceaf..39859ce 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: af95e2b6f6343411aa8bdf871abd7b1b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: af95e2b6f6343411aa8bdf871abd7b1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty.meta index 1bc9652..b108430 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty.meta @@ -1,8 +1,8 @@ -fileFormatVersion: 2 -guid: 885e89897e3a03241827ab7a14fe5fa0 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 885e89897e3a03241827ab7a14fe5fa0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs.meta index 304866f..3ee3ee6 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: aa8d703f0b73f4d6398b76812719b68b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: aa8d703f0b73f4d6398b76812719b68b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs.meta index 5937bb9..2c97969 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: aedf812e9637b4f92a35db1aedca8c92 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: aedf812e9637b4f92a35db1aedca8c92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs index 7899911..35c6d77 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs @@ -1 +1 @@ -// removed 2021-02-04 +// removed 2021-02-04 diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs.meta index f3a9310..bd6cb0d 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 8fc06e2fb29854a0c9e90c0188d36a08 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 8fc06e2fb29854a0c9e90c0188d36a08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs index 85dece4..f38dcba 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs @@ -1 +1 @@ -// removed 2021-02-04 +// removed 2021-02-04 diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta index 77c885d..3ec8c88 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 64df4eaebe4ff9a43a9fb318c3e8e321 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 64df4eaebe4ff9a43a9fb318c3e8e321 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs index 66bc3b4..89228e8 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs @@ -1,9 +1,9 @@ -namespace Telepathy -{ - public enum EventType - { - Connected, - Data, - Disconnected - } -} +namespace Telepathy +{ + public enum EventType + { + Connected, + Data, + Disconnected + } +} diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs.meta index ac88c1b..07b12ae 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 49f1a330755814803be5f27f493e1910 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 49f1a330755814803be5f27f493e1910 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE index 680deef..e1c8fdf 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2018, 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. +The MIT License (MIT) + +Copyright (c) 2018, 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. diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE.meta index 4d7664e..fbd8ff2 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 0ba11103b95fd4721bffbb08440d5b8e -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 0ba11103b95fd4721bffbb08440d5b8e +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs index 2d50aa3..e14fef6 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs @@ -1,15 +1,15 @@ -// A simple logger class that uses Console.WriteLine by default. -// Can also do Logger.LogMethod = Debug.Log for Unity etc. -// (this way we don't have to depend on UnityEngine.DLL and don't need a -// different version for every UnityEngine version here) -using System; - -namespace Telepathy -{ - public static class Log - { - public static Action Info = Console.WriteLine; - public static Action Warning = Console.WriteLine; - public static Action Error = Console.Error.WriteLine; - } -} +// A simple logger class that uses Console.WriteLine by default. +// Can also do Logger.LogMethod = Debug.Log for Unity etc. +// (this way we don't have to depend on UnityEngine.DLL and don't need a +// different version for every UnityEngine version here) +using System; + +namespace Telepathy +{ + public static class Log + { + public static Action Info = Console.WriteLine; + public static Action Warning = Console.WriteLine; + public static Action Error = Console.Error.WriteLine; + } +} diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs.meta index 8f78650..c31afde 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 0a123d054bef34d059057ac2ce936605 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 0a123d054bef34d059057ac2ce936605 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs index 2e10318..4c38ad1 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs @@ -1,222 +1,222 @@ -// a magnificent receive pipe to shield us from all of life's complexities. -// safely sends messages from receive thread to main thread. -// -> thread safety built in -// -> byte[] pooling coming in the future -// -// => hides all the complexity from telepathy -// => easy to switch between stack/queue/concurrentqueue/etc. -// => easy to test -using System; -using System.Collections.Generic; - -namespace Telepathy -{ - public class MagnificentReceivePipe - { - // queue entry message. only used in here. - // -> byte arrays are always of 4 + MaxMessageSize - // -> ArraySegment indicates the actual message content - struct Entry - { - public int connectionId; - public EventType eventType; - public ArraySegment data; - public Entry(int connectionId, EventType eventType, ArraySegment data) - { - this.connectionId = connectionId; - this.eventType = eventType; - this.data = data; - } - } - - // message queue - // ConcurrentQueue allocates. lock{} instead. - // - // IMPORTANT: lock{} all usages! - readonly Queue queue = new Queue(); - - // byte[] pool to avoid allocations - // Take & Return is beautifully encapsulated in the pipe. - // the outside does not need to worry about anything. - // and it can be tested easily. - // - // IMPORTANT: lock{} all usages! - Pool pool; - - // unfortunately having one receive pipe per connetionId is way slower - // in CCU tests. right now we have one pipe for all connections. - // => we still need to limit queued messages per connection to avoid one - // spamming connection being able to slow down everyone else since - // the queue would be full of just this connection's messages forever - // => let's use a simpler per-connectionId counter for now - Dictionary queueCounter = new Dictionary(); - - // constructor - public MagnificentReceivePipe(int MaxMessageSize) - { - // initialize pool to create max message sized byte[]s each time - pool = new Pool(() => new byte[MaxMessageSize]); - } - - // return amount of queued messages for this connectionId. - // for statistics. don't call Count and assume that it's the same after - // the call. - public int Count(int connectionId) - { - lock (this) - { - return queueCounter.TryGetValue(connectionId, out int count) - ? count - : 0; - } - } - - // total count - public int TotalCount - { - get { lock (this) { return queue.Count; } } - } - - // pool count for testing - public int PoolCount - { - get { lock (this) { return pool.Count(); } } - } - - // enqueue a message - // -> ArraySegment to avoid allocations later - // -> parameters passed directly so it's more obvious that we don't just - // queue a passed 'Message', instead we copy the ArraySegment into - // a byte[] and store it internally, etc.) - public void Enqueue(int connectionId, EventType eventType, ArraySegment message) - { - // pool & queue usage always needs to be locked - lock (this) - { - // does this message have a data array content? - ArraySegment segment = default; - if (message != default) - { - // ArraySegment is only valid until returning. - // copy it into a byte[] that we can store. - // ArraySegment array is only valid until returning, so copy - // it into a byte[] that we can queue safely. - - // get one from the pool first to avoid allocations - byte[] bytes = pool.Take(); - - // copy into it - Buffer.BlockCopy(message.Array, message.Offset, bytes, 0, message.Count); - - // indicate which part is the message - segment = new ArraySegment(bytes, 0, message.Count); - } - - // enqueue it - // IMPORTANT: pass the segment around pool byte[], - // NOT the 'message' that is only valid until returning! - Entry entry = new Entry(connectionId, eventType, segment); - queue.Enqueue(entry); - - // increase counter for this connectionId - int oldCount = Count(connectionId); - queueCounter[connectionId] = oldCount + 1; - } - } - - // peek the next message - // -> allows the caller to process it while pipe still holds on to the - // byte[] - // -> TryDequeue should be called after processing, so that the message - // is actually dequeued and the byte[] is returned to pool! - // => see TryDequeue comments! - // - // IMPORTANT: TryPeek & Dequeue need to be called from the SAME THREAD! - public bool TryPeek(out int connectionId, out EventType eventType, out ArraySegment data) - { - connectionId = 0; - eventType = EventType.Disconnected; - data = default; - - // pool & queue usage always needs to be locked - lock (this) - { - if (queue.Count > 0) - { - Entry entry = queue.Peek(); - connectionId = entry.connectionId; - eventType = entry.eventType; - data = entry.data; - return true; - } - return false; - } - } - - // dequeue the next message - // -> simply dequeues and returns the byte[] to pool (if any) - // -> use Peek to actually process the first element while the pipe - // still holds on to the byte[] - // -> doesn't return the element because the byte[] needs to be returned - // to the pool in dequeue. caller can't be allowed to work with a - // byte[] that is already returned to pool. - // => Peek & Dequeue is the most simple, clean solution for receive - // pipe pooling to avoid allocations! - // - // IMPORTANT: TryPeek & Dequeue need to be called from the SAME THREAD! - public bool TryDequeue() - { - // pool & queue usage always needs to be locked - lock (this) - { - if (queue.Count > 0) - { - // dequeue from queue - Entry entry = queue.Dequeue(); - - // return byte[] to pool (if any). - // not all message types have byte[] contents. - if (entry.data != default) - { - pool.Return(entry.data.Array); - } - - // decrease counter for this connectionId - queueCounter[entry.connectionId]--; - - // remove if zero. don't want to keep old connectionIds in - // there forever, it would cause slowly growing memory. - if (queueCounter[entry.connectionId] == 0) - queueCounter.Remove(entry.connectionId); - - return true; - } - return false; - } - } - - public void Clear() - { - // pool & queue usage always needs to be locked - lock (this) - { - // clear queue, but via dequeue to return each byte[] to pool - while (queue.Count > 0) - { - // dequeue - Entry entry = queue.Dequeue(); - - // return byte[] to pool (if any). - // not all message types have byte[] contents. - if (entry.data != default) - { - pool.Return(entry.data.Array); - } - } - - // clear counter too - queueCounter.Clear(); - } - } - } +// a magnificent receive pipe to shield us from all of life's complexities. +// safely sends messages from receive thread to main thread. +// -> thread safety built in +// -> byte[] pooling coming in the future +// +// => hides all the complexity from telepathy +// => easy to switch between stack/queue/concurrentqueue/etc. +// => easy to test +using System; +using System.Collections.Generic; + +namespace Telepathy +{ + public class MagnificentReceivePipe + { + // queue entry message. only used in here. + // -> byte arrays are always of 4 + MaxMessageSize + // -> ArraySegment indicates the actual message content + struct Entry + { + public int connectionId; + public EventType eventType; + public ArraySegment data; + public Entry(int connectionId, EventType eventType, ArraySegment data) + { + this.connectionId = connectionId; + this.eventType = eventType; + this.data = data; + } + } + + // message queue + // ConcurrentQueue allocates. lock{} instead. + // + // IMPORTANT: lock{} all usages! + readonly Queue queue = new Queue(); + + // byte[] pool to avoid allocations + // Take & Return is beautifully encapsulated in the pipe. + // the outside does not need to worry about anything. + // and it can be tested easily. + // + // IMPORTANT: lock{} all usages! + Pool pool; + + // unfortunately having one receive pipe per connetionId is way slower + // in CCU tests. right now we have one pipe for all connections. + // => we still need to limit queued messages per connection to avoid one + // spamming connection being able to slow down everyone else since + // the queue would be full of just this connection's messages forever + // => let's use a simpler per-connectionId counter for now + Dictionary queueCounter = new Dictionary(); + + // constructor + public MagnificentReceivePipe(int MaxMessageSize) + { + // initialize pool to create max message sized byte[]s each time + pool = new Pool(() => new byte[MaxMessageSize]); + } + + // return amount of queued messages for this connectionId. + // for statistics. don't call Count and assume that it's the same after + // the call. + public int Count(int connectionId) + { + lock (this) + { + return queueCounter.TryGetValue(connectionId, out int count) + ? count + : 0; + } + } + + // total count + public int TotalCount + { + get { lock (this) { return queue.Count; } } + } + + // pool count for testing + public int PoolCount + { + get { lock (this) { return pool.Count(); } } + } + + // enqueue a message + // -> ArraySegment to avoid allocations later + // -> parameters passed directly so it's more obvious that we don't just + // queue a passed 'Message', instead we copy the ArraySegment into + // a byte[] and store it internally, etc.) + public void Enqueue(int connectionId, EventType eventType, ArraySegment message) + { + // pool & queue usage always needs to be locked + lock (this) + { + // does this message have a data array content? + ArraySegment segment = default; + if (message != default) + { + // ArraySegment is only valid until returning. + // copy it into a byte[] that we can store. + // ArraySegment array is only valid until returning, so copy + // it into a byte[] that we can queue safely. + + // get one from the pool first to avoid allocations + byte[] bytes = pool.Take(); + + // copy into it + Buffer.BlockCopy(message.Array, message.Offset, bytes, 0, message.Count); + + // indicate which part is the message + segment = new ArraySegment(bytes, 0, message.Count); + } + + // enqueue it + // IMPORTANT: pass the segment around pool byte[], + // NOT the 'message' that is only valid until returning! + Entry entry = new Entry(connectionId, eventType, segment); + queue.Enqueue(entry); + + // increase counter for this connectionId + int oldCount = Count(connectionId); + queueCounter[connectionId] = oldCount + 1; + } + } + + // peek the next message + // -> allows the caller to process it while pipe still holds on to the + // byte[] + // -> TryDequeue should be called after processing, so that the message + // is actually dequeued and the byte[] is returned to pool! + // => see TryDequeue comments! + // + // IMPORTANT: TryPeek & Dequeue need to be called from the SAME THREAD! + public bool TryPeek(out int connectionId, out EventType eventType, out ArraySegment data) + { + connectionId = 0; + eventType = EventType.Disconnected; + data = default; + + // pool & queue usage always needs to be locked + lock (this) + { + if (queue.Count > 0) + { + Entry entry = queue.Peek(); + connectionId = entry.connectionId; + eventType = entry.eventType; + data = entry.data; + return true; + } + return false; + } + } + + // dequeue the next message + // -> simply dequeues and returns the byte[] to pool (if any) + // -> use Peek to actually process the first element while the pipe + // still holds on to the byte[] + // -> doesn't return the element because the byte[] needs to be returned + // to the pool in dequeue. caller can't be allowed to work with a + // byte[] that is already returned to pool. + // => Peek & Dequeue is the most simple, clean solution for receive + // pipe pooling to avoid allocations! + // + // IMPORTANT: TryPeek & Dequeue need to be called from the SAME THREAD! + public bool TryDequeue() + { + // pool & queue usage always needs to be locked + lock (this) + { + if (queue.Count > 0) + { + // dequeue from queue + Entry entry = queue.Dequeue(); + + // return byte[] to pool (if any). + // not all message types have byte[] contents. + if (entry.data != default) + { + pool.Return(entry.data.Array); + } + + // decrease counter for this connectionId + queueCounter[entry.connectionId]--; + + // remove if zero. don't want to keep old connectionIds in + // there forever, it would cause slowly growing memory. + if (queueCounter[entry.connectionId] == 0) + queueCounter.Remove(entry.connectionId); + + return true; + } + return false; + } + } + + public void Clear() + { + // pool & queue usage always needs to be locked + lock (this) + { + // clear queue, but via dequeue to return each byte[] to pool + while (queue.Count > 0) + { + // dequeue + Entry entry = queue.Dequeue(); + + // return byte[] to pool (if any). + // not all message types have byte[] contents. + if (entry.data != default) + { + pool.Return(entry.data.Array); + } + } + + // clear counter too + queueCounter.Clear(); + } + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta index 614bab6..7243331 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 010a208972a9a4e0cb0e7c18a60b4494 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 010a208972a9a4e0cb0e7c18a60b4494 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs index be456a0..431b542 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs @@ -1,165 +1,165 @@ -// a magnificent send pipe to shield us from all of life's complexities. -// safely sends messages from main thread to send thread. -// -> thread safety built in -// -> byte[] pooling coming in the future -// -// => hides all the complexity from telepathy -// => easy to switch between stack/queue/concurrentqueue/etc. -// => easy to test - -using System; -using System.Collections.Generic; - -namespace Telepathy -{ - public class MagnificentSendPipe - { - // message queue - // ConcurrentQueue allocates. lock{} instead. - // -> byte arrays are always of MaxMessageSize - // -> ArraySegment indicates the actual message content - // - // IMPORTANT: lock{} all usages! - readonly Queue> queue = new Queue>(); - - // byte[] pool to avoid allocations - // Take & Return is beautifully encapsulated in the pipe. - // the outside does not need to worry about anything. - // and it can be tested easily. - // - // IMPORTANT: lock{} all usages! - Pool pool; - - // constructor - public MagnificentSendPipe(int MaxMessageSize) - { - // initialize pool to create max message sized byte[]s each time - pool = new Pool(() => new byte[MaxMessageSize]); - } - - // for statistics. don't call Count and assume that it's the same after - // the call. - public int Count - { - get { lock (this) { return queue.Count; } } - } - - // pool count for testing - public int PoolCount - { - get { lock (this) { return pool.Count(); } } - } - - // enqueue a message - // arraysegment for allocation free sends later. - // -> the segment's array is only used until Enqueue() returns! - public void Enqueue(ArraySegment message) - { - // pool & queue usage always needs to be locked - lock (this) - { - // ArraySegment array is only valid until returning, so copy - // it into a byte[] that we can queue safely. - - // get one from the pool first to avoid allocations - byte[] bytes = pool.Take(); - - // copy into it - Buffer.BlockCopy(message.Array, message.Offset, bytes, 0, message.Count); - - // indicate which part is the message - ArraySegment segment = new ArraySegment(bytes, 0, message.Count); - - // now enqueue it - queue.Enqueue(segment); - } - } - - // send threads need to dequeue each byte[] and write it into the socket - // -> dequeueing one byte[] after another works, but it's WAY slower - // than dequeueing all immediately (locks only once) - // lock{} & DequeueAll is WAY faster than ConcurrentQueue & dequeue - // one after another: - // - // uMMORPG 450 CCU - // SafeQueue: 900-1440ms latency - // ConcurrentQueue: 2000ms latency - // - // -> the most obvious solution is to just return a list with all byte[] - // (which allocates) and then write each one into the socket - // -> a faster solution is to serialize each one into one payload buffer - // and pass that to the socket only once. fewer socket calls always - // give WAY better CPU performance(!) - // -> to avoid allocating a new list of entries each time, we simply - // serialize all entries into the payload here already - // => having all this complexity built into the pipe makes testing and - // modifying the algorithm super easy! - // - // IMPORTANT: serializing in here will allow us to return the byte[] - // entries back to a pool later to completely avoid - // allocations! - public bool DequeueAndSerializeAll(ref byte[] payload, out int packetSize) - { - // pool & queue usage always needs to be locked - lock (this) - { - // do nothing if empty - packetSize = 0; - if (queue.Count == 0) - return false; - - // we might have multiple pending messages. merge into one - // packet to avoid TCP overheads and improve performance. - // - // IMPORTANT: Mirror & DOTSNET already batch into MaxMessageSize - // chunks, but we STILL pack all pending messages - // into one large payload so we only give it to TCP - // ONCE. This is HUGE for performance so we keep it! - packetSize = 0; - foreach (ArraySegment message in queue) - packetSize += 4 + message.Count; // header + content - - // create payload buffer if not created yet or previous one is - // too small - // IMPORTANT: payload.Length might be > packetSize! don't use it! - if (payload == null || payload.Length < packetSize) - payload = new byte[packetSize]; - - // dequeue all byte[] messages and serialize into the packet - int position = 0; - while (queue.Count > 0) - { - // dequeue - ArraySegment message = queue.Dequeue(); - - // write header (size) into buffer at position - Utils.IntToBytesBigEndianNonAlloc(message.Count, payload, position); - position += 4; - - // copy message into payload at position - Buffer.BlockCopy(message.Array, message.Offset, payload, position, message.Count); - position += message.Count; - - // return to pool so it can be reused (avoids allocations!) - pool.Return(message.Array); - } - - // we did serialize something - return true; - } - } - - public void Clear() - { - // pool & queue usage always needs to be locked - lock (this) - { - // clear queue, but via dequeue to return each byte[] to pool - while (queue.Count > 0) - { - pool.Return(queue.Dequeue().Array); - } - } - } - } +// a magnificent send pipe to shield us from all of life's complexities. +// safely sends messages from main thread to send thread. +// -> thread safety built in +// -> byte[] pooling coming in the future +// +// => hides all the complexity from telepathy +// => easy to switch between stack/queue/concurrentqueue/etc. +// => easy to test + +using System; +using System.Collections.Generic; + +namespace Telepathy +{ + public class MagnificentSendPipe + { + // message queue + // ConcurrentQueue allocates. lock{} instead. + // -> byte arrays are always of MaxMessageSize + // -> ArraySegment indicates the actual message content + // + // IMPORTANT: lock{} all usages! + readonly Queue> queue = new Queue>(); + + // byte[] pool to avoid allocations + // Take & Return is beautifully encapsulated in the pipe. + // the outside does not need to worry about anything. + // and it can be tested easily. + // + // IMPORTANT: lock{} all usages! + Pool pool; + + // constructor + public MagnificentSendPipe(int MaxMessageSize) + { + // initialize pool to create max message sized byte[]s each time + pool = new Pool(() => new byte[MaxMessageSize]); + } + + // for statistics. don't call Count and assume that it's the same after + // the call. + public int Count + { + get { lock (this) { return queue.Count; } } + } + + // pool count for testing + public int PoolCount + { + get { lock (this) { return pool.Count(); } } + } + + // enqueue a message + // arraysegment for allocation free sends later. + // -> the segment's array is only used until Enqueue() returns! + public void Enqueue(ArraySegment message) + { + // pool & queue usage always needs to be locked + lock (this) + { + // ArraySegment array is only valid until returning, so copy + // it into a byte[] that we can queue safely. + + // get one from the pool first to avoid allocations + byte[] bytes = pool.Take(); + + // copy into it + Buffer.BlockCopy(message.Array, message.Offset, bytes, 0, message.Count); + + // indicate which part is the message + ArraySegment segment = new ArraySegment(bytes, 0, message.Count); + + // now enqueue it + queue.Enqueue(segment); + } + } + + // send threads need to dequeue each byte[] and write it into the socket + // -> dequeueing one byte[] after another works, but it's WAY slower + // than dequeueing all immediately (locks only once) + // lock{} & DequeueAll is WAY faster than ConcurrentQueue & dequeue + // one after another: + // + // uMMORPG 450 CCU + // SafeQueue: 900-1440ms latency + // ConcurrentQueue: 2000ms latency + // + // -> the most obvious solution is to just return a list with all byte[] + // (which allocates) and then write each one into the socket + // -> a faster solution is to serialize each one into one payload buffer + // and pass that to the socket only once. fewer socket calls always + // give WAY better CPU performance(!) + // -> to avoid allocating a new list of entries each time, we simply + // serialize all entries into the payload here already + // => having all this complexity built into the pipe makes testing and + // modifying the algorithm super easy! + // + // IMPORTANT: serializing in here will allow us to return the byte[] + // entries back to a pool later to completely avoid + // allocations! + public bool DequeueAndSerializeAll(ref byte[] payload, out int packetSize) + { + // pool & queue usage always needs to be locked + lock (this) + { + // do nothing if empty + packetSize = 0; + if (queue.Count == 0) + return false; + + // we might have multiple pending messages. merge into one + // packet to avoid TCP overheads and improve performance. + // + // IMPORTANT: Mirror & DOTSNET already batch into MaxMessageSize + // chunks, but we STILL pack all pending messages + // into one large payload so we only give it to TCP + // ONCE. This is HUGE for performance so we keep it! + packetSize = 0; + foreach (ArraySegment message in queue) + packetSize += 4 + message.Count; // header + content + + // create payload buffer if not created yet or previous one is + // too small + // IMPORTANT: payload.Length might be > packetSize! don't use it! + if (payload == null || payload.Length < packetSize) + payload = new byte[packetSize]; + + // dequeue all byte[] messages and serialize into the packet + int position = 0; + while (queue.Count > 0) + { + // dequeue + ArraySegment message = queue.Dequeue(); + + // write header (size) into buffer at position + Utils.IntToBytesBigEndianNonAlloc(message.Count, payload, position); + position += 4; + + // copy message into payload at position + Buffer.BlockCopy(message.Array, message.Offset, payload, position, message.Count); + position += message.Count; + + // return to pool so it can be reused (avoids allocations!) + pool.Return(message.Array); + } + + // we did serialize something + return true; + } + } + + public void Clear() + { + // pool & queue usage always needs to be locked + lock (this) + { + // clear queue, but via dequeue to return each byte[] to pool + while (queue.Count > 0) + { + pool.Return(queue.Dequeue().Array); + } + } + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs.meta index cf1415f..9a988a0 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: d490021c2e6a64374bc88168cec75c70 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: d490021c2e6a64374bc88168cec75c70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs index 7cfd73c..e2be5d2 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs @@ -1,67 +1,67 @@ -using System; -using System.IO; -using System.Net.Sockets; - -namespace Telepathy -{ - public static class NetworkStreamExtensions - { - // .Read returns '0' if remote closed the connection but throws an - // IOException if we voluntarily closed our own connection. - // - // let's add a ReadSafely method that returns '0' in both cases so we don't - // have to worry about exceptions, since a disconnect is a disconnect... - public static int ReadSafely(this NetworkStream stream, byte[] buffer, int offset, int size) - { - try - { - return stream.Read(buffer, offset, size); - } - // IOException happens if we voluntarily closed our own connection. - catch (IOException) - { - return 0; - } - // ObjectDisposedException can be thrown if Client.Disconnect() - // disposes the stream, while we are still trying to read here. - // catching it fixes https://github.com/vis2k/Telepathy/pull/104 - catch (ObjectDisposedException) - { - return 0; - } - } - - // helper function to read EXACTLY 'n' bytes - // -> default .Read reads up to 'n' bytes. this function reads exactly - // 'n' bytes - // -> this is blocking until 'n' bytes were received - // -> immediately returns false in case of disconnects - public static bool ReadExactly(this NetworkStream stream, byte[] buffer, int amount) - { - // there might not be enough bytes in the TCP buffer for .Read to read - // the whole amount at once, so we need to keep trying until we have all - // the bytes (blocking) - // - // note: this just is a faster version of reading one after another: - // for (int i = 0; i < amount; ++i) - // if (stream.Read(buffer, i, 1) == 0) - // return false; - // return true; - int bytesRead = 0; - while (bytesRead < amount) - { - // read up to 'remaining' bytes with the 'safe' read extension - int remaining = amount - bytesRead; - int result = stream.ReadSafely(buffer, bytesRead, remaining); - - // .Read returns 0 if disconnected - if (result == 0) - return false; - - // otherwise add to bytes read - bytesRead += result; - } - return true; - } - } +using System; +using System.IO; +using System.Net.Sockets; + +namespace Telepathy +{ + public static class NetworkStreamExtensions + { + // .Read returns '0' if remote closed the connection but throws an + // IOException if we voluntarily closed our own connection. + // + // let's add a ReadSafely method that returns '0' in both cases so we don't + // have to worry about exceptions, since a disconnect is a disconnect... + public static int ReadSafely(this NetworkStream stream, byte[] buffer, int offset, int size) + { + try + { + return stream.Read(buffer, offset, size); + } + // IOException happens if we voluntarily closed our own connection. + catch (IOException) + { + return 0; + } + // ObjectDisposedException can be thrown if Client.Disconnect() + // disposes the stream, while we are still trying to read here. + // catching it fixes https://github.com/vis2k/Telepathy/pull/104 + catch (ObjectDisposedException) + { + return 0; + } + } + + // helper function to read EXACTLY 'n' bytes + // -> default .Read reads up to 'n' bytes. this function reads exactly + // 'n' bytes + // -> this is blocking until 'n' bytes were received + // -> immediately returns false in case of disconnects + public static bool ReadExactly(this NetworkStream stream, byte[] buffer, int amount) + { + // there might not be enough bytes in the TCP buffer for .Read to read + // the whole amount at once, so we need to keep trying until we have all + // the bytes (blocking) + // + // note: this just is a faster version of reading one after another: + // for (int i = 0; i < amount; ++i) + // if (stream.Read(buffer, i, 1) == 0) + // return false; + // return true; + int bytesRead = 0; + while (bytesRead < amount) + { + // read up to 'remaining' bytes with the 'safe' read extension + int remaining = amount - bytesRead; + int result = stream.ReadSafely(buffer, bytesRead, remaining); + + // .Read returns 0 if disconnected + if (result == 0) + return false; + + // otherwise add to bytes read + bytesRead += result; + } + return true; + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta index e7e5744..abcc60b 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 7a8076c43fa8d4d45831adae232d4d3c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 7a8076c43fa8d4d45831adae232d4d3c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs index 4ec4fd2..4418c06 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs @@ -1,34 +1,34 @@ -// pool to avoid allocations. originally from libuv2k. -using System; -using System.Collections.Generic; - -namespace Telepathy -{ - public class Pool - { - // objects - readonly Stack objects = new Stack(); - - // some types might need additional parameters in their constructor, so - // we use a Func generator - readonly Func objectGenerator; - - // constructor - public Pool(Func objectGenerator) - { - this.objectGenerator = objectGenerator; - } - - // take an element from the pool, or create a new one if empty - public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator(); - - // return an element to the pool - public void Return(T item) => objects.Push(item); - - // clear the pool with the disposer function applied to each object - public void Clear() => objects.Clear(); - - // count to see how many objects are in the pool. useful for tests. - public int Count() => objects.Count; - } +// pool to avoid allocations. originally from libuv2k. +using System; +using System.Collections.Generic; + +namespace Telepathy +{ + public class Pool + { + // objects + readonly Stack objects = new Stack(); + + // some types might need additional parameters in their constructor, so + // we use a Func generator + readonly Func objectGenerator; + + // constructor + public Pool(Func objectGenerator) + { + this.objectGenerator = objectGenerator; + } + + // take an element from the pool, or create a new one if empty + public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator(); + + // return an element to the pool + public void Return(T item) => objects.Push(item); + + // clear the pool with the disposer function applied to each object + public void Clear() => objects.Clear(); + + // count to see how many objects are in the pool. useful for tests. + public int Count() => objects.Count; + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs.meta index 9a7dafc..3422560 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 6d3e530f6872642ec81e9b8b76277c93 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 6d3e530f6872642ec81e9b8b76277c93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs index 0b4ada7..a613fef 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs @@ -1,401 +1,401 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Threading; - -namespace Telepathy -{ - public class Server : Common - { - // events to hook into - // => OnData uses ArraySegment for allocation free receives later - public Action OnConnected; - public Action> OnData; - public Action OnDisconnected; - - // listener - public TcpListener listener; - Thread listenerThread; - - // disconnect if send queue gets too big. - // -> avoids ever growing queue memory if network is slower than input - // -> disconnecting is great for load balancing. better to disconnect - // one connection than risking every connection / the whole server - // -> huge queue would introduce multiple seconds of latency anyway - // - // Mirror/DOTSNET use MaxMessageSize batching, so for a 16kb max size: - // limit = 1,000 means 16 MB of memory/connection - // limit = 10,000 means 160 MB of memory/connection - public int SendQueueLimit = 10000; - public int ReceiveQueueLimit = 10000; - - // thread safe pipe for received messages - // IMPORTANT: unfortunately using one pipe per connection is way slower - // when testing 150 CCU. we need to use one pipe for all - // connections. this scales beautifully. - protected MagnificentReceivePipe receivePipe; - - // pipe count, useful for debugging / benchmarks - public int ReceivePipeTotalCount => receivePipe.TotalCount; - - // clients with - readonly ConcurrentDictionary clients = new ConcurrentDictionary(); - - // connectionId counter - int counter; - - // public next id function in case someone needs to reserve an id - // (e.g. if hostMode should always have 0 connection and external - // connections should start at 1, etc.) - public int NextConnectionId() - { - int id = Interlocked.Increment(ref counter); - - // it's very unlikely that we reach the uint limit of 2 billion. - // even with 1 new connection per second, this would take 68 years. - // -> but if it happens, then we should throw an exception because - // the caller probably should stop accepting clients. - // -> it's hardly worth using 'bool Next(out id)' for that case - // because it's just so unlikely. - if (id == int.MaxValue) - { - throw new Exception("connection id limit reached: " + id); - } - - return id; - } - - // check if the server is running - public bool Active => listenerThread != null && listenerThread.IsAlive; - - // constructor - public Server(int MaxMessageSize) : base(MaxMessageSize) {} - - // the listener thread's listen function - // note: no maxConnections parameter. high level API should handle that. - // (Transport can't send a 'too full' message anyway) - void Listen(int port) - { - // absolutely must wrap with try/catch, otherwise thread - // exceptions are silent - try - { - // start listener on all IPv4 and IPv6 address via .Create - listener = TcpListener.Create(port); - listener.Server.NoDelay = NoDelay; - // IMPORTANT: do not set send/receive timeouts on listener. - // On linux setting the recv timeout will cause the blocking - // Accept call to timeout with EACCEPT (which mono interprets - // as EWOULDBLOCK). - // https://stackoverflow.com/questions/1917814/eagain-error-for-accept-on-blocking-socket/1918118#1918118 - // => fixes https://github.com/vis2k/Mirror/issues/2695 - // - //listener.Server.SendTimeout = SendTimeout; - //listener.Server.ReceiveTimeout = ReceiveTimeout; - listener.Start(); - Log.Info("Server: listening port=" + port); - - // keep accepting new clients - while (true) - { - // wait and accept new client - // note: 'using' sucks here because it will try to - // dispose after thread was started but we still need it - // in the thread - TcpClient client = listener.AcceptTcpClient(); - - // set socket options - client.NoDelay = NoDelay; - client.SendTimeout = SendTimeout; - client.ReceiveTimeout = ReceiveTimeout; - - // generate the next connection id (thread safely) - int connectionId = NextConnectionId(); - - // add to dict immediately - ConnectionState connection = new ConnectionState(client, MaxMessageSize); - clients[connectionId] = connection; - - // spawn a send thread for each client - Thread sendThread = new Thread(() => - { - // wrap in try-catch, otherwise Thread exceptions - // are silent - try - { - // run the send loop - // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! - ThreadFunctions.SendLoop(connectionId, client, connection.sendPipe, connection.sendPending); - } - catch (ThreadAbortException) - { - // happens on stop. don't log anything. - // (we catch it in SendLoop too, but it still gets - // through to here when aborting. don't show an - // error.) - } - catch (Exception exception) - { - Log.Error("Server send thread exception: " + exception); - } - }); - sendThread.IsBackground = true; - sendThread.Start(); - - // spawn a receive thread for each client - Thread receiveThread = new Thread(() => - { - // wrap in try-catch, otherwise Thread exceptions - // are silent - try - { - // run the receive loop - // (receive pipe is shared across all loops) - ThreadFunctions.ReceiveLoop(connectionId, client, MaxMessageSize, receivePipe, ReceiveQueueLimit); - - // IMPORTANT: do NOT remove from clients after the - // thread ends. need to do it in Tick() so that the - // disconnect event in the pipe is still processed. - // (removing client immediately would mean that the - // pipe is lost and the disconnect event is never - // processed) - - // sendthread might be waiting on ManualResetEvent, - // so let's make sure to end it if the connection - // closed. - // otherwise the send thread would only end if it's - // actually sending data while the connection is - // closed. - sendThread.Interrupt(); - } - catch (Exception exception) - { - Log.Error("Server client thread exception: " + exception); - } - }); - receiveThread.IsBackground = true; - receiveThread.Start(); - } - } - catch (ThreadAbortException exception) - { - // UnityEditor causes AbortException if thread is still - // running when we press Play again next time. that's okay. - Log.Info("Server thread aborted. That's okay. " + exception); - } - catch (SocketException exception) - { - // calling StopServer will interrupt this thread with a - // 'SocketException: interrupted'. that's okay. - Log.Info("Server Thread stopped. That's okay. " + exception); - } - catch (Exception exception) - { - // something went wrong. probably important. - Log.Error("Server Exception: " + exception); - } - } - - // start listening for new connections in a background thread and spawn - // a new thread for each one. - public bool Start(int port) - { - // not if already started - if (Active) return false; - - // create receive pipe with max message size for pooling - // => create new pipes every time! - // if an old receive thread is still finishing up, it might still - // be using the old pipes. we don't want to risk any old data for - // our new start here. - receivePipe = new MagnificentReceivePipe(MaxMessageSize); - - // start the listener thread - // (on low priority. if main thread is too busy then there is not - // much value in accepting even more clients) - Log.Info("Server: Start port=" + port); - listenerThread = new Thread(() => { Listen(port); }); - listenerThread.IsBackground = true; - listenerThread.Priority = ThreadPriority.BelowNormal; - listenerThread.Start(); - return true; - } - - public void Stop() - { - // only if started - if (!Active) return; - - Log.Info("Server: stopping..."); - - // stop listening to connections so that no one can connect while we - // close the client connections - // (might be null if we call Stop so quickly after Start that the - // thread was interrupted before even creating the listener) - listener?.Stop(); - - // kill listener thread at all costs. only way to guarantee that - // .Active is immediately false after Stop. - // -> calling .Join would sometimes wait forever - listenerThread?.Interrupt(); - listenerThread = null; - - // close all client connections - foreach (KeyValuePair kvp in clients) - { - TcpClient client = kvp.Value.client; - // close the stream if not closed yet. it may have been closed - // by a disconnect already, so use try/catch - try { client.GetStream().Close(); } catch {} - client.Close(); - } - - // clear clients list - clients.Clear(); - - // reset the counter in case we start up again so - // clients get connection ID's starting from 1 - counter = 0; - } - - // send message to client using socket connection. - // arraysegment for allocation free sends later. - // -> the segment's array is only used until Send() returns! - public bool Send(int connectionId, ArraySegment message) - { - // respect max message size to avoid allocation attacks. - if (message.Count <= MaxMessageSize) - { - // find the connection - if (clients.TryGetValue(connectionId, out ConnectionState connection)) - { - // check send pipe limit - if (connection.sendPipe.Count < SendQueueLimit) - { - // add to thread safe send pipe and return immediately. - // calling Send here would be blocking (sometimes for long - // times if other side lags or wire was disconnected) - connection.sendPipe.Enqueue(message); - connection.sendPending.Set(); // interrupt SendThread WaitOne() - return true; - } - // disconnect if send queue gets too big. - // -> avoids ever growing queue memory if network is slower - // than input - // -> disconnecting is great for load balancing. better to - // disconnect one connection than risking every - // connection / the whole server - // - // note: while SendThread always grabs the WHOLE send queue - // immediately, it's still possible that the sending - // blocks for so long that the send queue just gets - // way too big. have a limit - better safe than sorry. - else - { - // log the reason - Log.Warning($"Server.Send: sendPipe for connection {connectionId} reached limit of {SendQueueLimit}. This can happen if we call send faster than the network can process messages. Disconnecting this connection for load balancing."); - - // just close it. send thread will take care of the rest. - connection.client.Close(); - return false; - } - } - - // sending to an invalid connectionId is expected sometimes. - // for example, if a client disconnects, the server might still - // try to send for one frame before it calls GetNextMessages - // again and realizes that a disconnect happened. - // so let's not spam the console with log messages. - //Logger.Log("Server.Send: invalid connectionId: " + connectionId); - return false; - } - Log.Error("Server.Send: message too big: " + message.Count + ". Limit: " + MaxMessageSize); - return false; - } - - // client's ip is sometimes needed by the server, e.g. for bans - public string GetClientAddress(int connectionId) - { - // find the connection - if (clients.TryGetValue(connectionId, out ConnectionState connection)) - { - return ((IPEndPoint)connection.client.Client.RemoteEndPoint).Address.ToString(); - } - return ""; - } - - // disconnect (kick) a client - public bool Disconnect(int connectionId) - { - // find the connection - if (clients.TryGetValue(connectionId, out ConnectionState connection)) - { - // just close it. send thread will take care of the rest. - connection.client.Close(); - Log.Info("Server.Disconnect connectionId:" + connectionId); - return true; - } - return false; - } - - // tick: processes up to 'limit' messages for each connection - // => limit parameter to avoid deadlocks / too long freezes if server or - // client is too slow to process network load - // => Mirror & DOTSNET need to have a process limit anyway. - // might as well do it here and make life easier. - // => returns amount of remaining messages to process, so the caller - // can call tick again as many times as needed (or up to a limit) - // - // Tick() may process multiple messages, but Mirror needs a way to stop - // processing immediately if a scene change messages arrives. Mirror - // can't process any other messages during a scene change. - // (could be useful for others too) - // => make sure to allocate the lambda only once in transports - public int Tick(int processLimit, Func checkEnabled = null) - { - // only if pipe was created yet (after start()) - if (receivePipe == null) - return 0; - - // process up to 'processLimit' messages for this connection - for (int i = 0; i < processLimit; ++i) - { - // check enabled in case a Mirror scene message arrived - if (checkEnabled != null && !checkEnabled()) - break; - - // peek first. allows us to process the first queued entry while - // still keeping the pooled byte[] alive by not removing anything. - if (receivePipe.TryPeek(out int connectionId, out EventType eventType, out ArraySegment message)) - { - switch (eventType) - { - case EventType.Connected: - OnConnected?.Invoke(connectionId); - break; - case EventType.Data: - OnData?.Invoke(connectionId, message); - break; - case EventType.Disconnected: - OnDisconnected?.Invoke(connectionId); - // remove disconnected connection now that the final - // disconnected message was processed. - clients.TryRemove(connectionId, out ConnectionState _); - break; - } - - // IMPORTANT: now dequeue and return it to pool AFTER we are - // done processing the event. - receivePipe.TryDequeue(); - } - // no more messages. stop the loop. - else break; - } - - // return what's left to process for next time - return receivePipe.TotalCount; - } - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Telepathy +{ + public class Server : Common + { + // events to hook into + // => OnData uses ArraySegment for allocation free receives later + public Action OnConnected; + public Action> OnData; + public Action OnDisconnected; + + // listener + public TcpListener listener; + Thread listenerThread; + + // disconnect if send queue gets too big. + // -> avoids ever growing queue memory if network is slower than input + // -> disconnecting is great for load balancing. better to disconnect + // one connection than risking every connection / the whole server + // -> huge queue would introduce multiple seconds of latency anyway + // + // Mirror/DOTSNET use MaxMessageSize batching, so for a 16kb max size: + // limit = 1,000 means 16 MB of memory/connection + // limit = 10,000 means 160 MB of memory/connection + public int SendQueueLimit = 10000; + public int ReceiveQueueLimit = 10000; + + // thread safe pipe for received messages + // IMPORTANT: unfortunately using one pipe per connection is way slower + // when testing 150 CCU. we need to use one pipe for all + // connections. this scales beautifully. + protected MagnificentReceivePipe receivePipe; + + // pipe count, useful for debugging / benchmarks + public int ReceivePipeTotalCount => receivePipe.TotalCount; + + // clients with + readonly ConcurrentDictionary clients = new ConcurrentDictionary(); + + // connectionId counter + int counter; + + // public next id function in case someone needs to reserve an id + // (e.g. if hostMode should always have 0 connection and external + // connections should start at 1, etc.) + public int NextConnectionId() + { + int id = Interlocked.Increment(ref counter); + + // it's very unlikely that we reach the uint limit of 2 billion. + // even with 1 new connection per second, this would take 68 years. + // -> but if it happens, then we should throw an exception because + // the caller probably should stop accepting clients. + // -> it's hardly worth using 'bool Next(out id)' for that case + // because it's just so unlikely. + if (id == int.MaxValue) + { + throw new Exception("connection id limit reached: " + id); + } + + return id; + } + + // check if the server is running + public bool Active => listenerThread != null && listenerThread.IsAlive; + + // constructor + public Server(int MaxMessageSize) : base(MaxMessageSize) {} + + // the listener thread's listen function + // note: no maxConnections parameter. high level API should handle that. + // (Transport can't send a 'too full' message anyway) + void Listen(int port) + { + // absolutely must wrap with try/catch, otherwise thread + // exceptions are silent + try + { + // start listener on all IPv4 and IPv6 address via .Create + listener = TcpListener.Create(port); + listener.Server.NoDelay = NoDelay; + // IMPORTANT: do not set send/receive timeouts on listener. + // On linux setting the recv timeout will cause the blocking + // Accept call to timeout with EACCEPT (which mono interprets + // as EWOULDBLOCK). + // https://stackoverflow.com/questions/1917814/eagain-error-for-accept-on-blocking-socket/1918118#1918118 + // => fixes https://github.com/vis2k/Mirror/issues/2695 + // + //listener.Server.SendTimeout = SendTimeout; + //listener.Server.ReceiveTimeout = ReceiveTimeout; + listener.Start(); + Log.Info("Server: listening port=" + port); + + // keep accepting new clients + while (true) + { + // wait and accept new client + // note: 'using' sucks here because it will try to + // dispose after thread was started but we still need it + // in the thread + TcpClient client = listener.AcceptTcpClient(); + + // set socket options + client.NoDelay = NoDelay; + client.SendTimeout = SendTimeout; + client.ReceiveTimeout = ReceiveTimeout; + + // generate the next connection id (thread safely) + int connectionId = NextConnectionId(); + + // add to dict immediately + ConnectionState connection = new ConnectionState(client, MaxMessageSize); + clients[connectionId] = connection; + + // spawn a send thread for each client + Thread sendThread = new Thread(() => + { + // wrap in try-catch, otherwise Thread exceptions + // are silent + try + { + // run the send loop + // IMPORTANT: DO NOT SHARE STATE ACROSS MULTIPLE THREADS! + ThreadFunctions.SendLoop(connectionId, client, connection.sendPipe, connection.sendPending); + } + catch (ThreadAbortException) + { + // happens on stop. don't log anything. + // (we catch it in SendLoop too, but it still gets + // through to here when aborting. don't show an + // error.) + } + catch (Exception exception) + { + Log.Error("Server send thread exception: " + exception); + } + }); + sendThread.IsBackground = true; + sendThread.Start(); + + // spawn a receive thread for each client + Thread receiveThread = new Thread(() => + { + // wrap in try-catch, otherwise Thread exceptions + // are silent + try + { + // run the receive loop + // (receive pipe is shared across all loops) + ThreadFunctions.ReceiveLoop(connectionId, client, MaxMessageSize, receivePipe, ReceiveQueueLimit); + + // IMPORTANT: do NOT remove from clients after the + // thread ends. need to do it in Tick() so that the + // disconnect event in the pipe is still processed. + // (removing client immediately would mean that the + // pipe is lost and the disconnect event is never + // processed) + + // sendthread might be waiting on ManualResetEvent, + // so let's make sure to end it if the connection + // closed. + // otherwise the send thread would only end if it's + // actually sending data while the connection is + // closed. + sendThread.Interrupt(); + } + catch (Exception exception) + { + Log.Error("Server client thread exception: " + exception); + } + }); + receiveThread.IsBackground = true; + receiveThread.Start(); + } + } + catch (ThreadAbortException exception) + { + // UnityEditor causes AbortException if thread is still + // running when we press Play again next time. that's okay. + Log.Info("Server thread aborted. That's okay. " + exception); + } + catch (SocketException exception) + { + // calling StopServer will interrupt this thread with a + // 'SocketException: interrupted'. that's okay. + Log.Info("Server Thread stopped. That's okay. " + exception); + } + catch (Exception exception) + { + // something went wrong. probably important. + Log.Error("Server Exception: " + exception); + } + } + + // start listening for new connections in a background thread and spawn + // a new thread for each one. + public bool Start(int port) + { + // not if already started + if (Active) return false; + + // create receive pipe with max message size for pooling + // => create new pipes every time! + // if an old receive thread is still finishing up, it might still + // be using the old pipes. we don't want to risk any old data for + // our new start here. + receivePipe = new MagnificentReceivePipe(MaxMessageSize); + + // start the listener thread + // (on low priority. if main thread is too busy then there is not + // much value in accepting even more clients) + Log.Info("Server: Start port=" + port); + listenerThread = new Thread(() => { Listen(port); }); + listenerThread.IsBackground = true; + listenerThread.Priority = ThreadPriority.BelowNormal; + listenerThread.Start(); + return true; + } + + public void Stop() + { + // only if started + if (!Active) return; + + Log.Info("Server: stopping..."); + + // stop listening to connections so that no one can connect while we + // close the client connections + // (might be null if we call Stop so quickly after Start that the + // thread was interrupted before even creating the listener) + listener?.Stop(); + + // kill listener thread at all costs. only way to guarantee that + // .Active is immediately false after Stop. + // -> calling .Join would sometimes wait forever + listenerThread?.Interrupt(); + listenerThread = null; + + // close all client connections + foreach (KeyValuePair kvp in clients) + { + TcpClient client = kvp.Value.client; + // close the stream if not closed yet. it may have been closed + // by a disconnect already, so use try/catch + try { client.GetStream().Close(); } catch {} + client.Close(); + } + + // clear clients list + clients.Clear(); + + // reset the counter in case we start up again so + // clients get connection ID's starting from 1 + counter = 0; + } + + // send message to client using socket connection. + // arraysegment for allocation free sends later. + // -> the segment's array is only used until Send() returns! + public bool Send(int connectionId, ArraySegment message) + { + // respect max message size to avoid allocation attacks. + if (message.Count <= MaxMessageSize) + { + // find the connection + if (clients.TryGetValue(connectionId, out ConnectionState connection)) + { + // check send pipe limit + if (connection.sendPipe.Count < SendQueueLimit) + { + // add to thread safe send pipe and return immediately. + // calling Send here would be blocking (sometimes for long + // times if other side lags or wire was disconnected) + connection.sendPipe.Enqueue(message); + connection.sendPending.Set(); // interrupt SendThread WaitOne() + return true; + } + // disconnect if send queue gets too big. + // -> avoids ever growing queue memory if network is slower + // than input + // -> disconnecting is great for load balancing. better to + // disconnect one connection than risking every + // connection / the whole server + // + // note: while SendThread always grabs the WHOLE send queue + // immediately, it's still possible that the sending + // blocks for so long that the send queue just gets + // way too big. have a limit - better safe than sorry. + else + { + // log the reason + Log.Warning($"Server.Send: sendPipe for connection {connectionId} reached limit of {SendQueueLimit}. This can happen if we call send faster than the network can process messages. Disconnecting this connection for load balancing."); + + // just close it. send thread will take care of the rest. + connection.client.Close(); + return false; + } + } + + // sending to an invalid connectionId is expected sometimes. + // for example, if a client disconnects, the server might still + // try to send for one frame before it calls GetNextMessages + // again and realizes that a disconnect happened. + // so let's not spam the console with log messages. + //Logger.Log("Server.Send: invalid connectionId: " + connectionId); + return false; + } + Log.Error("Server.Send: message too big: " + message.Count + ". Limit: " + MaxMessageSize); + return false; + } + + // client's ip is sometimes needed by the server, e.g. for bans + public string GetClientAddress(int connectionId) + { + // find the connection + if (clients.TryGetValue(connectionId, out ConnectionState connection)) + { + return ((IPEndPoint)connection.client.Client.RemoteEndPoint).Address.ToString(); + } + return ""; + } + + // disconnect (kick) a client + public bool Disconnect(int connectionId) + { + // find the connection + if (clients.TryGetValue(connectionId, out ConnectionState connection)) + { + // just close it. send thread will take care of the rest. + connection.client.Close(); + Log.Info("Server.Disconnect connectionId:" + connectionId); + return true; + } + return false; + } + + // tick: processes up to 'limit' messages for each connection + // => limit parameter to avoid deadlocks / too long freezes if server or + // client is too slow to process network load + // => Mirror & DOTSNET need to have a process limit anyway. + // might as well do it here and make life easier. + // => returns amount of remaining messages to process, so the caller + // can call tick again as many times as needed (or up to a limit) + // + // Tick() may process multiple messages, but Mirror needs a way to stop + // processing immediately if a scene change messages arrives. Mirror + // can't process any other messages during a scene change. + // (could be useful for others too) + // => make sure to allocate the lambda only once in transports + public int Tick(int processLimit, Func checkEnabled = null) + { + // only if pipe was created yet (after start()) + if (receivePipe == null) + return 0; + + // process up to 'processLimit' messages for this connection + for (int i = 0; i < processLimit; ++i) + { + // check enabled in case a Mirror scene message arrived + if (checkEnabled != null && !checkEnabled()) + break; + + // peek first. allows us to process the first queued entry while + // still keeping the pooled byte[] alive by not removing anything. + if (receivePipe.TryPeek(out int connectionId, out EventType eventType, out ArraySegment message)) + { + switch (eventType) + { + case EventType.Connected: + OnConnected?.Invoke(connectionId); + break; + case EventType.Data: + OnData?.Invoke(connectionId, message); + break; + case EventType.Disconnected: + OnDisconnected?.Invoke(connectionId); + // remove disconnected connection now that the final + // disconnected message was processed. + clients.TryRemove(connectionId, out ConnectionState _); + break; + } + + // IMPORTANT: now dequeue and return it to pool AFTER we are + // done processing the event. + receivePipe.TryDequeue(); + } + // no more messages. stop the loop. + else break; + } + + // return what's left to process for next time + return receivePipe.TotalCount; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs.meta index 9cee8b7..ca32f81 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: fb98a16841ccc4338a7e0b4e59136563 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: fb98a16841ccc4338a7e0b4e59136563 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef index cd8d16a..3f6e633 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef @@ -1,12 +1,12 @@ -{ - "name": "Telepathy", - "references": [], - "optionalUnityReferences": [], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [] +{ + "name": "Telepathy", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef.meta index 572c127..e7fc8cd 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: 725ee7191c021de4dbf9269590ded755 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 725ee7191c021de4dbf9269590ded755 +AssemblyDefinitionImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs index 6f026c9..a2615a9 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs @@ -1,244 +1,244 @@ -// IMPORTANT -// force all thread functions to be STATIC. -// => Common.Send/ReceiveLoop is EXTREMELY DANGEROUS because it's too easy to -// accidentally share Common state between threads. -// => header buffer, payload etc. were accidentally shared once after changing -// the thread functions from static to non static -// => C# does not automatically detect data races. best we can do is move all of -// our thread code into static functions and pass all state into them -// -// let's even keep them in a STATIC CLASS so it's 100% obvious that this should -// NOT EVER be changed to non static! -using System; -using System.Net.Sockets; -using System.Threading; - -namespace Telepathy -{ - public static class ThreadFunctions - { - // send message (via stream) with the message structure - // this function is blocking sometimes! - // (e.g. if someone has high latency or wire was cut off) - // -> payload is of multiple < parts - public static bool SendMessagesBlocking(NetworkStream stream, byte[] payload, int packetSize) - { - // stream.Write throws exceptions if client sends with high - // frequency and the server stops - try - { - // write the whole thing - stream.Write(payload, 0, packetSize); - return true; - } - catch (Exception exception) - { - // log as regular message because servers do shut down sometimes - Log.Info("Send: stream.Write exception: " + exception); - return false; - } - } - // read message (via stream) blocking. - // writes into byte[] and returns bytes written to avoid allocations. - public static bool ReadMessageBlocking(NetworkStream stream, int MaxMessageSize, byte[] headerBuffer, byte[] payloadBuffer, out int size) - { - size = 0; - - // buffer needs to be of Header + MaxMessageSize - if (payloadBuffer.Length != 4 + MaxMessageSize) - { - Log.Error($"ReadMessageBlocking: payloadBuffer needs to be of size 4 + MaxMessageSize = {4 + MaxMessageSize} instead of {payloadBuffer.Length}"); - return false; - } - - // read exactly 4 bytes for header (blocking) - if (!stream.ReadExactly(headerBuffer, 4)) - return false; - - // convert to int - size = Utils.BytesToIntBigEndian(headerBuffer); - - // protect against allocation attacks. an attacker might send - // multiple fake '2GB header' packets in a row, causing the server - // to allocate multiple 2GB byte arrays and run out of memory. - // - // also protect against size <= 0 which would cause issues - if (size > 0 && size <= MaxMessageSize) - { - // read exactly 'size' bytes for content (blocking) - return stream.ReadExactly(payloadBuffer, size); - } - Log.Warning("ReadMessageBlocking: possible header attack with a header of: " + size + " bytes."); - return false; - } - - // thread receive function is the same for client and server's clients - public static void ReceiveLoop(int connectionId, TcpClient client, int MaxMessageSize, MagnificentReceivePipe receivePipe, int QueueLimit) - { - // get NetworkStream from client - NetworkStream stream = client.GetStream(); - - // every receive loop needs it's own receive buffer of - // HeaderSize + MaxMessageSize - // to avoid runtime allocations. - // - // IMPORTANT: DO NOT make this a member, otherwise every connection - // on the server would use the same buffer simulatenously - byte[] receiveBuffer = new byte[4 + MaxMessageSize]; - - // avoid header[4] allocations - // - // IMPORTANT: DO NOT make this a member, otherwise every connection - // on the server would use the same buffer simulatenously - byte[] headerBuffer = new byte[4]; - - // absolutely must wrap with try/catch, otherwise thread exceptions - // are silent - try - { - // add connected event to pipe - receivePipe.Enqueue(connectionId, EventType.Connected, default); - - // let's talk about reading data. - // -> normally we would read as much as possible and then - // extract as many , messages - // as we received this time. this is really complicated - // and expensive to do though - // -> instead we use a trick: - // Read(2) -> size - // Read(size) -> content - // repeat - // Read is blocking, but it doesn't matter since the - // best thing to do until the full message arrives, - // is to wait. - // => this is the most elegant AND fast solution. - // + no resizing - // + no extra allocations, just one for the content - // + no crazy extraction logic - while (true) - { - // read the next message (blocking) or stop if stream closed - if (!ReadMessageBlocking(stream, MaxMessageSize, headerBuffer, receiveBuffer, out int size)) - // break instead of return so stream close still happens! - break; - - // create arraysegment for the read message - ArraySegment message = new ArraySegment(receiveBuffer, 0, size); - - // send to main thread via pipe - // -> it'll copy the message internally so we can reuse the - // receive buffer for next read! - receivePipe.Enqueue(connectionId, EventType.Data, message); - - // disconnect if receive pipe gets too big for this connectionId. - // -> avoids ever growing queue memory if network is slower - // than input - // -> disconnecting is great for load balancing. better to - // disconnect one connection than risking every - // connection / the whole server - if (receivePipe.Count(connectionId) >= QueueLimit) - { - // log the reason - Log.Warning($"receivePipe reached limit of {QueueLimit} for connectionId {connectionId}. This can happen if network messages come in way faster than we manage to process them. Disconnecting this connection for load balancing."); - - // IMPORTANT: do NOT clear the whole queue. we use one - // queue for all connections. - //receivePipe.Clear(); - - // just break. the finally{} will close everything. - break; - } - } - } - catch (Exception exception) - { - // something went wrong. the thread was interrupted or the - // connection closed or we closed our own connection or ... - // -> either way we should stop gracefully - Log.Info("ReceiveLoop: finished receive function for connectionId=" + connectionId + " reason: " + exception); - } - finally - { - // clean up no matter what - stream.Close(); - client.Close(); - - // add 'Disconnected' message after disconnecting properly. - // -> always AFTER closing the streams to avoid a race condition - // where Disconnected -> Reconnect wouldn't work because - // Connected is still true for a short moment before the stream - // would be closed. - receivePipe.Enqueue(connectionId, EventType.Disconnected, default); - } - } - // thread send function - // note: we really do need one per connection, so that if one connection - // blocks, the rest will still continue to get sends - public static void SendLoop(int connectionId, TcpClient client, MagnificentSendPipe sendPipe, ManualResetEvent sendPending) - { - // get NetworkStream from client - NetworkStream stream = client.GetStream(); - - // avoid payload[packetSize] allocations. size increases dynamically as - // needed for batching. - // - // IMPORTANT: DO NOT make this a member, otherwise every connection - // on the server would use the same buffer simulatenously - byte[] payload = null; - - try - { - while (client.Connected) // try this. client will get closed eventually. - { - // reset ManualResetEvent before we do anything else. this - // way there is no race condition. if Send() is called again - // while in here then it will be properly detected next time - // -> otherwise Send might be called right after dequeue but - // before .Reset, which would completely ignore it until - // the next Send call. - sendPending.Reset(); // WaitOne() blocks until .Set() again - - // dequeue & serialize all - // a locked{} TryDequeueAll is twice as fast as - // ConcurrentQueue, see SafeQueue.cs! - if (sendPipe.DequeueAndSerializeAll(ref payload, out int packetSize)) - { - // send messages (blocking) or stop if stream is closed - if (!SendMessagesBlocking(stream, payload, packetSize)) - // break instead of return so stream close still happens! - break; - } - - // don't choke up the CPU: wait until queue not empty anymore - sendPending.WaitOne(); - } - } - catch (ThreadAbortException) - { - // happens on stop. don't log anything. - } - catch (ThreadInterruptedException) - { - // happens if receive thread interrupts send thread. - } - catch (Exception exception) - { - // something went wrong. the thread was interrupted or the - // connection closed or we closed our own connection or ... - // -> either way we should stop gracefully - Log.Info("SendLoop Exception: connectionId=" + connectionId + " reason: " + exception); - } - finally - { - // clean up no matter what - // we might get SocketExceptions when sending if the 'host has - // failed to respond' - in which case we should close the connection - // which causes the ReceiveLoop to end and fire the Disconnected - // message. otherwise the connection would stay alive forever even - // though we can't send anymore. - stream.Close(); - client.Close(); - } - } - } +// IMPORTANT +// force all thread functions to be STATIC. +// => Common.Send/ReceiveLoop is EXTREMELY DANGEROUS because it's too easy to +// accidentally share Common state between threads. +// => header buffer, payload etc. were accidentally shared once after changing +// the thread functions from static to non static +// => C# does not automatically detect data races. best we can do is move all of +// our thread code into static functions and pass all state into them +// +// let's even keep them in a STATIC CLASS so it's 100% obvious that this should +// NOT EVER be changed to non static! +using System; +using System.Net.Sockets; +using System.Threading; + +namespace Telepathy +{ + public static class ThreadFunctions + { + // send message (via stream) with the message structure + // this function is blocking sometimes! + // (e.g. if someone has high latency or wire was cut off) + // -> payload is of multiple < parts + public static bool SendMessagesBlocking(NetworkStream stream, byte[] payload, int packetSize) + { + // stream.Write throws exceptions if client sends with high + // frequency and the server stops + try + { + // write the whole thing + stream.Write(payload, 0, packetSize); + return true; + } + catch (Exception exception) + { + // log as regular message because servers do shut down sometimes + Log.Info("Send: stream.Write exception: " + exception); + return false; + } + } + // read message (via stream) blocking. + // writes into byte[] and returns bytes written to avoid allocations. + public static bool ReadMessageBlocking(NetworkStream stream, int MaxMessageSize, byte[] headerBuffer, byte[] payloadBuffer, out int size) + { + size = 0; + + // buffer needs to be of Header + MaxMessageSize + if (payloadBuffer.Length != 4 + MaxMessageSize) + { + Log.Error($"ReadMessageBlocking: payloadBuffer needs to be of size 4 + MaxMessageSize = {4 + MaxMessageSize} instead of {payloadBuffer.Length}"); + return false; + } + + // read exactly 4 bytes for header (blocking) + if (!stream.ReadExactly(headerBuffer, 4)) + return false; + + // convert to int + size = Utils.BytesToIntBigEndian(headerBuffer); + + // protect against allocation attacks. an attacker might send + // multiple fake '2GB header' packets in a row, causing the server + // to allocate multiple 2GB byte arrays and run out of memory. + // + // also protect against size <= 0 which would cause issues + if (size > 0 && size <= MaxMessageSize) + { + // read exactly 'size' bytes for content (blocking) + return stream.ReadExactly(payloadBuffer, size); + } + Log.Warning("ReadMessageBlocking: possible header attack with a header of: " + size + " bytes."); + return false; + } + + // thread receive function is the same for client and server's clients + public static void ReceiveLoop(int connectionId, TcpClient client, int MaxMessageSize, MagnificentReceivePipe receivePipe, int QueueLimit) + { + // get NetworkStream from client + NetworkStream stream = client.GetStream(); + + // every receive loop needs it's own receive buffer of + // HeaderSize + MaxMessageSize + // to avoid runtime allocations. + // + // IMPORTANT: DO NOT make this a member, otherwise every connection + // on the server would use the same buffer simulatenously + byte[] receiveBuffer = new byte[4 + MaxMessageSize]; + + // avoid header[4] allocations + // + // IMPORTANT: DO NOT make this a member, otherwise every connection + // on the server would use the same buffer simulatenously + byte[] headerBuffer = new byte[4]; + + // absolutely must wrap with try/catch, otherwise thread exceptions + // are silent + try + { + // add connected event to pipe + receivePipe.Enqueue(connectionId, EventType.Connected, default); + + // let's talk about reading data. + // -> normally we would read as much as possible and then + // extract as many , messages + // as we received this time. this is really complicated + // and expensive to do though + // -> instead we use a trick: + // Read(2) -> size + // Read(size) -> content + // repeat + // Read is blocking, but it doesn't matter since the + // best thing to do until the full message arrives, + // is to wait. + // => this is the most elegant AND fast solution. + // + no resizing + // + no extra allocations, just one for the content + // + no crazy extraction logic + while (true) + { + // read the next message (blocking) or stop if stream closed + if (!ReadMessageBlocking(stream, MaxMessageSize, headerBuffer, receiveBuffer, out int size)) + // break instead of return so stream close still happens! + break; + + // create arraysegment for the read message + ArraySegment message = new ArraySegment(receiveBuffer, 0, size); + + // send to main thread via pipe + // -> it'll copy the message internally so we can reuse the + // receive buffer for next read! + receivePipe.Enqueue(connectionId, EventType.Data, message); + + // disconnect if receive pipe gets too big for this connectionId. + // -> avoids ever growing queue memory if network is slower + // than input + // -> disconnecting is great for load balancing. better to + // disconnect one connection than risking every + // connection / the whole server + if (receivePipe.Count(connectionId) >= QueueLimit) + { + // log the reason + Log.Warning($"receivePipe reached limit of {QueueLimit} for connectionId {connectionId}. This can happen if network messages come in way faster than we manage to process them. Disconnecting this connection for load balancing."); + + // IMPORTANT: do NOT clear the whole queue. we use one + // queue for all connections. + //receivePipe.Clear(); + + // just break. the finally{} will close everything. + break; + } + } + } + catch (Exception exception) + { + // something went wrong. the thread was interrupted or the + // connection closed or we closed our own connection or ... + // -> either way we should stop gracefully + Log.Info("ReceiveLoop: finished receive function for connectionId=" + connectionId + " reason: " + exception); + } + finally + { + // clean up no matter what + stream.Close(); + client.Close(); + + // add 'Disconnected' message after disconnecting properly. + // -> always AFTER closing the streams to avoid a race condition + // where Disconnected -> Reconnect wouldn't work because + // Connected is still true for a short moment before the stream + // would be closed. + receivePipe.Enqueue(connectionId, EventType.Disconnected, default); + } + } + // thread send function + // note: we really do need one per connection, so that if one connection + // blocks, the rest will still continue to get sends + public static void SendLoop(int connectionId, TcpClient client, MagnificentSendPipe sendPipe, ManualResetEvent sendPending) + { + // get NetworkStream from client + NetworkStream stream = client.GetStream(); + + // avoid payload[packetSize] allocations. size increases dynamically as + // needed for batching. + // + // IMPORTANT: DO NOT make this a member, otherwise every connection + // on the server would use the same buffer simulatenously + byte[] payload = null; + + try + { + while (client.Connected) // try this. client will get closed eventually. + { + // reset ManualResetEvent before we do anything else. this + // way there is no race condition. if Send() is called again + // while in here then it will be properly detected next time + // -> otherwise Send might be called right after dequeue but + // before .Reset, which would completely ignore it until + // the next Send call. + sendPending.Reset(); // WaitOne() blocks until .Set() again + + // dequeue & serialize all + // a locked{} TryDequeueAll is twice as fast as + // ConcurrentQueue, see SafeQueue.cs! + if (sendPipe.DequeueAndSerializeAll(ref payload, out int packetSize)) + { + // send messages (blocking) or stop if stream is closed + if (!SendMessagesBlocking(stream, payload, packetSize)) + // break instead of return so stream close still happens! + break; + } + + // don't choke up the CPU: wait until queue not empty anymore + sendPending.WaitOne(); + } + } + catch (ThreadAbortException) + { + // happens on stop. don't log anything. + } + catch (ThreadInterruptedException) + { + // happens if receive thread interrupts send thread. + } + catch (Exception exception) + { + // something went wrong. the thread was interrupted or the + // connection closed or we closed our own connection or ... + // -> either way we should stop gracefully + Log.Info("SendLoop Exception: connectionId=" + connectionId + " reason: " + exception); + } + finally + { + // clean up no matter what + // we might get SocketExceptions when sending if the 'host has + // failed to respond' - in which case we should close the connection + // which causes the ReceiveLoop to end and fire the Disconnected + // message. otherwise the connection would stay alive forever even + // though we can't send anymore. + stream.Close(); + client.Close(); + } + } + } } \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs.meta index ea536ac..9731903 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: d01598bf851164dc48a24c26913460b9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: d01598bf851164dc48a24c26913460b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs index 8f04fe9..6bf6540 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs @@ -1,23 +1,23 @@ -namespace Telepathy -{ - public static class Utils - { - // IntToBytes version that doesn't allocate a new byte[4] each time. - // -> important for MMO scale networking performance. - public static void IntToBytesBigEndianNonAlloc(int value, byte[] bytes, int offset = 0) - { - bytes[offset + 0] = (byte)(value >> 24); - bytes[offset + 1] = (byte)(value >> 16); - bytes[offset + 2] = (byte)(value >> 8); - bytes[offset + 3] = (byte)value; - } - - public static int BytesToIntBigEndian(byte[] bytes) - { - return (bytes[0] << 24) | - (bytes[1] << 16) | - (bytes[2] << 8) | - bytes[3]; - } - } -} +namespace Telepathy +{ + public static class Utils + { + // IntToBytes version that doesn't allocate a new byte[4] each time. + // -> important for MMO scale networking performance. + public static void IntToBytesBigEndianNonAlloc(int value, byte[] bytes, int offset = 0) + { + bytes[offset + 0] = (byte)(value >> 24); + bytes[offset + 1] = (byte)(value >> 16); + bytes[offset + 2] = (byte)(value >> 8); + bytes[offset + 3] = (byte)value; + } + + public static int BytesToIntBigEndian(byte[] bytes) + { + return (bytes[0] << 24) | + (bytes[1] << 16) | + (bytes[2] << 8) | + bytes[3]; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs.meta index 0a9253b..593f987 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 951d08c05297f4b3e8feb5bfcab86531 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 951d08c05297f4b3e8feb5bfcab86531 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION index 9ec0736..22232d5 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION @@ -1,62 +1,62 @@ -V1.8 [2021-06-02] -- fix: Do not set timeouts on listener (fixes https://github.com/vis2k/Mirror/issues/2695) -- fix: #104 - ReadSafely now catches ObjectDisposedException too - -V1.7 [2021-02-20] -- ReceiveTimeout: disabled by default for cases where people use Telepathy by - itself without pings etc. - -V1.6 [2021-02-10] -- configurable ReceiveTimeout to avoid TCPs high default timeout -- Server/Client receive queue limit now disconnects instead of showing a - warning. this is necessary for load balancing to avoid situations where one - spamming connection might fill the queue and slow down everyone else. - -V1.5 [2021-02-05] -- fix: client data races & flaky tests fixed by creating a new client state - object every time we connect. fixes data race where an old dieing thread - might still try to modify the current state -- fix: Client.ReceiveThreadFunction catches and ignores ObjectDisposedException - which can happen if Disconnect() closes and disposes the client, while the - ReceiveThread just starts up and still uses the client. -- Server/Client Tick() optional enabled check for Mirror scene changing - -V1.4 [2021-02-03] -- Server/Client.Tick: limit parameter added to process up to 'limit' messages. - makes Mirror & DOTSNET transports easier to implement -- stability: Server/Client send queue limit disconnects instead of showing a - warning. allows for load balancing. better to kick one connection and keep - the server running than slowing everything down for everyone. - -V1.3 [2021-02-02] -- perf: ReceivePipe: byte[] pool for allocation free receives (╯°□°)╯︵ ┻━┻ -- fix: header buffer, payload buffer data races because they were made non - static earlier. server threads would all access the same ones. - => all threaded code was moved into a static ThreadFunctions class to make it - 100% obvious that there should be no shared state in the future - -V1.2 [2021-02-02] -- Client/Server Tick & OnConnected/OnData/OnDisconnected events instead of - having the outside process messages via GetNextMessage. That's easier for - Mirror/DOTSNET and allows for allocation free data message processing later. -- MagnificientSend/RecvPipe to shield Telepathy from all the complexity -- perf: SendPipe: byte[] pool for allocation free sends (╯°□°)╯︵ ┻━┻ - -V1.1 [2021-02-01] -- stability: added more tests -- breaking: Server/Client.Send: ArraySegment parameter and copy internally so - that Transports don't need to worry about it -- perf: Buffer.BlockCopy instead of Array.Copy -- perf: SendMessageBlocking puts message header directly into payload now -- perf: receiveQueues use SafeQueue instead of ConcurrentQueue to avoid - allocations -- Common: removed static state -- perf: SafeQueue.TryDequeueAll: avoid queue.ToArray() allocations. copy into a - list instead. -- Logger.Log/LogWarning/LogError renamed to Log.Info/Warning/Error -- MaxMessageSize is now specified in constructor to prepare for pooling -- flaky tests are ignored for now -- smaller improvements - -V1.0 +V1.8 [2021-06-02] +- fix: Do not set timeouts on listener (fixes https://github.com/vis2k/Mirror/issues/2695) +- fix: #104 - ReadSafely now catches ObjectDisposedException too + +V1.7 [2021-02-20] +- ReceiveTimeout: disabled by default for cases where people use Telepathy by + itself without pings etc. + +V1.6 [2021-02-10] +- configurable ReceiveTimeout to avoid TCPs high default timeout +- Server/Client receive queue limit now disconnects instead of showing a + warning. this is necessary for load balancing to avoid situations where one + spamming connection might fill the queue and slow down everyone else. + +V1.5 [2021-02-05] +- fix: client data races & flaky tests fixed by creating a new client state + object every time we connect. fixes data race where an old dieing thread + might still try to modify the current state +- fix: Client.ReceiveThreadFunction catches and ignores ObjectDisposedException + which can happen if Disconnect() closes and disposes the client, while the + ReceiveThread just starts up and still uses the client. +- Server/Client Tick() optional enabled check for Mirror scene changing + +V1.4 [2021-02-03] +- Server/Client.Tick: limit parameter added to process up to 'limit' messages. + makes Mirror & DOTSNET transports easier to implement +- stability: Server/Client send queue limit disconnects instead of showing a + warning. allows for load balancing. better to kick one connection and keep + the server running than slowing everything down for everyone. + +V1.3 [2021-02-02] +- perf: ReceivePipe: byte[] pool for allocation free receives (╯°□°)╯︵ ┻━┻ +- fix: header buffer, payload buffer data races because they were made non + static earlier. server threads would all access the same ones. + => all threaded code was moved into a static ThreadFunctions class to make it + 100% obvious that there should be no shared state in the future + +V1.2 [2021-02-02] +- Client/Server Tick & OnConnected/OnData/OnDisconnected events instead of + having the outside process messages via GetNextMessage. That's easier for + Mirror/DOTSNET and allows for allocation free data message processing later. +- MagnificientSend/RecvPipe to shield Telepathy from all the complexity +- perf: SendPipe: byte[] pool for allocation free sends (╯°□°)╯︵ ┻━┻ + +V1.1 [2021-02-01] +- stability: added more tests +- breaking: Server/Client.Send: ArraySegment parameter and copy internally so + that Transports don't need to worry about it +- perf: Buffer.BlockCopy instead of Array.Copy +- perf: SendMessageBlocking puts message header directly into payload now +- perf: receiveQueues use SafeQueue instead of ConcurrentQueue to avoid + allocations +- Common: removed static state +- perf: SafeQueue.TryDequeueAll: avoid queue.ToArray() allocations. copy into a + list instead. +- Logger.Log/LogWarning/LogError renamed to Log.Info/Warning/Error +- MaxMessageSize is now specified in constructor to prepare for pooling +- flaky tests are ignored for now +- smaller improvements + +V1.0 - first stable release \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION.meta b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION.meta index 04c1c8a..2dd4957 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION.meta @@ -1,7 +1,7 @@ -fileFormatVersion: 2 -guid: d942af06608be434dbeeaa58207d20bd -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: d942af06608be434dbeeaa58207d20bd +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs b/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs index b5f2489..25e6257 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs +++ b/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs @@ -1,256 +1,256 @@ -// wraps Telepathy for use as HLAPI TransportLayer -using System; -using System.Net; -using System.Net.Sockets; -using UnityEngine; - -// Replaced by Kcp November 2020 -namespace Mirror -{ - [HelpURL("https://github.com/vis2k/Telepathy/blob/master/README.md")] - [DisallowMultipleComponent] - public class TelepathyTransport : Transport - { - // scheme used by this transport - // "tcp4" means tcp with 4 bytes header, network byte order - public const string Scheme = "tcp4"; - - public ushort port = 7777; - - [Header("Common")] - [Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")] - public bool NoDelay = true; - - [Tooltip("Send timeout in milliseconds.")] - public int SendTimeout = 5000; - - [Tooltip("Receive timeout in milliseconds. High by default so users don't time out during scene changes.")] - public int ReceiveTimeout = 30000; - - [Header("Server")] - [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")] - public int serverMaxMessageSize = 16 * 1024; - - [Tooltip("Server processes a limit amount of messages per tick to avoid a deadlock where it might end up processing forever if messages come in faster than we can process them.")] - public int serverMaxReceivesPerTick = 10000; - - [Tooltip("Server send queue limit per connection for pending messages. Telepathy will disconnect a connection's queues reach that limit for load balancing. Better to kick one slow client than slowing down the whole server.")] - public int serverSendQueueLimitPerConnection = 10000; - - [Tooltip("Server receive queue limit per connection for pending messages. Telepathy will disconnect a connection's queues reach that limit for load balancing. Better to kick one slow client than slowing down the whole server.")] - public int serverReceiveQueueLimitPerConnection = 10000; - - [Header("Client")] - [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker host might send multiple fake packets with 2GB headers, causing the connected clients to run out of memory after allocating multiple large packets.")] - public int clientMaxMessageSize = 16 * 1024; - - [Tooltip("Client processes a limit amount of messages per tick to avoid a deadlock where it might end up processing forever if messages come in faster than we can process them.")] - public int clientMaxReceivesPerTick = 1000; - - [Tooltip("Client send queue limit for pending messages. Telepathy will disconnect if the connection's queues reach that limit in order to avoid ever growing latencies.")] - public int clientSendQueueLimit = 10000; - - [Tooltip("Client receive queue limit for pending messages. Telepathy will disconnect if the connection's queues reach that limit in order to avoid ever growing latencies.")] - public int clientReceiveQueueLimit = 10000; - - Telepathy.Client client; - Telepathy.Server server; - - // scene change message needs to halt message processing immediately - // Telepathy.Tick() has a enabledCheck parameter that we can use, but - // let's only allocate it once. - Func enabledCheck; - - void Awake() - { - // tell Telepathy to use Unity's Debug.Log - Telepathy.Log.Info = Debug.Log; - Telepathy.Log.Warning = Debug.LogWarning; - Telepathy.Log.Error = Debug.LogError; - - // allocate enabled check only once - enabledCheck = () => enabled; - - Debug.Log("TelepathyTransport initialized!"); - } - - public override bool Available() - { - // C#'s built in TCP sockets run everywhere except on WebGL - return Application.platform != RuntimePlatform.WebGLPlayer; - } - - // client - private void CreateClient() - { - // create client - client = new Telepathy.Client(clientMaxMessageSize); - // client hooks - // other systems hook into transport events in OnCreate or - // OnStartRunning in no particular order. the only way to avoid - // race conditions where telepathy uses OnConnected before another - // system's hook (e.g. statistics OnData) was added is to wrap - // them all in a lambda and always call the latest hook. - // (= lazy call) - client.OnConnected = () => OnClientConnected.Invoke(); - client.OnData = (segment) => OnClientDataReceived.Invoke(segment, Channels.Reliable); - client.OnDisconnected = () => OnClientDisconnected.Invoke(); - - // client configuration - client.NoDelay = NoDelay; - client.SendTimeout = SendTimeout; - client.ReceiveTimeout = ReceiveTimeout; - client.SendQueueLimit = clientSendQueueLimit; - client.ReceiveQueueLimit = clientReceiveQueueLimit; - } - public override bool ClientConnected() => client != null && client.Connected; - public override void ClientConnect(string address) - { - CreateClient(); - client.Connect(address, port); - } - - public override void ClientConnect(Uri uri) - { - CreateClient(); - if (uri.Scheme != Scheme) - throw new ArgumentException($"Invalid url {uri}, use {Scheme}://host:port instead", nameof(uri)); - - int serverPort = uri.IsDefaultPort ? port : uri.Port; - client.Connect(uri.Host, serverPort); - } - public override void ClientSend(ArraySegment segment, int channelId) => client?.Send(segment); - public override void ClientDisconnect() - { - client?.Disconnect(); - client = null; - } - - // messages should always be processed in early update - public override void ClientEarlyUpdate() - { - // 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; - - // process a maximum amount of client messages per tick - // IMPORTANT: check .enabled to stop processing immediately after a - // scene change message arrives! - client?.Tick(clientMaxReceivesPerTick, enabledCheck); - } - - // 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 != null && server.Active; - public override void ServerStart() - { - // create server - server = new Telepathy.Server(serverMaxMessageSize); - - // server hooks - // other systems hook into transport events in OnCreate or - // OnStartRunning in no particular order. the only way to avoid - // race conditions where telepathy uses OnConnected before another - // system's hook (e.g. statistics OnData) was added is to wrap - // them all in a lambda and always call the latest hook. - // (= lazy call) - server.OnConnected = (connectionId) => OnServerConnected.Invoke(connectionId); - server.OnData = (connectionId, segment) => OnServerDataReceived.Invoke(connectionId, segment, Channels.Reliable); - server.OnDisconnected = (connectionId) => OnServerDisconnected.Invoke(connectionId); - - // server configuration - server.NoDelay = NoDelay; - server.SendTimeout = SendTimeout; - server.ReceiveTimeout = ReceiveTimeout; - server.SendQueueLimit = serverSendQueueLimitPerConnection; - server.ReceiveQueueLimit = serverReceiveQueueLimitPerConnection; - - server.Start(port); - } - - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) => server?.Send(connectionId, segment); - public override void ServerDisconnect(int connectionId) => server?.Disconnect(connectionId); - public override string ServerGetClientAddress(int connectionId) - { - try - { - return server?.GetClientAddress(connectionId); - } - catch (SocketException) - { - // using server.listener.LocalEndpoint causes an Exception - // in UWP + Unity 2019: - // Exception thrown at 0x00007FF9755DA388 in UWF.exe: - // Microsoft C++ exception: Il2CppExceptionWrapper at memory - // location 0x000000E15A0FCDD0. SocketException: An address - // incompatible with the requested protocol was used at - // System.Net.Sockets.Socket.get_LocalEndPoint () - // so let's at least catch it and recover - return "unknown"; - } - } - public override void ServerStop() - { - server?.Stop(); - server = null; - } - - // messages should always be processed in early update - public override void ServerEarlyUpdate() - { - // 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; - - // process a maximum amount of server messages per tick - // IMPORTANT: check .enabled to stop processing immediately after a - // scene change message arrives! - server?.Tick(serverMaxReceivesPerTick, enabledCheck); - } - - // common - public override void Shutdown() - { - Debug.Log("TelepathyTransport Shutdown()"); - client?.Disconnect(); - client = null; - server?.Stop(); - server = null; - } - - public override int GetMaxPacketSize(int channelId) - { - return serverMaxMessageSize; - } - - public override string ToString() - { - if (server != null && server.Active && server.listener != null) - { - // printing server.listener.LocalEndpoint causes an Exception - // in UWP + Unity 2019: - // Exception thrown at 0x00007FF9755DA388 in UWF.exe: - // Microsoft C++ exception: Il2CppExceptionWrapper at memory - // location 0x000000E15A0FCDD0. SocketException: An address - // incompatible with the requested protocol was used at - // System.Net.Sockets.Socket.get_LocalEndPoint () - // so let's use the regular port instead. - return "Telepathy Server port: " + port; - } - else if (client != null && (client.Connecting || client.Connected)) - { - return "Telepathy Client port: " + port; - } - return "Telepathy (inactive/disconnected)"; - } - } -} +// wraps Telepathy for use as HLAPI TransportLayer +using System; +using System.Net; +using System.Net.Sockets; +using UnityEngine; + +// Replaced by Kcp November 2020 +namespace Mirror +{ + [HelpURL("https://github.com/vis2k/Telepathy/blob/master/README.md")] + [DisallowMultipleComponent] + public class TelepathyTransport : Transport + { + // scheme used by this transport + // "tcp4" means tcp with 4 bytes header, network byte order + public const string Scheme = "tcp4"; + + public ushort port = 7777; + + [Header("Common")] + [Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")] + public bool NoDelay = true; + + [Tooltip("Send timeout in milliseconds.")] + public int SendTimeout = 5000; + + [Tooltip("Receive timeout in milliseconds. High by default so users don't time out during scene changes.")] + public int ReceiveTimeout = 30000; + + [Header("Server")] + [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")] + public int serverMaxMessageSize = 16 * 1024; + + [Tooltip("Server processes a limit amount of messages per tick to avoid a deadlock where it might end up processing forever if messages come in faster than we can process them.")] + public int serverMaxReceivesPerTick = 10000; + + [Tooltip("Server send queue limit per connection for pending messages. Telepathy will disconnect a connection's queues reach that limit for load balancing. Better to kick one slow client than slowing down the whole server.")] + public int serverSendQueueLimitPerConnection = 10000; + + [Tooltip("Server receive queue limit per connection for pending messages. Telepathy will disconnect a connection's queues reach that limit for load balancing. Better to kick one slow client than slowing down the whole server.")] + public int serverReceiveQueueLimitPerConnection = 10000; + + [Header("Client")] + [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker host might send multiple fake packets with 2GB headers, causing the connected clients to run out of memory after allocating multiple large packets.")] + public int clientMaxMessageSize = 16 * 1024; + + [Tooltip("Client processes a limit amount of messages per tick to avoid a deadlock where it might end up processing forever if messages come in faster than we can process them.")] + public int clientMaxReceivesPerTick = 1000; + + [Tooltip("Client send queue limit for pending messages. Telepathy will disconnect if the connection's queues reach that limit in order to avoid ever growing latencies.")] + public int clientSendQueueLimit = 10000; + + [Tooltip("Client receive queue limit for pending messages. Telepathy will disconnect if the connection's queues reach that limit in order to avoid ever growing latencies.")] + public int clientReceiveQueueLimit = 10000; + + Telepathy.Client client; + Telepathy.Server server; + + // scene change message needs to halt message processing immediately + // Telepathy.Tick() has a enabledCheck parameter that we can use, but + // let's only allocate it once. + Func enabledCheck; + + void Awake() + { + // tell Telepathy to use Unity's Debug.Log + Telepathy.Log.Info = Debug.Log; + Telepathy.Log.Warning = Debug.LogWarning; + Telepathy.Log.Error = Debug.LogError; + + // allocate enabled check only once + enabledCheck = () => enabled; + + Debug.Log("TelepathyTransport initialized!"); + } + + public override bool Available() + { + // C#'s built in TCP sockets run everywhere except on WebGL + return Application.platform != RuntimePlatform.WebGLPlayer; + } + + // client + private void CreateClient() + { + // create client + client = new Telepathy.Client(clientMaxMessageSize); + // client hooks + // other systems hook into transport events in OnCreate or + // OnStartRunning in no particular order. the only way to avoid + // race conditions where telepathy uses OnConnected before another + // system's hook (e.g. statistics OnData) was added is to wrap + // them all in a lambda and always call the latest hook. + // (= lazy call) + client.OnConnected = () => OnClientConnected.Invoke(); + client.OnData = (segment) => OnClientDataReceived.Invoke(segment, Channels.Reliable); + client.OnDisconnected = () => OnClientDisconnected.Invoke(); + + // client configuration + client.NoDelay = NoDelay; + client.SendTimeout = SendTimeout; + client.ReceiveTimeout = ReceiveTimeout; + client.SendQueueLimit = clientSendQueueLimit; + client.ReceiveQueueLimit = clientReceiveQueueLimit; + } + public override bool ClientConnected() => client != null && client.Connected; + public override void ClientConnect(string address) + { + CreateClient(); + client.Connect(address, port); + } + + public override void ClientConnect(Uri uri) + { + CreateClient(); + if (uri.Scheme != Scheme) + throw new ArgumentException($"Invalid url {uri}, use {Scheme}://host:port instead", nameof(uri)); + + int serverPort = uri.IsDefaultPort ? port : uri.Port; + client.Connect(uri.Host, serverPort); + } + public override void ClientSend(ArraySegment segment, int channelId) => client?.Send(segment); + public override void ClientDisconnect() + { + client?.Disconnect(); + client = null; + } + + // messages should always be processed in early update + public override void ClientEarlyUpdate() + { + // 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; + + // process a maximum amount of client messages per tick + // IMPORTANT: check .enabled to stop processing immediately after a + // scene change message arrives! + client?.Tick(clientMaxReceivesPerTick, enabledCheck); + } + + // 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 != null && server.Active; + public override void ServerStart() + { + // create server + server = new Telepathy.Server(serverMaxMessageSize); + + // server hooks + // other systems hook into transport events in OnCreate or + // OnStartRunning in no particular order. the only way to avoid + // race conditions where telepathy uses OnConnected before another + // system's hook (e.g. statistics OnData) was added is to wrap + // them all in a lambda and always call the latest hook. + // (= lazy call) + server.OnConnected = (connectionId) => OnServerConnected.Invoke(connectionId); + server.OnData = (connectionId, segment) => OnServerDataReceived.Invoke(connectionId, segment, Channels.Reliable); + server.OnDisconnected = (connectionId) => OnServerDisconnected.Invoke(connectionId); + + // server configuration + server.NoDelay = NoDelay; + server.SendTimeout = SendTimeout; + server.ReceiveTimeout = ReceiveTimeout; + server.SendQueueLimit = serverSendQueueLimitPerConnection; + server.ReceiveQueueLimit = serverReceiveQueueLimitPerConnection; + + server.Start(port); + } + + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) => server?.Send(connectionId, segment); + public override void ServerDisconnect(int connectionId) => server?.Disconnect(connectionId); + public override string ServerGetClientAddress(int connectionId) + { + try + { + return server?.GetClientAddress(connectionId); + } + catch (SocketException) + { + // using server.listener.LocalEndpoint causes an Exception + // in UWP + Unity 2019: + // Exception thrown at 0x00007FF9755DA388 in UWF.exe: + // Microsoft C++ exception: Il2CppExceptionWrapper at memory + // location 0x000000E15A0FCDD0. SocketException: An address + // incompatible with the requested protocol was used at + // System.Net.Sockets.Socket.get_LocalEndPoint () + // so let's at least catch it and recover + return "unknown"; + } + } + public override void ServerStop() + { + server?.Stop(); + server = null; + } + + // messages should always be processed in early update + public override void ServerEarlyUpdate() + { + // 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; + + // process a maximum amount of server messages per tick + // IMPORTANT: check .enabled to stop processing immediately after a + // scene change message arrives! + server?.Tick(serverMaxReceivesPerTick, enabledCheck); + } + + // common + public override void Shutdown() + { + Debug.Log("TelepathyTransport Shutdown()"); + client?.Disconnect(); + client = null; + server?.Stop(); + server = null; + } + + public override int GetMaxPacketSize(int channelId) + { + return serverMaxMessageSize; + } + + public override string ToString() + { + if (server != null && server.Active && server.listener != null) + { + // printing server.listener.LocalEndpoint causes an Exception + // in UWP + Unity 2019: + // Exception thrown at 0x00007FF9755DA388 in UWF.exe: + // Microsoft C++ exception: Il2CppExceptionWrapper at memory + // location 0x000000E15A0FCDD0. SocketException: An address + // incompatible with the requested protocol was used at + // System.Net.Sockets.Socket.get_LocalEndPoint () + // so let's use the regular port instead. + return $"Telepathy Server port: {port}"; + } + else if (client != null && (client.Connecting || client.Connected)) + { + return $"Telepathy Client port: {port}"; + } + return "Telepathy (inactive/disconnected)"; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs.meta b/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs.meta index 99cde3e..92fb72d 100644 --- a/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: c7424c1070fad4ba2a7a96b02fbeb4bb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 1000 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: c7424c1070fad4ba2a7a96b02fbeb4bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 1000 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Transport/Transport.cs b/Assets/Mirror/Runtime/Transport/Transport.cs index a944407..cafdf47 100644 --- a/Assets/Mirror/Runtime/Transport/Transport.cs +++ b/Assets/Mirror/Runtime/Transport/Transport.cs @@ -1,201 +1,173 @@ -// For future reference, here is what Transports need to do in Mirror: -// -// Connecting: -// * Transports are responsible to call either OnConnected || OnDisconnected -// in a certain time after a Connect was called. It can not end in limbo. -// -// Disconnecting: -// * Connections might disconnect voluntarily by the other end. -// * Connections might be disconnect involuntarily by the server. -// * Either way, Transports need to detect it and call OnDisconnected. -// -// Timeouts: -// * Transports should expose a configurable timeout -// * Transports are responsible for calling OnDisconnected after a timeout -// -// Channels: -// * Default channel is Reliable, as in reliable ordered (OR DISCONNECT) -// * Where possible, Unreliable should be supported (unordered, no guarantee) -// -// Other: -// * Transports functions are all bound to the main thread. -// (Transports can use other threads in the background if they manage them) -// * Transports should only process messages while the component is enabled. -// -using System; -using UnityEngine; - -namespace Mirror -{ - /// Abstract transport layer component - public abstract class Transport : MonoBehaviour - { - /// The current transport used by Mirror. - public static Transport activeTransport; - - /// Is this transport available in the current platform? - public abstract bool Available(); - - /// Called by Transport when the client connected to the server. - public Action OnClientConnected = () => Debug.LogWarning("OnClientConnected called with no handler"); - - /// Called by Transport when the client received a message from the server. - public Action, int> OnClientDataReceived = (data, channel) => Debug.LogWarning("OnClientDataReceived called with no handler"); - - /// Called by Transport when the client encountered an error. - public Action OnClientError = (error) => Debug.LogWarning("OnClientError called with no handler"); - - /// Called by Transport when the client disconnected from the server. - public Action OnClientDisconnected = () => Debug.LogWarning("OnClientDisconnected called with no handler"); - - /// True if the client is currently connected to the server. - public abstract bool ClientConnected(); - - /// Connects the client to the server at the address. - public abstract void ClientConnect(string address); - - /// Connects the client to the server at the Uri. - public virtual void ClientConnect(Uri uri) - { - // By default, to keep backwards compatibility, just connect to the host - // in the uri - ClientConnect(uri.Host); - } - - /// Sends a message to the server over the given channel. - // The ArraySegment is only valid until returning. Copy if needed. - // TODO make second version abstract after removing the obsolete version - // Deprecated 2021-05-17 - [Obsolete("Use ClientSend(segment, channelId) instead. channelId is now the last parameter.")] - public virtual void ClientSend(int channelId, ArraySegment segment) {} - public virtual void ClientSend(ArraySegment segment, int channelId) - { - // defaults to obsolete version to not force break transports. -#pragma warning disable 618 - ClientSend(channelId, segment); -#pragma warning restore 618 - } - - /// Disconnects the client from the server - public abstract void ClientDisconnect(); - - /// Returns server address as Uri. - // Useful for NetworkDiscovery. - public abstract Uri ServerUri(); - - /// Called by Transport when a new client connected to the server. - public Action OnServerConnected = (connId) => Debug.LogWarning("OnServerConnected called with no handler"); - - /// Called by Transport when the server received a message from a client. - public Action, int> OnServerDataReceived = (connId, data, channel) => Debug.LogWarning("OnServerDataReceived called with no handler"); - - /// Called by Transport when a server's connection encountered a problem. - /// If a Disconnect will also be raised, raise the Error first. - public Action OnServerError = (connId, error) => Debug.LogWarning("OnServerError called with no handler"); - - /// Called by Transport when a client disconnected from the server. - public Action OnServerDisconnected = (connId) => Debug.LogWarning("OnServerDisconnected called with no handler"); - - /// True if the server is currently listening for connections. - public abstract bool ServerActive(); - - /// Start listening for connections. - public abstract void ServerStart(); - - /// Send a message to a client over the given channel. - // TODO make second version abstract after removing the obsolete version - // Deprecated 2021-05-17 - [Obsolete("Use ServerSend(connectionId, segment, channelId) instead. channelId is now the last parameter.")] - public virtual void ServerSend(int connectionId, int channelId, ArraySegment segment) {} - public virtual void ServerSend(int connectionId, ArraySegment segment, int channelId) - { - // defaults to obsolete version to not force break transports. -#pragma warning disable 618 - ServerSend(connectionId, channelId, segment); -#pragma warning restore 618 - } - - /// Disconnect a client from the server. - public abstract void ServerDisconnect(int connectionId); - - /// Get a client's address on the server. - // Can be useful for Game Master IP bans etc. - public abstract string ServerGetClientAddress(int connectionId); - - /// Stop listening and disconnect all connections. - public abstract void ServerStop(); - - /// Maximum message size for the given channel. - // Different channels often have different sizes, ranging from MTU to - // several megabytes. - // - // Needs to return a value at all times, even if the Transport isn't - // running or available because it's needed for initializations. - public abstract int GetMaxPacketSize(int channelId = Channels.Reliable); - - /// Recommended Batching threshold for this transport. - // Uses GetMaxPacketSize by default. - // Some transports like kcp support large max packet sizes which should - // not be used for batching all the time because they end up being too - // slow (head of line blocking etc.). - public virtual int GetBatchThreshold(int channelId) - { - // change to GetMaxPacketSize by default after removing obsolete -#pragma warning disable 618 - return GetMaxBatchSize(channelId); -#pragma warning restore 618 - } - - // Deprecated 2021-06-17 - [Obsolete("GetMaxBatchSize was renamed to GetBatchThreshold.")] - public virtual int GetMaxBatchSize(int channelId) => - GetMaxPacketSize(channelId); - - // block Update & LateUpdate to show warnings if Transports still use - // them instead of using - // Client/ServerEarlyUpdate: to process incoming messages - // Client/ServerLateUpdate: to process outgoing messages - // those are called by NetworkClient/Server at the right time. - // - // allows transports to implement the proper network update order of: - // process_incoming() - // update_world() - // process_outgoing() - // - // => see NetworkLoop.cs for detailed explanations! -#pragma warning disable UNT0001 // Empty Unity message - public void Update() {} - public void LateUpdate() {} -#pragma warning restore UNT0001 // Empty Unity message - - /// - /// NetworkLoop NetworkEarly/LateUpdate were added for a proper network - /// update order. the goal is to: - /// process_incoming() - /// update_world() - /// process_outgoing() - /// in order to avoid unnecessary latency and data races. - /// - // => split into client and server parts so that we can cleanly call - // them from NetworkClient/Server - // => VIRTUAL for now so we can take our time to convert transports - // without breaking anything. - public virtual void ClientEarlyUpdate() {} - public virtual void ServerEarlyUpdate() {} - public virtual void ClientLateUpdate() {} - public virtual void ServerLateUpdate() {} - - /// Shut down the transport, both as client and server - public abstract void Shutdown(); - - /// Called by Unity when quitting. Inheriting Transports should call base for proper Shutdown. - public virtual void OnApplicationQuit() - { - // stop transport (e.g. to shut down threads) - // (when pressing Stop in the Editor, Unity keeps threads alive - // until we press Start again. so if Transports use threads, we - // really want them to end now and not after next start) - Shutdown(); - } - } -} +// For future reference, here is what Transports need to do in Mirror: +// +// Connecting: +// * Transports are responsible to call either OnConnected || OnDisconnected +// in a certain time after a Connect was called. It can not end in limbo. +// +// Disconnecting: +// * Connections might disconnect voluntarily by the other end. +// * Connections might be disconnect involuntarily by the server. +// * Either way, Transports need to detect it and call OnDisconnected. +// +// Timeouts: +// * Transports should expose a configurable timeout +// * Transports are responsible for calling OnDisconnected after a timeout +// +// Channels: +// * Default channel is Reliable, as in reliable ordered (OR DISCONNECT) +// * Where possible, Unreliable should be supported (unordered, no guarantee) +// +// Other: +// * Transports functions are all bound to the main thread. +// (Transports can use other threads in the background if they manage them) +// * Transports should only process messages while the component is enabled. +// +using System; +using UnityEngine; + +namespace Mirror +{ + /// Abstract transport layer component + public abstract class Transport : MonoBehaviour + { + /// The current transport used by Mirror. + public static Transport activeTransport; + + /// Is this transport available in the current platform? + public abstract bool Available(); + + /// Called by Transport when the client connected to the server. + public Action OnClientConnected = () => Debug.LogWarning("OnClientConnected called with no handler"); + + /// Called by Transport when the client received a message from the server. + public Action, int> OnClientDataReceived = (data, channel) => Debug.LogWarning("OnClientDataReceived called with no handler"); + + /// Called by Transport when the client encountered an error. + public Action OnClientError = (error) => Debug.LogWarning("OnClientError called with no handler"); + + /// Called by Transport when the client disconnected from the server. + public Action OnClientDisconnected = () => Debug.LogWarning("OnClientDisconnected called with no handler"); + + /// True if the client is currently connected to the server. + public abstract bool ClientConnected(); + + /// Connects the client to the server at the address. + public abstract void ClientConnect(string address); + + /// Connects the client to the server at the Uri. + public virtual void ClientConnect(Uri uri) + { + // By default, to keep backwards compatibility, just connect to the host + // in the uri + ClientConnect(uri.Host); + } + + /// Sends a message to the server over the given channel. + // The ArraySegment is only valid until returning. Copy if needed. + public abstract void ClientSend(ArraySegment segment, int channelId); + + /// Disconnects the client from the server + public abstract void ClientDisconnect(); + + /// Returns server address as Uri. + // Useful for NetworkDiscovery. + public abstract Uri ServerUri(); + + /// Called by Transport when a new client connected to the server. + public Action OnServerConnected = (connId) => Debug.LogWarning("OnServerConnected called with no handler"); + + /// Called by Transport when the server received a message from a client. + public Action, int> OnServerDataReceived = (connId, data, channel) => Debug.LogWarning("OnServerDataReceived called with no handler"); + + /// Called by Transport when a server's connection encountered a problem. + /// If a Disconnect will also be raised, raise the Error first. + public Action OnServerError = (connId, error) => Debug.LogWarning("OnServerError called with no handler"); + + /// Called by Transport when a client disconnected from the server. + public Action OnServerDisconnected = (connId) => Debug.LogWarning("OnServerDisconnected called with no handler"); + + /// True if the server is currently listening for connections. + public abstract bool ServerActive(); + + /// Start listening for connections. + public abstract void ServerStart(); + + /// Send a message to a client over the given channel. + public abstract void ServerSend(int connectionId, ArraySegment segment, int channelId); + + /// Disconnect a client from the server. + public abstract void ServerDisconnect(int connectionId); + + /// Get a client's address on the server. + // Can be useful for Game Master IP bans etc. + public abstract string ServerGetClientAddress(int connectionId); + + /// Stop listening and disconnect all connections. + public abstract void ServerStop(); + + /// Maximum message size for the given channel. + // Different channels often have different sizes, ranging from MTU to + // several megabytes. + // + // Needs to return a value at all times, even if the Transport isn't + // running or available because it's needed for initializations. + public abstract int GetMaxPacketSize(int channelId = Channels.Reliable); + + /// Recommended Batching threshold for this transport. + // Uses GetMaxPacketSize by default. + // Some transports like kcp support large max packet sizes which should + // not be used for batching all the time because they end up being too + // slow (head of line blocking etc.). + public virtual int GetBatchThreshold(int channelId) + { + return GetMaxPacketSize(channelId); + } + + // block Update & LateUpdate to show warnings if Transports still use + // them instead of using + // Client/ServerEarlyUpdate: to process incoming messages + // Client/ServerLateUpdate: to process outgoing messages + // those are called by NetworkClient/Server at the right time. + // + // allows transports to implement the proper network update order of: + // process_incoming() + // update_world() + // process_outgoing() + // + // => see NetworkLoop.cs for detailed explanations! +#pragma warning disable UNT0001 // Empty Unity message + public void Update() {} + public void LateUpdate() {} +#pragma warning restore UNT0001 // Empty Unity message + + /// + /// NetworkLoop NetworkEarly/LateUpdate were added for a proper network + /// update order. the goal is to: + /// process_incoming() + /// update_world() + /// process_outgoing() + /// in order to avoid unnecessary latency and data races. + /// + // => split into client and server parts so that we can cleanly call + // them from NetworkClient/Server + // => VIRTUAL for now so we can take our time to convert transports + // without breaking anything. + public virtual void ClientEarlyUpdate() {} + public virtual void ServerEarlyUpdate() {} + public virtual void ClientLateUpdate() {} + public virtual void ServerLateUpdate() {} + + /// Shut down the transport, both as client and server + public abstract void Shutdown(); + + /// Called by Unity when quitting. Inheriting Transports should call base for proper Shutdown. + public virtual void OnApplicationQuit() + { + // stop transport (e.g. to shut down threads) + // (when pressing Stop in the Editor, Unity keeps threads alive + // until we press Start again. so if Transports use threads, we + // really want them to end now and not after next start) + Shutdown(); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Transport.cs.meta b/Assets/Mirror/Runtime/Transport/Transport.cs.meta index 55072e1..ebbbebd 100644 --- a/Assets/Mirror/Runtime/Transport/Transport.cs.meta +++ b/Assets/Mirror/Runtime/Transport/Transport.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: cfffcac25d6d64ced9de620159e221b8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: cfffcac25d6d64ced9de620159e221b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Runtime/Utils.cs b/Assets/Mirror/Runtime/Utils.cs index 0bfc18f..69f3ed2 100644 --- a/Assets/Mirror/Runtime/Utils.cs +++ b/Assets/Mirror/Runtime/Utils.cs @@ -1,136 +1,121 @@ -using System; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using UnityEngine; - -namespace Mirror -{ - // Handles network messages on client and server - public delegate void NetworkMessageDelegate(NetworkConnection conn, NetworkReader reader, int channelId); - - // Handles requests to spawn objects on the client - public delegate GameObject SpawnDelegate(Vector3 position, Guid assetId); - - public delegate GameObject SpawnHandlerDelegate(SpawnMessage msg); - - // Handles requests to unspawn objects on the client - public delegate void UnSpawnDelegate(GameObject spawned); - - // invoke type for Cmd/Rpc - public enum MirrorInvokeType - { - Command, - ClientRpc - } - - // Deprecated 2021-03-15 - [Obsolete("Version has never been used, neither by UNET nor by Mirror.")] - public enum Version - { - Current = 1 - } - - // channels are const ints instead of an enum so people can add their own - // channels (can't extend an enum otherwise). - // - // note that Mirror is slowly moving towards quake style networking which - // will only require reliable for handshake, and unreliable for the rest. - // so eventually we can change this to an Enum and transports shouldn't - // add custom channels anymore. - public static class Channels - { - public const int Reliable = 0; // ordered - public const int Unreliable = 1; // unordered - - // Deprecated 2021-03-15 - [Obsolete("Use Channels.Reliable instead")] - public const int DefaultReliable = Reliable; - - // Deprecated 2021-03-15 - [Obsolete("Use Channels.Unreliable instead")] - public const int DefaultUnreliable = Unreliable; - } - - // -- helpers for float conversion without allocations -- - [StructLayout(LayoutKind.Explicit)] - internal struct UIntFloat - { - [FieldOffset(0)] - public float floatValue; - - [FieldOffset(0)] - public uint intValue; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct UIntDouble - { - [FieldOffset(0)] - public double doubleValue; - - [FieldOffset(0)] - public ulong longValue; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct UIntDecimal - { - [FieldOffset(0)] - public ulong longValue1; - - [FieldOffset(8)] - public ulong longValue2; - - [FieldOffset(0)] - public decimal decimalValue; - } - - public static class Utils - { - public static uint GetTrueRandomUInt() - { - // use Crypto RNG to avoid having time based duplicates - using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) - { - byte[] bytes = new byte[4]; - rng.GetBytes(bytes); - return BitConverter.ToUInt32(bytes, 0); - } - } - - public static bool IsPrefab(GameObject obj) - { -#if UNITY_EDITOR - return UnityEditor.PrefabUtility.IsPartOfPrefabAsset(obj); -#else - return false; -#endif - } - - public static bool IsSceneObjectWithPrefabParent(GameObject gameObject, out GameObject prefab) - { - prefab = null; - -#if UNITY_EDITOR - if (!UnityEditor.PrefabUtility.IsPartOfPrefabInstance(gameObject)) - { - return false; - } - prefab = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(gameObject); -#endif - - if (prefab == null) - { - Debug.LogError("Failed to find prefab parent for scene object [name:" + gameObject.name + "]"); - return false; - } - return true; - } - - // is a 2D point in screen? (from ummorpg) - // (if width = 1024, then indices from 0..1023 are valid (=1024 indices) - public static bool IsPointInScreen(Vector2 point) => - 0 <= point.x && point.x < Screen.width && - 0 <= point.y && point.y < Screen.height; - } -} +using System; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using UnityEngine; + +namespace Mirror +{ + // Handles network messages on client and server + public delegate void NetworkMessageDelegate(NetworkConnection conn, NetworkReader reader, int channelId); + + // Handles requests to spawn objects on the client + public delegate GameObject SpawnDelegate(Vector3 position, Guid assetId); + + public delegate GameObject SpawnHandlerDelegate(SpawnMessage msg); + + // Handles requests to unspawn objects on the client + public delegate void UnSpawnDelegate(GameObject spawned); + + // invoke type for Cmd/Rpc + public enum MirrorInvokeType + { + Command, + ClientRpc + } + + // channels are const ints instead of an enum so people can add their own + // channels (can't extend an enum otherwise). + // + // note that Mirror is slowly moving towards quake style networking which + // will only require reliable for handshake, and unreliable for the rest. + // so eventually we can change this to an Enum and transports shouldn't + // add custom channels anymore. + public static class Channels + { + public const int Reliable = 0; // ordered + public const int Unreliable = 1; // unordered + } + + // -- helpers for float conversion without allocations -- + [StructLayout(LayoutKind.Explicit)] + internal struct UIntFloat + { + [FieldOffset(0)] + public float floatValue; + + [FieldOffset(0)] + public uint intValue; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct UIntDouble + { + [FieldOffset(0)] + public double doubleValue; + + [FieldOffset(0)] + public ulong longValue; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct UIntDecimal + { + [FieldOffset(0)] + public ulong longValue1; + + [FieldOffset(8)] + public ulong longValue2; + + [FieldOffset(0)] + public decimal decimalValue; + } + + public static class Utils + { + public static uint GetTrueRandomUInt() + { + // use Crypto RNG to avoid having time based duplicates + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + byte[] bytes = new byte[4]; + rng.GetBytes(bytes); + return BitConverter.ToUInt32(bytes, 0); + } + } + + public static bool IsPrefab(GameObject obj) + { +#if UNITY_EDITOR + return UnityEditor.PrefabUtility.IsPartOfPrefabAsset(obj); +#else + return false; +#endif + } + + public static bool IsSceneObjectWithPrefabParent(GameObject gameObject, out GameObject prefab) + { + prefab = null; + +#if UNITY_EDITOR + if (!UnityEditor.PrefabUtility.IsPartOfPrefabInstance(gameObject)) + { + return false; + } + prefab = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(gameObject); +#endif + + if (prefab == null) + { + Debug.LogError($"Failed to find prefab parent for scene object [name:{gameObject.name}]"); + return false; + } + return true; + } + + // is a 2D point in screen? (from ummorpg) + // (if width = 1024, then indices from 0..1023 are valid (=1024 indices) + public static bool IsPointInScreen(Vector2 point) => + 0 <= point.x && point.x < Screen.width && + 0 <= point.y && point.y < Screen.height; + } +} diff --git a/Assets/Mirror/Runtime/Utils.cs.meta b/Assets/Mirror/Runtime/Utils.cs.meta index 7cf1557..b018b0e 100644 --- a/Assets/Mirror/Runtime/Utils.cs.meta +++ b/Assets/Mirror/Runtime/Utils.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: b530ce39098b54374a29ad308c8e4554 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: b530ce39098b54374a29ad308c8e4554 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Version.txt b/Assets/Mirror/Version.txt deleted file mode 100644 index 6b0ca88..0000000 --- a/Assets/Mirror/Version.txt +++ /dev/null @@ -1 +0,0 @@ -44.0.2 \ No newline at end of file diff --git a/Assets/Prefabs/picopark.prefab b/Assets/Prefabs/picopark.prefab index 05a4504..6e7ff7a 100644 --- a/Assets/Prefabs/picopark.prefab +++ b/Assets/Prefabs/picopark.prefab @@ -16,7 +16,7 @@ GameObject: - component: {fileID: 8584291556063275410} - component: {fileID: 8584291556063275409} - component: {fileID: 345238637415786487} - - component: {fileID: 3855804468415881115} + - component: {fileID: -2927710668536177812} m_Layer: 9 m_Name: picopark m_TagString: Untagged @@ -101,14 +101,16 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: syncMode: 0 - syncInterval: 0.1 + syncInterval: 0 LocalComponents: - {fileID: 8584291556063275412} characterSprite: {fileID: 8584291556063275423} insideDoor: 0 friendLayer: serializedVersion: 2 - m_Bits: 1024 + m_Bits: 256 + touchingNeighbours: [] + parentFrnd: {fileID: 0} --- !u!114 &8584291556063275412 MonoBehaviour: m_ObjectHideFlags: 0 @@ -129,7 +131,7 @@ MonoBehaviour: animator: {fileID: 8584291556063275409} groundLayerMask: serializedVersion: 2 - m_Bits: 1280 + m_Bits: 256 isGrounded: 1 isSwimming: 0 groundChecker: {fileID: 0} @@ -145,6 +147,8 @@ MonoBehaviour: insideDoor: 0 inWater: 0 waterBoost: 0 + groundCheckerDist: 0.19 + groundCheckerHeighMultipler: 0.52 --- !u!61 &8584291556063275411 BoxCollider2D: m_ObjectHideFlags: 0 @@ -228,7 +232,7 @@ MonoBehaviour: visible: 0 m_AssetId: 50c59bfe296a0ab3ca72103abcfa1d1a hasSpawned: 0 ---- !u!114 &3855804468415881115 +--- !u!114 &-2927710668536177812 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -243,15 +247,16 @@ MonoBehaviour: syncMode: 0 syncInterval: 0.1 clientAuthority: 1 - sendInterval: 0.05 + useLocalSpace: 0 + sendInterval: 0 syncPosition: 1 syncRotation: 1 syncScale: 0 - interpolatePosition: 1 - interpolateRotation: 1 + interpolatePosition: 0 + interpolateRotation: 0 interpolateScale: 0 bufferTimeMultiplier: 1 - bufferSizeLimit: 64 + bufferSizeLimit: 128 catchupThreshold: 4 catchupMultiplier: 0.1 showGizmos: 0 diff --git a/Assets/Scenes/MainMenu.unity b/Assets/Scenes/MainMenu.unity index 6ca7997..99b1f4f 100644 --- a/Assets/Scenes/MainMenu.unity +++ b/Assets/Scenes/MainMenu.unity @@ -516,8 +516,6 @@ MonoBehaviour: transport: {fileID: 119645375} networkAddress: localhost maxConnections: 100 - disconnectInactiveConnections: 0 - disconnectInactiveTimeout: 60 authenticator: {fileID: 0} playerPrefab: {fileID: 8584291556063275422, guid: 50c59bfe296a0ab3ca72103abcfa1d1a, type: 3} @@ -552,7 +550,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3} m_Name: m_EditorClassIdentifier: - showGUI: 1 offsetX: 0 offsetY: 0 --- !u!1 &132614580 diff --git a/Assets/Scenes/level1.unity b/Assets/Scenes/level1.unity index 1348a24..b0c132d 100644 --- a/Assets/Scenes/level1.unity +++ b/Assets/Scenes/level1.unity @@ -568,6 +568,83 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!1 &244458272 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 244458273} + - component: {fileID: 244458275} + - component: {fileID: 244458274} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &244458273 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 244458272} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1201792974} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &244458274 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 244458272} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 35 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 2 +--- !u!222 &244458275 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 244458272} + m_CullTransparentMesh: 0 --- !u!1 &313792650 GameObject: m_ObjectHideFlags: 0 @@ -728,6 +805,8 @@ MonoBehaviour: nextSceneName: level2 lobbyNameText: {fileID: 1591773398} playerCountTxt: {fileID: 2133612995} + playlist: 0100000002000000 + curSceneInd: 0 --- !u!114 &334331845 MonoBehaviour: m_ObjectHideFlags: 0 @@ -1228,6 +1307,87 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1201792973 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1201792974} + - component: {fileID: 1201792976} + - component: {fileID: 1201792975} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1201792974 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1201792973} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0.029679038, y: 0.03849614, z: 0.013566297} + m_Children: + - {fileID: 244458273} + m_Father: {fileID: 1311713852} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0.0000020864, y: -0.0000020296} + m_SizeDelta: {x: 50, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1201792975 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1201792973} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &1201792976 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1201792973} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 2 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 6 + m_TargetDisplay: 0 --- !u!1 &1275598170 GameObject: m_ObjectHideFlags: 0 @@ -1280,6 +1440,7 @@ GameObject: - component: {fileID: 1311713855} - component: {fileID: 1311713854} - component: {fileID: 1311713853} + - component: {fileID: 1311713856} m_Layer: 8 m_Name: white (14) m_TagString: Untagged @@ -1297,7 +1458,8 @@ Transform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -0.46, y: -5.05, z: 0.29492188} m_LocalScale: {x: 0.45710027, y: 0.35240662, z: 1} - m_Children: [] + m_Children: + - {fileID: 1201792974} m_Father: {fileID: 1275598171} m_RootOrder: 8 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -1388,7 +1550,7 @@ SpriteRenderer: m_SortingLayer: 0 m_SortingOrder: 3 m_Sprite: {fileID: 21300000, guid: e135ed1e773a1e046a13ebb09c556296, type: 3} - m_Color: {r: 0.43256715, g: 0.9490196, b: 0.28627446, a: 1} + m_Color: {r: 0.6509434, g: 0.0849272, b: 0.07983268, a: 1} m_FlipX: 0 m_FlipY: 0 m_DrawMode: 0 @@ -1398,6 +1560,24 @@ SpriteRenderer: m_WasSpriteAssigned: 1 m_MaskInteraction: 0 m_SpriteSortPoint: 0 +--- !u!114 &1311713856 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1311713851} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 95601679d599a9fd8a2fe7721e81b132, type: 3} + m_Name: + m_EditorClassIdentifier: + syncMode: 0 + syncInterval: 0.1 + playersRequired: 2 + AttachedPlayerCount: 0 + numberTxt: {fileID: 244458274} + playersTouching: [] --- !u!1 &1526303839 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/NetPlayer.cs b/Assets/Scripts/NetPlayer.cs index 1868ba6..482c346 100644 --- a/Assets/Scripts/NetPlayer.cs +++ b/Assets/Scripts/NetPlayer.cs @@ -11,11 +11,12 @@ public class NetPlayer : NetworkBehaviour [SyncVar] public bool insideDoor; public LayerMask friendLayer; + public List touchingNeighbours = new List(); void Start() { DontDestroyOnLoad(gameObject); if(!isLocalPlayer){ - gameObject.layer = LayerMask.NameToLayer("Friend"); + gameObject.layer = LayerMask.NameToLayer("Gnd"); //GetComponent().size = new Vector2(GetComponent().size.x/2f,GetComponent().size.y); foreach(Behaviour localComponent in LocalComponents){ localComponent.enabled=false; @@ -45,28 +46,47 @@ public class NetPlayer : NetworkBehaviour transform.position = SceneData.netSceneData.spawnPoint.position; } -public Transform frndTrans; + [SyncVar] + public Transform parentFrnd; bool oldFlipVal = false; - Transform oldFriendVal; - float oldFriendX; - void Update() - { - if(!isLocalPlayer){return;} - frndTrans = getOnFriend(); - // if(oldFriendVal!=frndTrans){//got on someones head, or got off - // if(oldFriendVal==null && frndTrans!=null){//got on - // oldFriendX = frndTrans.position.x; - // oldFriendVal = frndTrans; - // }else{//got off - // oldFriendX = 0; - // oldFriendVal = null; - // } - // } + Transform _parentFrnd; - // if(oldFriendVal!=null){ - // transform.Translate(new Vector2(frndTrans.position.x - oldFriendX,0)); - // } + [Command] + void CmdChangeParent(Transform newParent){ + transform.parent = newParent; + RpcChangeParent(newParent); + } + [ClientRpc] + void RpcChangeParent(Transform newParent){ + transform.parent = newParent; + + } + float t=0; + void FixedUpdate() + { + if(transform.parent!=null){ + if(t <1){ + t+=Time.deltaTime; + }else{ + GetComponent().useLocalSpace=true; + } + }else{ + GetComponent().useLocalSpace=false; + t=0; + } + if(!isLocalPlayer){return;} + parentFrnd = getOnFriend(); + transform.parent = parentFrnd; + if(_parentFrnd != parentFrnd){ + if(isServer){ + transform.parent = parentFrnd; + RpcChangeParent(parentFrnd); + }else{ + CmdChangeParent(parentFrnd); + } + _parentFrnd=parentFrnd; + } if(oldFlipVal != characterSprite.flipX){ if(isServer){ @@ -76,6 +96,15 @@ public Transform frndTrans; } oldFlipVal=characterSprite.flipX; } + + // bool someoneOnTop = false; + // foreach(NetPlayer neighbour in touchingNeighbours){ + // if(neighbour.parentFrnd == this){ + // someoneOnTop=true; + // break; + // } + // } + // GetComponent().isSomeoneOnTop =someoneOnTop; @@ -118,7 +147,30 @@ public Transform frndTrans; //return (Physics2D.Linecast(transform.position, groundChecker.position, groundLayerMask)); Collider2D col = GetComponentInChildren(); RaycastHit2D hit = Physics2D.BoxCast(col.bounds.center, new Vector2(col.bounds.size.x - (col.bounds.size.x / 5f), col.bounds.size.y), 0, Vector2.down, 0.1f, friendLayer); - friend = (hit) ? hit.collider.transform : null; + friend = (hit) ? ((hit.collider.transform.GetComponent()!=null) ? hit.collider.transform : null) : null; return friend; } + + void OnCollisionEnter2D(Collision2D col){ + NetPlayer obj = col.collider.transform.GetComponent(); + if(obj!=null){ + if(!touchingNeighbours.Contains(obj)){ + touchingNeighbours.Add(obj); + } + } + } + void OnCollisionExit2D(Collision2D col){ + NetPlayer obj = col.collider.transform.GetComponent(); + if(obj!=null){ + if(touchingNeighbours.Contains(obj)){ + touchingNeighbours.Remove(obj); + } + } + } + + void UpdatePushBoxes(){ + foreach(PushBox box in FindObjectsOfType()){ + box.UpdateNeighbourCount(); + } + } } diff --git a/Assets/Scripts/PlayerController.cs b/Assets/Scripts/PlayerController.cs index fc72991..7bc6cc6 100644 --- a/Assets/Scripts/PlayerController.cs +++ b/Assets/Scripts/PlayerController.cs @@ -40,7 +40,7 @@ public class PlayerController : NetworkBehaviour void FixedUpdate() { isGrounded = getGrounded(); - + if (_grounded != isGrounded) { if (isGrounded) @@ -76,19 +76,21 @@ public class PlayerController : NetworkBehaviour float HorizontalAxis = 0; if (Input.GetKey(InputManager.data().leftInput)) { HorizontalAxis = -1; } else if (Input.GetKey(InputManager.data().rightInput)) { HorizontalAxis = 1; } - // if (GameManager.isPaused) { HorizontalAxis = 0; } + // if (GameManager.isPaused) { HorizontalAxis = 0; } //Move according to input //Exit the door - if(enteringDoor && !Input.GetKey(InputManager.data().interactingKey)){ - enteringDoor=false; + if (enteringDoor && !Input.GetKey(InputManager.data().interactingKey)) + { + enteringDoor = false; } - if(insideDoor && !enteringDoor && Input.GetKey(InputManager.data().interactingKey)){ + if (insideDoor && !enteringDoor && Input.GetKey(InputManager.data().interactingKey)) + { Debug.Log("Exiting door"); transform.position = SceneData.netSceneData.door.transform.position; - insideDoor=false; - enteringDoor=true; - if(GetComponent()!=null){GetComponent().CallChangeInsideDoor(insideDoor);} + insideDoor = false; + enteringDoor = true; + if (GetComponent() != null) { GetComponent().CallChangeInsideDoor(insideDoor); } } if (listenToInput) { @@ -106,13 +108,15 @@ public class PlayerController : NetworkBehaviour } //Enter the door - if(inDoor && Input.GetKey(InputManager.data().interactingKey) && !enteringDoor){ - if(SceneData.netSceneData.doorExit!=null){ + if (inDoor && Input.GetKey(InputManager.data().interactingKey) && !enteringDoor) + { + if (SceneData.netSceneData.doorExit != null) + { Debug.Log("Entering door"); transform.position = SceneData.netSceneData.doorExit.position; insideDoor = true; - enteringDoor=true; - if(GetComponent()!=null){GetComponent().CallChangeInsideDoor(insideDoor);} + enteringDoor = true; + if (GetComponent() != null) { GetComponent().CallChangeInsideDoor(insideDoor); } } } @@ -136,37 +140,39 @@ public class PlayerController : NetworkBehaviour //Apply moving input to player - if(GetComponent()!=null){ - if(GetComponent().frndTrans!=null){ - rigidbody.velocity = GetComponent().frndTrans.GetComponent().velocity; - } - } + // if (GetComponent() != null) + // { + // if (GetComponent().parentFrnd != null) + // { + // rigidbody.velocity = GetComponent().parentFrnd.GetComponent().velocity; + // } + // } rigidbody.transform.Translate(new Vector2(moveSpeed * moveInput, 0)); - - bool _canJump = canJump(); - if ((Input.GetKey(InputManager.data().jumpInput)) && _canJump) - { - jumpT = 0; - jumpReleased = false; - rigidbody.velocity = new Vector2(rigidbody.velocity.x, 0); - if(jumpSFX!=null)AudioSingleton.getSFXSource().PlayOneShot(jumpSFX); - } + + bool _canJump = canJump(); + if ((Input.GetKey(InputManager.data().jumpInput)) && _canJump) + { + jumpT = 0; + jumpReleased = false; + rigidbody.velocity = new Vector2(rigidbody.velocity.x, 0); + if (jumpSFX != null) AudioSingleton.getSFXSource().PlayOneShot(jumpSFX); + } - //Blocks continous jump button - if (!Input.GetKey(InputManager.data().jumpInput)) { jumpReleased = true; } + //Blocks continous jump button + if (!Input.GetKey(InputManager.data().jumpInput)) { jumpReleased = true; } + + //Apply Jump to player + if (jumpT < jumpDuration) + { + jumpT += Time.deltaTime; + float progress = (jumpDuration - jumpT) / jumpDuration; + //|| jumpT < jumpDuration/2f + if ((Input.GetKey(InputManager.data().jumpInput)) || b) { rigidbody.AddForce(new Vector2(0, jumpForce * progress)); } + } + else { b = false; } - //Apply Jump to player - if (jumpT < jumpDuration) - { - jumpT += Time.deltaTime; - float progress = (jumpDuration - jumpT) / jumpDuration; - //|| jumpT < jumpDuration/2f - if ((Input.GetKey(InputManager.data().jumpInput)) || b) { rigidbody.AddForce(new Vector2(0, jumpForce * progress)); } - } - else { b = false; } - _isSwimming = inWater; } @@ -176,7 +182,7 @@ public class PlayerController : NetworkBehaviour public bool waterBoost; public bool canJump() { - return jumpT >= jumpDuration && isGrounded && listenToInput && jumpReleased && !isSwimming;//&& !GameManager.isPaused + return jumpT >= jumpDuration && isGrounded && listenToInput && jumpReleased && !isSwimming && transform.childCount <= 0;//&& !GameManager.isPaused } public bool canBotJump() @@ -209,15 +215,22 @@ public class PlayerController : NetworkBehaviour { // Debug.Log("Hey look im flying"); } - + public float groundCheckerDist = 0.1f; + public float groundCheckerHeighMultipler = 1f; public bool getGrounded() { //return (Physics2D.Linecast(transform.position, groundChecker.position, groundLayerMask)); Collider2D col = GetComponentInChildren(); - return (Physics2D.BoxCast(col.bounds.center, new Vector2(col.bounds.size.x - (col.bounds.size.x / 5f), col.bounds.size.y), 0, Vector2.down, 0.1f, groundLayerMask)); + return (Physics2D.BoxCast(col.bounds.center, new Vector2(col.bounds.size.x - (col.bounds.size.x / 5f), col.bounds.size.y * groundCheckerHeighMultipler), 0, Vector2.down, groundCheckerDist, groundLayerMask)); + } + + void OnDrawGizmos() + { + Collider2D col = GetComponentInChildren(); + Gizmos.color = Color.red; + Gizmos.DrawWireCube(col.bounds.center - new Vector3(0, groundCheckerDist), new Vector2(col.bounds.size.x - (col.bounds.size.x / 5f), col.bounds.size.y * groundCheckerHeighMultipler)); } - // public bool getInWater(){Collider2D col = GetComponentInChildren(); // return (Physics2D.BoxCast(col.bounds.center, new Vector2(col.bounds.size.x - (col.bounds.size.x / 5f), col.bounds.size.y), 0, Vector2.down, 0.1f, waterLayerMask)); // } @@ -231,14 +244,17 @@ public class PlayerController : NetworkBehaviour // } -public static class AudioSingleton{ +public static class AudioSingleton +{ private static AudioSource music; private static AudioSource sfx; - + public static AudioMixer mixer = Resources.Load("MasterMixer") as AudioMixer; - public static AudioSource getMusicSource(){ - - if(music == null){ + public static AudioSource getMusicSource() + { + + if (music == null) + { GameObject go = new GameObject("Music Audio Source"); go.AddComponent(); music = go.GetComponent(); @@ -247,13 +263,15 @@ public static class AudioSingleton{ return music; } - public static AudioSource getSFXSource(){ + public static AudioSource getSFXSource() + { //AudioMixer mixer = Resources.Load("MasterMixer") as AudioMixer; - if(sfx == null){ + if (sfx == null) + { GameObject go = new GameObject("SFX Audio Source"); go.AddComponent(); - sfx= go.GetComponent(); - Debug.Log("sfx : " + (sfx==null).ToString() + ", mixer: " + (mixer == null).ToString() ); + sfx = go.GetComponent(); + Debug.Log("sfx : " + (sfx == null).ToString() + ", mixer: " + (mixer == null).ToString()); sfx.outputAudioMixerGroup = mixer.FindMatchingGroups("SFX")[0]; } diff --git a/Assets/Scripts/PushBox.cs b/Assets/Scripts/PushBox.cs new file mode 100644 index 0000000..d92f4ea --- /dev/null +++ b/Assets/Scripts/PushBox.cs @@ -0,0 +1,85 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; +using Mirror; +public class PushBox : NetworkBehaviour +{ + public int playersRequired; + public Text numberTxt; + + public List DTP; + public List Neighbours; + public List targets; + public List scannedList; + + void Start() + { + UpdateText(); + } + + void OnCollisionEnter2D(Collision2D col) + { + NetPlayer player = col.collider.GetComponent(); + if (player != null) + { + if (!DTP.Contains(player)) + { + DTP.Add(player); + UpdateNeighbourCount(); + + } + } + } + + void OnCollisionExit2D(Collision2D col) + { + NetPlayer player = col.collider.GetComponent(); + if (player != null) + { + if (DTP.Contains(player)) + { + DTP.Remove(player); + UpdateNeighbourCount(); + } + } + } + + + public void UpdateNeighbourCount() + { + targets = new List(); + Neighbours = new List(); + scannedList= new List(); + + targets.AddRange(DTP); + + int failCount = 0; + while(targets.Count > 0 && failCount < 50){ + failCount++; + + Neighbours.Add(targets[0]); + scannedList.Add(targets[0]); + foreach(NetPlayer neighbour in targets[0].touchingNeighbours){ + if(!scannedList.Contains(neighbour)){ + targets.Add(neighbour); + } + } + scannedList.Add(targets[0]); + targets.RemoveAt(0); + } + + if(failCount >= 50){ + Debug.LogError("Fail switch triggered"); + } + + GetComponent().simulated=((playersRequired - Neighbours.Count) > 0); + + UpdateText(); + } + + void UpdateText() + { + numberTxt.text = (playersRequired - Neighbours.Count).ToString(); + } +} diff --git a/Assets/Scripts/PushBox.cs.meta b/Assets/Scripts/PushBox.cs.meta new file mode 100644 index 0000000..46d1b1a --- /dev/null +++ b/Assets/Scripts/PushBox.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95601679d599a9fd8a2fe7721e81b132 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LRM.csproj b/LRM.csproj index 9f62baa..6382083 100644 --- a/LRM.csproj +++ b/LRM.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 diff --git a/Mirror.Authenticators.csproj b/Mirror.Authenticators.csproj index 6e403ad..f3d44bb 100644 --- a/Mirror.Authenticators.csproj +++ b/Mirror.Authenticators.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 diff --git a/Mirror.CompilerSymbols.csproj b/Mirror.CompilerSymbols.csproj index a1c8877..cbdbe19 100644 --- a/Mirror.CompilerSymbols.csproj +++ b/Mirror.CompilerSymbols.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;NET_STANDARD_2_0 + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;NET_STANDARD_2_0;DEVELOPMENT_BUILD prompt 4 0169 diff --git a/Mirror.Components.csproj b/Mirror.Components.csproj index afd88f6..7016c90 100644 --- a/Mirror.Components.csproj +++ b/Mirror.Components.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 diff --git a/Mirror.Editor.csproj b/Mirror.Editor.csproj index c136d47..5b952c8 100644 --- a/Mirror.Editor.csproj +++ b/Mirror.Editor.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;NET_STANDARD_2_0 + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;NET_STANDARD_2_0;DEVELOPMENT_BUILD prompt 4 0169 @@ -50,6 +50,7 @@ + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.dll @@ -618,9 +619,9 @@ {0f940bd2-732e-d4ba-3ad8-f7b21201a69b} Mirror - - {7e3a9708-574f-f809-b966-c5b7ec3f91a7} - Mirror.Weaver + + {811c67e7-94d9-6b80-4e0e-aaae0be2fb1c} + Unity.Mirror.CodeGen {d5284a81-f67c-7e2c-3e13-cdac4bdbd32c} diff --git a/Mirror.Examples.csproj b/Mirror.Examples.csproj index 6dbe353..32252f1 100644 --- a/Mirror.Examples.csproj +++ b/Mirror.Examples.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 @@ -77,6 +77,7 @@ + diff --git a/Mirror.csproj b/Mirror.csproj index 06ad21d..e013b8a 100644 --- a/Mirror.csproj +++ b/Mirror.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 @@ -40,8 +40,8 @@ - + @@ -70,6 +70,7 @@ + @@ -114,12 +115,12 @@ - + - + @@ -130,6 +131,7 @@ + diff --git a/NanoPark.sln b/NanoPark.sln index 815ed4a..c15e695 100644 --- a/NanoPark.sln +++ b/NanoPark.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEngine.UI", "UnityEngi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.TextMeshPro", "Unity.TextMeshPro.csproj", "{3fefc22c-d458-d273-9057-6267b53e6cc9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{794ed552-07b4-f11d-7366-ef1425d6d5d5}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Timeline", "Unity.Timeline.csproj", "{5b2d7a62-8e2c-4d7f-06c7-1d15a86edf92}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telepathy", "Telepathy.csproj", "{6d2f51d2-1ac4-e809-ec4f-893ec41a9c18}" @@ -19,8 +21,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Components", "Mirror EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Examples", "Mirror.Examples.csproj", "{1d1c4363-d4dc-90d5-4b63-6b3bf57e146b}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{794ed552-07b4-f11d-7366-ef1425d6d5d5}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LRM", "LRM.csproj", "{626a637e-6afe-97f0-9fd0-95b0bb04b632}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "where-allocations", "where-allocations.csproj", "{1a356b3a-d31e-3350-c514-470dfa96d5c8}" @@ -35,11 +35,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Timeline.Editor", "Un EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEditor.TestRunner", "UnityEditor.TestRunner.csproj", "{baa2589a-d157-c0a3-002d-be633f433da7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Mirror.CodeGen", "Unity.Mirror.CodeGen.csproj", "{811c67e7-94d9-6b80-4e0e-aaae0be2fb1c}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.2D.Sprite.Editor", "Unity.2D.Sprite.Editor.csproj", "{e236b30d-5074-92cb-40e7-21dea2ea8108}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityEditor.UI", "UnityEditor.UI.csproj", "{d5284a81-f67c-7e2c-3e13-cdac4bdbd32c}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Weaver", "Mirror.Weaver.csproj", "{7e3a9708-574f-f809-b966-c5b7ec3f91a7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{b7a43d13-c374-302d-8940-a1f8e530fe4c}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Rider.Editor", "Unity.Rider.Editor.csproj", "{697a9ea7-0789-5e8a-cd1f-a1c0d2c40c50}" EndProject @@ -51,10 +53,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.CollabProxy.Editor", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.VSCode.Editor", "Unity.VSCode.Editor.csproj", "{0c9389be-bc21-f173-bc53-1836cfd21b9e}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.CompilerSymbols", "Mirror.CompilerSymbols.csproj", "{1a877a05-c390-8d4f-0281-9c52b1040235}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParrelSync", "ParrelSync.csproj", "{9c8046d9-3477-2d3b-eeb8-eea73ce7e4d4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.CompilerSymbols", "Mirror.CompilerSymbols.csproj", "{1a877a05-c390-8d4f-0281-9c52b1040235}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +72,8 @@ Global {779772c6-608b-44d7-da1a-f2b38bd2f9b6}.Debug|Any CPU.Build.0 = Debug|Any CPU {3fefc22c-d458-d273-9057-6267b53e6cc9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3fefc22c-d458-d273-9057-6267b53e6cc9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {794ed552-07b4-f11d-7366-ef1425d6d5d5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {794ed552-07b4-f11d-7366-ef1425d6d5d5}.Debug|Any CPU.Build.0 = Debug|Any CPU {5b2d7a62-8e2c-4d7f-06c7-1d15a86edf92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5b2d7a62-8e2c-4d7f-06c7-1d15a86edf92}.Debug|Any CPU.Build.0 = Debug|Any CPU {6d2f51d2-1ac4-e809-ec4f-893ec41a9c18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -78,8 +82,6 @@ Global {c3fd6ccb-0e34-cb84-f45c-a0a5b5346b75}.Debug|Any CPU.Build.0 = Debug|Any CPU {1d1c4363-d4dc-90d5-4b63-6b3bf57e146b}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1d1c4363-d4dc-90d5-4b63-6b3bf57e146b}.Debug|Any CPU.Build.0 = Debug|Any CPU - {794ed552-07b4-f11d-7366-ef1425d6d5d5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {794ed552-07b4-f11d-7366-ef1425d6d5d5}.Debug|Any CPU.Build.0 = Debug|Any CPU {626a637e-6afe-97f0-9fd0-95b0bb04b632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {626a637e-6afe-97f0-9fd0-95b0bb04b632}.Debug|Any CPU.Build.0 = Debug|Any CPU {1a356b3a-d31e-3350-c514-470dfa96d5c8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -94,12 +96,14 @@ Global {533199e0-035b-b106-d535-d93fe9aeea08}.Debug|Any CPU.Build.0 = Debug|Any CPU {baa2589a-d157-c0a3-002d-be633f433da7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {baa2589a-d157-c0a3-002d-be633f433da7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {811c67e7-94d9-6b80-4e0e-aaae0be2fb1c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {811c67e7-94d9-6b80-4e0e-aaae0be2fb1c}.Debug|Any CPU.Build.0 = Debug|Any CPU {e236b30d-5074-92cb-40e7-21dea2ea8108}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {e236b30d-5074-92cb-40e7-21dea2ea8108}.Debug|Any CPU.Build.0 = Debug|Any CPU {d5284a81-f67c-7e2c-3e13-cdac4bdbd32c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {d5284a81-f67c-7e2c-3e13-cdac4bdbd32c}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7e3a9708-574f-f809-b966-c5b7ec3f91a7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7e3a9708-574f-f809-b966-c5b7ec3f91a7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {b7a43d13-c374-302d-8940-a1f8e530fe4c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {b7a43d13-c374-302d-8940-a1f8e530fe4c}.Debug|Any CPU.Build.0 = Debug|Any CPU {697a9ea7-0789-5e8a-cd1f-a1c0d2c40c50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {697a9ea7-0789-5e8a-cd1f-a1c0d2c40c50}.Debug|Any CPU.Build.0 = Debug|Any CPU {44e08a38-511b-ef34-9fbc-6bb7267f9200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -110,10 +114,10 @@ Global {d9b84d7e-6b8e-4b6f-1f08-0c556ec5fbfc}.Debug|Any CPU.Build.0 = Debug|Any CPU {0c9389be-bc21-f173-bc53-1836cfd21b9e}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0c9389be-bc21-f173-bc53-1836cfd21b9e}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1a877a05-c390-8d4f-0281-9c52b1040235}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1a877a05-c390-8d4f-0281-9c52b1040235}.Debug|Any CPU.Build.0 = Debug|Any CPU {9c8046d9-3477-2d3b-eeb8-eea73ce7e4d4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9c8046d9-3477-2d3b-eeb8-eea73ce7e4d4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1a877a05-c390-8d4f-0281-9c52b1040235}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1a877a05-c390-8d4f-0281-9c52b1040235}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SimpleWebTransport.csproj b/SimpleWebTransport.csproj index 697c638..24afe97 100644 --- a/SimpleWebTransport.csproj +++ b/SimpleWebTransport.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 diff --git a/Telepathy.csproj b/Telepathy.csproj index 732457b..2ce30c7 100644 --- a/Telepathy.csproj +++ b/Telepathy.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 diff --git a/Unity.Mirror.CodeGen.csproj b/Unity.Mirror.CodeGen.csproj index 93e6492..56f6c80 100644 --- a/Unity.Mirror.CodeGen.csproj +++ b/Unity.Mirror.CodeGen.csproj @@ -22,16 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER - prompt - 4 - 0169 - True - - - pdbonly - true - Temp\bin\Release\ + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_4_6;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;NET_STANDARD_2_0;DEVELOPMENT_BUILD prompt 4 0169 @@ -45,604 +36,604 @@ false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AIModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ARModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AccessibilityModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AndroidJNIModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AnimationModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AssetBundleModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AudioModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ClothModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ClusterInputModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ClusterRendererModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.CoreModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.CrashReportingModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.DSPGraphModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.DirectorModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.GameCenterModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.GridModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.HotReloadModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.IMGUIModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ImageConversionModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.InputModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.InputLegacyModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.JSONSerializeModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.LocalizationModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ParticleSystemModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.PerformanceReportingModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.PhysicsModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.Physics2DModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ProfilerModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ScreenCaptureModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SharedInternalsModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SpriteMaskModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SpriteShapeModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.StreamingModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SubstanceModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SubsystemsModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TLSModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TerrainModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TerrainPhysicsModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TextCoreModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TextRenderingModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TilemapModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UIModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UIElementsModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UNETModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UmbraModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityAnalyticsModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityConnectModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityTestProtocolModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestAssetBundleModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestAudioModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestTextureModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestWWWModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VFXModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VRModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VehiclesModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VideoModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.WindModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.XRModule.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEditor.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/UnityExtensions/Unity/UnityVR/Editor/UnityEditor.VR.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEditor.Graphs.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/PlaybackEngines/LinuxStandaloneSupport/UnityEditor.LinuxStandalone.Extensions.dll - - - /media/RocketSpeed/Unity_Projects/NanoPark/Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll - - - /media/RocketSpeed/Unity_Projects/NanoPark/Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll - - - /media/RocketSpeed/Unity_Projects/NanoPark/Library/PackageCache/com.unity.ext.nunit@1.0.0/net35/unity-custom/nunit.framework.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/mscorlib.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Core.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Runtime.Serialization.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.Linq.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.Vectors.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Net.Http.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Microsoft.CSharp.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Data.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/Microsoft.Win32.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.AppContext.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Concurrent.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.NonGeneric.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Specialized.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Annotations.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.EventBasedAsync.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.TypeConverter.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Console.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Data.Common.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Contracts.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Debug.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.FileVersionInfo.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Process.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.StackTrace.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TextWriterTraceListener.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Tools.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TraceSource.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Drawing.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Dynamic.Runtime.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Calendars.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Compression.ZipFile.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.DriveInfo.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Watcher.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.IsolatedStorage.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.MemoryMappedFiles.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Pipes.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.UnmanagedMemoryStream.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Expressions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Parallel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Queryable.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Http.Rtc.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NameResolution.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NetworkInformation.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Ping.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Requests.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Security.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Sockets.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebHeaderCollection.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.Client.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ObjectModel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.ILGeneration.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.Lightweight.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Reader.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.ResourceManager.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Writer.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.CompilerServices.VisualC.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Handles.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.RuntimeInformation.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.WindowsRuntime.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Numerics.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Formatters.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Json.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Xml.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Claims.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Algorithms.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Csp.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Encoding.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.X509Certificates.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Principal.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.SecureString.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Duplex.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Http.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.NetTcp.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Primitives.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Security.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.Extensions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.RegularExpressions.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Overlapped.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.Parallel.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Thread.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.ThreadPool.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Timer.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ValueTuple.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.ReaderWriter.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XDocument.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.XDocument.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlDocument.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlSerializer.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/netstandard.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.Lang.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/Boo.Lang.dll - - - /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/Unity.CompilationPipeline.Common.dll - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AIModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ARModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AccessibilityModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AndroidJNIModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AnimationModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AssetBundleModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.AudioModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ClothModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ClusterInputModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ClusterRendererModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.CoreModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.CrashReportingModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.DSPGraphModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.DirectorModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.GameCenterModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.GridModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.HotReloadModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.IMGUIModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ImageConversionModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.InputModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.InputLegacyModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.JSONSerializeModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.LocalizationModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ParticleSystemModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.PerformanceReportingModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.PhysicsModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.Physics2DModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ProfilerModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.ScreenCaptureModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SharedInternalsModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SpriteMaskModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SpriteShapeModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.StreamingModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SubstanceModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.SubsystemsModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TLSModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TerrainModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TerrainPhysicsModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TextCoreModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TextRenderingModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.TilemapModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UIModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UIElementsModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UNETModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UmbraModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityAnalyticsModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityConnectModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityTestProtocolModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestAssetBundleModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestAudioModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestTextureModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestWWWModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VFXModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VRModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VehiclesModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.VideoModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.WindModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEngine/UnityEngine.XRModule.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEditor.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/UnityExtensions/Unity/UnityVR/Editor/UnityEditor.VR.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/UnityEditor.Graphs.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/PlaybackEngines/LinuxStandaloneSupport/UnityEditor.LinuxStandalone.Extensions.dll + + + /media/RocketSpeed/Unity_Projects/NanoPark/Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll + + + /media/RocketSpeed/Unity_Projects/NanoPark/Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll + + + /media/RocketSpeed/Unity_Projects/NanoPark/Library/PackageCache/com.unity.ext.nunit@1.0.0/net35/unity-custom/nunit.framework.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/mscorlib.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Core.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Runtime.Serialization.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.Linq.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.Vectors.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Net.Http.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Microsoft.CSharp.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Data.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/Microsoft.Win32.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.AppContext.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Concurrent.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.NonGeneric.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Specialized.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Annotations.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.EventBasedAsync.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.TypeConverter.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Console.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Data.Common.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Contracts.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Debug.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.FileVersionInfo.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Process.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.StackTrace.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TextWriterTraceListener.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Tools.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TraceSource.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Drawing.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Dynamic.Runtime.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Calendars.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Compression.ZipFile.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.DriveInfo.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Watcher.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.IsolatedStorage.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.MemoryMappedFiles.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Pipes.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.UnmanagedMemoryStream.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Expressions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Parallel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Queryable.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Http.Rtc.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NameResolution.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NetworkInformation.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Ping.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Requests.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Security.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Sockets.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebHeaderCollection.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.Client.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ObjectModel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.ILGeneration.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.Lightweight.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Reader.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.ResourceManager.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Writer.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.CompilerServices.VisualC.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Handles.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.RuntimeInformation.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.WindowsRuntime.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Numerics.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Formatters.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Json.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Xml.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Claims.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Algorithms.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Csp.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Encoding.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.X509Certificates.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Principal.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.SecureString.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Duplex.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Http.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.NetTcp.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Primitives.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Security.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.Extensions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.RegularExpressions.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Overlapped.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.Parallel.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Thread.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.ThreadPool.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Timer.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ValueTuple.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.ReaderWriter.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XDocument.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.XDocument.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlDocument.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlSerializer.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/netstandard.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.Lang.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/Boo.Lang.dll + + + /home/warlock/Unity/Hub/Editor/2019.3.0f6/Editor/Data/Managed/Unity.CompilationPipeline.Common.dll + diff --git a/kcp2k.csproj b/kcp2k.csproj index b2184db..dfe674e 100644 --- a/kcp2k.csproj +++ b/kcp2k.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169 diff --git a/obj/Debug/Assembly-CSharp.csproj.AssemblyReference.cache b/obj/Debug/Assembly-CSharp.csproj.AssemblyReference.cache index 9e5d610..b47ade2 100644 Binary files a/obj/Debug/Assembly-CSharp.csproj.AssemblyReference.cache and b/obj/Debug/Assembly-CSharp.csproj.AssemblyReference.cache differ diff --git a/obj/Debug/Mirror.csproj.AssemblyReference.cache b/obj/Debug/Mirror.csproj.AssemblyReference.cache new file mode 100644 index 0000000..204fdef Binary files /dev/null and b/obj/Debug/Mirror.csproj.AssemblyReference.cache differ diff --git a/obj/Debug/SimpleWebTransport.csproj.AssemblyReference.cache b/obj/Debug/SimpleWebTransport.csproj.AssemblyReference.cache index abccd54..c96a529 100644 Binary files a/obj/Debug/SimpleWebTransport.csproj.AssemblyReference.cache and b/obj/Debug/SimpleWebTransport.csproj.AssemblyReference.cache differ diff --git a/obj/Debug/Unity.TextMeshPro.Editor.csproj.AssemblyReference.cache b/obj/Debug/Unity.TextMeshPro.Editor.csproj.AssemblyReference.cache index d47a743..204fdef 100644 Binary files a/obj/Debug/Unity.TextMeshPro.Editor.csproj.AssemblyReference.cache and b/obj/Debug/Unity.TextMeshPro.Editor.csproj.AssemblyReference.cache differ diff --git a/where-allocations.csproj b/where-allocations.csproj index 57d34c6..d0fbb05 100644 --- a/where-allocations.csproj +++ b/where-allocations.csproj @@ -22,7 +22,7 @@ full false Temp\bin\Debug\ - DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + DEBUG;TRACE;UNITY_2019_3_0;UNITY_2019_3;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_2019_3_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_VR;ENABLE_WEBCAM;ENABLE_UNITYWEBREQUEST;ENABLE_WWW;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE;PLATFORM_STANDALONE_LINUX;UNITY_STANDALONE_LINUX;UNITY_STANDALONE;UNITY_STANDALONE_LINUX_API;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_SPATIALTRACKING;ENABLE_MODULAR_UNITYENGINE_ASSEMBLIES;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_LINUX;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;IGNORANCE;IGNORANCE_1;IGNORANCE_1_4;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER;DEVELOPMENT_BUILD prompt 4 0169