AddDynamic Paramter Datatype (UE5-EA2)

Hey all, question regarding the parameters passed to the AddDynamic on a Dynamic Multicast Delegate, I’m trying to create a function which takes a similar parameter datatype - e.g. &UClassName::FuncName.

I’ve tried out some templating such as just trying to just T it, or TFunction as well, with no luck.

Having looked at the internals of the AddDynamic function, it looks like Unreal is using typename FDelegate::template TMethodPtrResolver< UserClass >::FMethodPtr InMethodPtr as the parameter for the AddDynamic method, which is a part of the template class for dynamic multicast delegates TBaseDynamicMulticastDelegate, but how to actually utilize any of these is another question entirely.

Suggestions?

1 Like

You have to describe what you want to achieve, what is your goal.

Creating a function which can take the same datatype as what the AddDynamic function takes as a parameter, e.g. &UClassName::FuncName.

For further specifics if you want to know, it’s for the sake of creating a function where, when the user picks up a weapon in game, a set of functions from the weapon are bound to a set of dynamic multicast delegates on the player character which are broadcast by player inputs. So at the moment that looks like this:

void ADerivedPlayer::EquipWeapon(AActor* ActorToEquip, EALSOverlayState OverlayStateParam)
{
	Super::EquipWeapon(ActorToEquip, OverlayStateParam);
	
	Fire.AddDynamic(Cast<AWeaponBase>(ActorToEquip), &AWeaponBase::Fire);
	CeaseFire.AddDynamic(Cast<AWeaponBase>(ActorToEquip), &AWeaponBase::CeaseFire);
	ToggleFiringMode.AddDynamic(Cast<AWeaponBase>(ActorToEquip), &AWeaponBase::ToggleFiringMode);
	Reload.AddDynamic(Cast<AWeaponBase>(ActorToEquip), &AWeaponBase::Reload);
}

The idea is to have a template on the super / parent class which will do all the binds required like so:

template <typename T>
void APlayer::BindWeaponEvents(T* Weapon, SomeDatatype FireDelegate, SomeDatatype CeasefireDelegate, SomeDatatype ToggleFiringModeDelegate, SomeDatatype ReloadDelegate)
{
	Fire.AddDynamic(Weapon, FireDelegate);
	CeaseFire.AddDynamic(Weapon, CeasefireDelegate);
	ToggleFiringMode.AddDynamic(Weapon, ToggleFiringModeDelegate);
	Reload.AddDynamic(Weapon, ReloadDelegate);
}

That way when a weapon is required to be bound to these delegates, whether on pickup or on weapon switching etc, only this one function needs to be called like so:

void ADerivedPlayer::EquipWeapon(AActor* ActorToEquip, EALSOverlayState OverlayStateParam)
{
	Super::EquipWeapon(ActorToEquip, OverlayStateParam);
	
	BindWeaponEvents(ActorToEquip, &AWeaponBase::Fire, &AWeaponBase::CeaseFire, &AWeaponBase::ToggleFiringMode, &AWeaponBase::Reload);
}

Is it not sufficient? Did I miss something?

class APlayer : public AActor
{
  //...
  UFUNCTION()
  virtual void EquipWeapon(AWeapon* Weapon, EALSOverlayState OverlayStateParams);
};

class AWeapon : public AActor
{
  //...
  UFUNCTION()
  virtual void Fire();

  UFUNCTION()
  virtual void CeaseFire();
  // etc.
};

void APlayer::EquipWeapon(AWeapon* Weapon, EALSOverlayState OverlayStateParam)
{
  //...
  OnFire.AddDynamic(Weapon, &AWeapon::Fire);
  OnCeaseFire.AddDynamic(Weapon, &AWeapon::CeaseFire);
  OnToggleFiringMode.AddDynamoc(Weapon, &AWeapon::ToggleFiringMode);
  OnReload.AddDynamic(Weapon, &AWeapon::Reload);
}

If you have a class like AWeaponBase then this class should define the base interface (like your Fire, ToggleFiringMode, etc.) for any weapon.
Why do you want to parameterize it?

The ‘APlayer’ and ‘AWeapon’ classes are both from different modules which are designed to be totally stand alone / decoupled, and as such have no awareness of each others existence, so that modules can be switched out if necessary, e.g. a different weapon module can be plugged in if desired.

So various aspects that require output or input to or from other modules are using either delegates or interfaces - on the module level at least. Classes derived from the modules in the core / project code are where the integration between modules occurs, e.g. in this example the ‘ADerivedPlayer’ class, which is tying together the two modules by subscribing the methods from the weapons module to the delegates from the player module.

