Problem with Construction Script, Delegates and Play in Editor

Hello Unreal Engine forum, I request your assistance.

There seems to be a problem with the following code/Blueprint and I cannot figure out where it goes wrong (also attached as a zip with source, content, uproject and config):

First version:

.h


#include "DelegateObject.generated.h"

DECLARE_DYNAMIC_DELEGATE(FMySimpleDynamicDelegate);

UCLASS(BlueprintType, EditinlineNew)
class BPCS_DELEGATE_PIE_API UDelegateObject : public UObject
{
	GENERATED_BODY()

public:
	UDelegateObject();
	
	UFUNCTION()
	void DefaultDelegateFunction();
	
	UFUNCTION(BlueprintCallable, Category = "Delegate")
	bool ExecuteDelegateIfBound();
	
	UPROPERTY(BlueprintReadWrite, Category = "Delegate")
	FMySimpleDynamicDelegate MySimpleDynamicDelegate;
};

.cpp


#include "DelegateObject.h"

UDelegateObject::UDelegateObject()
	: Super()
{
	MySimpleDynamicDelegate.BindDynamic(this, &UDelegateObject::DefaultDelegateFunction);
}

void UDelegateObject::DefaultDelegateFunction()
{
	UE_LOG(LogTemp, Log, TEXT("UDelegateObject::DefaultDelegateFunction"));

	if (GEngine)
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("UDelegateObject::DefaultDelegateFunction"));
	}
}

bool UDelegateObject::ExecuteDelegateIfBound()
{
	return MySimpleDynamicDelegate.ExecuteIfBound();
}

Brief overview (please see zip file for full Actor-Blueprint):
Construction Script: Construct Object, assign to variable, Bind Delegate to BP function which just logs like UDelegateObject::DefaultDelegateFunction, execute

Second version:

.h


#include "GameFramework/Actor.h"

#include "DelegateActor.generated.h"

UCLASS(Blueprintable)
class BPCS_DELEGATE_PIE_API ADelegateActor : public AActor
{
	GENERATED_BODY()

public:
	ADelegateActor();
	
	virtual void BeginPlay() override;
	
	UPROPERTY(BlueprintReadWrite, Category = "Delegate", EditAnywhere, Instanced)
	class UDelegateObject* DelegateObject;
};


.cpp


#include "DelegateActor.h"

#include "DelegateObject.h"

ADelegateActor::ADelegateActor()
	: Super()
{
	DelegateObject = nullptr;
}

void ADelegateActor::BeginPlay()
{
	Super::BeginPlay();

	if (DelegateObject)
	{
		if (!DelegateObject->ExecuteDelegateIfBound())
		{
			UE_LOG(LogTemp, Log, TEXT("ADelegateActor::BeginPlay delegate object not bound"));

			if (GEngine)
			{
				GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("ADelegateActor::BeginPlay delegate object not bound"));
			}
		}
	}
}

Brief overview (please see zip file for full Actor-Blueprint):
Construction Script: Construct Object, assign to native member variable, Bind Delegate to BP function which just logs like UDelegateObject::DefaultDelegateFunction, execute
BeginPlay: execute using native member variable

Causing Construction Script to run for both Blueprint Actor subclasses calls the expected delegate. Whenever I “Play in Editor” the delegate is NOT bound at all anymore (or as it turns out, not bound correctly/consistently, see below). Even though it should be bound to some default function. If I use “Play Standalone” instead the blueprint function is called and bound as expected both times, when running construction script and from BeginPlay.

I already tried to debug what happens to the delegates during duplication of the Level for PIE including all of its Actors and stuff but had trouble spotting the exact cause of this so I could then act/fix accordingly.

Although there are a couple of strange things going on. First of all the UDelegateObject’s MySimpleDynamicDelegate should be bound to the Actors. Checking UDelegateObject::ExecuteDelegateIfBound() this is the case when Construction Script runs in the editor. For PIE the Object the delgates are bound to turn into UDelegateObject’s. The function name is still as expected though. So the bound function cannot be found correctly. Either the Object, or the FunctionName does not match.

Stepping over the MySimpleDynamicDelegate.ExecuteIfBound() will then show the this pointer as name “None”_<some numbers> etc.

Looking at the serialization of UDelegateProperties during PIE duplication the TScriptDelegate’s FWeakObjectPtr.Get call (within its serializer) also returns UObjects with name “None”_<some numbers>.

Am I doing something wrong? Is this an issue/bug with PIE?

