Unreal Engine C++: Event, Dispatch, Delegates etc.

An alternative explanation to the obscure unreal doc. (first draft, some parts missing)

Concept

In Unreal Engine there is a fundamental mechanism called 'delegates' or also named 'events'. The concept is very similar to C# delegates or Qt signal & slot system. Here I'll explain what are delegates or event dispatchers and their C++ syntax In Unreal Engine. Let's start with a pseudo code example:

void some_function1(int param){ ... }
void some_function2(int param){ ... }

DelegateType my_delegate;
my_delegate.add(some_function1);
my_delegate.add(some_function2);
// the call below will call some_function1(40) and some_function2(40)
my_delegate.broadcast(40);

This pseudo illustrate the basic usage of a delegate, my_delegate is an object that can store one or several function pointers (or even object methods) and call back those functions with a single call to broadcast(). Parameters provided to broadcast() will be passed along as well.

One concrete use case for Delegates arise when coding UI elements. For instance, you would have a class in charge of drawing buttons or a checkbox etc. and you can use a delegate to notify the user whenever the element / checkbox is triggered. Here is some pseudo code to illustrate this:

 
// "Service provider": a class in charge of drawing some UI elements:
class My_checkbox{
    DelegateType my_delegate;
public:
    // draw the UI:
    void draw() { ... }

    // function handling mouse events
    void mouse_event() {
        if( click toggles our checkbox ) 
            my_delegate.broadcast(); // calls user functions
    }

    // "Client"/"user" of this class call this to be notified on click events:
    void on_checkbox_toggle_do( Function_pointer action) { my_delegate.add(action); }

};

If you are familiar with Qt signal & slot system you'll see that a delegate is the equivalent of a signal. When in Qt we 'connect' a signal to a slot in Unreal we 'bind' or 'add' some function (or lambda, class method etc.) Finally broadcasting in Unreal is the equivalent to emitting a signal in Qt. 

In Unreal's BluePrint you'll come across things named 'event dispatcher' which is just another name for 'delegate'. As we'll see later you can also call a delegate defined in C++ from the BluePrint.

Concrete C++ example

Dynamic Multicast Delegate

There are many types of delegates in unreal: static, dynamic, multicast, sparse, event... They all have their own drawbacks or advantages in terms of memory, performance or flexibility. I'll explain those differences in the next section. First, let's see how we implement a very common type of delegate in Unreal, the 'dynamic multicast delegate':

// We first declare the signature of the delegate through a macro provided by Unreal.
// Our delegate type is named 'FMyDelegate'
// we can bind to this delegate a functions with the type following: ' void some_fun(FString m)'
// Note: delegate type must always start with the letter 'F'
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDelegate, FString, message);

UCLASS()
class FPSGAME_API AMyActor : public AActor {
    GENERATED_BODY()	
public:	
    AMyActor(){ PrimaryActorTick.bCanEverTick = true; }

    // declaring an instance of our delegate named 'send_message' 
    // 'send_message' is what will be exposed in the BluePrint thanks to 'BlueprintAssignable'
    // Note: only dynamic multicast delegates can use the 'BlueprintAssignable' property        
    UPROPERTY(BlueprintAssignable)
    FMyDelegate send_message; 

    UFUNCTION()
    void mySlot1(FString str){
        GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, TEXT("Slot1"));
        GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, str);
    }

    UFUNCTION()
    void mySlot2(FString str){
        GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, TEXT("Slot2"));
        GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, str);
    }
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override{
        Super::BeginPlay();
        // Remark: you must use UFUNCTIONs with dynamic delegates.
        send_message.AddDynamic(this, &AMyActor::mySlot1);
        send_message.AddDynamic(this, &AMyActor::mySlot2);
    }

public:	
    // Called every frame
    virtual void Tick(float DeltaTime) override{
        Super::Tick(DeltaTime);
        send_message.Broadcast(TEXT("Hello unreal"));
    }
};

