beehave icon indicating copy to clipboard operation
beehave copied to clipboard

Interruptions

Open and3rson opened this issue 2 years ago • 2 comments

Is your feature request related to a problem? Please describe. There doesn't seem to be a way to notify an action that it needs to perform some cleanup due to interruption (say, stop moving if the player becomes visible.)

This is open to discussion since I'm not sure what would be the best way to achieve this.

Describe the solution you'd like I'd suggest to add some sort of Interruptor node (or interrupt(priority) method to the tree itself), so that when an interruption happens (say, an Interruptor node returns a SUCCESS, etc), nodes with lower priority that are in RUNNING state would have interrupt method called. I'm still relatively new to behavior trees, so please let me know if there are better ways to achieve this.

Describe alternatives you've considered Let's say we have a "move" action that builds a path and starts the motion process (which is handled outside of the tree - e. g. monster.walk_to_position(xy) method which starts a Tween). Now, in case the player becomes visible, I'd like to stop the movement, which means I'll need to explicitly call monster.stop_movement() before attacking. This "leaks" part of starting/stopping the walking process from "move" action to "attack" action.

and3rson avatar Sep 13 '22 18:09 and3rson

et's say we have a "move" action that builds a path and starts the motion process (which is handled outside of the tree - e. g. monster.walk_to_position(xy) method which starts a Tween). Now, in case the player becomes visible, I'd like to stop the movement, which means I'll need to explicitly call monster.stop_movement() before attacking. This "leaks" part of starting/stopping the walking process from "move" action to "attack" action.

Hey! I understand your suggestion and there could be an exit() method that is called when ever a node returns FAILURE:

		if response != FAILURE:
		   ...
		else:
			c.exit()
			last_execution_index += 1

But I am missing a good example where this is necessary. The example you provide is a wrong implementation of a behavior. You should move with each tick in the direction of the position and not define in a behavior calls that will create sideffects.

creadicted avatar Sep 14 '22 10:09 creadicted

@creadicted there is a deeper issue here and I think I know what @and3rson is getting at here:

A node can be in RUNNING state (within a Sequence* or Selector*) but at the root of the tree is a condition that suddenly prevents the running node to be called any longer. As a result, that node will hang in RUNNING state (but effectively it should be interrupted automatically)

bitbrain avatar Sep 14 '22 15:09 bitbrain

First thanks for this addon. I am new to Godot but i will try to add my ideas on this issue.

First is the fact that "long running and independent" processes like a tween are not really compatible with behaviour trees. A basic interpolate is nearer to the "ideal" pattern needed. But unfortunately tweens have so much more ready made functionality available. It would be a shame to see tweens and behaviour tree as excluding each other.

On the other hand, long running processes could be integrated if one puts some kind of guard on them that are dependent on the behaviour tree. For example in the case of the tween, it has a tween_step signal than could be used to check (and reset to false) for some flag that would be set every time that its behaviour tree node is executed. And if it isn't set, it would for example pause or even stop the tween. I didn't try this particular implementation. Does it have any synchronization problems or something similar? But the general idea remains.

As an alternative there are variation on the standard behaviour tree that use interesting ideas like the Conditional Aborts of Unity's Behaviour Designer. Here is a video giving some explanation of it: Behavior Designer Conditional Aborts

plataris avatar Sep 19 '22 05:09 plataris

Since I need this for a game I'm making, I will give yet another proposal lol

Based on this behaviour tree implementation, we could add an interrupt method for when you need to do some clean up in a long running node (ie. stop a timer, disconnect a signal) like this:

func interrupt(actor: Node, blackboard: Blackboard) -> void:
	pass # do something to clean up the long running action

So the responsibility to define the interrupting behaviour would be of the ActionNode itself. It will have to override tick() and interrupt() for a node that returns RUNNING.

Since the only composite nodes that can leave a running node "forgotten" are Sequence and Selector, we only need to add some logic to call the interrupt() method.

Let's take the Sequence composite node as an example. It will proccess every child until one of them fails or all of them succeed. If one of them fails, there's a possibilty that a node that was previously running will hang in this state forever. So we need to check if there's a "running_action" in the blackboard, call the interrupt method on it and set it as null.

The code would look something like this:

func tick(actor: Node, blackboard: Blackboard) -> int:
	for c in get_children():
		var response = c.tick(actor, blackboard)
		
		if c is ConditionLeaf:
			blackboard.set_value("last_condition", c)
			blackboard.set_value("last_condition_status", response)

		if response != SUCCESS:
			if c is ActionLeaf and response == RUNNING:
				blackboard.set_value("running_action", c)
			
			if response == FAILURE:
				# A node that was previously running should be interrupted.
				var running_action: Node = blackboard.get_value("running_action")
				if running_action != null:
					running_action.interrupt(actor, blackboard)
					blackboard.set_value("running_action", null)
			
			return response

	return SUCCESS

The logic for the Selector is basically the same but inverted. I made some tests and it seems that it solved my problems. I will make a PR later but this surely needs a double check 😅.

lostptr avatar Dec 12 '22 01:12 lostptr

@lostptr can this be closed now or is there anything else outstanding?

bitbrain avatar Dec 13 '22 23:12 bitbrain

I believe this can be closed

lostptr avatar Dec 13 '22 23:12 lostptr