Flatter, safer, but more resource intensive server query

This commit is contained in:
Garry Newman 2016-07-18 16:01:52 +01:00
parent f84c84e950
commit 99e1740961
10 changed files with 314 additions and 155 deletions

View File

@ -1,30 +0,0 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
public partial class Client
{
[TestMethod]
public void UpdateStats()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
client.Stats.UpdateStats();
}
}
[TestMethod]
public void UpdateSUpdateGlobalStatstats()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
client.Stats.UpdateGlobalStats( 1 );
client.Stats.UpdateGlobalStats( 3 );
client.Stats.UpdateGlobalStats( 7 );
}
}
}
}

View File

@ -111,26 +111,7 @@ public void GetVoice()
} }
} }
[TestMethod]
public void GetServers()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var query = client.ServerList.Test();
for ( int i = 0; i < 100 ; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 5 );
if ( query.Finished )
break;
}
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
}
}
[TestMethod] [TestMethod]
public void InventoryDefinitions() public void InventoryDefinitions()

View File

@ -87,7 +87,7 @@
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="Client.cs" /> <Compile Include="Client.cs" />
<Compile Include="Client.Networking.cs" /> <Compile Include="Networking.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -97,7 +97,8 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Client.Stats.cs" /> <Compile Include="Serverlist.cs" />
<Compile Include="Stats.cs" />
</ItemGroup> </ItemGroup>
<Choose> <Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">

View File

@ -5,7 +5,10 @@
namespace Facepunch.Steamworks.Test namespace Facepunch.Steamworks.Test
{ {
public partial class Client [TestClass]
[DeploymentItem( "FacepunchSteamworksApi.dll" )]
[DeploymentItem( "steam_appid.txt" )]
public partial class Networking
{ {
[TestMethod] [TestMethod]
public void PeerToPeerSend() public void PeerToPeerSend()

View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( "FacepunchSteamworksApi.dll" )]
[DeploymentItem( "steam_appid.txt" )]
public partial class ServerList
{
[TestMethod]
public void InternetList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var query = client.ServerList.Test();
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 10 );
if ( query.Finished )
break;
}
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
query.Dispose();
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 1 );
}
}
}
[TestMethod]
public void MultipleInternetList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var queries = new List< Facepunch.Steamworks.ServerList.Request >();
for ( int i = 0; i < 10; i++ )
queries.Add( client.ServerList.Test() );
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 5 );
if ( queries.Any( x => x.Finished ) )
break;
}
foreach ( var query in queries )
{
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
client.Update();
query.Dispose();
client.Update();
}
}
}
[TestMethod]
public void HistoryList()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var query = client.ServerList.History( new Dictionary<string, string>() );
while ( true )
{
client.Update();
System.Threading.Thread.Sleep( 2 );
if ( query.Finished )
break;
}
Console.WriteLine( "Responded: " + query.Responded.Count.ToString() );
Console.WriteLine( "Unresponsive: " + query.Unresponsive.Count.ToString() );
query.Dispose();
for ( int i = 0; i < 100; i++ )
{
client.Update();
System.Threading.Thread.Sleep( 1 );
}
}
}
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.Text;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( "FacepunchSteamworksApi.dll" )]
[DeploymentItem( "steam_appid.txt" )]
public class Stats
{
[TestMethod]
public void UpdateStats()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
client.Stats.UpdateStats();
}
}
[TestMethod]
public void UpdateSUpdateGlobalStatstats()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
client.Stats.UpdateGlobalStats( 1 );
client.Stats.UpdateGlobalStats( 3 );
client.Stats.UpdateGlobalStats( 7 );
}
}
[TestMethod]
public void GetClientFloat()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetFloat( "deaths" );
Console.WriteLine( v );
}
}
[TestMethod]
public void GetClientInt()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetInt( "deaths" );
Console.WriteLine( v );
}
}
[TestMethod]
public void GetGlobalFloat()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetGlobalFloat( "deaths" );
Console.WriteLine( v );
}
}
[TestMethod]
public void GetGlobalInt()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
var v = client.Stats.GetGlobalInt( "deaths" );
Console.WriteLine( v );
}
}
}
}

View File

