From e21156df35897579e1ba5b8dd3a83653a1e24bdb Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sat, 2 Nov 2024 15:41:34 +0100 Subject: [PATCH] Save external BTs on plugin callback Also fixes dirty state handling, however it's currently unused. --- editor/limbo_ai_editor_plugin.cpp | 94 +++++++++++++++++-------------- editor/limbo_ai_editor_plugin.h | 13 +++-- util/limbo_compat.h | 2 + 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index b039520..589ec5f 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -97,7 +97,7 @@ void LimboAIEditor::_commit_action_with_update(EditorUndoRedoManager *p_undo_red p_undo_redo->add_do_method(this, LW_NAME(_update_task_tree), task_tree->get_bt()); p_undo_redo->add_undo_method(this, LW_NAME(_update_task_tree), task_tree->get_bt()); p_undo_redo->commit_action(); - _mark_as_dirty(true); + _set_as_dirty(task_tree->get_bt(), true); } void LimboAIEditor::_add_task(const Ref &p_task, bool p_as_sibling) { @@ -201,17 +201,21 @@ void LimboAIEditor::_new_bt() { EDIT_RESOURCE(bt); } -void LimboAIEditor::_save_bt(String p_path) { - ERR_FAIL_COND_MSG(p_path.is_empty(), "Empty p_path"); - ERR_FAIL_COND_MSG(task_tree->get_bt().is_null(), "Behavior Tree is null."); +void LimboAIEditor::_save_bt(const Ref &p_bt, const String &p_path) { + ERR_FAIL_COND(p_path.is_empty()); + ERR_FAIL_COND(!p_path.begins_with("res://")); + ERR_FAIL_COND(p_bt.is_null()); + + if (p_bt->get_path() != p_path) { #ifdef LIMBOAI_MODULE - task_tree->get_bt()->set_path(p_path, true); + task_tree->get_bt()->set_path(p_path, true); #elif LIMBOAI_GDEXTENSION - task_tree->get_bt()->take_over_path(p_path); + task_tree->get_bt()->take_over_path(p_path); #endif + } // This is a workaround, because EditorNode::save_resource() function is not accessible in GDExtension. - if (RESOURCE_IS_BUILT_IN(task_tree->get_bt())) { + if (RESOURCE_IS_BUILT_IN(p_bt)) { // If built-in resource - save the containing resource instead. String file_path = p_path.get_slice("::", 0); ERR_FAIL_COND_MSG(!RESOURCE_EXISTS(file_path, "Resource"), "LimboAI: SAVE FAILED - resource file doesn't exist: " + file_path); @@ -234,14 +238,21 @@ void LimboAIEditor::_save_bt(String p_path) { } } else { // If external resource - save to file. - RESOURCE_SAVE(task_tree->get_bt(), p_path, ResourceSaver::FLAG_CHANGE_PATH); + RESOURCE_SAVE(p_bt, p_path, ResourceSaver::FLAG_CHANGE_PATH); } + _set_as_dirty(p_bt, false); _update_tabs(); - _mark_as_dirty(false); } -void LimboAIEditor::_load_bt(String p_path) { +void LimboAIEditor::_save_current_bt(const String &p_path) { + ERR_FAIL_COND_MSG(p_path.is_empty(), "LimboAI: SAVE FAILED - p_path is empty"); + ERR_FAIL_COND_MSG(task_tree->get_bt().is_null(), "LimboAI: SAVE FAILED - bt is null"); + + _save_bt(task_tree->get_bt(), p_path); +} + +void LimboAIEditor::_load_bt(const String &p_path) { ERR_FAIL_COND_MSG(p_path.is_empty(), "Empty p_path"); Ref bt = RESOURCE_LOAD(p_path, "BehaviorTree"); ERR_FAIL_COND(!bt.is_valid()); @@ -295,8 +306,8 @@ void LimboAIEditor::edit_bt(const Ref &p_behavior_tree, bool p_for task_tree->load_bt(p_behavior_tree); - if (task_tree->get_bt().is_valid() && !task_tree->get_bt()->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty))) { - task_tree->get_bt()->connect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty).bind(true)); + if (task_tree->get_bt().is_valid() && !task_tree->get_bt()->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_set_as_dirty))) { + task_tree->get_bt()->connect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_set_as_dirty).bind(task_tree->get_bt(), true)); } int idx = history.find(p_behavior_tree); @@ -361,12 +372,11 @@ void LimboAIEditor::get_window_layout(const Ref &p_configuration) { p_configuration->set_value("LimboAI", "bteditor_hsplit", split_offset); } -void LimboAIEditor::_mark_as_dirty(bool p_dirty) { - Ref bt = task_tree->get_bt(); - if (p_dirty && !dirty.has(bt)) { - dirty.insert(bt); - } else if (p_dirty == false && dirty.has(bt)) { - dirty.erase(bt); +void LimboAIEditor::_set_as_dirty(const Ref &p_bt, bool p_dirty) { + if (p_dirty && !dirty.has(p_bt)) { + dirty.insert(p_bt); + } else if (p_dirty == false && dirty.has(p_bt)) { + dirty.erase(p_bt); } } @@ -899,7 +909,7 @@ void LimboAIEditor::_on_save_pressed() { if (path.is_empty()) { save_dialog->popup_centered_ratio(); } else { - _save_bt(path); + _save_current_bt(path); } } @@ -1087,8 +1097,8 @@ void LimboAIEditor::_tab_clicked(int p_tab) { void LimboAIEditor::_tab_closed(int p_tab) { ERR_FAIL_INDEX(p_tab, history.size()); Ref history_bt = history[p_tab]; - if (history_bt.is_valid() && history_bt->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty))) { - history_bt->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty)); + if (history_bt.is_valid() && history_bt->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_set_as_dirty))) { + history_bt->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_set_as_dirty)); } if (tab_search_context.has(history_bt)) { tab_search_context.erase(history_bt); @@ -1268,10 +1278,13 @@ void LimboAIEditor::_reload_modified() { void LimboAIEditor::_resave_modified(String _str) { for (const String &res_path : disk_changed_files) { - Ref res = RESOURCE_LOAD(res_path, "BehaviorTree"); - if (res.is_valid()) { - ERR_FAIL_COND(!res->is_class("BehaviorTree")); - RESOURCE_SAVE(res, res->get_path(), 0); + Ref bt = RESOURCE_LOAD(res_path, "BehaviorTree"); + if (bt.is_valid()) { + ERR_FAIL_COND(!bt->is_class("BehaviorTree")); + if (RESOURCE_IS_EXTERNAL(bt)) { + // Only resave external - scene files are handled by the editor. + _save_bt(bt, bt->get_path()); + } } } task_tree->update_tree(); @@ -1296,14 +1309,13 @@ void LimboAIEditor::_rename_task_confirmed() { undo_redo->commit_action(); } -void LimboAIEditor::apply_changes() { +void LimboAIEditor::save_all(bool p_external_only) { for (int i = 0; i < history.size(); i++) { Ref bt = history.get(i); String path = bt->get_path(); - if (RESOURCE_EXISTS(path, "BehaviorTree")) { - RESOURCE_SAVE(bt, path, 0); + if (RESOURCE_EXISTS(path, "BehaviorTree") && (!p_external_only || RESOURCE_PATH_IS_EXTERNAL(path))) { + _save_bt(bt, path); } - dirty.clear(); } } @@ -1450,14 +1462,14 @@ void LimboAIEditor::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { task_tree->unload(); for (int i = 0; i < history.size(); i++) { - if (history[i]->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty))) { - history[i]->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty)); + if (history[i]->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_set_as_dirty))) { + history[i]->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_set_as_dirty)); } } } break; case NOTIFICATION_READY: { // **** Signals - save_dialog->connect("file_selected", callable_mp(this, &LimboAIEditor::_save_bt)); + save_dialog->connect("file_selected", callable_mp(this, &LimboAIEditor::_save_current_bt)); load_dialog->connect("file_selected", callable_mp(this, &LimboAIEditor::_load_bt)); extract_dialog->connect("file_selected", callable_mp(this, &LimboAIEditor::_extract_subtree)); new_btn->connect(LW_NAME(pressed), callable_mp(this, &LimboAIEditor::_new_bt)); @@ -1521,7 +1533,7 @@ void LimboAIEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_remove_task", "task"), &LimboAIEditor::_remove_task); ClassDB::bind_method(D_METHOD("_add_task_with_prototype", "prototype_task"), &LimboAIEditor::_add_task_with_prototype); ClassDB::bind_method(D_METHOD("_new_bt"), &LimboAIEditor::_new_bt); - ClassDB::bind_method(D_METHOD("_save_bt", "path"), &LimboAIEditor::_save_bt); + ClassDB::bind_method(D_METHOD("_save_bt", "path"), &LimboAIEditor::_save_current_bt); ClassDB::bind_method(D_METHOD("_load_bt", "path"), &LimboAIEditor::_load_bt); ClassDB::bind_method(D_METHOD("_update_task_tree", "bt", "specific_task"), &LimboAIEditor::_update_task_tree, DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("edit_bt", "behavior_tree", "force_refresh"), &LimboAIEditor::edit_bt, Variant(false)); @@ -1878,14 +1890,6 @@ LimboAIEditor::~LimboAIEditor() { //**** LimboAIEditorPlugin -#ifdef LIMBOAI_MODULE -void LimboAIEditorPlugin::apply_changes() { -#elif LIMBOAI_GDEXTENSION -void LimboAIEditorPlugin::_apply_changes() { -#endif - limbo_ai_editor->apply_changes(); -} - void LimboAIEditorPlugin::_bind_methods() { } @@ -1960,6 +1964,14 @@ bool LimboAIEditorPlugin::_handles(Object *p_object) const { return false; } +#ifdef LIMBOAI_MODULE +void LimboAIEditorPlugin::save_external_data() { +#elif LIMBOAI_GDEXTENSION +void LimboAIEditorPlugin::_save_external_data() { +#endif + limbo_ai_editor->save_all(true); +} + #ifdef LIMBOAI_GDEXTENSION Ref LimboAIEditorPlugin::_get_plugin_icon() const { return LimboUtility::get_singleton()->get_task_icon("LimboAI"); diff --git a/editor/limbo_ai_editor_plugin.h b/editor/limbo_ai_editor_plugin.h index 42e2839..6068aec 100644 --- a/editor/limbo_ai_editor_plugin.h +++ b/editor/limbo_ai_editor_plugin.h @@ -205,11 +205,12 @@ private: void _update_misc_menu(); void _update_banners(); void _new_bt(); - void _save_bt(String p_path); - void _load_bt(String p_path); + void _save_bt(const Ref &p_bt, const String &p_path); + void _save_current_bt(const String &p_path); + void _load_bt(const String &p_path); void _update_task_tree(const Ref &p_bt, const Ref &p_specific_task = nullptr); void _disable_editing(); - void _mark_as_dirty(bool p_dirty); + void _set_as_dirty(const Ref &p_bt, bool p_dirty); void _create_user_task_dir(); void _remove_task_from_favorite(const String &p_task); void _save_and_restart(); @@ -271,7 +272,7 @@ public: void set_window_layout(const Ref &p_configuration); void get_window_layout(const Ref &p_configuration); - void apply_changes(); + void save_all(bool p_external_only = false); #ifdef LIMBOAI_GDEXTENSION virtual void _shortcut_input(const Ref &p_event) override { _process_shortcut_input(p_event); } @@ -297,23 +298,23 @@ public: virtual String get_name() const override { return "LimboAI"; } virtual void make_visible(bool p_visible) override; - virtual void apply_changes() override; virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void set_window_layout(Ref p_configuration) override; virtual void get_window_layout(Ref p_configuration) override; + virtual void save_external_data() override; #elif LIMBOAI_GDEXTENSION bool _has_main_screen() const override { return true; } virtual String _get_plugin_name() const override { return "LimboAI"; } virtual void _make_visible(bool p_visible) override; - virtual void _apply_changes() override; virtual void _edit(Object *p_object) override; virtual bool _handles(Object *p_object) const override; virtual Ref _get_plugin_icon() const override; virtual void _set_window_layout(const Ref &p_configuration) override; virtual void _get_window_layout(const Ref &p_configuration) override; + virtual void _save_external_data() override; #endif // LIMBOAI_MODULE & LIMBOAI_GDEXTENSION LimboAIEditorPlugin(); diff --git a/util/limbo_compat.h b/util/limbo_compat.h index 6f1da9e..5740477 100644 --- a/util/limbo_compat.h +++ b/util/limbo_compat.h @@ -175,7 +175,9 @@ Variant VARIANT_DEFAULT(Variant::Type p_type); #define IS_RESOURCE_FILE(m_path) (m_path.begins_with("res://") && m_path.find("::") == -1) #define RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type) #define RESOURCE_IS_BUILT_IN(m_res) (m_res->get_path().is_empty() || m_res->get_path().contains("::")) +#define RESOURCE_IS_EXTERNAL(m_res) (!RESOURCE_IS_BUILT_IN(m_res)) #define RESOURCE_PATH_IS_BUILT_IN(m_path) (m_path.is_empty() || m_path.contains("::")) +#define RESOURCE_PATH_IS_EXTERNAL(m_path) (!RESOURCE_PATH_IS_BUILT_IN(m_path)) #ifdef TOOLS_ENABLED