CommonUI ActivatableWidget OwningPlayer not set on the second PIE session

Good evening/night/morning/Easter to all kind souls!

I’m completely stumped, and have spent 2 days with a headache. The code works perfectly as expected when the editor is fresh, but if I stop PIE and start again it fails repeatedly until I restart the editor. Then it works once and fails again. I need the owning player to work with focus, which means Gamepad navigation only works on the first PIE session. The (blueprint) error that started my quest is: :police_car_light: The PlayerController is not a valid local player so it can't focus on {widget}

I know it’s not a CommonUI bug because I have another branch when I was learning CommonUI that I kept while rebuilding the structure, and the PlayerController is properly propagated. I have compared the code far and wide, and fundamentally there’s really no difference…

I did something somewhere to the Editor that is kept in memory between PIE sessions for some reason, and I don’t know Unreal enough to understand what I did to it. I didn’t even think it was possible for any data to survive?!

The culprit:

MoodyViewportLayout.h file
UCLASS(Abstract)
class MOODYUI_API UMoodyViewportLayout : public UCommonUserWidget {
	GENERATED_BODY()

	UPROPERTY(meta=(BindWidget))
	TObjectPtr<UCommonActivatableWidgetStack> MenuStack;

public:
	UFUNCTION(BlueprintCallable)
	void PushMenu(const TSubclassOf<UMoodyActivatableMenu> Menu) const;
};
MoodyViewportLayout.cpp file
void UMoodyViewportLayout::PushMenu(const TSubclassOf<UMoodyActivatableMenu> Menu) const {

	// UMoodyActivatableMenu is a direct subclass of UCommonActivatableWidget
	UMoodyActivatableMenu* NewMenu = MenuStack->AddWidget<UMoodyActivatableMenu>(Menu);
	APlayerController* NMO = NewMenu->GetOwningPlayer();
	
	DLOG("MenuStack Owner: " + MenuStack->GetOwningPlayer()->GetName())
	DLOG("New Menu Owner: " + (NMO?NMO->GetName():"___null___"))
}
The code that initializes the UI
// This code is inside a UWorldSubsystem, called from a direct subclass of APlayerController
void UMoodyUISubsystem::SpawnUI(APlayerController* PlayerController, UMoodyUIDefinitionDataAsset* UIDefinition) {
	UMoodyViewportLayout* InViewportLayout = UIDefinition->GetViewportLayout();
	TSubclassOf<UMoodyActivatableMenu> InRootMenu = UIDefinition->GetRootMenu();

	if (InViewportLayout) {
		RootLayout = InViewportLayout;
		RootLayout->SetOwningPlayer(PlayerController);
		RootLayout->AddToViewport();
		if (InRootMenu) RootLayout->PushMenu(InRootMenu);
	}
}
Output on the first PIE session
LogSurvivalGame: Display: Standalone : MenuStack Owner: BP_MainMenuPlayerController_C_0 [in UMoodyViewportLayout::PushMenu (13)]

LogSurvivalGame: Display: Standalone : New Menu Owner: BP_MainMenuPlayerController_C_0 [in UMoodyViewportLayout::PushMenu (14)]
Output on any other PIE session
LogSurvivalGame: Display: Standalone : MenuStack Owner: BP_MainMenuPlayerController_C_0 [in UMoodyViewportLayout::PushMenu (13)]

LogSurvivalGame: Display: Standalone : New Menu Owner: ___null___ [in UMoodyViewportLayout::PushMenu (14)]

I have tried an insane amount of things so I won’t list them all (Because I forgot most of them) including some I tried while rubber ducking this post. But amongst them:

  • Deleted everything but Config, Source, Content, .vsconfig and the .uproject and rebuilt the whole thing
  • Rebuilt the whole UI on the blueprint side and it still does it
  • Used SetOwningLocalPlayer and SetOwningPlayer
  • Tried casting to its parent with AddWidget<UCommonActivatableWidget>()
  • Tried changing the DataAsset and code to actually use UCommonActivatableWidget instead
  • Tried using the following Lambda, it did change the owner in the logging but did nothing for focus and its error:
MenuStack->AddWidget<UMoodyActivatableMenu>(Menu, [this](UMoodyActivatableMenu& Menu) {
	Menu.SetOwningPlayer(this->GetOwningPlayer());
});
  • I have spent a lot of time digging in the code looking left and right
  • It works perfectly in packaged build (which is not surprising)
  • I have been through 12 stages of depression
  • I was thinking about making a brand new project and importing my code in it since the other branch works, but i’m also thinking that this is not a reasonable approach because it will become a nightmare once the project grows a little, so i’ll keep busting my brain on my keyboard to learn how to fix that editor…

Other random tidbids of info:

  • Unreal Engine 5.5.4
  • The plugins I have installed in my .uproject are the same in both branches: ModelingToolsEditorMode, CommonUI, ModelViewViewModel.
  • The private modules in the module’s Build.cs: Core, CoreUObject, Engine, EnhancedInput, Slate, SlateCore, UMG, CommonUI, MoodyCore (only has my *LOG macros)
  • This is me giving up for the night. I prefer warning those who are kind enough to try and help this poor soul, I will be able to try again in about 20h.
  • If you want any other information, or if you want the code, I can post the whole UI module somewhere tomorrow

Thank you very, very much
ItsMoodyDoom

Now that’s an helpful community, no wonder there’s no documentation anywhere, donno if it’s just because nobody cares or if it’s gatekeeping but whatever. I have solved my issue after plenty of headache. For safekeeping if anyone ever find themselves into a similar situation…

I have continued to search, keeping on that path of “The IDE does keep stuff between PIE sessions”, I stumbled on some posts talking about Online Subsystems doing something similar, I understood I was on the right path. After a couple unsuccessful attempts, a meltdown where I said **** that i’ll solve it another day, and a stroke of genius in the shower, I have succeeded.

The issue is in CommonUI’s UCommonActivatableWidgetStack’s behavior of pooling widgets to reuse them, and the fact that my Data Asset holds a TObjectPtr<> of my layout blueprint. That object apparently was kept in memory, which means the stack’s cached widgets did too. GetOwningPlayer was wrong because the OwningPlayer reference was from the first session, and that object doesn’t exist anymore. I solved my issue by changing the TObjectPtr<> to a TSubclassOf<> and I CreateWidget() it instead.