@ -14,9 +14,6 @@ public class Request : IDisposable
internal Client client; internal Client client;
internal IntPtr Id; internal IntPtr Id;
private IntPtr m_pVTable;
private GCHandle m_pGCHandle;
public struct Server public struct Server
{ {
public string Name { get; set; } public string Name { get; set; }
@ -56,8 +53,6 @@ public string ConnectionAddress
} }
} }
internal static Server FromSteam( gameserveritem_t item ) internal static Server FromSteam( gameserveritem_t item )
{ {
return new Server() return new Server()
@ -100,21 +95,11 @@ internal static Server FromSteam( gameserveritem_t item )
/// </summary> /// </summary>
public bool Finished = false; public bool Finished = false;
internal Request() internal Request( Client c )
{ {
// client = c;
// Create a fake vtable for Steam to respond to
//
var vt = new VTable()
{
responded = OnServerResponded,
nonresponsive = NonResponsive,
complete = Complete
};
m_pVTable = Marshal.AllocHGlobal( Marshal.SizeOf( typeof( VTable ) ) ); client.OnUpdate += Update;
Marshal.StructureToPtr( vt, m_pVTable, false );
m_pGCHandle = GCHandle.Alloc( m_pVTable, GCHandleType.Pinned );
} }
~Request() ~Request()
@ -122,88 +107,98 @@ internal Request()
Dispose(); Dispose();
} }
int lastCount = 0;
internal List<int> watchlist = new List<int>();
private void Update()
{
if ( Id == IntPtr.Zero )
return;
//
// Add any servers we're not watching to our watch list
//
var count = client.native.servers.GetServerCount( Id );
if ( count != lastCount )
{
for ( int i = lastCount; i < count; i++ )
{
watchlist.Add( i );
}
lastCount = count;
}
//
// Remove any servers that respond successfully
//
watchlist.RemoveAll( x =>
{
var info = client.native.servers.GetServerDetails( Id, x );
if ( info.m_bHadSuccessfulResponse )
{
OnServer( info );
return true;
}
return false;
} );
//
// If we've finished refreshing
//
if ( client.native.servers.IsRefreshing( Id ) == false )
{
//
// Put any other servers on the 'no response' list
//
watchlist.RemoveAll( x =>
{
var info = client.native.servers.GetServerDetails( Id, x );
OnServer( info );
return true;
} );
Finished = true;
client.OnUpdate -= Update;
client.native.servers.CancelQuery( Id );
Id = IntPtr.Zero;
}
}
private void OnServer( gameserveritem_t info )
{
if ( info.m_bHadSuccessfulResponse )
{
Responded.Add( Server.FromSteam( info ) );
}
else
{
Unresponsive.Add( Server.FromSteam( info ) );
}
}
/// <summary> /// <summary>
/// Disposing will end the query /// Disposing will end the query
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
client.OnUpdate -= Update;
// //
// Cancel the query if it's still running // Cancel the query if it's still running
// //
if ( !Finished && Id != IntPtr.Zero ) if ( Id != IntPtr.Zero )
{ {
if ( client.Valid ) if ( client.Valid )
client.native.servers.CancelQuery( Id ); client.native.servers.CancelQuery( Id );
Id = IntPtr.Zero; Id = IntPtr.Zero;
} }
//
// Release the pinned GC resources
//
if ( m_pVTable != IntPtr.Zero )
{
Marshal.FreeHGlobal( m_pVTable );
m_pVTable = IntPtr.Zero;
}
if ( m_pGCHandle.IsAllocated )
{
m_pGCHandle.Free();
}
} }
private void Complete( IntPtr thisptr, IntPtr RequestId, int response )
{
if ( RequestId != Id )
throw new Exception( "Request ID is invalid!" );
Finished = true;
Id = IntPtr.Zero;
}
private void NonResponsive( IntPtr thisptr, IntPtr RequestId, int iServer )
{
if ( RequestId != Id )
throw new Exception( "Request ID is invalid!" );
var info = client.native.servers.GetServerDetails( Id, iServer );
Unresponsive.Add( Server.FromSteam( info ) );
}
private void OnServerResponded( IntPtr thisptr, IntPtr RequestId, int iServer )
{
if ( RequestId != Id )
throw new Exception( "Request ID is invalid!" );
var info = client.native.servers.GetServerDetails( Id, iServer );
Responded.Add( Server.FromSteam( info ) );
System.Diagnostics.Debug.WriteLine( info.m_szServerName );
}
internal IntPtr GetVTablePointer()
{
return m_pGCHandle.AddrOfPinnedObject();
}
[StructLayout( LayoutKind.Sequential )]
internal class VTable
{
[UnmanagedFunctionPointer( CallingConvention.ThisCall )]
internal delegate void InternalServerResponded( IntPtr thisptr, IntPtr hRequest, int iServer );
[UnmanagedFunctionPointer( CallingConvention.ThisCall )]
internal delegate void InternalServerFailedToRespond( IntPtr thisptr, IntPtr hRequest, int iServer );
[UnmanagedFunctionPointer( CallingConvention.ThisCall )]
internal delegate void InternalRefreshComplete( IntPtr thisptr, IntPtr hRequest, int response );
[NonSerialized, MarshalAs(UnmanagedType.FunctionPtr)]
internal InternalServerResponded responded;
[NonSerialized, MarshalAs(UnmanagedType.FunctionPtr)]
internal InternalServerFailedToRespond nonresponsive;
[NonSerialized, MarshalAs(UnmanagedType.FunctionPtr)]
internal InternalRefreshComplete complete;
}
} }
} }
} }

