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.

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);
11 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.

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

2 Likes

Facing similar issue, is there a better workaround now?

1 Like