Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 fix interrupt of sequence star/random + improve docs #249

Merged
merged 4 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading