question

Markus Henschel avatar image
Markus Henschel asked

Playfab UE4 Plugin: Holding TSharedPtr to UPlayFabAuthenticationContext doesn't work in cooked game

We are supporting multiple users with playfab in UE4 using C++. For that we store the UPlayFabAuthenticationContext we get from login per user and use it later in all the requests to differentiate between users. It works fine in editor but in a monolithic packaged game we get crashes.

I digged into this and the problem is that the playfab plugin passes UPlayFabAuthenticationContext around as a TSharedPtr. The problematic code is in PlayFab.h:

template<typename T>
TSharedPtr<T> MakeSharedUObject()
{    
  return TSharedPtr<T>(NewObject<T>((UObject*)GetTransientPackage(), NAME_None, RF_Standalone),
  [](T* Instance)
  {
      // Recommended way of destroying UObjects according to https://udn.unrealengine.com/questions/402414/view.html
      Instance->ClearFlags(RF_Standalone);
  });
}

The code tries to keep the created object alive by supplying the RF_Standalone object flag and let it be garbage collected by removing the flag in a custom deleted. This will not work outside of the editor AFAIK.

We work around the problem by storing the UPlayFabAuthenticationContext reference as a TStrongObjectPtr internally once we get it from playfab. This will keep it alive. It looks like this is what the playfab code is actually trying to archive.

Currently internally in playfab code the TSharedptr<UPlayFabAuthenticationContext> is created right before passing it to a delegate for the game to consume. If the TSharedPtr is used directly in that delegate there is no problem. But if the game (or possibly in the playfab plugin?) holds a reference with this TSharedPtr by capturing it in a lambda or something similar this will cause very hard to find crashes. And the natural thing you would expect from a shared pointer would be to be usable for holding a reference to an object (otherwise you could just pass in a raw pointer to the delegate).

As said we can work around this but I think you will want to fix this. If not please put least some warning in the code/documentation.

unreal
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Seth Du avatar image
Seth Du answered

We have informed the project team about this issue and will keep you updated if there is feedback from the team.

4 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

franklinchen avatar image franklinchen commented ·

Hi @Markus Henschel, thank you for your feedback, the engineering team is still investigating and discussing the fix. We don't want to break current users, so it may take some time to have next update.

0 Likes 0 ·
Markus Henschel avatar image Markus Henschel franklinchen commented ·

Hi @FranklinChen. That's perfectly fine. We have a workaround in place.

0 Likes 0 ·
franklinchen avatar image franklinchen Markus Henschel commented ·

Thanks @Markus Henschel, this feedback is very helpful to improve our dev experience.

0 Likes 0 ·
Show more comments
snmx avatar image
snmx answered

Hello, looks like it is not fixed now. When I build shiped nor development version for android the crush occures. The problem is that garbage collector try to clean AuthenticationContext on level switch

6 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Seth Du avatar image Seth Du ♦ commented ·

Have you updated to the lateset version of Unreal Marketplace plugin?

0 Likes 0 ·
snmx avatar image snmx Seth Du ♦ commented ·

I'm using plugin version 1.31.200121 from marketplace with ue4.24. I'm getting this error while running development build on android after level switches: errorlog.txt

0 Likes 0 ·
errorlog.txt (7.3 KiB)
snmx avatar image snmx commented ·

I'm using plugin version 1.31.200121 from marketplace with ue4.24. I'm getting this error while running development build on android after level switches: errorlog.txt

0 Likes 0 ·
errorlog.txt (7.3 KiB)
franklinchen avatar image franklinchen commented ·

Hi @SNMX, our SDK team told me that they could not reproduce this issue in their demo project: Packaged a blueprint only version of a game to an android device that loads, logs in with playfab, loads a level, and emits write events throughout the entire sequence of events.

Could you please provide a zip of your unreal blueprint project for us to check out? Please remove your sensitive information and import biz logic to keep it as simple as possible. Thank you.

0 Likes 0 ·
snmx avatar image snmx franklinchen commented ·

Hello @FranklinChen, I don't think that it's a problem on your side rather that is a way how UE4 works. After switching levels only Game Instance survives. That is one and only persistent object in game. I think that it could be avoided by creating an object that destroys and creates on level load (like in Game Mode) and setting a reference to himself at Game Instance (if it necessary to have that object persistent-like). That should pull a pointer again each time the level loads. It could cause some troubles but should do the trick. I'll check it asap and send you a project with both crash and workaround from BP.

Also there is a lot of delegates there. That's not really clean and simple solution, I think: having both delegates and async execution pin on a same node

0 Likes 0 ·
snmx avatar image snmx franklinchen commented ·

Here's crash log. Sorry don't have much time right now to make a test project, softlaunch backlog is endless.errorlog.txt

0 Likes 0 ·
errorlog.txt (11.5 KiB)
Markus Henschel avatar image
Markus Henschel answered

Hello Playfab Team,

Looking at how this was fixed I don't think it will work. The solution you applied now is using the RF_StrongRefOnFrame object flag. I've never used that myself but by looking at the unreal engine code it seems like this is only intended to keep objects alive for asyncronous blueprint tasks. But if you just store the TSharedPtr you get from playfab somewhere in code this will still not prevent it from being garbage collected. So I think SNMX above is right. This is not fixed.

I can only repeat my suggestion from the initial post to use TStrongObjecPtr from Epic. Or alternatively call AddToRoot/RemoveFromRoot on the object in the template specialization of TSharedPtr you are using.

Regards

Markus

2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

franklinchen avatar image franklinchen commented ·

Thanks Markus, I just forwarded your feedback to SDK team.

0 Likes 0 ·
Markus Henschel avatar image Markus Henschel franklinchen commented ·

Something like this should work and not break compatibility with existing code:

template<typename T>
TSharedPtr<T> MakeSharedUObject()
{
	TSharedRef< TStrongObjectPtr<T> > SharedRefToStrongObjPtr = MakeShared< TStrongObjectPtr<T> >(NewObject<T>());


	return TSharedPtr<T>(SharedRefToStrongObjPtr, SharedRefToStrongObjPtr->Get());
}

This creates a TStrongObjectPtr which is designed to keep the UObjects alive. The TStrongObjectPtr is being referenced by a shared ref that handles the reference counting. Then a TSharedPtr<T> is created that shares the reference count with the shared ref but points directly to the UObject so the rest of the code can work as before.

The main problem is that TSharedPtr is not designed to work with UObject derived classes. Just using TStrongObjectPtr would have been the way to go. But since this would break compatibility it's too late now for that I guess. But the approach above should work almost the same and requires no custom deleter or obscure object flags.

1 Like 1 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.