Compare commits

...

8 Commits

Author SHA1 Message Date
monxa 031e589c07
Merge cd85e6dd30 into 60a767032e 2024-09-28 16:45:18 +00:00
Alexander Montag cd85e6dd30 Initialize controls in constructor, bind emit callbacks without wrapper in TreeSearchPanel 2024-09-28 16:45:11 +00:00
Alexander Montag 3b73f24f33 Make TreeSearchMode member of TreeSearch 2024-09-28 17:58:42 +02:00
Alexander Montag 329e90dfc6 Rename TreeSearch::notify_item_edited
Also: Run clang-format. Remove comment.
2024-09-28 17:46:29 +02:00
Alexander Montag 84b2a60521 Adjust tooltips + misc-menu entry 2024-09-28 17:29:45 +02:00
Alexander Montag cc8f099d82 Improve TreeSearch performance; part1 2024-09-28 12:08:27 +00:00
Serhii Snitsaruk 60a767032e
Merge pull request #226 from limbonaut/fix-hsm-exit-crash
Fix invalid access errors on exit in LimboHSM
2024-09-26 16:54:31 +02:00
Serhii Snitsaruk 60142b191d
Fix invalid access crash on exit in LimboHSM
Since #131, `LimboState::_exit()` became a source of potential crashes
if object references are used without a validity check. It's too easy
to miss this, which can lead to game crashing during runtime.

This fix reverts #131 change and proposes alternative approach of
re-activating root HSM upon tree entering if it was previously active.
Note that it's not an ideal solution, as some state will be lost upon
re-parenting: HSM exits and then re-activates and enters its initial state.
2024-09-22 13:57:15 +02:00
7 changed files with 89 additions and 86 deletions

View File