View File

@ -48,25 +48,41 @@ public unsafe Request Test()
//fixed ( void* a = array ) //fixed ( void* a = array )
{ {
var request = new Request() var request = new Request( client );
{
client = client
};
request.Id = client.native.servers.RequestInternetServerList( client.AppId, new IntPtr[] { }, request.GetVTablePointer() ); request.Id = client.native.servers.RequestInternetServerList( client.AppId, new IntPtr[] { }, IntPtr.Zero );
return request; return request;
} }
} }
class ResponseClass : ISteamMatchmakingServerListResponse
{
internal override IntPtr GetIntPtr()
{
return IntPtr.Zero;
}
internal override void RefreshComplete( uint hRequest, uint response )
{
throw new NotImplementedException();
}
internal override void ServerFailedToRespond( uint hRequest, int iServer )
{
throw new NotImplementedException();
}
internal override void ServerResponded( uint hRequest, int iServer )
{
throw new NotImplementedException();
}
}
public unsafe Request History( Dictionary< string, string > filter ) public unsafe Request History( Dictionary< string, string > filter )
{ {
var request = new Request() var request = new Request( client );
{ request.Id = client.native.servers.RequestHistoryServerList( client.AppId, new IntPtr[] { }, IntPtr.Zero );
client = client
};
request.Id = client.native.servers.RequestHistoryServerList( client.AppId, new IntPtr[] { }, request.GetVTablePointer() );
return request; return request;
} }

View File

@ -139,7 +139,7 @@ public Client( uint appId )
public void Dispose() public void Dispose()
{ {
if ( native != null) if ( native != null)
{ {
native.Dispose(); native.Dispose();
native = null; native = null;
@ -157,15 +157,23 @@ private void InternalOnWarning( int nSeverity, System.Text.StringBuilder text )
} }
} }
internal event Action OnUpdate;
/// <summary> /// <summary>
/// Should be called at least once every frame /// Should be called at least once every frame
/// </summary> /// </summary>
public void Update() public void Update()
{ {
if ( native == null )
return;
Valve.Steamworks.SteamAPI.RunCallbacks(); Valve.Steamworks.SteamAPI.RunCallbacks();
Voice.Update(); Voice.Update();
Inventory.Update(); Inventory.Update();
Networking.Update(); Networking.Update();
if ( OnUpdate != null )
OnUpdate();
} }
public bool Valid public bool Valid

View File

@ -44,22 +44,30 @@ public void UpdateGlobalStats( int days = 1 )
public int GetInt( string name ) public int GetInt( string name )
{ {
return 0; int data = 0;
client.native.userstats.GetStat( name, ref data );
return data;
} }
public int GetGlobalInt( string name ) public long GetGlobalInt( string name )
{ {
return 0; long data = 0;
client.native.userstats.GetGlobalStat( name, ref data );
return data;
} }
public int GetFloat( string name ) public float GetFloat( string name )
{ {
return 0; float data = 0;
client.native.userstats.GetStat0( name, ref data );
return data;
} }
public int GetGlobalFloat( string name ) public double GetGlobalFloat( string name )
{ {
return 0; double data = 0;
client.native.userstats.GetGlobalStat0( name, ref data );
return data;
} }
} }