From 16ce5f4d6335ff815917cacbcac9599b4982c84a Mon Sep 17 00:00:00 2001 From: Garry Newman Date: Thu, 2 May 2019 15:23:47 +0100 Subject: [PATCH] NetworkingUtils (Ping stuff) --- .../Facepunch.Steamworks.Test.csproj | 1 + Facepunch.Steamworks.Test/NetworkingUtils.cs | 62 ++++++++++++++ Facepunch.Steamworks.Test/UgcEditor.cs | 4 +- .../Interfaces/ISteamNetworkingUtils.cs | 30 +++---- Facepunch.Steamworks/SteamClient.cs | 1 + Facepunch.Steamworks/SteamNetworkingUtils.cs | 82 +++++++++++++++++++ .../Structs/SteamNetworking.cs | 47 ++++++++++- Generator/Cleanup.cs | 1 + Generator/CodeWriter/Types/BaseType.cs | 7 +- 9 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 Facepunch.Steamworks.Test/NetworkingUtils.cs create mode 100644 Facepunch.Steamworks/SteamNetworkingUtils.cs diff --git a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj index e25411f..43da746 100644 --- a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj +++ b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj @@ -100,6 +100,7 @@ + diff --git a/Facepunch.Steamworks.Test/NetworkingUtils.cs b/Facepunch.Steamworks.Test/NetworkingUtils.cs new file mode 100644 index 0000000..6f8d974 --- /dev/null +++ b/Facepunch.Steamworks.Test/NetworkingUtils.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Steamworks +{ + [TestClass] + [DeploymentItem( "steam_api64.dll" )] + public class NetworkUtilsTest + { + static string GarrysLocation = "lhr=19+1,ams=25+2/25+1,par=29+2,fra=31+3/30+1,lux=33+3,vie=44+4/41+1,waw=47+4/45+1,sto2=48+4/46+2,sto=50+5/46+2,iad=107+10/91+1,sgp=186+18,gru=252+25/234+1"; + + [TestMethod] + public async Task LocalPingLocation() + { + await SteamNetworkingUtils.WaitForPingDataAsync(); + + for ( int i = 0; i < 10; i++ ) + { + var pl = SteamNetworkingUtils.LocalPingLocation; + if ( !pl.HasValue ) + { + await Task.Delay( 1000 ); + continue; + } + + Console.WriteLine( $"{i} Seconds Until Result: {pl}" ); + return; + } + + Assert.Fail(); + } + + [TestMethod] + public void PingLocationParse() + { + var pl = Data.PingLocation.TryParseFromString( GarrysLocation ); + + Assert.IsTrue( pl.HasValue ); + + Console.WriteLine( $"Parsed OKAY! {pl}" ); + } + + [TestMethod] + public async Task GetEstimatedPing() + { + await SteamNetworkingUtils.WaitForPingDataAsync(); + + var garrysping = Data.PingLocation.TryParseFromString( GarrysLocation ); + Assert.IsTrue( garrysping.HasValue ); + + var ping = SteamNetworkingUtils.EstimatePingTo( garrysping.Value ); + Assert.IsTrue( ping > 0 ); + + Console.WriteLine( $"Ping returned: {ping}" ); + } + } + +} diff --git a/Facepunch.Steamworks.Test/UgcEditor.cs b/Facepunch.Steamworks.Test/UgcEditor.cs index 1548721..27427e1 100644 --- a/Facepunch.Steamworks.Test/UgcEditor.cs +++ b/Facepunch.Steamworks.Test/UgcEditor.cs @@ -45,7 +45,7 @@ namespace Steamworks } [TestMethod] - public async Task UploadBigFile() + public async Task UploadBigishFile() { var created = Ugc.Editor.NewCommunityFile .WithTitle( "Unit Test Upload Item" ) @@ -65,7 +65,7 @@ namespace Steamworks // Upload a file of random bytes var rand = new Random(); - var testFile = new byte[1024 * 1024 * 256]; + var testFile = new byte[1024 * 1024 * 32]; rand.NextBytes( testFile ); System.IO.File.WriteAllBytes( testFolder.FullName + "/testfile1.bin", testFile ); diff --git a/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs b/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs index 85af70c..bb78c03 100644 --- a/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs +++ b/Facepunch.Steamworks/Generated/Interfaces/ISteamNetworkingUtils.cs @@ -34,58 +34,58 @@ namespace Steamworks #region FunctionMeta [UnmanagedFunctionPointer( CallingConvention.ThisCall )] - private delegate float FGetLocalPingLocation( IntPtr self, SteamNetworkPingLocation_t result ); + private delegate float FGetLocalPingLocation( IntPtr self, ref PingLocation result ); private FGetLocalPingLocation _GetLocalPingLocation; #endregion - internal float GetLocalPingLocation( SteamNetworkPingLocation_t result ) + internal float GetLocalPingLocation( ref PingLocation result ) { - return _GetLocalPingLocation( Self, result ); + return _GetLocalPingLocation( Self, ref result ); } #region FunctionMeta [UnmanagedFunctionPointer( CallingConvention.ThisCall )] - private delegate int FEstimatePingTimeBetweenTwoLocations( IntPtr self, SteamNetworkPingLocation_t location1, SteamNetworkPingLocation_t location2 ); + private delegate int FEstimatePingTimeBetweenTwoLocations( IntPtr self, ref PingLocation location1, ref PingLocation location2 ); private FEstimatePingTimeBetweenTwoLocations _EstimatePingTimeBetweenTwoLocations; #endregion - internal int EstimatePingTimeBetweenTwoLocations( SteamNetworkPingLocation_t location1, SteamNetworkPingLocation_t location2 ) + internal int EstimatePingTimeBetweenTwoLocations( ref PingLocation location1, ref PingLocation location2 ) { - return _EstimatePingTimeBetweenTwoLocations( Self, location1, location2 ); + return _EstimatePingTimeBetweenTwoLocations( Self, ref location1, ref location2 ); } #region FunctionMeta [UnmanagedFunctionPointer( CallingConvention.ThisCall )] - private delegate int FEstimatePingTimeFromLocalHost( IntPtr self, SteamNetworkPingLocation_t remoteLocation ); + private delegate int FEstimatePingTimeFromLocalHost( IntPtr self, ref PingLocation remoteLocation ); private FEstimatePingTimeFromLocalHost _EstimatePingTimeFromLocalHost; #endregion - internal int EstimatePingTimeFromLocalHost( SteamNetworkPingLocation_t remoteLocation ) + internal int EstimatePingTimeFromLocalHost( ref PingLocation remoteLocation ) { - return _EstimatePingTimeFromLocalHost( Self, remoteLocation ); + return _EstimatePingTimeFromLocalHost( Self, ref remoteLocation ); } #region FunctionMeta [UnmanagedFunctionPointer( CallingConvention.ThisCall )] - private delegate void FConvertPingLocationToString( IntPtr self, SteamNetworkPingLocation_t location, StringBuilder pszBuf, int cchBufSize ); + private delegate void FConvertPingLocationToString( IntPtr self, ref PingLocation location, StringBuilder pszBuf, int cchBufSize ); private FConvertPingLocationToString _ConvertPingLocationToString; #endregion - internal void ConvertPingLocationToString( SteamNetworkPingLocation_t location, StringBuilder pszBuf, int cchBufSize ) + internal void ConvertPingLocationToString( ref PingLocation location, StringBuilder pszBuf, int cchBufSize ) { - _ConvertPingLocationToString( Self, location, pszBuf, cchBufSize ); + _ConvertPingLocationToString( Self, ref location, pszBuf, cchBufSize ); } #region FunctionMeta [UnmanagedFunctionPointer( CallingConvention.ThisCall )] [return: MarshalAs( UnmanagedType.I1 )] - private delegate bool FParsePingLocationString( IntPtr self, string pszString, SteamNetworkPingLocation_t result ); + private delegate bool FParsePingLocationString( IntPtr self, string pszString, ref PingLocation result ); private FParsePingLocationString _ParsePingLocationString; #endregion - internal bool ParsePingLocationString( string pszString, SteamNetworkPingLocation_t result ) + internal bool ParsePingLocationString( string pszString, ref PingLocation result ) { - return _ParsePingLocationString( Self, pszString, result ); + return _ParsePingLocationString( Self, pszString, ref result ); } #region FunctionMeta diff --git a/Facepunch.Steamworks/SteamClient.cs b/Facepunch.Steamworks/SteamClient.cs index 018235f..bd5ca77 100644 --- a/Facepunch.Steamworks/SteamClient.cs +++ b/Facepunch.Steamworks/SteamClient.cs @@ -84,6 +84,7 @@ namespace Steamworks SteamNetworking.Shutdown(); SteamMatchmaking.Shutdown(); SteamParties.Shutdown(); + SteamNetworkingUtils.Shutdown(); SteamAPI.Shutdown(); } diff --git a/Facepunch.Steamworks/SteamNetworkingUtils.cs b/Facepunch.Steamworks/SteamNetworkingUtils.cs new file mode 100644 index 0000000..f6a6aea --- /dev/null +++ b/Facepunch.Steamworks/SteamNetworkingUtils.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Steamworks.Data; + +namespace Steamworks +{ + /// + /// Undocumented Parental Settings + /// + public static class SteamNetworkingUtils + { + static ISteamNetworkingUtils _internal; + internal static ISteamNetworkingUtils Internal + { + get + { + if ( _internal == null ) + { + _internal = new ISteamNetworkingUtils(); + _internal.InitUserless(); + } + + return _internal; + } + } + + internal static void Shutdown() + { + _internal = null; + } + + /// + /// Return location info for the current host. + /// + /// It takes a few seconds to initialize access to the relay network. If + /// you call this very soon after startup the data may not be available yet. + /// + /// This always return the most up-to-date information we have available + /// right now, even if we are in the middle of re-calculating ping times. + /// + public static PingLocation? LocalPingLocation + { + get + { + PingLocation location = default; + var age = Internal.GetLocalPingLocation( ref location ); + if ( age < 0 ) + return null; + + return location; + } + } + + /// + /// Same as PingLocation.EstimatePingTo, but assumes that one location is the local host. + /// This is a bit faster, especially if you need to calculate a bunch of + /// these in a loop to find the fastest one. + /// + public static int EstimatePingTo( PingLocation target ) + { + return Internal.EstimatePingTimeFromLocalHost( ref target ); + } + + /// + /// If you need ping information straight away, wait on this. It will return + /// immediately if you already have up to date ping data + /// + public static async Task WaitForPingDataAsync( float maxAgeInSeconds = 60 * 5 ) + { + if ( Internal.CheckPingDataUpToDate( 60.0f ) ) + return; + + while ( Internal.IsPingMeasurementInProgress() ) + { + await Task.Delay( 10 ); + } + } + } +} \ No newline at end of file diff --git a/Facepunch.Steamworks/Structs/SteamNetworking.cs b/Facepunch.Steamworks/Structs/SteamNetworking.cs index 7bdc3a1..1784a51 100644 --- a/Facepunch.Steamworks/Structs/SteamNetworking.cs +++ b/Facepunch.Steamworks/Structs/SteamNetworking.cs @@ -58,9 +58,54 @@ namespace Steamworks.Data /// ISteamNetworkingUtils(). /// /// - public struct SteamNetworkPingLocation_t + public struct PingLocation { [MarshalAs( UnmanagedType.ByValArray, SizeConst = 512, ArraySubType = UnmanagedType.U8 )] public ushort[] Data; + + public static PingLocation? TryParseFromString( string str ) + { + var result = default( PingLocation ); + if ( !SteamNetworkingUtils.Internal.ParsePingLocationString( str, ref result ) ) + return null; + + return result; + } + + + public override string ToString() + { + var sb = Helpers.TakeStringBuilder(); + SteamNetworkingUtils.Internal.ConvertPingLocationToString( ref this, sb, sb.Capacity ); + return sb.ToString(); + } + + + /// Estimate the round-trip latency between two arbitrary locations, in + /// milliseconds. This is a conservative estimate, based on routing through + /// the relay network. For most basic relayed connections, this ping time + /// will be pretty accurate, since it will be based on the route likely to + /// be actually used. + /// + /// If a direct IP route is used (perhaps via NAT traversal), then the route + /// will be different, and the ping time might be better. Or it might actually + /// be a bit worse! Standard IP routing is frequently suboptimal! + /// + /// But even in this case, the estimate obtained using this method is a + /// reasonable upper bound on the ping time. (Also it has the advantage + /// of returning immediately and not sending any packets.) + /// + /// In a few cases we might not able to estimate the route. In this case + /// a negative value is returned. k_nSteamNetworkingPing_Failed means + /// the reason was because of some networking difficulty. (Failure to + /// ping, etc) k_nSteamNetworkingPing_Unknown is returned if we cannot + /// currently answer the question for some other reason. + /// + /// Do you need to be able to do this from a backend/matchmaking server? + /// You are looking for the "ticketgen" library. + public int EstimatePingTo( PingLocation target ) + { + return SteamNetworkingUtils.Internal.EstimatePingTimeBetweenTwoLocations( ref this, ref target ); + } } } \ No newline at end of file diff --git a/Generator/Cleanup.cs b/Generator/Cleanup.cs index 9ea92bb..73aaa6c 100644 --- a/Generator/Cleanup.cs +++ b/Generator/Cleanup.cs @@ -26,6 +26,7 @@ public static class Cleanup type = type.Replace( "SteamItemInstanceID_t", "InventoryItemId" ); type = type.Replace( "SteamItemDef_t", "InventoryDefId" ); type = type.Replace( "ChatRoomEnterResponse", "RoomEnter" ); + type = type.Replace( "SteamNetworkPingLocation_t", "PingLocation" ); return type; } diff --git a/Generator/CodeWriter/Types/BaseType.cs b/Generator/CodeWriter/Types/BaseType.cs index 8ee3ff5..1d17ac3 100644 --- a/Generator/CodeWriter/Types/BaseType.cs +++ b/Generator/CodeWriter/Types/BaseType.cs @@ -17,8 +17,6 @@ internal class BaseType { type = Cleanup.ConvertType( type ); - type = type.Trim( '&' ); - if ( type == "SteamAPIWarningMessageHook_t" ) return new PointerType { NativeType = type, VarName = varname }; if ( type == "intptr_t" ) return new PointerType { NativeType = type, VarName = varname }; @@ -28,7 +26,7 @@ internal class BaseType if ( type.Replace( " ", "" ).StartsWith( "constchar*" ) ) return new ConstCharType { NativeType = type, VarName = varname }; if ( type == "char *" ) return new StringBuilderType { NativeType = type, VarName = varname }; - var basicType = type.Replace( "const ", "" ).Trim( ' ', '*' ); + var basicType = type.Replace( "const ", "" ).Trim( ' ', '*', '&' ); if ( basicType == "void" ) return new PointerType { NativeType = type, VarName = varname }; if ( basicType.StartsWith( "ISteam" ) ) return new PointerType { NativeType = type, VarName = varname }; @@ -49,6 +47,7 @@ internal class BaseType if ( basicType.EndsWith( "_t" ) ) return new StructType { NativeType = type, VarName = varname, StructName = basicType }; if ( basicType == "InventoryItemId" ) return new StructType { NativeType = type, VarName = varname, StructName = basicType }; if ( basicType == "InventoryDefId" ) return new StructType { NativeType = type, VarName = varname, StructName = basicType }; + if ( basicType == "PingLocation" ) return new StructType { NativeType = type, VarName = varname, StructName = basicType }; if ( basicType.StartsWith( "E" ) && char.IsUpper( basicType[1] ) ) return new EnumType { NativeType = type.Substring( 1 ), VarName = varname }; return new BaseType { NativeType = type, VarName = varname }; @@ -62,7 +61,7 @@ internal class BaseType public virtual string ReturnType => TypeName; - public virtual string Ref => !IsVector && NativeType.EndsWith( "*" ) || NativeType.EndsWith( "**" ) ? "ref " : ""; + public virtual string Ref => !IsVector && NativeType.EndsWith( "*" ) || NativeType.EndsWith( "**" ) || NativeType.Contains( "&" ) ? "ref " : ""; public virtual bool IsVector { get