@ -457,7 +457,7 @@ void LimboAIEditor::_process_shortcut_input(const Ref<InputEvent> &p_event) {
_on_save_pressed(); _on_save_pressed();
} else if (LW_IS_SHORTCUT("limbo_ai/load_behavior_tree", p_event)) { } else if (LW_IS_SHORTCUT("limbo_ai/load_behavior_tree", p_event)) {
_popup_file_dialog(load_dialog); _popup_file_dialog(load_dialog);
} else if (LW_IS_SHORTCUT("limbo_ai/search_tree", p_event)) { } else if (LW_IS_SHORTCUT("limbo_ai/find_task", p_event)) {
task_tree->tree_search_show_and_focus(); task_tree->tree_search_show_and_focus();
} else { } else {
handled = false; handled = false;
@ -1326,7 +1326,7 @@ void LimboAIEditor::_update_misc_menu() {
MISC_CREATE_SCRIPT_TEMPLATE); MISC_CREATE_SCRIPT_TEMPLATE);
misc_menu->add_separator(); misc_menu->add_separator();
misc_menu->add_icon_item(theme_cache.search_icon, ("Search Tree"), MISC_SEARCH_TREE); misc_menu->add_icon_shortcut(theme_cache.search_icon, LW_GET_SHORTCUT("limbo_ai/find_task"), MISC_SEARCH_TREE);
} }
void LimboAIEditor::_update_banners() { void LimboAIEditor::_update_banners() {
@ -1522,8 +1522,7 @@ LimboAIEditor::LimboAIEditor() {
LW_SHORTCUT("limbo_ai/jump_to_owner", TTR("Jump to Owner"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(J))); LW_SHORTCUT("limbo_ai/jump_to_owner", TTR("Jump to Owner"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(J)));
LW_SHORTCUT("limbo_ai/close_tab", TTR("Close Tab"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(W))); LW_SHORTCUT("limbo_ai/close_tab", TTR("Close Tab"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(W)));
LW_SHORTCUT("limbo_ai/find_task", TTR("Find Task"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F))); LW_SHORTCUT("limbo_ai/find_task", TTR("Find Task"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F)));
LW_SHORTCUT("limbo_ai/hide_tree_search", TTR("Hide BehaviorTrees Search Panel"), (Key)(LW_KEY(ESCAPE))); LW_SHORTCUT("limbo_ai/hide_tree_search", TTR("Close Search"), (Key)(LW_KEY(ESCAPE)));
LW_SHORTCUT("limbo_ai/search_tree", TTR("Shows the BehaviorTree Search Panel"), (Key)(LW_KEY_MASK(CMD_OR_CTRL) | LW_KEY(F)));
set_process_shortcut_input(true); set_process_shortcut_input(true);

View File

@ -112,7 +112,7 @@ void TaskTree::_update_item(TreeItem *p_item) {
if (!warning_text.is_empty()) { if (!warning_text.is_empty()) {
p_item->add_button(0, theme_cache.task_warning_icon, 0, false, warning_text); p_item->add_button(0, theme_cache.task_warning_icon, 0, false, warning_text);
} }
tree_search->on_item_edited(p_item); // this is necessary to preserve custom drawing from tree search. tree_search->notify_item_edited(p_item); // this is necessary to preserve custom drawing from tree search.
} }
void TaskTree::_update_tree() { void TaskTree::_update_tree() {
@ -582,8 +582,6 @@ TaskTree::TaskTree() {
editable = true; editable = true;
updating_tree = false; updating_tree = false;
// for Tree + TreeSearch, we want a VBoxContainer. For now, rather than changing this classes type, let's do nesting:
// TaskTree -> VBoxContainer -> [Tree, TreeSearchPanel]
VBoxContainer *vbox_container = memnew(VBoxContainer); VBoxContainer *vbox_container = memnew(VBoxContainer);
add_child(vbox_container); add_child(vbox_container);
vbox_container->set_anchors_preset(PRESET_FULL_RECT); vbox_container->set_anchors_preset(PRESET_FULL_RECT);

View File

@ -51,12 +51,11 @@ void TreeSearchPanel::_initialize_controls() {
label_filter->set_text(TTR("Filter")); label_filter->set_text(TTR("Filter"));
BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons)));
close_button->set_theme_type_variation("FlatButton"); close_button->set_theme_type_variation("FlatButton");
// positioning and sizing // positioning and sizing
this->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE); set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_WIDE);
this->set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically set_v_size_flags(SIZE_SHRINK_CENTER); // Do not expand vertically
line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL); line_edit_search->set_h_size_flags(SIZE_EXPAND_FILL);
@ -72,34 +71,28 @@ void TreeSearchPanel::_initialize_controls() {
_add_spacer(0.25); _add_spacer(0.25);
} }
void TreeSearchPanel::_initialize_close_callbacks() {
Callable calleable_set_invisible = Callable(this, "set_visible").bind(false); // don't need a custom bind.
close_button->connect("pressed", calleable_set_invisible);
close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search"));
}
void TreeSearchPanel::_add_spacer(float p_width_multiplier) { void TreeSearchPanel::_add_spacer(float p_width_multiplier) {
Control *spacer = memnew(Control); Control *spacer = memnew(Control);
spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * p_width_multiplier, 0.0)); spacer->set_custom_minimum_size(Vector2(8.0 * EDSCALE * p_width_multiplier, 0.0));
add_child(spacer); add_child(spacer);
} }
void TreeSearchPanel::_emit_text_submitted(const String &p_text) {
this->emit_signal("text_submitted");
}
void TreeSearchPanel::_emit_update_requested() {
emit_signal("update_requested");
}
void TreeSearchPanel::_notification(int p_what) { void TreeSearchPanel::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_READY: { case NOTIFICATION_READY: {
_initialize_controls(); BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons)));
line_edit_search->connect("text_changed", callable_mp(this, &TreeSearchPanel::_emit_update_requested).unbind(1));
_initialize_close_callbacks(); // close callbacks
line_edit_search->connect("text_submitted", callable_mp(this, &TreeSearchPanel::_emit_text_submitted)); close_button->connect("pressed", Callable(this, "set_visible").bind(false));
check_button_filter_highlight->connect("pressed", callable_mp(this, &TreeSearchPanel::_emit_update_requested)); close_button->set_shortcut(LW_GET_SHORTCUT("limbo_ai/hide_tree_search"));
// search callbacks
Callable c_update_requested = Callable(this, "emit_signal").bind("update_requested");
Callable c_text_submitted = Callable((Object *)this, "emit_signal").bind("text_submitted");
line_edit_search->connect("text_changed", c_update_requested.unbind(1));
check_button_filter_highlight->connect("pressed", c_update_requested);
line_edit_search->connect("text_submitted", c_text_submitted.unbind(1));
break; break;
} }
} }
@ -111,28 +104,26 @@ void TreeSearchPanel::_bind_methods() {
} }
TreeSearchPanel::TreeSearchPanel() { TreeSearchPanel::TreeSearchPanel() {
this->set_visible(false); _initialize_controls();
set_visible(false);
} }
bool TreeSearchPanel::has_focus() { TreeSearch::TreeSearchMode TreeSearchPanel::get_search_mode() {
return false;
}
TreeSearchMode TreeSearchPanel::get_search_mode() {
if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) { if (!check_button_filter_highlight || !check_button_filter_highlight->is_pressed()) {
return TreeSearchMode::HIGHLIGHT; return TreeSearch::TreeSearchMode::HIGHLIGHT;
} }
return TreeSearchMode::FILTER; return TreeSearch::TreeSearchMode::FILTER;
} }
String TreeSearchPanel::get_text() { String TreeSearchPanel::get_text() {
if (!line_edit_search) if (!line_edit_search) {
return String(); return String();
}
return line_edit_search->get_text(); return line_edit_search->get_text();
} }
void TreeSearchPanel::show_and_focus() { void TreeSearchPanel::show_and_focus() {
this->set_visible(true); set_visible(true);
line_edit_search->grab_focus(); line_edit_search->grab_focus();
} }
@ -200,7 +191,7 @@ void TreeSearch::_highlight_tree(const String &p_search_mask) {
// custom draw callback for highlighting (bind the parent_drw_method to this) // custom draw callback for highlighting (bind the parent_drw_method to this)
void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method) { void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, Rect2 p_rect, Callable p_parent_draw_method) {
if (!p_tree_item){ if (!p_tree_item) {
return; return;
} }
// call any parent draw methods such as for probability FIRST. // call any parent draw methods such as for probability FIRST.
@ -320,12 +311,18 @@ Vector<TreeItem *> TreeSearch::_find_matching_entries(TreeItem *p_tree_item, con
return p_accum; return p_accum;
StringSearchIndices item_search_indices = _substring_bounds(p_tree_item->get_text(0), p_search_mask); StringSearchIndices item_search_indices = _substring_bounds(p_tree_item->get_text(0), p_search_mask);
if (item_search_indices.hit()) if (item_search_indices.hit())
p_accum.insert(p_accum.bsearch(p_tree_item, true), p_tree_item); p_accum.push_back(p_tree_item);
for (int i = 0; i < p_tree_item->get_child_count(); i++) { for (int i = 0; i < p_tree_item->get_child_count(); i++) {
TreeItem *child = p_tree_item->get_child(i); TreeItem *child = p_tree_item->get_child(i);
_find_matching_entries(child, p_search_mask, p_accum); _find_matching_entries(child, p_search_mask, p_accum);
} }
// sort the result if we are at the root
if (p_tree_item == p_tree_item->get_tree()->get_root()) {
p_accum.sort();
}
return p_accum; return p_accum;
} }
@ -442,7 +439,7 @@ inline bool TreeSearch::_vector_has_bsearch(Vector<T *> p_vec, T *element) {
return in_array && p_vec[idx] == element; return in_array && p_vec[idx] == element;
} }
void TreeSearch::on_item_edited(TreeItem *item) { void TreeSearch::notify_item_edited(TreeItem *item) {
if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM) { if (item->get_cell_mode(0) != TreeItem::CELL_MODE_CUSTOM) {
return; return;
} }
@ -480,7 +477,6 @@ void TreeSearch::update_search(Tree *p_tree) {
if (search_mode == TreeSearchMode::FILTER) { if (search_mode == TreeSearchMode::FILTER) {
_filter_tree(search_mask); _filter_tree(search_mask);
} }
} }
TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) { TreeSearch::TreeSearch(TreeSearchPanel *p_search_panel) {

View File

@ -24,48 +24,17 @@
#endif // LIMBOAI_GDEXTENSION #endif // LIMBOAI_GDEXTENSION
#ifdef LIMBOAI_MODULE #ifdef LIMBOAI_MODULE
#include "core/templates/hash_map.h"
#include "scene/gui/check_box.h" #include "scene/gui/check_box.h"
#include "scene/gui/flow_container.h" #include "scene/gui/flow_container.h"
#include "scene/gui/label.h" #include "scene/gui/label.h"
#include "scene/gui/line_edit.h" #include "scene/gui/line_edit.h"
#include "scene/gui/tree.h" #include "scene/gui/tree.h"
#include "core/templates/hash_map.h"
#endif // LIMBOAI_MODULE #endif // LIMBOAI_MODULE
using namespace godot; using namespace godot;
enum TreeSearchMode { class TreeSearchPanel;
HIGHLIGHT = 0,
FILTER = 1
};
class TreeSearchPanel : public HFlowContainer {
GDCLASS(TreeSearchPanel, HFlowContainer)
private:
Button *toggle_button_filter_highlight;
Button *close_button;
Label *label_filter;
LineEdit *line_edit_search;
CheckBox *check_button_filter_highlight;
void _initialize_controls();
void _initialize_close_callbacks();
void _add_spacer(float width_multiplier = 1.f);
void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect);
void _emit_text_submitted(const String &p_text);
void _emit_update_requested();
void _notification(int p_what);
protected:
static void _bind_methods();
public:
TreeSearchMode get_search_mode();
String get_text();
void show_and_focus();
TreeSearchPanel();
bool has_focus();
};
class TreeSearch : public RefCounted { class TreeSearch : public RefCounted {
GDCLASS(TreeSearch, RefCounted) GDCLASS(TreeSearch, RefCounted)
@ -115,11 +84,41 @@ protected:
static void _bind_methods() {} static void _bind_methods() {}
public: public:
void update_search(Tree *p_tree); enum TreeSearchMode {
void on_item_edited(TreeItem *p_item); HIGHLIGHT = 0,
FILTER = 1
};
TreeSearch(){ERR_FAIL_MSG("TreeSearch needs a TreeSearchPanel to work properly");} void update_search(Tree *p_tree);
TreeSearch(TreeSearchPanel * p_search_panel); void notify_item_edited(TreeItem *p_item);
TreeSearch() { ERR_FAIL_MSG("TreeSearch needs a TreeSearchPanel to work properly"); }
TreeSearch(TreeSearchPanel *p_search_panel);
};
class TreeSearchPanel : public HFlowContainer {
GDCLASS(TreeSearchPanel, HFlowContainer)
private:
Button *toggle_button_filter_highlight;
Button *close_button;
Label *label_filter;
LineEdit *line_edit_search;
CheckBox *check_button_filter_highlight;
void _initialize_controls();
void _add_spacer(float width_multiplier = 1.f);
void _on_draw_highlight(TreeItem *p_item, Rect2 p_rect);
void _notification(int p_what);
protected:
static void _bind_methods();
public:
TreeSearch::TreeSearchMode get_search_mode();
String get_text();
void show_and_focus();
TreeSearchPanel();
}; };
#endif // TREE_SEARCH_H #endif // TREE_SEARCH_H

View File

@ -263,6 +263,21 @@ void LimboHSM::_validate_property(PropertyInfo &p_property) const {
void LimboHSM::_notification(int p_what) { void LimboHSM::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: { case NOTIFICATION_POST_ENTER_TREE: {
if (was_active && is_root()) {
// Re-activate the root HSM if it was previously active.
// Typically, this happens when the node is re-entered scene repeatedly (e.g., re-parenting, pooling).
set_active(true);
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (is_root()) {
// Remember active status for re-parenting and exit state machine
// to release resources and signal connections if active.
was_active = active;
if (is_active()) {
_exit();
}
}
} break; } break;
case NOTIFICATION_PROCESS: { case NOTIFICATION_PROCESS: {
_update(get_process_delta_time()); _update(get_process_delta_time());

View File

@ -55,6 +55,7 @@ private:
LimboState *previous_active; LimboState *previous_active;
LimboState *next_active; LimboState *next_active;
bool updating = false; bool updating = false;
bool was_active = false;
HashMap<TransitionKey, Transition, TransitionKeyHasher> transitions; HashMap<TransitionKey, Transition, TransitionKeyHasher> transitions;

View File

@ -190,11 +190,6 @@ void LimboState::_notification(int p_what) {
_update_blackboard_plan(); _update_blackboard_plan();
} }
} break; } break;
case NOTIFICATION_PREDELETE: {
if (is_active()) {
_exit();
}
} break;
} }
} }