diff --git a/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.cpp b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.cpp index 4d74cf11e2b5c208a30eb553c986a9636dabd562..ac0cfaa2420a1dd2d11d01b77a4fb38c02a62fc6 100644 --- a/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.cpp +++ b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.cpp @@ -105,22 +105,25 @@ namespace armarx::skills::gui &PeriodicUpdateWidget::startTimerIfEnabled); connect(this, &SkillMemoryGUI::connectGui, skillGroupBox, &SkillGroupBox::connectGui); - // update cascade - connect(this, &SkillMemoryGUI::updateGui, skillGroupBox, &SkillGroupBox::updateGui); + // timer + connect(this->updateWidget, + &PeriodicUpdateWidget::update, + this->memory.get(), + &SkillManagerWrapper::updateFromMemory); + + // updates + connect(this->memory.get(), + &SkillManagerWrapper::updateAvailable, + this, + &SkillMemoryGUI::updateGui); + + connect(this, &SkillMemoryGUI::updateGui, this->skillGroupBox, &SkillGroupBox::updateGui); - // we don't want to update the skill details periodically, update can be triggered manually - /* - connect( - this, &SkillMemoryGUI::updateGui, skillDetailGroupBox, &SkillDetailGroupBox::updateGui); - */ connect(this, &SkillMemoryGUI::updateGui, - skillExecutionTreeWidget, - &SkillExecutionTreeWidget::updateExecutions); + this->skillExecutionTreeWidget, + &SkillExecutionTreeWidget::updateGui); - // timer -> update - connect( - this->updateWidget, &PeriodicUpdateWidget::update, this, &SkillMemoryGUI::updateGui); // stop all connect(stopAllButton, diff --git a/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.h b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.h index 648f7cee806866a36363559267f68c537f203c1c..09c26b287bc62ebd08ff3ee58366285bfc419860 100644 --- a/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.h +++ b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.h @@ -52,10 +52,7 @@ namespace armarx::skills::gui */ void connectGui(std::string observerName); - /** - * @brief Notify widgets to update themselves - */ - void updateGui(); + void updateGui(SkillManagerWrapper::Snapshot update); private: void setupUi(); diff --git a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.cpp b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.cpp index aae241ae41b69d9770544708373be904023a671b..780c2bb728874f9865d7147ee2a17be55e925527 100644 --- a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.cpp +++ b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.cpp @@ -20,11 +20,11 @@ namespace armarx::skills::gui // Stop skill QAction* stopSkillAction = new QAction("Stop execution", this); - const auto& executions = memory->fetchExecutions(); + const auto& executions = memory->getExecutions(); if (executions.count(selectedExecution.skillExecutionId) == 0) return; skills::SkillStatus currentStatus = - memory->fetchExecutions().at(selectedExecution.skillExecutionId).status; + memory->getExecutions().at(selectedExecution.skillExecutionId).status; stopSkillAction->setDisabled(currentStatus == skills::SkillStatus::Aborted || currentStatus == skills::SkillStatus::Failed || currentStatus == skills::SkillStatus::Succeeded); @@ -60,7 +60,7 @@ namespace armarx::skills::gui return; // we don't want to hold state in the gui, so we need to get the parameters from memory: skills::SkillExecutionID currentExecutionId = this->selectedExecution.skillExecutionId; - auto executions = memory->fetchExecutions(); + auto executions = memory->getExecutions(); if (executions.empty()) return; @@ -140,13 +140,14 @@ namespace armarx::skills::gui } void - SkillExecutionTreeWidget::updateExecutions() + SkillExecutionTreeWidget::updateGui(SkillManagerWrapper::Snapshot update) { - auto currentManagerStatuses = memory->fetchExecutions(); - if (currentManagerStatuses.empty()) + if (update.statuses.empty()) + { return; + } - for (const auto& [k, v] : currentManagerStatuses) + for (const auto& [k, v] : update.statuses) { skills::SkillExecutionID executionId = k; skills::SkillStatusUpdate statusUpdate = v; diff --git a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h index 795045302b4b47655e196e4cea566d53140f90d6..09fbdae081ed61aed705a278cbc903c053f30a80 100644 --- a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h +++ b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h @@ -9,6 +9,7 @@ namespace armarx::skills::gui { + using StatusMap = std::map<skills::SkillExecutionID, skills::SkillStatusUpdate>; class SkillExecutionTreeWidget : public QTreeWidget, public MemoryCommunicatorBase { @@ -38,7 +39,7 @@ namespace armarx::skills::gui public slots: void disconnectGui(); - void updateExecutions(); + void updateGui(SkillManagerWrapper::Snapshot update); private slots: void executionSelectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); diff --git a/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.cpp b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.cpp index d8ff3fd78b6d76c4b8d0ca7d805260e8bccd4681..1b7b492b5c24dcc87f536eccb2dffae29a94ca89 100644 --- a/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.cpp +++ b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.cpp @@ -10,8 +10,8 @@ namespace armarx::skills::gui using SkillMap = std::map<skills::ProviderID, std::map<skills::SkillID, skills::SkillDescription>>; - const StatusMap - SkillManagerWrapper::fetchExecutions() + StatusMap + SkillManagerWrapper::fetchExecutionsFromMemory() { if (!memory) { @@ -74,16 +74,20 @@ namespace armarx::skills::gui return true; } - void - SkillManagerWrapper::filterSkillUpdate( - std::map<skills::manager::dto::SkillID, skills::manager::dto::SkillDescription>& update) + SkillManagerWrapper::Snapshot + SkillManagerWrapper::filterUpdate(Snapshot update) { // empty search => do not filter if (this->currentSkillSearch.empty()) { - return; + return update; } + Snapshot filtered; + + // for now we don't change the executions + filtered.statuses = update.statuses; + std::vector<std::string> substrings; { @@ -95,22 +99,31 @@ namespace armarx::skills::gui } - for (auto it = update.begin(); it != update.end();) + for (auto& provider_and_descrMap : update.skills) { - if (not matches(simox::alg::to_lower(skills::SkillID::FromIce(it->first).skillName), - substrings)) - { - it = update.erase(it); - } - else + using DescriptionMap = std::map<skills::SkillID, skills::SkillDescription>; + + DescriptionMap& descriptionMap = provider_and_descrMap.second; + skills::ProviderID provider = provider_and_descrMap.first; + + for (auto& skill_and_description : descriptionMap) { - ++it; + skills::SkillID sid = skill_and_description.first; + skills::SkillDescription descr = skill_and_description.second; + + if (matches(simox::alg::to_lower(sid.skillName), substrings)) + { + // add to map + filtered.skills[provider][sid] = descr; + } } } + + return filtered; } - const SkillMap - SkillManagerWrapper::fetchSkills() + SkillMap + SkillManagerWrapper::fetchSkillsFromMemory() { if (!memory) { @@ -125,9 +138,6 @@ namespace armarx::skills::gui auto managerSkills = memory->getSkillDescriptions(); - this->filterSkillUpdate(managerSkills); - - for (const auto& [sid, desc] : managerSkills) { auto description = skills::SkillDescription::FromIce(desc); @@ -179,13 +189,29 @@ namespace armarx::skills::gui SkillManagerWrapper::acceptSearchRequest(std::string const& search) { this->currentSkillSearch = search; + emit searchAccepted(); } void SkillManagerWrapper::stopAllExecutions() { ARMARX_IMPORTANT << "Stopping all running executions."; - const auto& executions = this->fetchExecutions(); + + StatusMap executions; + + // we ALWAYS want the newest information when stopping all! + // e.g. there is some new skill not known to the GUI which we explicitely want to stop too. + // the stop-all function is often used in an emergency, so we'll live with the extra call... + try + { + executions = this->fetchExecutionsFromMemory(); + } + catch (...) // if any error occurs, we use the snapshot as backup. better to miss a skill + // than to not do anything. + { + executions = this->getExecutions(); + } + for (auto& [executionId, status] : executions) { // select all running executions... @@ -197,6 +223,18 @@ namespace armarx::skills::gui } } + void + SkillManagerWrapper::updateFromMemory() + { + std::scoped_lock l(mutex_snapshot); + + snapshot.statuses = fetchExecutionsFromMemory(); + snapshot.skills = fetchSkillsFromMemory(); + + // notify registered widgets of update + emit updateAvailable(filterUpdate(snapshot)); + } + const std::optional<ProviderID> SkillManagerWrapper::findFirstProvider(SkillMap const& map, SkillID const& skillId) { @@ -221,6 +259,23 @@ namespace armarx::skills::gui return std::nullopt; } + SkillMap + SkillManagerWrapper::getSkills() + { + std::scoped_lock l(mutex_snapshot); + + Snapshot filtered = filterUpdate(snapshot); + + return filtered.skills; + } + + StatusMap + SkillManagerWrapper::getExecutions() + { + std::scoped_lock l(mutex_snapshot); + return snapshot.statuses; + } + void SkillManagerWrapper::stopExecution(skills::SkillExecutionID const& executionId, const unsigned int max_retries) @@ -295,8 +350,13 @@ namespace armarx::skills::gui } std::map<skills::SkillID, skills::SkillDescription> skillDescriptions; + if (this->UPDATE_ON_EXECUTION_REQUEST) + { + skillDescriptions = this->fetchSkillsFromMemory().at(providerId.value()); + } + else { - skillDescriptions = this->fetchSkills().at(providerId.value()); + skillDescriptions = this->getSkills().at(providerId.value()); } if (skillDescriptions.find(skillId) == skillDescriptions.end()) diff --git a/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.h b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.h index 22eec04e20bfd499aa444eae74136be6d4c5490f..e191e1f963d82b29abeafe0535fe5f50a384bda3 100644 --- a/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.h +++ b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.h @@ -52,23 +52,36 @@ namespace armarx::skills::gui void stopExecution(skills::SkillExecutionID const& executionId, const unsigned int max_retries = 0); + static const std::optional<skills::ProviderID> findFirstProvider(SkillMap const& map, + SkillID const& skillId); + /** - * @brief Fetches and returns the latest skills update from memory. - * @return The map representing all skills in memory. Empty, if error occurred. + * @brief Returns the latest skills snapshot. + * @return The map representing all currently known skills. */ - const SkillMap fetchSkills(); + SkillMap getSkills(); /** - * @brief Fetches and returns the latest status update from memory. - * @return The map containing status updates for all execution ids. Empty, if error occurred. + * @brief Returns the latest status snapshot. + * @return The map containing status updates for all execution ids. */ - const StatusMap fetchExecutions(); + StatusMap getExecutions(); - static const std::optional<skills::ProviderID> findFirstProvider(SkillMap const& map, - SkillID const& skillId); + struct Snapshot + { + StatusMap statuses; + SkillMap skills; + + // default constructor (construct empty) + Snapshot() : statuses({}), skills({}) + { + } + }; signals: void connectionUpdate(std::string const& message, std::string const& error); + void updateAvailable(Snapshot update); + void searchAccepted(); public slots: /** @@ -87,17 +100,44 @@ namespace armarx::skills::gui */ void stopAllExecutions(); + /** + * @brief Requests this wrapper to overwrite its own state from memory. + */ + void updateFromMemory(); + private: mutable std::mutex mutex_memory; + mutable std::mutex mutex_snapshot; armarx::skills::manager::dti::SkillManagerInterfacePrx memory; std::string currentSkillSearch = ""; + Snapshot snapshot; + + /** + * @brief Fetches and returns the latest skills update from memory. + * @return The map representing all skills in memory. Empty, if error occurred. + */ + SkillMap fetchSkillsFromMemory(); + + /** + * @brief Fetches and returns the latest status update from memory. + * @return The map containing status updates for all execution ids. Empty, if error occurred. + */ + StatusMap fetchExecutionsFromMemory(); + /** - * @brief Modifies the given map to match the search. - * @param update The map to modify. + * @brief Returns the given snapshot, matching the search. + * @param update The snapshot to modify. */ - void filterSkillUpdate(std::map<skills::manager::dto::SkillID, - skills::manager::dto::SkillDescription>& update); + Snapshot filterUpdate(Snapshot update); + + // config + // TODO: perhaps use proper config file for the gui + + // should we do an ice call to use the latest information when requesting an execution? + static constexpr bool UPDATE_ON_EXECUTION_REQUEST = true; + + // --- }; } // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.cpp b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.cpp index 137140d252e90e4e6d2fc92c4c3d51cb18ad514e..8eaa3633f7248dcb7c78b91c16874ccc7811a407 100644 --- a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.cpp +++ b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.cpp @@ -8,7 +8,7 @@ namespace armarx::skills::gui if (_skillId.skillName == SkillID::UNKNOWN) return; - auto const& skills = memory->fetchSkills(); + auto const& skills = memory->getSkills(); std::optional<skills::ProviderID> provider_opt; // check skillId diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.h b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.h index f777490cfba0c7d45b83f601ec69ea2d91903728..1723870bc6fb5a0c32c24efad41d17c9baf0fb58 100644 --- a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.h +++ b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.h @@ -32,14 +32,9 @@ namespace armarx::skills::gui signals: void disconnectGui(); - /** - * @brief Notify widgets to update themselves - */ - void updateGui(); - public slots: /** - * @brief Update subwidgets of an updated skill selection. + * @brief Notify subwidgets of an updated skill selection. * @param skillId */ void updateSkillDetails(skills::SkillID& skillId); diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.cpp b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.cpp index 0287c5468f3a6b1f27e182e88bdbf589f4736af5..57b26011bc7e71920c1f08ade4c0c6434e850877 100644 --- a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.cpp +++ b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.cpp @@ -91,7 +91,7 @@ namespace armarx::skills::gui ARMARX_CHECK(sid.isFullySpecified()); // maybe the search is empty? - auto skillsMap = memory->fetchSkills(); + auto skillsMap = memory->getSkills(); if (skillsMap.count(sid.providerId.value()) == 0 || skillsMap.at(sid.providerId.value()).count(sid) == 0) { @@ -99,7 +99,7 @@ namespace armarx::skills::gui return; } - auto descr = memory->fetchSkills().at(sid.providerId.value()).at(sid); + auto descr = memory->getSkills().at(sid.providerId.value()).at(sid); this->updateContents(sid, descr); } @@ -196,7 +196,7 @@ namespace armarx::skills::gui return; } skills::SkillID& skillId = shownSkill.value().skillId; - auto& skills = memory->fetchSkills(); + const auto skills = memory->getSkills(); ARMARX_CHECK(skillId.isProviderSpecified()); // find description diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.cpp b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.cpp index 91fb11c021677e96890a001a0538fc6cf98f4aa0..30b26364853dd4d96fb159847edb575b372ae833 100644 --- a/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.cpp +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.cpp @@ -12,12 +12,31 @@ namespace armarx::skills::gui emit searchRequest(search); } + void + SkillGroupBox::filterAndFetch() + { + memory->updateFromMemory(); + handleSearch(); + } + void SkillGroupBox::connectGui(std::string observerName) { setTitle(QString::fromStdString(observerName)); } + void + SkillGroupBox::updateGui(SkillManagerWrapper::Snapshot update) + { + skillTreeWidget->updateGui(update.skills); + } + + void + SkillGroupBox::searchCallback() + { + this->skillTreeWidget->updateGui(memory->getSkills()); + } + void SkillGroupBox::setupUi() { @@ -43,7 +62,12 @@ namespace armarx::skills::gui // text this->searchBar->setPlaceholderText(QString::fromStdString("Search ...")); - this->acceptSearchButton->setText(QString::fromStdString("Search")); + this->acceptSearchButton->setText(QString::fromStdString("Update and Search")); + + // some further information to explain the search bar + this->searchBar->setToolTip( + QString::fromStdString("Accepting the search (with Enter) searches all currently known " + "skills. This is not the same as 'Update & Search'!")); connectSignals(); } @@ -52,12 +76,16 @@ namespace armarx::skills::gui SkillGroupBox::connectSignals() { connect( - this->acceptSearchButton, &QPushButton::clicked, this, &SkillGroupBox::handleSearch); + this->acceptSearchButton, &QPushButton::clicked, this, &SkillGroupBox::filterAndFetch); connect(this->searchBar, &QLineEdit::editingFinished, this, &SkillGroupBox::handleSearch); connect(this, &SkillGroupBox::searchRequest, this->memory.get(), &SkillManagerWrapper::acceptSearchRequest); + connect(memory.get(), + &SkillManagerWrapper::searchAccepted, + this, + &SkillGroupBox::searchCallback); connect(skillTreeWidget, &SkillTreeWidget::updateSkillDetails, @@ -67,8 +95,5 @@ namespace armarx::skills::gui // disconnect connect( this, &SkillGroupBox::disconnectGui, skillTreeWidget, &SkillTreeWidget::disconnectGui); - - // update cascade - connect(this, &SkillGroupBox::updateGui, skillTreeWidget, &SkillTreeWidget::updateSkills); } } // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.h b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.h index 2ac6044ba72807ef300ae48fa19fe53d9230d1f3..7089f01b0e0103592b6eda7ff4f8d5d3571ca915 100644 --- a/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.h +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.h @@ -36,20 +36,25 @@ namespace armarx::skills::gui void disconnectGui(); - /** - * @brief Notify widgets to update themselves - */ - void updateGui(); - public slots: void connectGui(std::string observerName); + void updateGui(SkillManagerWrapper::Snapshot update); + + // is called, after the search has been accepted in the memory wrapper. + void searchCallback(); + private slots: /** * Takes string from line edit and emits the search request. */ void handleSearch(); + /** + * @brief Applies the update, and requests an update from memory. + */ + void filterAndFetch(); + private: void setupUi(); void connectSignals(); diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.cpp b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.cpp index c1b100130e4eef7e0e49e287d163e6f880f4f523..a9ebcdd98cfbde9b54abcb929504dd384e0a7c5f 100644 --- a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.cpp +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.cpp @@ -25,10 +25,9 @@ namespace armarx::skills::gui } void - SkillTreeWidget::updateSkills() + SkillTreeWidget::updateGui(SkillMap update) { setSortingEnabled(false); - const auto skills = memory->fetchSkills(); // update tree view. Remove non-existing elements int i = 0; @@ -38,7 +37,7 @@ namespace armarx::skills::gui auto providerName = providerItem->text(0).toStdString(); skills::ProviderID providerId{.providerName = providerName}; - if (skills.find(providerId) == skills.end()) + if (update.find(providerId) == update.end()) { providerItem = nullptr; // reset auto remove = this->takeTopLevelItem(i); @@ -49,8 +48,8 @@ namespace armarx::skills::gui ++i; // sanity check - ARMARX_CHECK(skills.count(providerId) > 0); - auto& providedSkills = skills.at(providerId); + ARMARX_CHECK(update.count(providerId) > 0); + auto& providedSkills = update.at(providerId); int j = 0; while (j < providerItem->childCount()) @@ -75,7 +74,7 @@ namespace armarx::skills::gui } // update tree view. Add new elements - for (const auto& [providerId, providedSkills] : skills) + for (const auto& [providerId, providedSkills] : update) { bool newProvider = false; QTreeWidgetItem* providerItem = nullptr; diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.h b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.h index 61ec5cc85314d0318a951e8216a620d1f1d9a7c8..f94bdcaae2d62dd45dc10f8a98f6916117a0eb4d 100644 --- a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.h +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.h @@ -9,6 +9,8 @@ namespace armarx::skills::gui { + using SkillMap = + std::map<skills::ProviderID, std::map<skills::SkillID, skills::SkillDescription>>; class SkillTreeWidget : public QTreeWidget, public MemoryCommunicatorBase { @@ -42,7 +44,8 @@ namespace armarx::skills::gui public slots: void disconnectGui(); - void updateSkills(); + + void updateGui(SkillMap update); private slots: void skillSelectionChanged(QTreeWidgetItem* current, int column);