Skip to content

Commit

Permalink
πŸ› fix interrupt of sequence star/random + improve docs (#249)
Browse files Browse the repository at this point in the history
* πŸ› fix sequence star interrupt

* improve docs

* improve docs further
  • Loading branch information
bitbrain authored Nov 17, 2023
1 parent ac045a5 commit 1acf549
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 32 deletions.
3 changes: 3 additions & 0 deletions addons/beehave/nodes/composites/sequence_random.gd
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ func tick(actor: Node, blackboard: Blackboard) -> int:
c.after_run(actor, blackboard)
FAILURE:
_children_bag.erase(c)
# Interrupt any child that was RUNNING before
# but do not reset!
super.interrupt(actor, blackboard)
c.after_run(actor, blackboard)
return FAILURE
RUNNING:
Expand Down
5 changes: 4 additions & 1 deletion addons/beehave/nodes/composites/sequence_star.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class_name SequenceStarComposite extends Composite
## `SUCCESS` in case all of the children report a `SUCCESS` status code.
## If at least one child reports a `FAILURE` status code, this node will also
## return `FAILURE` and tick again.
## In case a child returns `RUNNING` this node will restart.
## In case a child returns `RUNNING` this node will tick again.

var successful_index: int = 0

Expand All @@ -32,6 +32,9 @@ func tick(actor: Node, blackboard: Blackboard) -> int:
successful_index += 1
c.after_run(actor, blackboard)
FAILURE:
# Interrupt any child that was RUNNING before
# but do not reset!
super.interrupt(actor, blackboard)
c.after_run(actor, blackboard)
return FAILURE
RUNNING:
Expand Down
11 changes: 8 additions & 3 deletions docs/manual/composites.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ With composite nodes, you can craft unique and dynamic Behavior Trees for your g

## Restarting a composite

If a parent node restarts a child node, it means that the parent node will start the child node from scratch the next time it is ticked. This means that any progress made by the child node will be reset, and it will start its execution from the beginning.
When a parent node restarts, it means the whole composite node begins its evaluation again from the beginning. This does not interrupt the running child node.

## Ticking again a composite

If a parent node ticks a child node again, it means that the parent node will immediately tick the child node again on the next frame, without waiting for the current frame to finish executing. This allows the child node to continue its execution from where it left off without resetting its progress.
When a composite node ticks again, it will "jump" to the currently `RUNNING` child node and tick it.

In other words, restarting a child node means that the parent node will give the child node a fresh start, while ticking it again means that the parent node will let the child node continue its execution from where it left off.
For [sequence nodes](sequence.md), on the first frame it starts ticking all its children in order, starting from the first one, until either all of them return `SUCCESS` or one of them returns `RUNNING`. If one of them returned `RUNNING`, on the next frame, the sequence will only tick the running child node. Not all of them, just the running one - and possibly the child nodes following it, if the running child has completed and returned `SUCCESS`.
For [selector nodes](selector.md), on the first frame it starts ticking each child in order until one of them returns `SUCCESS` or one of them returns `RUNNING`. If one of them returned `RUNNING`, on the next frame, the selector will only tick the running child node, skipping all the previous ones, and possibly the child nodes following it, if the running child has completed and returned `FAILURE`.

## Interrupting child nodes

A sequence may interrupt any `RUNNING` child node. The `interrupt` method will be called recursively on all descendant nodes of the interrupted child node.
12 changes: 6 additions & 6 deletions examples/random_tree_example/RandomAction.gd
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ var weights = [3., 3., 1.]
func _get_random_action():
var sum = 0.
for w in weights:
sum += w
sum += w
var rnd = randf_range(0, sum)
for i in weights.size():
if rnd <= weights[i]:
return i
rnd -= weights[i]
if rnd <= weights[i]:
return i
rnd -= weights[i]
return weights.size() - 1

func tick(actor: Node, blackboard: Blackboard) -> int:
var step = Time.get_ticks_msec() / reset_duration_msec
if step != last_step:
action = _get_random_action()
last_step = step
action = _get_random_action()
last_step = step
return action
40 changes: 20 additions & 20 deletions examples/random_tree_example/RandomTree.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ extends BeehaveTree
@export_range(1, 100) var randomize_node_count: int = 20
@export var randomize_tree: bool = false:
set(v):
if !v:
return
randomize_tree = false
_randomize_tree()
if !v:
return
randomize_tree = false
_randomize_tree()

class TNode extends RefCounted:
var children = []
Expand All @@ -17,7 +17,7 @@ var long_name_suffix = "LooooooooongNameSuffix"
func get_name_suffix():
var r = randi_range(0, 0)
if r:
return long_name_suffix
return long_name_suffix
return ""

var tree_root: TNode
Expand All @@ -31,33 +31,33 @@ func _make_random_tree():
tree_root = TNode.new()
tree_nodes = [tree_root]
for i in randomize_node_count - 1:
var n = _get_random_node()
var new_node = TNode.new()
n.children.append(new_node)
tree_nodes.append(new_node)
var n = _get_random_node()
var new_node = TNode.new()
n.children.append(new_node)
tree_nodes.append(new_node)

func _parse_tree_node(parent, node, index: int):
var n
if node.children.size() > 1:
n = SelectorReactiveComposite.new()
n.name = "SelectorReactiveComposite%s-%d" % [get_name_suffix(), index]
n = SelectorReactiveComposite.new()
n.name = "SelectorReactiveComposite%s-%d" % [get_name_suffix(), index]
elif node.children.size() == 1:
n = InverterDecorator.new()
n.name = "InverterDecorator%s-%d" % [get_name_suffix(), index]
n = InverterDecorator.new()
n.name = "InverterDecorator%s-%d" % [get_name_suffix(), index]
else:
n = RandomAction.new()
n.name = "RandomAction%s-%d" % [get_name_suffix(), index]
n = RandomAction.new()
n.name = "RandomAction%s-%d" % [get_name_suffix(), index]
parent.add_child(n)
if Engine.is_editor_hint():
n.owner = get_tree().get_edited_scene_root()
n.owner = get_tree().get_edited_scene_root()
var i = 0
for ch in node.children:
_parse_tree_node(n, ch, i)
i += 1
_parse_tree_node(n, ch, i)
i += 1

func _randomize_tree():
for ch in get_children():
remove_child(ch)
ch.queue_free()
remove_child(ch)
ch.queue_free()
_make_random_tree()
_parse_tree_node(self, tree_root, 0)
6 changes: 4 additions & 2 deletions test/nodes/composites/sequence_star_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,13 @@ func test_keeps_running_child_until_failure() -> void:

assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE)
assert_that(action1.count).is_equal(1)
assert_that(action2.count).is_equal(3)
# action2 will reset as it failed
assert_that(action2.count).is_equal(0)

assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE)
assert_that(action1.count).is_equal(1)
assert_that(action2.count).is_equal(4)
# action2 has reset previously but sequence star will tick again
assert_that(action2.count).is_equal(1)


func test_tick_again_when_child_returns_failure() -> void:
Expand Down

0 comments on commit 1acf549

Please sign in to comment.