As you can see we declare a delegate type with a macro:

DECLARE_DYNAMIC_MULTICAST_DELEGATE_XXX(FMyDelegateType, MyArgType1, MyArgName1);

The first parameter defines the name of the delegate's type then the following parameters in the macro define the signature of the function. If we intend to bind a function with 2 parameters for instance:

void some_fun(int a, FString& m)

then we need to use:

DECLARE_DYNAMIC_MULTICAST_DELEGATE_twoParams(FDelegateTypeName, int, param1, FString&, param2);

Remark that in the case of a dynamic delegate you need to specify the type and names of the parameters each separated by commas. Officially you can declare up to eight parameters ('_eightParams'). Lastly you can use this macro in global space, namespace or even inside a class:

// Global space:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_twoParams(FMyDelegate1, ... )
namespace MyProject {
    // Fine to use in a namespace:
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_twoParams(FMyDelegate2, ... )
    class MyClass {
         // or even within the class declaration:
         DECLARE_DYNAMIC_MULTICAST_DELEGATE_twoParams(FMyDelegate3, ... )
    };
}

Static Multicast Delegate

Next up is the static version of our multicast delegate,

// Note delegate type must always start with F
// Multi-cast delegate signatures are not allowed to use a return value.
// For static delegates the name of each parameter is not necessary
DECLARE_MULTICAST_DELEGATE_OneParam(FMyDelegate, FString);

void static_slot(FString str){ ... } 

UCLASS()
class FPSGAME_API AMyActor : public AActor {
    GENERATED_BODY()	
public:
    // you cannot use UPROPERTY for a static delegate! 
    // Following error is thrown if otherwise: 
    // "unrecognized type must be a UCLASS, USTRUCT, or UENUM" 
    // therefore this attribute won't be serialized (e.g. saved to a file)
    FMyDelegate send_message;

    void my_slot1(FString str, bool payload) { ...    }


protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override{
        Super::BeginPlay();
        // Contrary to dynamic delegates you can bind to many things 
        // function, class method etc.         
        send_message.AddStatic(&staticSlot); 
        // payload allowed
        send_message.AddUObject(this, &AMyActor::my_slot1, /*payload argument:*/true);
        send_message.AddLambda([](FString str) { ... }); 
		// True after adding functions: 
		bool s = send_message.IsBound();
    }

public:	
    // Called every frame
    virtual void Tick(float DeltaTime) override{
        Super::Tick(DeltaTime);
        send_message.Broadcast(TEXT("Hello unreal"));
    }
} 

Binding

First contrary to dynamic delegates, static delegates can bind to many things! A series of methods allows you to bind to un-managed function pointers: raw functions pointer, class methods and lambdas.

TODO: rolling menu

Then you can bind memory managed pointers. These keep a weak reference to your object. You can safely use IsBound() to call them with Broadcast():

TODO: rolling menu?

here we give the function name as a string (FName("funcName")) instead of a pointer. note that it's preferable to do:

auto funName = GET_FUNCTION_NAME_CHECKED(AMyActor, mySlot2);
rod_send_message.AddUFunction(this, funName);

Remove()

RemoveAll()

Payload

up to four arguments?

Summary: Static Vs Dynamic

Static delegates:

Dynamic delegates:

Single cast delegates

There are only slight differences between multi-cast and single-cast delegates, mainly you can only bind one function at a time. It follows that when the delegate is invoked the return value of the currently bound function is passed along. In addition instead of using .Broadcast() we now rely on .Execute() or ExecuteIfBound().

TODO: rolling menu?

Dynamic single cast:

// Note delegate type must always start with F
// single cast delegate can return a value since we can only bind a single function.
DECLARE_DYNAMIC_DELEGATE_OneParam(FRodDoSomething, FString, message);

class {

    // '(BlueprintAssignable)' is only allowed 
    // on multicast delegate properties
    UPROPERTY()
    FRodDoSomething rod_send_message;

