Skip to content

Commit 644c589

Browse files
committed
Fix BTForEach crash if elements are removed from the array during iteration
(cherry picked from commit b8c9db0)
1 parent 1cb8580 commit 644c589

File tree

2 files changed

+23
-4
lines changed

2 files changed

+23
-4
lines changed

bt/tasks/decorators/bt_for_each.cpp

+7-4
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,15 @@ void BTForEach::_enter() {
4343
}
4444

4545
BT::Status BTForEach::_tick(double p_delta) {
46-
ERR_FAIL_COND_V_MSG(get_child_count() == 0, FAILURE, "ForEach decorator has no child.");
47-
ERR_FAIL_COND_V_MSG(save_var == StringName(), FAILURE, "ForEach save variable is not set.");
48-
ERR_FAIL_COND_V_MSG(array_var == StringName(), FAILURE, "ForEach array variable is not set.");
46+
ERR_FAIL_COND_V_MSG(get_child_count() == 0, FAILURE, "BTForEach: Decorator has no child.");
47+
ERR_FAIL_COND_V_MSG(save_var == StringName(), FAILURE, "BTForEach: Save variable is not set.");
48+
ERR_FAIL_COND_V_MSG(array_var == StringName(), FAILURE, "BTForEach: Array variable is not set.");
4949

5050
Array arr = get_blackboard()->get_var(array_var, Variant());
51-
if (arr.size() == 0) {
51+
if (current_idx >= arr.size()) {
52+
if (current_idx != 0) {
53+
WARN_PRINT("BTForEach: Array size changed during iteration.");
54+
}
5255
return SUCCESS;
5356
}
5457
Variant elem = arr[current_idx];

tests/test_for_each.h

+16
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,22 @@ TEST_CASE("[Modules][LimboAI] BTForEach") {
9696
CHECK_ENTRIES_TICKS_EXITS(task, 3, 6, 3);
9797
CHECK(blackboard->get_var("element", "wetgoop") == "mushroom");
9898
}
99+
100+
SUBCASE("Shouldn't crash if elements are removed during iteration") {
101+
CHECK(fe->execute(0.01666) == BTTask::RUNNING);
102+
CHECK(task->get_status() == BTTask::SUCCESS);
103+
CHECK_ENTRIES_TICKS_EXITS(task, 1, 1, 1);
104+
CHECK(blackboard->get_var("element", "wetgoop") == "apple");
105+
106+
arr.clear();
107+
108+
ERR_PRINT_OFF;
109+
CHECK(fe->execute(0.01666) == BTTask::SUCCESS); // Returns SUCCESS and prints a warning without executing child task.
110+
ERR_PRINT_ON;
111+
CHECK(task->get_status() == BTTask::SUCCESS); // Same status.
112+
CHECK_ENTRIES_TICKS_EXITS(task, 1, 1, 1); // Task is not re-executed as there is not enough elements to continue iteration.
113+
CHECK(blackboard->get_var("element", "wetgoop") == "apple"); // Not changed.
114+
}
99115
}
100116

101117
} //namespace TestForEach

0 commit comments

Comments
 (0)