diff --git a/Facepunch.Steamworks.Test/Client/FriendsTest.cs b/Facepunch.Steamworks.Test/Client/FriendsTest.cs index bcaf8a4..3efe14e 100644 --- a/Facepunch.Steamworks.Test/Client/FriendsTest.cs +++ b/Facepunch.Steamworks.Test/Client/FriendsTest.cs @@ -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 = " -:+#"; diff --git a/Facepunch.Steamworks.Test/Client/LeaderboardTest.cs b/Facepunch.Steamworks.Test/Client/LeaderboardTest.cs index 3628d11..ea1d8a8 100644 --- a/Facepunch.Steamworks.Test/Client/LeaderboardTest.cs +++ b/Facepunch.Steamworks.Test/Client/LeaderboardTest.cs @@ -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() ) ); diff --git a/Facepunch.Steamworks.Test/Client/LobbyTest.cs b/Facepunch.Steamworks.Test/Client/LobbyTest.cs index 17241a3..ee36c82 100644 --- a/Facepunch.Steamworks.Test/Client/LobbyTest.cs +++ b/Facepunch.Steamworks.Test/Client/LobbyTest.cs @@ -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")); diff --git a/Facepunch.Steamworks/Client/Friends.cs b/Facepunch.Steamworks/Client/Friends.cs index 9b09bf7..ad17ca9 100644 --- a/Facepunch.Steamworks/Client/Friends.cs +++ b/Facepunch.Steamworks/Client/Friends.cs @@ -121,13 +121,12 @@ public bool UpdateInformation( ulong steamid ) { return !client.native.friends.RequestUserInformation( steamid, false ); } - + /// - /// Get this user's name + /// Get this user's name immediately. This should work for people on your friends list. /// - 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 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 callback ) // if not it'll time out anyway } - PersonaCallbacks.Add( new PersonaCallback + AvatarCallbacks.Add( new PersonaCallback { SteamId = steamid, Size = size, @@ -296,15 +295,54 @@ public void GetAvatar( AvatarSize size, ulong steamid, Action callback ) }); } - private class PersonaCallback + public const string DefaultName = "[unknown]"; + + public void GetName( ulong steamid, Action 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 + { + SteamId = steamid, + Callback = callback, + Time = DateTime.Now + } ); + } + + private class PersonaCallback { public ulong SteamId; public AvatarSize Size; - public Action Callback; + public Action Callback; public DateTime Time; } - List PersonaCallbacks = new List(); + List> AvatarCallbacks = new List>(); + List> NameCallbacks = new List>(); 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( List> callbacks, Func, 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> _fetchInformationRequests = - new Dictionary>(); - private static readonly List> _tempCallbackList - = new List>(); - - 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(); - _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 ); } } diff --git a/Facepunch.Steamworks/Client/Leaderboard.cs b/Facepunch.Steamworks/Client/Leaderboard.cs index c338906..bbdcc2a 100644 --- a/Facepunch.Steamworks/Client/Leaderboard.cs +++ b/Facepunch.Steamworks/Client/Leaderboard.cs @@ -283,7 +283,6 @@ private unsafe void ReadScores( LeaderboardScoresDownloaded_t result, List> 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; - - /// - /// 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 - /// - public string Name; } } } diff --git a/Facepunch.Steamworks/Interfaces/Workshop.Item.cs b/Facepunch.Steamworks/Interfaces/Workshop.Item.cs index 615b38c..2c05216 100644 --- a/Facepunch.Steamworks/Interfaces/Workshop.Item.cs +++ b/Facepunch.Steamworks/Interfaces/Workshop.Item.cs @@ -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;