Cleaner approach for fetching usernames

This commit is contained in:
James King 2019-12-05 10:17:17 +00:00
parent 7c4aaddd1c
commit 605054339a
6 changed files with 124 additions and 121 deletions

View File

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
@ -98,6 +99,35 @@ public void CachedAvatar()
}
}
[TestMethod]
public void FetchUsername()
{
using ( var client = new Facepunch.Steamworks.Client( 252490 ) )
{
Assert.IsTrue(client.IsValid);
const ulong id = 76561198095600584u;
var passed = false;
var timeout = TimeSpan.FromSeconds(10d);
client.Friends.GetName( id, name =>
{
Console.WriteLine( name );
passed = true;
} );
while ( !passed && timeout > TimeSpan.Zero )
{
client.Update();
System.Threading.Thread.Sleep( 10 );
timeout -= TimeSpan.FromMilliseconds( 10 );
}
Assert.IsTrue( passed );
}
}
public static void DrawImage( Image img )
{
var grad = " -:+#";

View File

@ -58,7 +58,7 @@ public void GetLeaderboard()
foreach ( var entry in board.Results )
{
Console.WriteLine( $"{entry.GlobalRank}: {entry.SteamId} ({entry.Name}) with {entry.Score}" );
Console.WriteLine( $"{entry.GlobalRank}: {entry.SteamId} ({client.Friends.GetCachedName(entry.SteamId)}) with {entry.Score}" );
if ( entry.SubScores != null )
Console.WriteLine( " - " + string.Join( ";", entry.SubScores.Select( x => x.ToString() ).ToArray() ) );
@ -97,7 +97,7 @@ public void GetLeaderboardCallback()
{
foreach ( var entry in results )
{
Console.WriteLine( $"{entry.GlobalRank}: {entry.SteamId} ({entry.Name}) with {entry.Score}" );
Console.WriteLine( $"{entry.GlobalRank}: {entry.SteamId} ({client.Friends.GetCachedName(entry.SteamId)}) with {entry.Score}" );
if ( entry.SubScores != null )
Console.WriteLine( " - " + string.Join( ";", entry.SubScores.Select( x => x.ToString() ).ToArray() ) );

View File

@ -365,7 +365,7 @@ public void SetGetUserMetadata()
client.Lobby.OnLobbyMemberDataUpdated = (steamID) =>
{
string name = client.Friends.GetName(steamID);
string name = client.Friends.GetCachedName(steamID);
Console.WriteLine(name + " updated data");
Assert.IsTrue(client.Lobby.GetMemberData(steamID, "testkey") == "testvalue");
Console.WriteLine("testkey is now: " + client.Lobby.GetMemberData(steamID, "testkey"));

View File

@ -121,13 +121,12 @@ public bool UpdateInformation( ulong steamid )
{
return !client.native.friends.RequestUserInformation( steamid, false );
}
/// <summary>
/// Get this user's name
/// Get this user's name immediately. This should work for people on your friends list.
/// </summary>
public string GetName( ulong steamid )
public string GetCachedName( ulong steamid )
{
client.native.friends.RequestUserInformation( steamid, true );
return client.native.friends.GetFriendPersonaName( steamid );
}
@ -270,7 +269,7 @@ public void GetAvatar( AvatarSize size, ulong steamid, Action<Image> callback )
}
// Lets request it from Steam
if (!client.native.friends.RequestUserInformation(steamid, false))
if ( !client.native.friends.RequestUserInformation( steamid, false ) )
{
// from docs: false means that we already have all the details about that user, and functions that require this information can be used immediately
// but that's probably not true because we just checked the cache
@ -287,7 +286,7 @@ public void GetAvatar( AvatarSize size, ulong steamid, Action<Image> callback )
// if not it'll time out anyway
}
PersonaCallbacks.Add( new PersonaCallback
AvatarCallbacks.Add( new PersonaCallback<Image>
{
SteamId = steamid,
Size = size,
@ -296,15 +295,54 @@ public void GetAvatar( AvatarSize size, ulong steamid, Action<Image> callback )
});
}
private class PersonaCallback
public const string DefaultName = "[unknown]";
public void GetName( ulong steamid, Action<string> callback )
{
// Maybe we already have it downloaded?
var name = GetCachedName( steamid );
if ( name != null && name != DefaultName )
{
callback( name );
return;
}
// Lets request it from Steam
if ( !client.native.friends.RequestUserInformation( steamid, true ) )
{
// from docs: false means that we already have all the details about that user, and functions that require this information can be used immediately
// but that's probably not true because we just checked the cache
// check again in case it was just a race
name = GetCachedName( steamid );
if ( name != null && name != DefaultName )
{
callback( name );
return;
}
// maybe Steam returns false if it was already requested? just add another callback and hope it comes
// if not it'll time out anyway
}
NameCallbacks.Add( new PersonaCallback<string>
{
SteamId = steamid,
Callback = callback,
Time = DateTime.Now
} );
}
private class PersonaCallback<T>
{
public ulong SteamId;
public AvatarSize Size;
public Action<Image> Callback;
public Action<T> Callback;
public DateTime Time;
}
List<PersonaCallback> PersonaCallbacks = new List<PersonaCallback>();
List<PersonaCallback<Image>> AvatarCallbacks = new List<PersonaCallback<Image>>();
List<PersonaCallback<string>> NameCallbacks = new List<PersonaCallback<string>>();
public SteamFriend Get( ulong steamid )
{
@ -324,121 +362,50 @@ public SteamFriend Get( ulong steamid )
internal void Cycle()
{
if ( PersonaCallbacks.Count == 0 ) return;
CycleCallbacks( AvatarCallbacks, callback => GetCachedAvatar( callback.Size, callback.SteamId ) );
CycleCallbacks( NameCallbacks, callback => GetCachedName( callback.SteamId ) );
}
private void CycleCallbacks<T>( List<PersonaCallback<T>> callbacks, Func<PersonaCallback<T>, T> getter )
{
if ( callbacks.Count == 0 ) return;
var timeOut = DateTime.Now.AddSeconds( -10 );
for ( int i = PersonaCallbacks.Count-1; i >= 0; i-- )
for ( int i = callbacks.Count-1; i >= 0; i-- )
{
var cb = PersonaCallbacks[i];
var cb = callbacks[i];
// Timeout
if ( cb.Time < timeOut )
if ( cb.Time >= timeOut ) continue;
if ( cb.Callback != null )
{
if ( cb.Callback != null )
{
// final attempt, will be null unless the callback was missed somehow
var image = GetCachedAvatar( cb.Size, cb.SteamId );
// final attempt, will be null unless the callback was missed somehow
var image = getter( cb );
cb.Callback( image );
}
PersonaCallbacks.Remove( cb );
continue;
cb.Callback( image );
}
callbacks.Remove( cb );
}
}
public delegate void FetchInformationCallback(ulong steamid);
private const int PersonaChangeName = 0x0001;
private const int PersonaChangeAvatar = 0x0040;
private readonly Dictionary<ulong, Dictionary<int, FetchInformationCallback>> _fetchInformationRequests =
new Dictionary<ulong, Dictionary<int, FetchInformationCallback>>();
private static readonly List<KeyValuePair<int, FetchInformationCallback>> _tempCallbackList
= new List<KeyValuePair<int, FetchInformationCallback>>();
public void FetchUserInformation( ulong steamid, bool nameOnly, FetchInformationCallback callback )
{
if ( callback == null ) throw new ArgumentNullException( nameof(callback) );
if ( !client.native.friends.RequestUserInformation( steamid, nameOnly ) )
{
// Already got this user's info
callback( steamid );
return;
}
var flags = PersonaChangeName | (nameOnly ? 0 : PersonaChangeAvatar);
if ( !_fetchInformationRequests.TryGetValue( steamid, out var outer) )
{
outer = new Dictionary<int, FetchInformationCallback>();
_fetchInformationRequests.Add( steamid, outer );
}
FetchInformationCallback inner;
if ( !outer.TryGetValue( flags, out inner ) )
{
outer.Add( flags, callback );
return;
}
outer[flags] = inner + callback;
}
private void OnPersonaStateChange( PersonaStateChange_t data )
{
// k_EPersonaChangeAvatar
if ( (data.ChangeFlags & PersonaChangeName) != 0 )
{
LoadNameForSteamId( data.SteamID );
}
if ( (data.ChangeFlags & PersonaChangeAvatar) != 0 )
{
LoadAvatarForSteamId( data.SteamID );
}
if ( _fetchInformationRequests.TryGetValue( data.SteamID, out var outer ) )
{
var list = _tempCallbackList;
list.Clear();
foreach ( var pair in outer )
{
list.Add(pair);
}
foreach ( var pair in list )
{
var flags = pair.Key & data.ChangeFlags;
if ( flags == 0 )
{
// No new information for this request
continue;
}
outer.Remove( pair.Key );
if ( flags == pair.Key )
{
// All requested information has been provided
pair.Value( data.SteamID );
continue;
}
// Still missing some information
var remainingFlags = pair.Key ^ flags;
if ( outer.TryGetValue( remainingFlags, out var existing ) )
{
outer[remainingFlags] = existing + pair.Value;
}
else
{
outer.Add( remainingFlags, pair.Value );
}
}
}
//
// Find and refresh this friend's status
//
@ -452,20 +419,33 @@ private void OnPersonaStateChange( PersonaStateChange_t data )
void LoadAvatarForSteamId( ulong Steamid )
{
for ( int i = PersonaCallbacks.Count - 1; i >= 0; i-- )
for ( int i = AvatarCallbacks.Count - 1; i >= 0; i-- )
{
var cb = PersonaCallbacks[i];
var cb = AvatarCallbacks[i];
if ( cb.SteamId != Steamid ) continue;
var image = GetCachedAvatar( cb.Size, cb.SteamId );
if ( image == null ) continue;
PersonaCallbacks.Remove( cb );
AvatarCallbacks.Remove( cb );
if ( cb.Callback != null )
{
cb.Callback( image );
}
cb.Callback?.Invoke( image );
}
}
void LoadNameForSteamId( ulong Steamid )
{
for ( int i = NameCallbacks.Count - 1; i >= 0; i-- )
{
var cb = NameCallbacks[i];
if ( cb.SteamId != Steamid ) continue;
var name = GetCachedName( cb.SteamId );
if ( name == null ) continue;
NameCallbacks.Remove( cb );
cb.Callback?.Invoke( name );
}
}

View File

@ -283,7 +283,6 @@ private unsafe void ReadScores( LeaderboardScoresDownloaded_t result, List<Entry
Score = entry.Score,
SteamId = entry.SteamIDUser,
SubScores = entry.CDetails == 0 ? null : subEntriesBuffer.Take( entry.CDetails ).ToArray(),
Name = client.Friends.GetName( entry.SteamIDUser ),
AttachedFile = (entry.UGC >> 32) == 0xffffffff ? null : new RemoteFile( client.RemoteStorage, entry.UGC )
} );
}
@ -379,12 +378,6 @@ public struct Entry
public int[] SubScores;
public int GlobalRank;
public RemoteFile AttachedFile;
/// <summary>
/// Note that the player's name might not be immediately available.
/// If that's the case you'll have to use Friends.GetName to find the name
/// </summary>
public string Name;
}
}
}

View File

@ -276,8 +276,8 @@ public string OwnerName
{
if ( _ownerName == null && workshop.friends != null )
{
_ownerName = workshop.friends.GetName( OwnerId );
if ( _ownerName == "[unknown]" )
_ownerName = workshop.friends.GetCachedName( OwnerId );
if ( _ownerName == Friends.DefaultName )
{
_ownerName = null;
return string.Empty;