Steamworks.NET 20.1.0: SteamUserStats.RequestCurrentStats() CallResult<UserStatsReceived_t> not received
Trying to setup a simple Achievements system and RequestCurrentStats() is the required starting point: it needs to be called and a successful reply must be received before calling APIs such as SetAchievement/ClearAchievement/StoreStats/IndicateAchievement.
I have this setup called (UserStatsReceivedCallResult stored in a class field so it won't get GC'd)
UserStatsReceivedCallResult = CallResult<UserStatsReceived_t>.Create(OnReceiveUserStats);
SteamUserStats.RequestCurrentStats();
and I have confirmed that SteamAPI.RunCallbacks() gets successfully run every Update() frame and SteamAPI.Init() has run and I have correct APP_ID, logged in to Steam with an account that has access to the game and achievements are defined in Steamworks web dashboard.
But my OnReceiveUserStats method NEVER gets called.
Weirder still, if I try to call SetAchievement/CleareAchievement, they will return TRUE, indicating that the CallResult<UserStatsReceived_t> from RequestCurrentStats() HAS been internally received by Steamworks.NET, but the dispatcher/CallResult just never gets to call my handler method.
First, be sure that the class whose member you've been set isn't garbage collected too. Second, the way that Steamworks.net exposes the CallResult system is flawed in multiple ways, but I will tell with this same confidence that the language C# itself is extremely flawed. Especially the Mono and ILCPP related things.
In short, you never get guarantee that your CallResults will ever be called. This is in fact true for everything IDisposable that you use with unity.
There are two solutions that worked for me but requires a lot of care and manual lifetime management.
1st: Forget CallResult<T> and instead poll on your Call handle. It's annoying but it works. 2nd: make CallResult<T> non disposable and rewrite the event dispatcher. Actually rewrite it on such a way to provide await/async solution.
3rd: change the CallResult<T>.Create to register this in an internal list and when a callback occurs, delete that form the internal list. I suggest that this is done By Steamworks. Net devs as it is a bug, misdocumented and as soon as you need to create parallel call to certain api by using a class member assigned CallResult, only the last call will receive its Result.
- DISOBEY the CallResult recommendation. Do not make a class member using CallResult<T>. Instead make some manager contain a list of CallResult<T>() instead. Then you create a new CallResult<T>, add it to this list, create a GC Handle on the CallResult, use the Set on it and pass directly a lambda function or Action. In the said action /lambda you must have a way to manually call Dispose and remove the call result from said list. Pro tip: instead of list use the following: List<KeyValuePair<SteamApiCall_t, CallResult<T>>>, or any way to associate your result with your expected response. This way all your request will get their callback even unlike if you follow the documentation and try to do parallel stuff.
My case was starting to download 3 leader boards at the same time and only getting the last piece of data.
@KesKim
This confused me as well. You need to call the Set method on the CallResult to make it work:
OnRequestUserStatsCallback = CallResult<UserStatsReceived_t>.Create(OnRequestCurrentStats);
var steamAPICallT = SteamUserStats.RequestUserStats(SteamUser.GetSteamID());
OnRequestUserStatsCallback.Set(steamAPICallT);
I got problem like OP and land into this post. Then in the end I noticed it was human error from copy-pasting!!!!
and also for OP's case it should be Callback not CallResult
UserStatsReceivedCallResult = Callback<UserStatsReceived_t>.Create(OnReceiveUserStats);
SteamUserStats.RequestCurrentStats();
Do not mix Callback and CallResult everyone! lesson learned!
After fixing this Callback came as expected, Steamworks.NET is working OK. It is user's fault for lack of sleep, resolved.
PS1. Also notice in georg-walkingtreegames' reply, it is another API which would have to use CallResult
RequestCurrentStats in this case requires Callback. PERIOD.