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?

3 Likes

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.

3 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.

1 Like

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);
15 Likes

This is not a safe solution, that UObject can be garbage-collected at any time.

Delegates (both dynamic and non-dynamic) do not keep strong/reflected references to UObjects and therefore cannot keep them alive. As soon as the garbage collector does a pass, this will fail.

3 Likes

Are you sure? I suspected as much, but I put those UObjects in a

UPROPERTY()
TArray<class ULambdaWrapper*> LambdaWrappers;

and manually forced garbage collection and so far I haven’t run into any issues.

So long as those wrapper objects have a valid outer, then in that case the UPROPERTY() will keep them alive - but now you have object lifetimes to manage too, all for the sake of using a lambda (which is supposed to be convenient). It doesn’t make it any better of a solution IMO.

3 Likes

Facing similar issue, is there a better workaround now?

1 Like

The only proper solution is to make a BP and native version of the event


	DECLARE_MULTICAST_DELEGATE_OneParam(FOnPoiActorAddedSignature, ALgcPoiActor* PoiActor);
	DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPoiActorAddedSignatureBp, ALgcPoiActor*, PoiActor);

Everything else sets you up for failure and bugs. I don’t know why the most dangerous and wrong hack got the most likes in this thread. There were replies warning about it afterawards that were correct, trying to ā€œwork aroundā€ the problem with hacks is just wrong. There is a reason things are not supported directly like that. So…

Use two signature types and two events, broadcast them always together (write a helper function if needed to wrap them to follow DRY principle) and you re good! This is also several times done in UE’s engine code itself.

1 Like