As promised here’s the zip file: BPCS_Delegate_PIE.zip (379 KB)
I’m using 4.11.0 built from source.

Thanks for any help.

Hey, did you get to the bottom of this? I saw the post a while back but was away at the time. Just looked now and it seems pretty clearly to be a bug in the object instancing code.

If you override PostDuplicate on UDelegateObject you can see it’s duplicated the delegate correctly, but then immediately after that the duplication code will call PostLoad which invokes InstanceSubobjectTemplates, and eventually it ends up replacing the pointer to the blueprint actor with a pointer to the UDelegateObject. This instancing stuff is basically supposed to map references during duplication. In this case it should be leaving the reference untouched, but it’s not. My gut feeling is it’s getting confused because the default value for the object within the delegate property happens to be the outer object itself.

Anyway, if you remove the line assigning the delegate a default binding in the constructor, it seems to work as expected.

To be honest I’ve begun to feel like the engine is rife with these kind of bugs, it seems like you encounter them all the time as soon as you start to do anything remotely non-standard.

Hi @kamrann, no I didn’t make any progress in this area. I kind of left this sitting around for a while working on other bits and pieces. Doing so I may have discovered another problem with constructors, default subobjects and the like but it’s too early to call and it’s not holding back progress so far so I’ll deal with that later (in a new thread).

Now back to the current problem, I’m very thankful for your insights. Unfortunately I really want a default binding for the delegate. I didn’t test this yet but I guess if removing the default binding from the constructor works, I can instead do something like this right?


//added to DelegateObject.h
virtual void PostLoad() override;


//DelegateObject.cpp
UDelegateObject::UDelegateObject()
	: Super()
{
	//DO NOT bind delegate to default!
}

void UDelegateObject::PostLoad()
{
	Super::PostLoad();

	//if delegate is NOT bound yet
	if (!MySimpleDynamicDelegate.IsBound())
	{
		//bind to default
		MySimpleDynamicDelegate.BindDynamic(this, &UDelegateObject::DefaultDelegateFunction);
	}
}

//everything else unchanged

Or if your feeling is correct it should work if I bind to a default function in another object (not this).

Thanks again :slight_smile:

I’m guessing that because the object already exists in the Editor World, it’s not being reconstructed when you go into PIE maybe, but somehow the delegates bindings are being cleared anyway? IIRC PIE shares the same world as the editor one. I bet what you’re doing probably works in a packaged build or standalone.

The C++ version of the Blueprint ‘Construction Script’ btw is ‘OnConstruction()’, which I believe is virtual. PostLoad() works too though I guess :slight_smile: I do a lot of my startup / initialization stuff in ‘PostInitializeComponents()’ which runs directly after the constructor, and is definitely called in-game when using PIE.

Also prevents any crashes you may get when opening a blueprint of the object and it can’t find a valid world etc…



void AMyActor::PostInitializeComponents()
{
    Super::PostInitializeComponents();

    if (GetWorld() && GetWorld()->IsGameWorld())
    {
         // Do Funky Stuff
    }
}


@UnrealEverything: Yeah, I expect that would work, though I still haven’t got it clear in my head exactly when PostLoad gets called and when it doesn’t. As @TheJamsh says though, there are of course various other overrides you could use. I think the key thing here is just not to bind the delegate in the constructor.

Cheers to both of you.

I’ve now tested the modified code I’ve posted above. Standalone Game still works as it did before. Concerning PIE the delegates now work when they have ExecuteIfBound called from both Blueprint/C++ BeginPlay. But there’s still a difference that I am unsure about. In Standalone Game mode the delegates also fire from the Blueprint Construction Script but not running PIE (added a breakpoint in UDelegateObject::ExecuteDelegateIfBound and it only triggers from BeginPlay). Is this the intended behaviour (because Construction Script has already run in the editor?)?

Thanks again.

The constructor is not executed at run time for actors placed in a level, this is by design. You shouldn’t perform any logic that can’t be serialized on it.

I think so, but I’m really not clear on this. Until not long ago I actually thought the construction script only ran in the editor. My understanding was that it was just meant for procedural setup and adjustments during the design stage and didn’t have a place at runtime, but it seems it’s not so straightforward.

Anyway yeah, there’s a good bet that the construction script just isn’t run at all for PIE, as opposed to this being an issue specific to your delegate setup. Personally, I don’t put any runtime-required initialization into Blueprint construction scripts or OnConstruction, I use the other initialization methods.