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