    UFUNCTION()
    void mySlot(FString str);

    UFUNCTION()
    void mySlot2(FString str);
}

// Only binds to UFUNCTIONS, can't use payload 
// Only the second bind will be take into account since it's a single cast:
rod_send_message.BindDynamic(this, &AMyActor::mySlot);
rod_send_message.BindDynamic(this, &AMyActor::mySlot2);
// True after adding a function:
s = rod_send_message.IsBound();

// Can possibly return a value if we specifed one:
rod_send_message.ExecuteIfBound(TEXT("Hello unreal"));
rod_send_message.Execute(TEXT("Hello unreal"));

Static single cast:

TODO: rolling menu?

TODO: code for static single cast delegate

// Note delegate type must always start with F
// single cast delegate can return a value since we can only bind a single function.
DECLARE_DELEGATE_OneParam(FRodDoSomething, FString, message);

etc todo

As before with multi-cast delegates, single-cast static delegates also benefit from many binding types:

Memory managed pointers.

You can safely use ExecuteIfBound() (or IsBound()):

Use Unbind() to remove a function.

Sparse delegates

TODO: check with mitchelli's slides

Sparse delegates are exactly the same as a dynamic multi-cast delegate except that it optimize for memory at the expense of slow bind times

DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_<Num>Params(FDelegateName, args...)

Events

or events:

DECLARE_EVENT_<Num>Params

DECLARE_DERIVED_EVENT_<Num>Params

events are Similar to multicast, but only the class that declares it can call Broadcast() IsBound() and Clear(). This allows you to expose the event/delegate in your public interface for user to add callbacks but keep control of when to Broadcast in your class.

Removing callback

TODO: investigate API

(FDelegateHandle )

FDelegateHandle hdle = OnPostResolvedSceneColorHandle = RendererModule->GetResolvedSceneColorCallbacks().AddRaw(this, &FWhiteNoiseCSManager::Execute_RenderThread);
RendererModule->GetResolvedSceneColorCallbacks().Remove(OnPostResolvedSceneColorHandle);
OnPostResolvedSceneColorHandle.Reset();

Delegate summary

Events: dynamic multi cast delegate but can't broadcast outside owner class

Sparse delegate: slow bind but memory efficient dyn multi cast delegate

Notes:
You can check if a founction .IsBound() or remove previously added functions with RemoveXxxx() (see doc)

Bonus

Delegate functions support the same Specifiers as UFunctions, but use the UDELEGATE() macro instead of UFUNCTION(). For example, the following code adds the BlueprintAuthorityOnly Specifier to the FInstigatedAnyDamageSignature delegate

	UDELEGATE(BlueprintAuthorityOnly)
	DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(
			FInstigatedAnyDamageSignature, 
			float, Damage, 
			const UDamageType*, DamageType)

Discuss the various bindings

BindRaw to bind a method of an object BindSP is the same but safer since it uses a shared pointer, so your delegate won't crash if the pointer gets deleted.

BindStatic to bind normal functions I guess

https://forums.unrealengine.com/development-discussion/c-gameplay-programming/33283-delegate-createsp-createraw-createuobject-when-to-use-them

Create delegates through helper functions

Instead of declaring a delegate and calling .BindRaw(), .BindStatic() etc. It is possible to construct and bind a delegate in a single line:

FExecuteAction on_clicked = FExecuteAction::CreateSP(
		this_ptr,
		&FPersonaMeshDetails::OnChanged,
		TypeAndText.Key,
		LODIndex);	
FTimerDelegate del = FTimerDelegate::CreateStatic(&UAREffectStatics::SpawnProjectile); ??

FExecuteAction::CreateBind()
FExecuteAction::CreateRaw()

to do: list of helper functions.

References

Blue print equivalent

No comments

(optional field, I won't disclose or spam but it's necessary to notify you if I respond to your comment)
All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.
Anti-spam question: