BTNodes, Delegates, and BTNodeMemory

So we’ve been running into an issue over and over. We will make a BTNode (Task, Service, or Decorator) and make a memory for it, since it’s not instanced. No problems there. But if we want that node to respond to a delegate from another system and store that info in BTMemory, that’s where things tend to go down hill.

If we bind a function from our BTNode, to the delegate and it gets called. We have no access to the BTMemory. The other examples we’ve seen and have this issue, happen to have the data required to access the BTMemory passed into the delegate. Not all delegates are going to have the information required to get that memory. And we shouldn’t be changing other systems to make sure its delegates pass information we need to work.

What I’m trying to do (but its turning into a mess) is allow the BTMemory to have a “delegate handler object” with a function that we can use to bind too. This handler can also store information we can use to get our memory.

So I need to store the BTComp and my BTNode! Which is just gross:/ I could also just make it instanced, but it seems like the BT system would rather I not do that.

My questions are:

  • Is there a better way to get a node’s memory, that I can store in my “Delegate Handler” object. Like a handle I can pass to a static function to get the memory pointer back? Like: UBehaviorTreeComponent::GetMemory(MyMemHandle)
  • Should I just go with an instanced BTNode in these cases. I can see some cases not being a huge deal, but other’s I’d have a bigger issue with being not instanced
  • Is there some other slick idea you guys would do instead.

Thanks! :slight_smile:

1 Like

Hey Troy,

There’s a really cool feature UE4 delegates have that allows you to pass in some payload when binding to a delegate, and that could be a generic solution you’re looking for here.

The way it works is you implement a function with a signature required by the delegate you want to bind to, and then append extra parameters you need. And then, when you bind to a delegate you simply specify values of the params the delegate doesn’t support on it’s own.

Code say a 1k words, I mean something like this:

// delegate type declaration
DECLARE_MULTICAST_DELEGATE_OneParam(FOnStateChanged, UObject&);

...

// your function declaration, in some UMyClass
void HandleSuff(UObject& InObject, const int32 MyStuff);

...

// and bind values while binding the delegate
OnStateChangedDeledateInstance..AddUObject(this, &UMyClass::HandleSuff, 1337);

And when HandleStuff gets called InObject will be whatever delegate’s passing in, but MyStuff will be 1337.

In your case I’d suggest using TWeakObjectPtr, that will make it possible to validate if the value coming in is still valid, and if so get access to the memory. Never tried it with TWeakObjectPtr before, but done it multiple time with other types, so this should work as well.

My mind was blown when I’ve learned about it, hope yours blown as well :wink: Give it a try and let me know if you run into issues.

Cheers,

–mieszko

Ah, I didn’t know that. Didn’t Boost have something like this at one point? That’s a much better solution than my handler. This problem screams lambda captures, but the bind should work.

Do you know of a slicker way to get at the memory without having to store the BTComponent?

You can store a raw pointer to a specific memory piece, but that’s not safe. I suggested using BTComponent, since it gives you access to a lot more than just memory (including means of influencing BT flow, like finishing current task for example).

BTW, you can bind lambdas to our delegates as well. Just saying :wink:

I ended up going with the BTComp. You can bind lambdas? I’m going to have to give that a shot.

I got the weak ptr to work. But that will not work if my callback function has the FUNCTION macro on it. The generated file poops out. I didn’t dive into the magic that is going on there. But I thought that was odd.

UFUNCTIONs are special class citizens, yeah. There’s extra stuff generated for them, and sometimes (if it’s for example a “Reliable” ufunction) you don’t even see the actual declarations. For blueprint-bindable delegates (which require function bound to be an UFunction) we have “dynamic” delegates, which are somehow magically different. I’ve never dug deep into that stuff to be honest, I’m just a consumer :wink:

Yeah, I started with the dynamic, but the AddDynamic macro function wouldn’t allow me to bind the extra param. And there wasn’t enough info in the comment block to explain how to do that with Dynamic. I didn’t need to expose this to blueprint, so I went the non-dynamic route.

I didn’t know that :slight_smile: I tend to stay away from dynamic delegates, they “feel” slower :smiley: