Skip to content
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
27 changes: 27 additions & 0 deletions samples/subsys/zephyrbt/subtree_reuse/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (c) 2026 O.S. Systems Software LTDA.
# Copyright (c) 2026 Freedom Veiculos Eletricos
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
include(${ZEPHYR_ZEPHYRBT_MODULE_DIR}/cmake/zephyrbt-from-behaviourtreecpp-xml.cmake)

project(zephyrbt_subtree_reuse)

zephyr_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_sources(app PRIVATE
src/main.c
)

zephyrbt_define_from_behaviourtreecpp_xml(app
models/subtree_reuse.xml
${CMAKE_BINARY_DIR}/include
${CMAKE_BINARY_DIR}/src
1024
0
yes
)
75 changes: 75 additions & 0 deletions samples/subsys/zephyrbt/subtree_reuse/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.. Copyright (c) 2026 O.S. Systems Software LTDA.
.. Copyright (c) 2026 Freedom Veiculos Eletricos
.. SPDX-License-Identifier: Apache-2.0
.. _zephyrbt_subtree_reuse:

Zephyr Behaviour Tree - Subtree Reuse
#####################################

Overview
********

This sample validates the correct behavior tree traversal when a subtree is
used multiple times. It reproduces the scenario from `GitHub issue #28`_ where
incorrect indices caused nodes to be skipped during execution.

The behavior tree structure is:

.. code-block:: text

Sequence
├── SubTree (btree_subtree) → sub
├── A
├── SubTree (btree_subtree) → sub
└── B

The expected execution order is: sub → A → sub → B

Prior to the fix, the second instance of the subtree had incorrect sibling
indices, causing node A to be skipped entirely.

.. _GitHub issue #28: https://github.com/OSSystems/ZephyrBT/issues/28

Building and Running
********************

This application can be built and executed on ``qemu_cortex_m3`` as follows:

.. code-block:: console

west build -p -b qemu_cortex_m3 samples/subsys/zephyrbt/subtree_reuse -t run

To build for another board, change "qemu_cortex_m3" above to that board's name.

Sample Output
=============

.. code-block:: console

*** Booting Zephyr OS ***
D: zephyrbt_action_sub_init stub function
D: zephyrbt_action_a_init stub function
D: zephyrbt_action_sub_init stub function
D: zephyrbt_action_b_init stub function
D: tick
D: eval sequence [control, 4]
D: Deep: 1
D: sequence [control, 4]
D: eval sub [action, 3]
D: Deep: 2
D: zephyrbt_action_sub stub function
D: eval a [action, 2]
D: Deep: 2
D: zephyrbt_action_a stub function
D: eval sub [action, 1]
D: Deep: 2
D: zephyrbt_action_sub stub function
D: eval b [action, 0]
D: Deep: 2
D: zephyrbt_action_b stub function

The key verification points are:

1. All four nodes (sub, a, sub, b) are evaluated in the correct order
2. Node 'a' is not skipped (which was the bug symptom)
3. Each subtree instance has a unique index (3 and 1)
11 changes: 11 additions & 0 deletions samples/subsys/zephyrbt/subtree_reuse/include/zephyrbt_user.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2026 O.S. Systems Software LTDA.
* Copyright (c) 2026 Freedom Veiculos Eletricos
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYRBT_USER_H
#define ZEPHYRBT_USER_H

#endif /* ZEPHYRBT_USER_H */
29 changes: 29 additions & 0 deletions samples/subsys/zephyrbt/subtree_reuse/models/subtree_reuse.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<root BTCPP_format="4"
main_tree_to_execute="btree_debug">
<BehaviorTree ID="btree_debug">
<Sequence>
<SubTree ID="btree_subtree"
_autoremap="true"/>
<A/>
<SubTree ID="btree_subtree"
_autoremap="true"/>
<B/>
</Sequence>
</BehaviorTree>

<BehaviorTree ID="btree_subtree">
<sub/>
</BehaviorTree>

<!-- Description of Node Models (used by Groot) -->
<TreeNodesModel>
<Action ID="A"
editable="true"/>
<Action ID="B"
editable="true"/>
<Action ID="sub"
editable="true"/>
</TreeNodesModel>

</root>
20 changes: 20 additions & 0 deletions samples/subsys/zephyrbt/subtree_reuse/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) 2026 O.S. Systems Software LTDA.
# Copyright (c) 2026 Freedom Veiculos Eletricos
# SPDX-License-Identifier: Apache-2.0

CONFIG_LOG=y
CONFIG_LOG_MODE_MINIMAL=y

CONFIG_ZEPHYR_BEHAVIOUR_TREE=y
CONFIG_ZEPHYR_BEHAVIOUR_TREE_DYNAMIC=y
CONFIG_ZEPHYR_BEHAVIOUR_TREE_NODE_INFO=y
CONFIG_ZEPHYR_BEHAVIOUR_TREE_NODE_INIT=y
CONFIG_ZEPHYR_BEHAVIOUR_TREE_NODE_CONTEXT=y
CONFIG_ZEPHYR_BEHAVIOUR_TREE_LOG_LEVEL_DBG=y

CONFIG_DYNAMIC_THREAD=y
CONFIG_DYNAMIC_THREAD_POOL_SIZE=10
CONFIG_THREAD_STACK_INFO=y

CONFIG_KERNEL_MEM_POOL=y
CONFIG_HEAP_MEM_POOL_SIZE=8192
37 changes: 37 additions & 0 deletions samples/subsys/zephyrbt/subtree_reuse/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) 2026 O.S. Systems Software LTDA.
# Copyright (c) 2026 Freedom Veiculos Eletricos
# SPDX-License-Identifier: Apache-2.0

sample:
name: Zephyr Behaviour Tree - Subtree Reuse (Issue #28)
description: |
This sample validates correct traversal when a subtree is used multiple
times in the same behavior tree. It reproduces the scenario from issue #28
where incorrect indices caused nodes to be skipped during execution.
common:
tags: zephyrbt
tests:
samples.zephyrbt.subtree_reuse:
platform_allow:
- qemu_cortex_m3
- nucleo_f767zi
harness: console
harness_config:
type: multi_line
regex:
- "D: tick"
- "D: eval sequence \\[control, 4\\]"
- "D: Deep: 1"
- "D: sequence \\[control, 4\\]"
- "D: eval sub \\[action, 3\\]"
- "D: Deep: 2"
- "D: zephyrbt_action_sub stub function"
- "D: eval a \\[action, 2\\]"
- "D: Deep: 2"
- "D: zephyrbt_action_a stub function"
- "D: eval sub \\[action, 1\\]"
- "D: Deep: 2"
- "D: zephyrbt_action_sub stub function"
- "D: eval b \\[action, 0\\]"
- "D: Deep: 2"
- "D: zephyrbt_action_b stub function"
13 changes: 13 additions & 0 deletions samples/subsys/zephyrbt/subtree_reuse/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright (c) 2026 O.S. Systems Software LTDA.
* Copyright (c) 2026 Freedom Veiculos Eletricos
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/kernel.h>

int main(void)
{
return 0;
}
43 changes: 39 additions & 4 deletions scripts/generate-zephyrbt-from-behaviourtreecpp-xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# Copyright (c) 2024-2025 O.S. Systems Software LTDA.
# Copyright (c) 2024-2025 Freedom Veiculos Eletricos
# Copyright (c) 2024-2026 O.S. Systems Software LTDA.
# Copyright (c) 2024-2026 Freedom Veiculos Eletricos
# SPDX-License-Identifier: Apache-2.0

"""
Expand All @@ -20,6 +20,7 @@ import struct

from natsort import natsorted
import xml.etree.ElementTree as ET
import copy

REPO_ROOT = Path(__file__).absolute().parents[1]
HEADER = """/*
Expand Down Expand Up @@ -422,6 +423,34 @@ def addBlackboardVariables(idx_nodes_set, blackboard_set):
index = loadBlackboardVariables(index, str(blackboard[0][1]), blackboard_set)


def expand_subtrees(elem, root):
"""
Recursively expand SubTree nodes by replacing them with deep copies
of their referenced tree content. This ensures each subtree instance
has unique element IDs when a subtree is used multiple times.
"""
i = 0
while i < len(elem):
child = elem[i]
if child.tag == 'SubTree':
subtree_id = child.get('ID')
for tree in root.iter('BehaviorTree'):
if tree.attrib.get('ID') == subtree_id:
# Create a deep copy of the subtree content
subtree_content = copy.deepcopy(tree[0])
# Remove the SubTree placeholder
elem.remove(child)
# Insert the expanded content
elem.insert(i, subtree_content)
# Recursively expand any nested subtrees in the copy
expand_subtrees(subtree_content, root)
break
else:
# Recursively process non-SubTree children
expand_subtrees(child, root)
i += 1


def build_bt_set(builtin, root):
main = root.attrib.get('main_tree_to_execute')

Expand All @@ -438,8 +467,14 @@ def build_bt_set(builtin, root):
if elem.attrib.get('ID') != main:
continue

parseNodes(builtin, idx_nodes_set, custom_set, 0, elem[0], root)
assemblyBT(builtin, idx_nodes_set, bt_set, 0, elem[0], elem[0], root)
# Deep copy and expand the main tree before processing.
# This ensures each subtree instance has unique element IDs
# when a subtree is used multiple times (fixes issue #28).
main_tree = copy.deepcopy(elem[0])
expand_subtrees(main_tree, root)

parseNodes(builtin, idx_nodes_set, custom_set, 0, main_tree, root)
assemblyBT(builtin, idx_nodes_set, bt_set, 0, main_tree, main_tree, root)
break

addBuiltinBlackboard(idx_nodes_set, blackboard_set, builtin)
Expand Down