But I want to make it much easier to plug in any weapon class to the player class events by providing the BindWeaponEvents function

I think you’re looking for TDelegate<>.

Assume your delegates take no parameters and return nothing – i.e., they’re just an event with no additional data – in which case I believe this will work, though (as I’m writing this in the forum browser on lunch break rather than in the editor) I won’t swear to it:

template <typename T>
void APlayer::BindWeaponEvents(T* Weapon, TDelegate<void()> FireDelegate, TDelegate<void()> CeasefireDelegate,
	TDelegate<void()> ToggleFiringModeDelegate, TDelegate<void()> ReloadDelegate)
{
	Fire.AddDynamic(Weapon, FireDelegate);
	CeaseFire.AddDynamic(Weapon, CeasefireDelegate);
	ToggleFiringMode.AddDynamic(Weapon, ToggleFiringModeDelegate);
	Reload.AddDynamic(Weapon, ReloadDelegate);	
}

Unfortunately no luck! TDelegate gives me a Cannot convert rvalue of type void(AWeaponBase::*)() to parameter type TDelegate<void()>.

Though I have come up with something that seems to be getting quite close… As of where at the least, the IDE isn’t complaining about it until it tries building:

template <typename T, typename G>
void BindWeaponEvents(T* Weapon, void(G::* Func)())
{
	Fire.AddDynamic(Weapon, Func);
}

When building this one gives me a cannot convert argument 2 from 'void (__cdecl AWeaponBase::* )(void)' to 'void (__cdecl AActor::* )(void)'

Update:
Seemingly a bit closer to a solution, I made both parameters use the same type:

template <typename T>
void BindWeaponEvents(T* Weapon, void(T::* Func)())
{
	Fire.AddDynamic(Weapon, Func);
}

Then when calling the method, I do a cast on the actor being passed in:

AWeaponBase* WeaponBase = Cast<AWeaponBase>(ActorToEquip);
BindWeaponEvents(WeaponBase, &AWeaponBase::Fire);

This compiles successfully, but when it actually gets called during runtime, it gets an assertion failed error:
Assertion failed: Result && Result[2] != (TCHAR)'0' [File:C:\Epic\UE_5.0EA\Engine\Source\Runtime\Core\Public\Delegates/Delegate.h] [Line: 469] 'Func' does not look like a member function

AddDynamic is a macro. The preprocessor does not understand c ++ and will not substitute the function pointer given as a parameter to macro parameter.

You also need to use a macro for this to work, something like:

#define BindWeaponEvents(Weapon, EventType, Func) EventType.AddDynamic(Weapon, Func)

But since it’s a macro, you won’t be able to “pin” it to a specific class like normal member function.

Another possibility is to use directly what the macro is being expanded in
to, but that will require you to manually type the function name
(see (void __Internal_AddDynamic function from DelegateSignatureImpl.inl)

The best solution is probably to write a wrapper.

Hey,

I stumbled on this post when trying to achieve the same thing and I finally found a solution :slightly_smiling_face:

Here is FMyDelegateSignature declaration :

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDelegateSignature, const FMyEvent &, Event);

Then, the tricky part, when you need to put the function pointer as a parameter; hopefully Unreal comes with something to achieve this, and I’m passing the parameters to __Internal_AddUniqueDynamic() to do the binding :

template<typename UserClass>
void AddGlobalEventListener(const FName& EventName, UserClass* Owner, typename FMyDelegateSignature::FDelegate::TMethodPtrResolver<UserClass>::FMethodPtr InMethodPtr, FName FuncName)
{
     check(Owner)
     UE_LOG(LogUGC, Log, TEXT("Add Global Event Listener for %s : %s"), *EventName.ToString(), *FuncName.ToString());
    FMyDelegateSignature& Signature = GetGlobalEvent(EventName);
    Signature.__Internal_AddUniqueDynamic(Owner, InMethodPtr, FuncName);
}

The function can be called like this

MyObject->AddGlobalEventListener(GlobalEventName, this, &UAction::ExecuteAction, TEXT("ExecuteAction"));

But you can define a macro like AddDynamic() to be more elegant :

#define AddGlobalEvent(EventName, UserObject, FuncName) AddGlobalEventListener(EventName, UserObject, FuncName, STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )

And you can call your function like this

MyObject->AddGlobalEvent(GlobalEventName, this, &UAction::ExecuteAction);

And voilà ! I hope it helps !