Subsystem function calls and variable assignment not working on startup?

This post is related to my last post about storing a pointer to a blueprint in a subsystem. You can read about that here.

The new problem I’ve run into has to deal with my subsystem not responding to assignments or function calls at startup of my program. Here is the blueprint graph, and an explanation on how it works.

I built a small program that gives a multiple choice quiz to the user. I have a blueprint class that is my game instance, and the Event Init is called as soon as the program starts. I have a QuizManager subsystem that holds all the quiz data and a pointer to a Widget Blueprint that has a C++ base class. The Event Init is meant to initialize the UI and store it in the subsystem, as well as tell the Quiz Manager to load the quiz data by reading it from some xml files. I extended a test wire from the init to the start quiz to test if the quiz presentation was working.

The Start Quiz event starts the quiz and presents the questions to the user. My downcast to the blueprint class kept failing for some reason, and after debugging it for a while I realized that for whatever reason all my interactions with the Quiz Manager subsystem were failing. All assignments and function calls are just stepped over when I try to debug and not call my C++ code. This lead to a failed downcast of the Quiz Widget which would prevent the quiz from running. Debugging some more I added a delay between the LoadQuizData call and the Set of the Procedure name. Now my InitQuiz function is being called, but LoadQuizData isn’t being called, and the downcast is still failing. Next I tried to promote the BP_QuizWidget to a variable and store it in the game instance as soon as it’s created instead of the subsystem. This solved the downcasting problem, but it also made it clear that the creation of the widget wasn’t the problem, the problem was trying to store a pointer to it in the subsystem.

So then I moved the delay call to the start of the Init, and deleted the local BP_QuizWidget variable and stored it back in the subsystem. Now everything works perfectly. If I remove the Delay call everything stops working because the subsystem ignores all interactions. I don’t understand why this is behaving the way it is, and I’m hoping that someone here can provide an answer.

A few possible theories:
Subsystems are not initialized on startup?
A race condition between subsystem initialization and blueprint graph execution?
I’m dumb and didn’t implement this correctly?

Any help or insight would be appreciated, thanks in advance!

2 Likes

This is a little dense for me to follow at a glance, but from the gist of it, my suggestion would be to avoid executing logic from startup in multiple places, especially if it relies on other logic having already been executed (As an example, you require a player exist for that logic, but what happens if a player fails to load in on game start?).

I would make your logic synchronous. That logic appears to be for the benefit of the quiz manager, in which case it should be called by the quiz manager. I’m not sure what that blueprint belongs to, but it doesn’t appear to be holding any data itself, so I see no reason why it should remain there. If it’s a UI widget, then I would just assign the UI events to quiz manager delegates, and let the quiz manager do everything itself.

As for that test wire, I haven’t used BP for awhile, but going from Load, skipping the StartQuiz call, then immediately to Set(Procedure Name) doesn’t seem right. The Set node is required an input name, which it is attempting to access from the event call parameter. But you don’t call the event, so I would assume that results in undefined behavior.

My apologies, I should have added more details. Here are some clarifications for the things you’re asking.

The QuizManager is a C++ class that inherits from UGameInstanceSubsystem, it exists only as a C++ class. My understanding is that subsystems begin to exist at the start of the program, and there is only ever one instance, sort of like a singleton.
At least that’s what the UE4 API website says:
“UGameInstanceSubsystem is a base class for auto instanced and initialized systems that share the lifetime of the game instance”.

Here is my setup for using it as the QuizManager:

UCLASS()
class MYTEST_API UQuizManager : public UGameInstanceSubsystem
{
	GENERATED_BODY()
public:
	UQuizManager();
	
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	UProcedureQuiz* QuizWidget; //I called the blueprint BP_ProcedureQuiz

	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	FString ProcedureName;

	//The LoadQuizData and RunQuiz functions are also declared here, but I'm leaving them out to save space in the post.
};

The QuizWidget is a C++ class called UProcedureQuiz that inherits from UUserWidget. I have a blueprint class of BP_ProcedureQuiz which was a UUserWidget blueprint that I reparented to have the UProcedureQuiz C++ class as a parent.

UCLASS()
class MYTEST_API UProcedureQuiz: public UUserWidget
{
	GENERATED_BODY()
public:
	UProcedureQuiz(const FObjectInitializer& ObjectInitializer);
	~UProcedureQuiz();
};

The constructor of the UQuizManager sets the ProcedureName to an empty string, so the test wire calling the SET without a parameter is fine. I just made my RunQuiz function ignore the ProcedureName and present a random set of questions.

My thinking was that at startup I would initialize the QuizWidget, store it in the QuizManager subsystem, and tell the QuizManager to LoadQuizData which reads it in from a xml file. So when StartQuiz is called the QuizWidget is ready to be displayed, and the quiz data has been read in.

The problem is that without a Delay call at the start of the Init event, all of my interactions with QuizManager fail. The QuizWidget isn’t set, and LoadQuizData isn’t called. The blueprint graph just steps over those nodes when I set a breakpoint. I want to understand why my calls to QuizManager fail without the Delay call beforehand.

Sorry I haven’t gotten back to you more quickly.

Is StartQuiz() dependent on LoadQuizData() having already been called?

Since you said you required a delay, I assume that LoadQuizData() and StartQuiz() are independent of each other. In which case, I would assume that LoadQuizData() uses some form of delegate when the data is retrieved successfully, possibly even ASynchronously.

Specific to your project, I would create a call trace through UE_LOG, set them at primary junctions of your functions so you can see precisely what order things are being called.

As a bonus, this is a fantastic breakdown of the engine’s flow, and will show you how/when actors are spawned/initialized.

No worries, I appreciate any time you can spare.

You are correct, StartQuiz() depends on LoadQuizData() being called first.

The video you linked said that subsystems are tied to the lifetime of the corresponding object. This event graph is inside my game instance blueprint. Is it possible that when the EventInit is called the lifetime of my game instance blueprint hasn’t started yet? It’s not like the EvenInit has finished executing when I’m making my subsystem calls. I’ll test this tomorrow. If all of the init nodes can run from the StartQuiz event after EventInit is called and finished this may explain the behavior I’m seeing.

My best guess for why the Delay makes it work is that the UE4 engine counts any object that is in a Delay wait state as having started its lifetime. But that is just a guess. I’ll post more info after I run some tests. The UE_LOG is also a good idea, I’ll try that too.

Finally figured it out. You were on the right track with that video on the engine flow, but it didn’t click for me until I found this flowchart on the UE documentation website.

The real problem is that in the GameInstance event graph the player controller and subsystems have not been initialized by the engine yet and return None.

Moving my initialization code from the GameInstance EventInit to the game mode EventBeginPlay fixed the problem because GetPlayerController has been initialized and returns a valid value. Same with the QuizManager subsystem call. Here is the blueprint event graph in the game mode. No delay needed!

2 Likes

I would reply earlier, but it isn’t sending email notifications unfortunately.

Glad to hear it though. I highly recommend using Debugging immediately any time code is working in an unexpected manner. UE is very complex, and the more in depth your logic gets the more you’ll have to solve how to fit it into the UE logic structure. Debugging will help immensely.

I don’t know if blueprint debugging works the same as c++ debugging in VS, but for c++ you not only see it line by line, but you also are presented a list of local variables, which you can see all information stored in memory. It’s a lifesaver for finding nullptrs.

1 Like