Download

Dynamic Multicast delegate: How to bind lambda?

Hello. Suppose you have a delegate defined as follows:


DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDelegate, FMyData, eventData);

You can subscribe to events as follows:



FMyDelegate delegate;
delegate.AddDynamic(this, &UFooBar::MyHandler);

However, I need to pass additional data to my handler. For example:


void UFooBar::MyHandler(FMyData eventData)
{
    SomeSystem::GiveData(locallyVisibleData, eventData);
}

Ideally, I want to capture locallyVisibleData in a lambda and bind the lambda to my delegate. However, I have not found any way of doing this.
How can I bind a lambda to dynamic multicast delegates?

One way to pass local data is by creating a dummy UObject that will hold locallyVisibleData, e.g.



UMyDummy dummy = NewObject ...;
dummy.locallyVisibleData = locallyVisibleData;
dummy.realUserObject = this;
delegate.AddDynamic(dummy, &UMyDummy::MyHandler);


The above approach will yield much more boilerplate, hard to read and inefficient code than lambdas though.

Is there any other way to pass local data into dynamic multicast delegates?

Have you tried BindLambda?

Thanks for the pointer. This function is part of TBaseDelegate.

The ‘issue’ is that dynamic multicast delegates inherits directly from TBaseDynamicMulticastDelegate which in turn inherits from TMulticastScriptDelegate. The BindLambda only is part of TBaseDelegate, which isn’t in that hierarchy tree.

On that note, there is a non-dynamic delegate declared using DECLARE_MULTICAST_DELEGATE.

That declares a class of type TMulticastDelegate<void, …>. This void returning delegate has an AddLambda function. However, sadly, this type of event is not visible to blueprints… if you mark such a delegate with an UPROPERTY, the UHT complains that you must specify a UCLASS, USTRUCT or UENUM.

I’m just trying to find a way of exposing an event to both blueprints and C++ AND be able to use lambdas on it. One way to do it would be to declare two events: one dynamic multicast and one just multicast; then, the code just calls both events whenever it’s triggered. However that solution leaves me quite unsatisfied…

… Declaring two events is what I do :s

I stumbled upon the same issue. Is it really not possible to have a delegate that can be used in both BP world and from C++ by binding a lambda function to it?

I have found some useful tutorials about binding the lamda c++
https://www.orfeasel.com/understandi…a-expressions/ Here It is.

2 Likes

I use BindWeakLambda because I’ve run into some really strange crashes before. Normally if you bind dynamically, it handles the object being destroyed as well.

There’s still no answer to this? I’m also looking to bind a lambda to delegate shared between BP and C++. Any workaround besides doubling delegates?

Dynamic delegates store invocation lists as object pointer + function name to call. It is not possible to bind a lambda or pass additional parameters through a dynamic delegate.

I expected lambda binding to not be possible, unfortunately.

Creating two separate events is a viable solution of course. I was wondering what other solutions other developers found, or whether it even is an issue.
A thought I had the other day: maybe would be viable to support the two event approach with a dedicated macro; it simply declares a blueprint and native event: the native event receives one subscriber that triggers the blueprint event. I doubt it’s worth to implement such a mechanism though, especially in smaller projects, due to its complexity.

Unfortunately you can not create such a macro yourself, because DECLARE_DYNAMIC_DELEGATE_* macros must be clearly written in the file for UHT to parse them and generate the dynamic delegate reflection info.

I just faced a similar problem and solved it using a little wrapper class:

UCLASS()
class ULambdaWrapper : public UObject
{
	GENERATED_BODY()
public:
	TFunction<void()> CallFn;
	UFUNCTION()
	void Dispatch() { CallFn(); }
};

That way you can “bind” a lambda like this:

auto btnWrap = NewObject<ULambdaWrapper>();
btnWrap->CallFn = [this]{//do lambda stuff};
someButton->OnClicked.AddDynamic(btnWrap, &ULambdaWrapper::Dispatch);
1 Like