From 3845a9d50b7152b6345282fbd82d1212e5ef2e54 Mon Sep 17 00:00:00 2001 From: Daniel Stonier Date: Sun, 1 Jan 2023 11:20:12 -0500 Subject: [PATCH] [sequences] no memory -> partial memory Resolves #329. --- py_trees/composites.py | 18 +++++++++++--- tests/test_sequences.py | 54 ++++++++++++++++++++++++++++++++--------- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/py_trees/composites.py b/py_trees/composites.py index a8671e87..a8b8bd74 100644 --- a/py_trees/composites.py +++ b/py_trees/composites.py @@ -439,15 +439,19 @@ def tick(self): # initialise index = 0 - if self.status != common.Status.RUNNING or not self.memory: + if self.status != common.Status.RUNNING: self.current_child = self.children[0] if self.children else None for child in self.children: if child.status != common.Status.INVALID: child.stop(common.Status.INVALID) - # user specific initialisation - self.initialise() - else: # self.memory is True and status is RUNNING + self.initialise() # user specific initialisation + elif self.memory and common.Status.RUNNING: index = self.children.index(self.current_child) + elif not self.memory and common.Status.RUNNING: + self.current_child = self.children[0] if self.children else None + else: + # previous conditional checks should cover all variations + raise RuntimeError("Sequence reached an unknown / invalid state") # customised work self.update() @@ -465,6 +469,12 @@ def tick(self): yield node if node is child and node.status != common.Status.SUCCESS: self.status = node.status + if not self.memory: + # invalidate the remainder of the sequence + # i.e. kill dangling runners + for child in itertools.islice(self.children, index + 1, None): + if child.status != common.Status.INVALID: + child.stop(common.Status.INVALID) yield self return try: diff --git a/tests/test_sequences.py b/tests/test_sequences.py index 132a5fb8..884872a6 100644 --- a/tests/test_sequences.py +++ b/tests/test_sequences.py @@ -38,20 +38,53 @@ def assert_details(text, expected, result): # Tests ############################################################################## +def test_running_with_no_memory_children_do_not_reset(): + console.banner('Tick-Running with No Memory - Children Do Not Reset') + assert_banner() + root = py_trees.composites.Sequence(name="Sequence w/o Memory", memory=False) + child_1 = py_trees.behaviours.TickCounter(1) # R-S + child_2 = py_trees.behaviours.Success('Success') + root.add_children([child_1, child_2]) + + # Expect + # 1. R - [R, I] + # 2. S - [S, S] <- NB: first child doesn't reset + + root.tick_once() + print(py_trees.display.unicode_tree(root, show_status=True)) + assert_details("1::Sequence Status", py_trees.common.Status.RUNNING, root.status) + assert(root.status == py_trees.common.Status.RUNNING) + assert_details("2::Child 1 Status", py_trees.common.Status.RUNNING, child_1.status) + assert(child_1.status == py_trees.common.Status.RUNNING) + assert_details("2::Child 2 Status", py_trees.common.Status.INVALID, child_2.status) + assert(child_2.status == py_trees.common.Status.INVALID) -def test_running_with_no_memory(): - console.banner('Tick-Running with No Memory') + root.tick_once() + print(py_trees.display.unicode_tree(root, show_status=True)) + assert_details("2::Selector Status", py_trees.common.Status.SUCCESS, root.status) + assert(root.status == py_trees.common.Status.SUCCESS) + assert_details("2::Child 1 Status", py_trees.common.Status.SUCCESS, child_1.status) + assert(child_1.status == py_trees.common.Status.SUCCESS) + assert_details("2::Child 2 Status", py_trees.common.Status.SUCCESS, child_2.status) + assert(child_2.status == py_trees.common.Status.SUCCESS) + +def test_running_with_no_memory_invalidate_dangling_runners(): + console.banner('Tick-Running with No Memory - Invalidate Dangling Runners') assert_banner() root = py_trees.composites.Sequence(name="Sequence w/o Memory", memory=False) child_1 = py_trees.behaviours.StatusSequence( - name="Success-Failure", + name="Success-Running", sequence=[py_trees.common.Status.SUCCESS, - py_trees.common.Status.FAILURE], + py_trees.common.Status.RUNNING], eventually=None - ) - child_2 = py_trees.behaviours.Running(name="Running") + ) + child_2 = py_trees.behaviours.Running('Running') root.add_children([child_1, child_2]) + # Expect + # 1. R - [S, R] + # 2. R - [R, I] <- NB: second child is invalidated + root.tick_once() print(py_trees.display.unicode_tree(root, show_status=True)) assert_details("1::Sequence Status", py_trees.common.Status.RUNNING, root.status) @@ -63,14 +96,13 @@ def test_running_with_no_memory(): root.tick_once() print(py_trees.display.unicode_tree(root, show_status=True)) - assert_details("2::Selector Status", py_trees.common.Status.FAILURE, root.status) - assert(root.status == py_trees.common.Status.FAILURE) - assert_details("2::Child 1 Status", py_trees.common.Status.FAILURE, child_1.status) - assert(child_1.status == py_trees.common.Status.FAILURE) + assert_details("2::Selector Status", py_trees.common.Status.RUNNING, root.status) + assert(root.status == py_trees.common.Status.RUNNING) + assert_details("2::Child 1 Status", py_trees.common.Status.RUNNING, child_1.status) + assert(child_1.status == py_trees.common.Status.RUNNING) assert_details("2::Child 2 Status", py_trees.common.Status.INVALID, child_2.status) assert(child_2.status == py_trees.common.Status.INVALID) - def test_running_with_memory_proceeds(): # This test should check two things: # 1) Skipped higher priorities are set to INVALID