Delegates defined in C++ and their inputs


#1

Continuing the discussion from Hello 3.0.5093 with support for event dispatchers, delegates, Blueprint functions, custom events, name collision resolution and more!:

Hi folks,

Quick question about using C++ delegates in :sk:. I’ve got a one param multicast delegate in my character class that tells everyone when it takes damage. Currently it broadcasts a pointer to the character that is being damaged. I wanted to add more info to the delegate for things like status changes, who hit the character, what hit the character etc. The problem is that the sk function I bind to the event doesn’t seem to need to have the same number or type of inputs. Is there a way currently to get that info and pass it into the function?

Cheers!


Almost ready to dive into SK but i have a few questions
#2

What might be confusing you is the fact that the argument to the _my_first_sk_delegate_do coroutine is not a delegate but a closure which is more powerful and does not require that the argument lists match. The compiler generates special code that will route the arguments properly. Look at the signature of _my_first_sk_delegate_do - its first parameter is a closure with its own parameter list. The names of those parameters become local variables in your closure code (just like any regular function parameters) that you can use, or not use, pass on, or not pass on as you wish. Did I confuse you more now? :grin:

Probably best is if you post a code example, illustrating this issue, and the parameter list of _my_first_sk_delegate_do, to clarify this more.


#3

That was definitely part of my confusion. Still getting used to the ideas in all these newfangled languages :smiley: In my academic code I tend to write somewhat old school style C/C++ code for my simulators. I can definitely see how powerful these things are, but they’re still a bit alien to me.

Here’s the setup. Everything is kind of skeletal, just poking at the beginnings of characters, conditions, etc.

I have a BlobberCharacter that is derived from UObject (BlobberCharacters belong to BlobberPawns). Inside BlobberCharacter.h I have the following delegate:

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCharacterTakeDamageSignature, UBlobberCharacter*, DamagedCharacter);

/**
*	Called when this character takes damage from any source.
*/
UPROPERTY(BlueprintAssignable, Category = "Character Events")
	FCharacterTakeDamageSignature OnCharacterTakeDamage;

And the delegate is called here.

/**
*   Called to apply damage to a character.
*/
UFUNCTION(BlueprintCallable)
	void TakeDamage() { this->OnCharacterTakeDamage.Broadcast(this); }

I’d like to expand this in the future to include things like what’s causing the damage, the kind of damage, etc. But for now I’m just trying to work out how everything works.

In the :sk: BlobberCharacter class I have branch _on_character_take_damage_do with this signature

((BlobberCharacter damaged_character) code;
 BlobberCharacter damaged_character)

So in this simple example I can “bind” the delegate with

branch [this.@characters.first._on_character_take_damage_do [this.on_damage_event(this.@characters.first)]]

here I’m feeding the character to the :sk: function manually, but when I expand on the system I may not know all of the required parameters at the time I invoke it in :sk: (i’ve was planning on C++ doing a lot of
the more “complex” tests and computations etc).

So essentially I’m just not sure how to access the parameters fed into the event from C++ if I don’t know them when I set up the _on_character_take_damage_do initially. In this case I guess it’s the damaged_character in the event signature?

Cheers!

Edit… wait a second. Gah… I was so tired last night I didn’t think of simply shoving the damaged_character into the branch statement like so.

branch [this.@characters.first._on_character_take_damage_do [this.on_damage_event(damaged_character)]]

Which works… palm, meet face.


#4

Yup that’s it! :slight_smile:

Btw, if you ever wanted to remove your handler from the delegate event, you’d store the InvokedCoroutine returned from the branch command in a variable like this:

@damage_handler : branch [this.@characters.first._on_character_take_damage_do [this.on_damage_event(damaged_character)]]

Then later when you don’t want it any more, just abort it: @damage_handler.abort.


#5

Ah that was going to be the next thing I tackled. I wanted to have conditions (not the best word for it but it works) applied to parties and characters that could be for example:

“On the first successful attack against this character within five turns, character casts group shield.”

So I was curious about how to go about removing it after the fifth turn, thanks for pre-solving that one for me. :smiley:

Just one question left on this topic: If an object is destroyed, I’m guessing all the coroutines that are running on it go with it?

Edit: Just thought of one more. Does that kind of pattern have a name in programming/compsci? It’s been a long time since my compilers class and I don’t recognize it from elsewhere. How does the language know how/where damaged_character is defined?


#6

Yes, if an object/actor is destroyed, all coroutines still running on it will be aborted.

damaged_character is an argument of the closure code as declared in the signature of _on_character_take_damage_do. In C++, you have to repeat a closure signature when you create a closure literal (e.g. [](AActor * DamagedCharacter) { DoStuff(DamagedCharacter) }), but Sk allows you to save yourself the typing and just use the names of the parameters specified in the declaration.


#7

OH ok. Yeah that makes sense. Code as data is something I’ve dabbled with in the past very briefly.

I’d forgotten about closures in C++. Embarrassed to admit I’m still not up to speed with c++11. I’m still used to using function pointers. Probably why I’m still trying wrap my head around closures in :sk:. They might confuse me right now, but that’s how we learn :smiley: